[hamradio-commits] [liquid-dsp] 01/03: New upstream version 1.2.0+git20161107

Andreas E. Bombe aeb at moszumanska.debian.org
Sun Dec 11 17:04:55 UTC 2016


This is an automated email from the git hooks/post-receive script.

aeb pushed a commit to branch master
in repository liquid-dsp.

commit 42b3e8824b182902596a7f7835ec190954f8bb8e
Author: Andreas Bombe <aeb at debian.org>
Date:   Wed Dec 7 14:41:37 2016 +0100

    New upstream version 1.2.0+git20161107
---
 HISTORY                                            |  108 +
 LICENSE                                            |   20 +
 README.md                                          |  156 +
 TROUBLESHOOTING                                    |   73 +
 autotest/autotest.c                                |  464 ++
 autotest/autotest.h                                |  227 +
 autotest/autotestlib.c                             |  171 +
 autotest/null_autotest.c                           |   30 +
 bench/bench.c                                      |  476 ++
 bench/example_benchmark.h                          |   61 +
 bench/fftbench.c                                   |  539 ++
 bench/main.c                                       |   53 +
 bench/null_benchmark.c                             |   44 +
 bootstrap.sh                                       |   33 +
 configure.ac                                       |  249 +
 examples/README.md                                 |  635 ++
 examples/agc_crcf_example.c                        |  129 +
 examples/agc_crcf_qpsk_example.c                   |  128 +
 examples/agc_rrrf_example.c                        |  123 +
 examples/ampmodem_example.c                        |  170 +
 examples/asgramcf_example.c                        |   82 +
 examples/asgramf_example.c                         |   62 +
 examples/autocorr_cccf_example.c                   |  159 +
 examples/bpacketsync_example.c                     |  173 +
 examples/bpresync_example.c                        |  197 +
 examples/bsequence_example.c                       |   52 +
 examples/cbufferf_example.c                        |   48 +
 examples/cgsolve_example.c                         |   77 +
 examples/channel_cccf_example.c                    |  205 +
 examples/chromosome_example.c                      |   87 +
 examples/compand_cf_example.c                      |   64 +
 examples/compand_example.c                         |  102 +
 examples/complementary_codes_example.c             |  135 +
 examples/conversion_example.c                      |  139 +
 examples/cpfskmodem_example.c                      |  213 +
 examples/cpfskmodem_psd_example.c                  |  165 +
 examples/crc_example.c                             |   75 +
 examples/cvsd_example.c                            |  130 +
 examples/detector_cccf_example.c                   |  194 +
 examples/dotprod_cccf_example.c                    |   42 +
 examples/dotprod_rrrf_example.c                    |   34 +
 examples/eqlms_cccf_blind_example.c                |  317 +
 examples/eqlms_cccf_block_example.c                |  188 +
 examples/eqlms_cccf_decisiondirected_example.c     |  285 +
 examples/eqlms_cccf_example.c                      |  162 +
 examples/eqrls_cccf_example.c                      |  162 +
 examples/fading_generator_example.c                |  103 +
 examples/fec_example.c                             |  141 +
 examples/fec_soft_example.c                        |  186 +
 examples/fft_example.c                             |  104 +
 examples/fftfilt_crcf_example.c                    |  135 +
 examples/firdecim_crcf_example.c                   |  196 +
 examples/firdes_kaiser_example.c                   |  101 +
 examples/firdespm_example.c                        |  110 +
 examples/firfarrow_rrrf_example.c                  |   96 +
 examples/firfilt_cccf_example.c                    |  139 +
 examples/firfilt_crcf_example.c                    |  126 +
 examples/firfilt_rrrf_example.c                    |   92 +
 examples/firhilb_decim_example.c                   |   87 +
 examples/firhilb_example.c                         |  121 +
 examples/firhilb_interp_example.c                  |   86 +
 examples/firinterp_crcf_example.c                  |  145 +
 examples/firpfbch2_crcf_example.c                  |  169 +
 examples/firpfbch_crcf_analysis_example.c          |  148 +
 examples/firpfbch_crcf_example.c                   |  298 +
 examples/firpfbch_crcf_synthesis_example.c         |  157 +
 examples/flexframesync_example.c                   |  176 +
 examples/flexframesync_reconfig_example.c          |  122 +
 examples/framesync64_example.c                     |  194 +
 examples/freqmodem_example.c                       |  136 +
 examples/fskmodem_example.c                        |  153 +
 examples/gasearch_example.c                        |  131 +
 examples/gasearch_knapsack_example.c               |  203 +
 examples/gmskframesync_example.c                   |  232 +
 examples/gmskmodem_example.c                       |  143 +
 examples/gradsearch_datafit_example.c              |  149 +
 examples/gradsearch_example.c                      |  111 +
 examples/iirdecim_crcf_example.c                   |  155 +
 examples/iirdes_analog_example.c                   |  264 +
 examples/iirdes_example.c                          |  262 +
 examples/iirdes_pll_example.c                      |  150 +
 examples/iirfilt_cccf_example.c                    |  205 +
 examples/iirfilt_crcf_dcblocker_example.c          |  100 +
 examples/iirfilt_crcf_example.c                    |  130 +
 examples/iirinterp_crcf_example.c                  |  135 +
 examples/interleaver_example.c                     |   60 +
 examples/interleaver_scatterplot_example.c         |  144 +
 examples/interleaver_soft_example.c                |   89 +
 examples/kbd_window_example.c                      |   58 +
 examples/libliquid_example.c                       |   20 +
 examples/lpc_example.c                             |  143 +
 examples/matched_filter_example.c                  |  206 +
 examples/math_lngamma_example.c                    |   55 +
 examples/math_primitive_root_example.c             |   29 +
 examples/modem_arb_example.c                       |  131 +
 examples/modem_example.c                           |  117 +
 examples/modem_soft_example.c                      |  118 +
 examples/modular_arithmetic_example.c              |   35 +
 examples/msequence_example.c                       |  106 +
 examples/msourcecf_example.c                       |  115 +
 examples/msresamp2_crcf_example.c                  |  238 +
 examples/msresamp_crcf_example.c                   |  224 +
 examples/nco_example.c                             |   35 +
 examples/nco_pll_example.c                         |  153 +
 examples/nco_pll_modem_example.c                   |  184 +
 examples/nyquist_filter_example.c                  |  124 +
 examples/ofdmflexframesync_example.c               |  227 +
 examples/ofdmframegen_example.c                    |   71 +
 examples/ofdmframesync_example.c                   |  278 +
 examples/packetizer_example.c                      |  157 +
 examples/packetizer_soft_example.c                 |  196 +
 examples/pll_example.c                             |   92 +
 examples/poly_findroots_example.c                  |   36 +
 examples/polyfit_example.c                         |   72 +
 examples/polyfit_lagrange_example.c                |   77 +
 examples/qdetector_cccf_example.c                  |  227 +
 examples/qnsearch_example.c                        |   67 +
 examples/qpacketmodem_example.c                    |  139 +
 examples/qpacketmodem_performance_example.c        |  199 +
 examples/qpilotsync_example.c                      |  163 +
 examples/quantize_example.c                        |  122 +
 examples/random_histogram_example.c                |  357 +
 examples/repack_bytes_example.c                    |   65 +
 examples/resamp2_cccf_example.c                    |  131 +
 examples/resamp2_crcf_decim_example.c              |  147 +
 examples/resamp2_crcf_example.c                    |  126 +
 examples/resamp2_crcf_filter_example.c             |  110 +
 examples/resamp2_crcf_interp_example.c             |  146 +
 examples/resamp_crcf_example.c                     |  246 +
 examples/ricek_channel_example.c                   |  178 +
 examples/scramble_example.c                        |  135 +
 examples/smatrix_example.c                         |  210 +
 examples/spgramcf_example.c                        |   96 +
 examples/spgramcf_waterfall_example.c              |  152 +
 examples/spgramf_example.c                         |   87 +
 examples/symstreamcf_example.c                     |   85 +
 examples/symsync_crcf_example.c                    |  160 +
 examples/symsync_crcf_full_example.c               |  314 +
 examples/symsync_crcf_kaiser_example.c             |  208 +
 examples/symtrack_cccf_example.c                   |  213 +
 examples/wdelayf_example.c                         |   57 +
 examples/windowf_example.c                         |   63 +
 include/liquid.h                                   | 6867 ++++++++++++++++++++
 include/liquid.internal.h                          | 1840 ++++++
 makefile.in                                        | 1672 +++++
 sandbox/README.md                                  |   15 +
 sandbox/bpresync_test.c                            |  267 +
 sandbox/count_ones_gentab.c                        |   90 +
 sandbox/cpmodem_test.c                             |  153 +
 sandbox/crc_gentab.c                               |   67 +
 sandbox/ellip_func_test.c                          |   35 +
 sandbox/ellip_test.c                               |   88 +
 sandbox/eqlms_cccf_test.c                          |  208 +
 sandbox/fct_test.c                                 |   75 +
 sandbox/fec_g2412product_test.c                    |  450 ++
 sandbox/fec_golay2412_test.c                       |  353 +
 sandbox/fec_golay_test.c                           |  228 +
 sandbox/fec_hamming128_example.c                   |   44 +
 sandbox/fec_hamming128_gentab.c                    |   57 +
 sandbox/fec_hamming128_test.c                      |  127 +
 sandbox/fec_hamming3126_example.c                  |  254 +
 sandbox/fec_hamming74_gentab.c                     |  182 +
 sandbox/fec_hamming84_gentab.c                     |  191 +
 sandbox/fec_hamming_test.c                         |  216 +
 sandbox/fec_ldpc_test.c                            |  204 +
 sandbox/fec_rep3_test.c                            |   31 +
 sandbox/fec_rep5_test.c                            |   49 +
 sandbox/fec_secded2216_test.c                      |  258 +
 sandbox/fec_secded3932_test.c                      |  274 +
 sandbox/fec_secded7264_test.c                      |  256 +
 sandbox/fec_secded_punctured_test.c                |  285 +
 sandbox/fec_spc2216_test.c                         | 1007 +++
 sandbox/fec_sumproduct_test.c                      |   73 +
 sandbox/fecsoft_ber_test.c                         |  232 +
 sandbox/fecsoft_conv_test.c                        |  261 +
 sandbox/fecsoft_hamming128_gentab.c                |   77 +
 sandbox/fecsoft_ldpc_test.c                        |  180 +
 sandbox/fft_dual_radix_test.c                      |  226 +
 sandbox/fft_mixed_radix_test.c                     |  310 +
 sandbox/fft_r2r_test.c                             |  144 +
 sandbox/fft_rader_prime_radix2_test.c              |  386 ++
 sandbox/fft_rader_prime_test.c                     |  310 +
 sandbox/fft_recursive_plan_test.c                  |  142 +
 sandbox/fft_recursive_test.c                       |  239 +
 sandbox/firdes_energy_test.c                       |   80 +
 sandbox/firdes_fexp_test.c                         |  246 +
 sandbox/firdes_gmskrx_test.c                       |  243 +
 sandbox/firdes_length_test.c                       |   97 +
 sandbox/firfarrow_rrrf_test.c                      |  102 +
 sandbox/firpfbch2_analysis_equivalence_test.c      |  267 +
 sandbox/firpfbch2_test.c                           |  380 ++
 sandbox/firpfbch_analysis_alignment_test.c         |  148 +
 sandbox/firpfbch_analysis_equivalence_test.c       |  217 +
 sandbox/firpfbch_analysis_test.c                   |  104 +
 sandbox/firpfbch_synthesis_equivalence_test.c      |  203 +
 sandbox/fskmodem_test.c                            |  302 +
 sandbox/gmskmodem_coherent_test.c                  |  191 +
 sandbox/gmskmodem_equalizer_test.c                 |  270 +
 sandbox/gmskmodem_test.c                           |  174 +
 sandbox/householder_test.c                         |  210 +
 sandbox/iirdes_example.c                           |  410 ++
 sandbox/iirfilt_intdiff_test.c                     |  250 +
 sandbox/levinson_test.c                            |  141 +
 sandbox/matched_filter_cfo_test.c                  |  216 +
 sandbox/matched_filter_test.c                      |   96 +
 sandbox/math_cacosf_test.c                         |  120 +
 sandbox/math_casinf_test.c                         |  125 +
 sandbox/math_catanf_test.c                         |  109 +
 sandbox/math_cexpf_test.c                          |  113 +
 sandbox/math_clogf_test.c                          |  109 +
 sandbox/math_csqrtf_test.c                         |  117 +
 sandbox/math_lngamma_test.c                        |  123 +
 sandbox/matrix_eig_test.c                          |   64 +
 sandbox/matrix_test.c                              |  246 +
 sandbox/mdct_test.c                                |  146 +
 sandbox/minsearch2_test.c                          |   95 +
 sandbox/minsearch_test.c                           |   89 +
 sandbox/modem_demodulate_arb_gentab.c              |  320 +
 sandbox/modem_demodulate_soft_gentab.c             |  404 ++
 sandbox/modem_demodulate_soft_test.c               |  139 +
 sandbox/mskmodem_test.c                            |  423 ++
 sandbox/msresamp_crcf_test.c                       |  161 +
 sandbox/newbench_example.c                         |  116 +
 sandbox/ofdm_ber_test.c                            |  156 +
 sandbox/ofdmframe_papr_test.c                      |  223 +
 sandbox/ofdmframesync_cfo_test.c                   |  212 +
 sandbox/ofdmoqam_firpfbch_cfo_test.c               |  243 +
 sandbox/ofdmoqam_firpfbch_test.c                   |  184 +
 sandbox/packetizer_persistent_ber_test.c           |  396 ++
 sandbox/pll_design_test.c                          |  133 +
 sandbox/predemod_sync_test.c                       |  218 +
 sandbox/quasinewton_test.c                         |  427 ++
 sandbox/recursive_qpsk_test.c                      |  150 +
 sandbox/resamp2_crcf_filterbank_test.c             |  135 +
 sandbox/resamp2_crcf_interp_recreate_test.c        |   93 +
 sandbox/reverse_byte_gentab.c                      |   70 +
 sandbox/rkaiser2_test.c                            |  265 +
 sandbox/shadowing_test.c                           |  106 +
 sandbox/simplex_test.c                             |   67 +
 sandbox/svd_test.c                                 |  333 +
 sandbox/symsync_crcf_test.c                        |  277 +
 sandbox/symsync_eqlms_test.c                       |  326 +
 sandbox/thiran_allpass_iir_test.c                  |   71 +
 sandbox/vectorcf_test.c                            |   36 +
 scripts/autoscript.c                               |  416 ++
 scripts/autoscript.h                               |   70 +
 scripts/ax_check_compile_flag.m4                   |   72 +
 scripts/ax_ext.m4                                  |  163 +
 scripts/ax_gcc_archflag.m4                         |  225 +
 scripts/ax_gcc_x86_cpuid.m4                        |   79 +
 scripts/benchmark_compare.c                        |  339 +
 scripts/config.guess                               | 1502 +++++
 scripts/config.sub                                 | 1714 +++++
 scripts/example_autotest.h                         |   35 +
 scripts/example_benchmark.h                        |   61 +
 scripts/install-sh                                 |  507 ++
 scripts/main.c                                     |   74 +
 scripts/update_copyright.py                        |  168 +
 src/agc/bench/agc_crcf_benchmark.c                 |   59 +
 src/agc/src/agc.c                                  |  270 +
 src/agc/src/agc_crcf.c                             |   42 +
 src/agc/src/agc_rrrf.c                             |   42 +
 src/agc/tests/agc_crcf_autotest.c                  |  172 +
 src/audio/bench/cvsd_benchmark.c                   |  106 +
 src/audio/src/cvsd.c                               |  231 +
 src/audio/tests/cvsd_autotest.c                    |   63 +
 src/buffer/bench/cbuffercf_benchmark.c             |  109 +
 src/buffer/bench/window_push_benchmark.c           |   68 +
 src/buffer/bench/window_read_benchmark.c           |   69 +
 src/buffer/src/buffer.c                            |  274 +
 src/buffer/src/buffercf.c                          |   49 +
 src/buffer/src/bufferf.c                           |   49 +
 src/buffer/src/cbuffer.c                           |  337 +
 src/buffer/src/wdelay.c                            |  141 +
 src/buffer/src/window.c                            |  229 +
 src/buffer/tests/cbuffer_autotest.c                |  294 +
 src/buffer/tests/sbuffer_autotest.c                |   90 +
 src/buffer/tests/wdelay_autotest.c                 |   87 +
 src/buffer/tests/window_autotest.c                 |  143 +
 src/buffer/window.readme.txt                       |   45 +
 src/channel/src/channel.c                          |  353 +
 src/channel/src/channel_cccf.c                     |   52 +
 src/channel/src/tvmpch.c                           |  213 +
 src/dotprod/bench/dotprod_cccf_benchmark.c         |   78 +
 src/dotprod/bench/dotprod_crcf_benchmark.c         |   78 +
 src/dotprod/bench/dotprod_rrrf_benchmark.c         |   72 +
 src/dotprod/bench/sumsqcf_benchmark.c              |   68 +
 src/dotprod/bench/sumsqf_benchmark.c               |   68 +
 src/dotprod/src/dotprod.c                          |  168 +
 src/dotprod/src/dotprod_cccf.c                     |   35 +
 src/dotprod/src/dotprod_cccf.mmx.c                 |  382 ++
 src/dotprod/src/dotprod_cccf.neon.c                |  342 +
 src/dotprod/src/dotprod_crcf.av.c                  |  209 +
 src/dotprod/src/dotprod_crcf.c                     |   35 +
 src/dotprod/src/dotprod_crcf.mmx.c                 |  287 +
 src/dotprod/src/dotprod_crcf.neon.c                |  303 +
 src/dotprod/src/dotprod_rrrf.av.c                  |  202 +
 src/dotprod/src/dotprod_rrrf.c                     |   34 +
 src/dotprod/src/dotprod_rrrf.mmx.c                 |  301 +
 src/dotprod/src/dotprod_rrrf.neon.c                |  222 +
 src/dotprod/src/dotprod_rrrf.sse4.c                |  259 +
 src/dotprod/src/sumsq.c                            |   73 +
 src/dotprod/src/sumsq.mmx.c                        |  115 +
 src/dotprod/tests/dotprod_cccf_autotest.c          |  222 +
 src/dotprod/tests/dotprod_crcf_autotest.c          |  167 +
 src/dotprod/tests/dotprod_rrrf_autotest.c          |  372 ++
 src/dotprod/tests/sumsqcf_autotest.c               |  126 +
 src/dotprod/tests/sumsqf_autotest.c                |  127 +
 src/equalization/bench/eqlms_cccf_benchmark.c      |   86 +
 src/equalization/bench/eqrls_cccf_benchmark.c      |   86 +
 src/equalization/src/eqlms.c                       |  472 ++
 src/equalization/src/eqrls.c                       |  411 ++
 src/equalization/src/equalizer_cccf.c              |   46 +
 src/equalization/src/equalizer_rrrf.c              |   46 +
 src/equalization/tests/eqlms_cccf_autotest.c       |  212 +
 src/equalization/tests/eqrls_rrrf_autotest.c       |   89 +
 src/fec/bench/crc_benchmark.c                      |   78 +
 src/fec/bench/fec_decode_benchmark.c               |  182 +
 src/fec/bench/fec_encode_benchmark.c               |  166 +
 src/fec/bench/fecsoft_decode_benchmark.c           |  190 +
 src/fec/bench/interleaver_benchmark.c              |   78 +
 src/fec/bench/packetizer_decode_benchmark.c        |   95 +
 src/fec/bench/sumproduct_benchmark.c               |  157 +
 src/fec/readme.rs.txt                              |   52 +
 src/fec/src/c_ones_mod2.c                          |   45 +
 src/fec/src/c_ones_mod2_gentab.c                   |   64 +
 src/fec/src/crc.c                                  |  363 ++
 src/fec/src/fec.c                                  |  665 ++
 src/fec/src/fec_conv.c                             |  296 +
 src/fec/src/fec_conv_pmatrix.c                     |   99 +
 src/fec/src/fec_conv_poly.c                        |   57 +
 src/fec/src/fec_conv_punctured.c                   |  455 ++
 src/fec/src/fec_golay2412.c                        |  401 ++
 src/fec/src/fec_hamming128.c                       |  466 ++
 src/fec/src/fec_hamming128_gentab.c                |  320 +
 src/fec/src/fec_hamming1511.c                      |  137 +
 src/fec/src/fec_hamming3126.c                      |  145 +
 src/fec/src/fec_hamming74.c                        |  231 +
 src/fec/src/fec_hamming84.c                        |  220 +
 src/fec/src/fec_pass.c                             |   65 +
 src/fec/src/fec_rep3.c                             |  147 +
 src/fec/src/fec_rep5.c                             |  150 +
 src/fec/src/fec_rs.c                               |  314 +
 src/fec/src/fec_secded2216.c                       |  327 +
 src/fec/src/fec_secded3932.c                       |  353 +
 src/fec/src/fec_secded7264.c                       |  342 +
 src/fec/src/interleaver.c                          |  322 +
 src/fec/src/packetizer.c                           |  407 ++
 src/fec/src/sumproduct.c                           |  234 +
 src/fec/tests/crc_autotest.c                       |  122 +
 src/fec/tests/fec_autotest.c                       |  132 +
 src/fec/tests/fec_golay2412_autotest.c             |  107 +
 src/fec/tests/fec_hamming128_autotest.c            |  105 +
 src/fec/tests/fec_hamming1511_autotest.c           |   67 +
 src/fec/tests/fec_hamming3126_autotest.c           |   67 +
 src/fec/tests/fec_hamming74_autotest.c             |  101 +
 src/fec/tests/fec_hamming84_autotest.c             |  101 +
 src/fec/tests/fec_reedsolomon_autotest.c           |   85 +
 src/fec/tests/fec_rep3_autotest.c                  |   63 +
 src/fec/tests/fec_rep5_autotest.c                  |   83 +
 src/fec/tests/fec_secded2216_autotest.c            |  148 +
 src/fec/tests/fec_secded3932_autotest.c            |  164 +
 src/fec/tests/fec_secded7264_autotest.c            |  179 +
 src/fec/tests/fec_soft_autotest.c                  |  140 +
 src/fec/tests/interleaver_autotest.c               |   87 +
 src/fec/tests/packetizer_autotest.c                |   67 +
 src/fft/bench/fft_composite_benchmark.c            |  229 +
 src/fft/bench/fft_prime_benchmark.c                |  131 +
 src/fft/bench/fft_r2r_benchmark.c                  |  100 +
 src/fft/bench/fft_radix2_benchmark.c               |   50 +
 src/fft/bench/fft_runbench.c                       |   70 +
 src/fft/bench/fft_runbench.h                       |   46 +
 src/fft/src/asgram.c                               |  218 +
 src/fft/src/dct_execute.c                          |  108 +
 src/fft/src/fct_execute.c                          |  114 +
 src/fft/src/fft_common.c                           |  351 +
 src/fft/src/fft_dft.c                              |  523 ++
 src/fft/src/fft_mixed_radix.c                      |  242 +
 src/fft/src/fft_r2r_1d.c                           |  249 +
 src/fft/src/fft_rader.c                            |  160 +
 src/fft/src/fft_rader2.c                           |  229 +
 src/fft/src/fft_radix2.c                           |  137 +
 src/fft/src/fft_utilities.c                        |  101 +
 src/fft/src/fftf.c                                 |   47 +
 src/fft/src/spgram.c                               |  439 ++
 src/fft/src/spgramcf.c                             |   51 +
 src/fft/src/spgramf.c                              |   51 +
 src/fft/tests/data/fft_data_10.c                   |   52 +
 src/fft/tests/data/fft_data_120.c                  |  272 +
 src/fft/tests/data/fft_data_130.c                  |  292 +
 src/fft/tests/data/fft_data_157.c                  |  346 +
 src/fft/tests/data/fft_data_16.c                   |   64 +
 src/fft/tests/data/fft_data_17.c                   |   66 +
 src/fft/tests/data/fft_data_192.c                  |  416 ++
 src/fft/tests/data/fft_data_2.c                    |   36 +
 src/fft/tests/data/fft_data_20.c                   |   72 +
 src/fft/tests/data/fft_data_21.c                   |   74 +
 src/fft/tests/data/fft_data_22.c                   |   76 +
 src/fft/tests/data/fft_data_24.c                   |   80 +
 src/fft/tests/data/fft_data_26.c                   |   84 +
 src/fft/tests/data/fft_data_3.c                    |   38 +
 src/fft/tests/data/fft_data_30.c                   |   92 +
 src/fft/tests/data/fft_data_317.c                  |  666 ++
 src/fft/tests/data/fft_data_32.c                   |   96 +
 src/fft/tests/data/fft_data_35.c                   |  102 +
 src/fft/tests/data/fft_data_36.c                   |  104 +
 src/fft/tests/data/fft_data_4.c                    |   40 +
 src/fft/tests/data/fft_data_43.c                   |  118 +
 src/fft/tests/data/fft_data_48.c                   |  128 +
 src/fft/tests/data/fft_data_5.c                    |   42 +
 src/fft/tests/data/fft_data_509.c                  | 1050 +++
 src/fft/tests/data/fft_data_6.c                    |   44 +
 src/fft/tests/data/fft_data_63.c                   |  158 +
 src/fft/tests/data/fft_data_64.c                   |  160 +
 src/fft/tests/data/fft_data_7.c                    |   46 +
 src/fft/tests/data/fft_data_79.c                   |  190 +
 src/fft/tests/data/fft_data_8.c                    |   48 +
 src/fft/tests/data/fft_data_9.c                    |   50 +
 src/fft/tests/data/fft_data_92.c                   |  216 +
 src/fft/tests/data/fft_data_96.c                   |  224 +
 src/fft/tests/data/fft_r2rdata_27.c                |  126 +
 src/fft/tests/data/fft_r2rdata_32.c                |  133 +
 src/fft/tests/data/fft_r2rdata_8.c                 |  125 +
 src/fft/tests/fft_composite_autotest.c             |   53 +
 src/fft/tests/fft_prime_autotest.c                 |   41 +
 src/fft/tests/fft_r2r_autotest.c                   |  105 +
 src/fft/tests/fft_radix2_autotest.c                |   42 +
 src/fft/tests/fft_runtest.c                        |   66 +
 src/fft/tests/fft_runtest.h                        |  177 +
 src/fft/tests/fft_shift_autotest.c                 |   75 +
 src/fft/tests/fft_small_autotest.c                 |   41 +
 src/fft/tests/gen_fft_data.m                       |   68 +
 src/fft/tests/gen_fft_files.m                      |   11 +
 src/filter/bench/fftfilt_crcf_benchmark.c          |   85 +
 src/filter/bench/firdecim_crcf_benchmark.c         |   76 +
 src/filter/bench/firfilt_crcf_benchmark.c          |   79 +
 src/filter/bench/firhilb_benchmark.c               |   68 +
 src/filter/bench/firinterp_crcf_benchmark.c        |   71 +
 src/filter/bench/iirdecim_crcf_benchmark.c         |   80 +
 src/filter/bench/iirfilt_crcf_benchmark.c          |  127 +
 src/filter/bench/iirinterp_crcf_benchmark.c        |   75 +
 src/filter/bench/resamp2_crcf_benchmark.c          |   99 +
 src/filter/bench/resamp_crcf_benchmark.c           |   74 +
 src/filter/bench/symsync_crcf_benchmark.c          |   84 +
 src/filter/dds.readme.txt                          |   34 +
 src/filter/iirdes.readme.txt                       |   45 +
 src/filter/src/autocorr.c                          |  173 +
 src/filter/src/bessel.c                            |  263 +
 src/filter/src/bilinear.c                          |  198 +
 src/filter/src/butter.c                            |   66 +
 src/filter/src/cheby1.c                            |   92 +
 src/filter/src/cheby2.c                            |  104 +
 src/filter/src/ellip.c                             |  400 ++
 src/filter/src/fftfilt.c                           |  267 +
 src/filter/src/filter_cccf.c                       |   83 +
 src/filter/src/filter_crcf.c                       |   84 +
 src/filter/src/filter_rrrf.c                       |   85 +
 src/filter/src/firdecim.c                          |  224 +
 src/filter/src/firdes.c                            |  622 ++
 src/filter/src/firdespm.c                          |  798 +++
 src/filter/src/firfarrow.c                         |  370 ++
 src/filter/src/firfilt.c                           |  405 ++
 src/filter/src/firhilb.c                           |  301 +
 src/filter/src/firinterp.c                         |  216 +
 src/filter/src/firpfb.c                            |  346 +
 src/filter/src/fnyquist.c                          |  345 +
 src/filter/src/gmsk.c                              |  194 +
 src/filter/src/group_delay.c                       |  118 +
 src/filter/src/hM3.c                               |  127 +
 src/filter/src/iirdecim.c                          |  183 +
 src/filter/src/iirdes.c                            |  705 ++
 src/filter/src/iirdes.pll.c                        |  120 +
 src/filter/src/iirfilt.c                           |  656 ++
 src/filter/src/iirfiltsos.c                        |  202 +
 src/filter/src/iirinterp.c                         |  174 +
 src/filter/src/lpc.c                               |  155 +
 src/filter/src/msresamp.c                          |  349 +
 src/filter/src/msresamp2.c                         |  354 +
 src/filter/src/rcos.c                              |   81 +
 src/filter/src/resamp.c                            |  364 ++
 src/filter/src/resamp.fixed.c                      |  188 +
 src/filter/src/resamp2.c                           |  360 +
 src/filter/src/rkaiser.c                           |  516 ++
 src/filter/src/rrcos.c                             |   95 +
 src/filter/src/symsync.c                           |  698 ++
 src/filter/tests/data/fftfilt_cccf_data_h13x256.c  |  559 ++
 src/filter/tests/data/fftfilt_cccf_data_h23x256.c  |  569 ++
 src/filter/tests/data/fftfilt_cccf_data_h4x256.c   |  550 ++
 src/filter/tests/data/fftfilt_cccf_data_h7x256.c   |  553 ++
 src/filter/tests/data/fftfilt_crcf_data_h13x256.c  |  559 ++
 src/filter/tests/data/fftfilt_crcf_data_h23x256.c  |  569 ++
 src/filter/tests/data/fftfilt_crcf_data_h4x256.c   |  550 ++
 src/filter/tests/data/fftfilt_crcf_data_h7x256.c   |  553 ++
 src/filter/tests/data/fftfilt_rrrf_data_h13x256.c  |  557 ++
 src/filter/tests/data/fftfilt_rrrf_data_h23x256.c  |  567 ++
 src/filter/tests/data/fftfilt_rrrf_data_h4x256.c   |  548 ++
 src/filter/tests/data/fftfilt_rrrf_data_h7x256.c   |  551 ++
 src/filter/tests/data/firdecim_cccf_data_M2h4x20.c |   68 +
 src/filter/tests/data/firdecim_cccf_data_M3h7x30.c |   81 +
 .../tests/data/firdecim_cccf_data_M4h13x40.c       |   97 +
 .../tests/data/firdecim_cccf_data_M5h23x50.c       |  117 +
 src/filter/tests/data/firdecim_crcf_data_M2h4x20.c |   68 +
 src/filter/tests/data/firdecim_crcf_data_M3h7x30.c |   81 +
 .../tests/data/firdecim_crcf_data_M4h13x40.c       |   97 +
 .../tests/data/firdecim_crcf_data_M5h23x50.c       |  117 +
 src/filter/tests/data/firdecim_rrrf_data_M2h4x20.c |   66 +
 src/filter/tests/data/firdecim_rrrf_data_M3h7x30.c |   79 +
 .../tests/data/firdecim_rrrf_data_M4h13x40.c       |   95 +
 .../tests/data/firdecim_rrrf_data_M5h23x50.c       |  115 +
 src/filter/tests/data/firfilt_cccf_data_h13x32.c   |  111 +
 src/filter/tests/data/firfilt_cccf_data_h23x64.c   |  185 +
 src/filter/tests/data/firfilt_cccf_data_h4x8.c     |   54 +
 src/filter/tests/data/firfilt_cccf_data_h7x16.c    |   73 +
 src/filter/tests/data/firfilt_crcf_data_h13x32.c   |  111 +
 src/filter/tests/data/firfilt_crcf_data_h23x64.c   |  185 +
 src/filter/tests/data/firfilt_crcf_data_h4x8.c     |   54 +
 src/filter/tests/data/firfilt_crcf_data_h7x16.c    |   73 +
 src/filter/tests/data/firfilt_rrrf_data_h13x32.c   |  109 +
 src/filter/tests/data/firfilt_rrrf_data_h23x64.c   |  183 +
 src/filter/tests/data/firfilt_rrrf_data_h4x8.c     |   52 +
 src/filter/tests/data/firfilt_rrrf_data_h7x16.c    |   71 +
 src/filter/tests/data/gen_firfilt_data.m           |  159 +
 src/filter/tests/data/gen_iirfilt_data.m           |  222 +
 src/filter/tests/data/iirfilt_cccf_data_h3x64.c    |  170 +
 src/filter/tests/data/iirfilt_cccf_data_h5x64.c    |  174 +
 src/filter/tests/data/iirfilt_cccf_data_h7x64.c    |  178 +
 src/filter/tests/data/iirfilt_crcf_data_h3x64.c    |  170 +
 src/filter/tests/data/iirfilt_crcf_data_h5x64.c    |  174 +
 src/filter/tests/data/iirfilt_crcf_data_h7x64.c    |  178 +
 src/filter/tests/data/iirfilt_rrrf_data_h3x64.c    |  168 +
 src/filter/tests/data/iirfilt_rrrf_data_h5x64.c    |  172 +
 src/filter/tests/data/iirfilt_rrrf_data_h7x64.c    |  176 +
 src/filter/tests/fftfilt_autotest.h                |  114 +
 src/filter/tests/fftfilt_runtest.c                 |  175 +
 src/filter/tests/fftfilt_xxxf_autotest.c           |  119 +
 src/filter/tests/filter_crosscorr_autotest.c       |  100 +
 src/filter/tests/firdecim_autotest.h               |  118 +
 src/filter/tests/firdecim_runtest.c                |  135 +
 src/filter/tests/firdecim_xxxf_autotest.c          |  131 +
 src/filter/tests/firdes_autotest.c                 |  120 +
 src/filter/tests/firdespm_autotest.c               |  147 +
 src/filter/tests/firfilt_autotest.h                |  114 +
 src/filter/tests/firfilt_runtest.c                 |  132 +
 src/filter/tests/firfilt_xxxf_autotest.c           |  119 +
 src/filter/tests/firhilb_autotest.c                |  117 +
 src/filter/tests/firinterp_autotest.c              |  152 +
 src/filter/tests/firpfb_autotest.c                 |   78 +
 src/filter/tests/groupdelay_autotest.c             |  240 +
 src/filter/tests/iirdes_autotest.c                 |  300 +
 src/filter/tests/iirfilt_autotest.h                |  115 +
 src/filter/tests/iirfilt_runtest.c                 |  135 +
 src/filter/tests/iirfilt_xxxf_autotest.c           |  110 +
 src/filter/tests/iirfiltsos_rrrf_autotest.c        |  121 +
 src/filter/tests/msresamp_crcf_autotest.c          |  167 +
 src/filter/tests/resamp2_crcf_autotest.c           |  175 +
 src/filter/tests/resamp_crcf_autotest.c            |  167 +
 src/filter/tests/symsync_crcf_autotest.c           |  177 +
 src/filter/tests/symsync_rrrf_autotest.c           |  173 +
 src/framing/bench/bpacketsync_benchmark.c          |  100 +
 src/framing/bench/bpresync_benchmark.c             |   93 +
 src/framing/bench/bsync_benchmark.c                |   90 +
 src/framing/bench/detector_benchmark.c             |   95 +
 src/framing/bench/flexframesync_benchmark.c        |  130 +
 src/framing/bench/framesync64_benchmark.c          |  103 +
 src/framing/bench/gmskframesync_benchmark.c        |  178 +
 src/framing/bench/presync_benchmark.c              |   93 +
 src/framing/packetizer.readme.txt                  |   58 +
 src/framing/readme.flexframe.txt                   |   43 +
 src/framing/readme.framesync64.md                  |   38 +
 src/framing/readme.gmskframe.txt                   |   53 +
 src/framing/readme.ofdm.txt                        |  116 +
 src/framing/src/bpacketgen.c                       |  289 +
 src/framing/src/bpacketsync.c                      |  456 ++
 src/framing/src/bpresync.c                         |  231 +
 src/framing/src/bpresync_cccf.c                    |   53 +
 src/framing/src/bsync.c                            |  182 +
 src/framing/src/bsync_cccf.c                       |   47 +
 src/framing/src/bsync_crcf.c                       |   47 +
 src/framing/src/bsync_rrrf.c                       |   47 +
 src/framing/src/detector_cccf.c                    |  531 ++
 src/framing/src/flexframegen.c                     |  471 ++
 src/framing/src/flexframesync.c                    |  854 +++
 src/framing/src/framedatastats.c                   |   67 +
 src/framing/src/framegen64.c                       |  173 +
 src/framing/src/framesync64.c                      |  565 ++
 src/framing/src/framesyncstats.c                   |   89 +
 src/framing/src/gmskframegen.c                     |  434 ++
 src/framing/src/gmskframesync.c                    |  930 +++
 src/framing/src/msource.c                          |  547 ++
 src/framing/src/msourcecf.c                        |   48 +
 src/framing/src/ofdmflexframegen.c                 |  682 ++
 src/framing/src/ofdmflexframesync.c                |  657 ++
 src/framing/src/presync.c                          |  236 +
 src/framing/src/presync_cccf.c                     |   56 +
 src/framing/src/qdetector_cccf.c                   |  608 ++
 src/framing/src/qpacketmodem.c                     |  318 +
 src/framing/src/qpilotgen.c                        |  160 +
 src/framing/src/qpilotsync.c                       |  299 +
 src/framing/src/symstream.c                        |  169 +
 src/framing/src/symstreamcf.c                      |   45 +
 src/framing/src/symtrack.c                         |  330 +
 src/framing/src/symtrack_cccf.c                    |   55 +
 src/framing/tests/bpacketsync_autotest.c           |   97 +
 src/framing/tests/bsync_autotest.c                 |  152 +
 src/framing/tests/detector_autotest.c              |  174 +
 src/framing/tests/flexframesync_autotest.c         |   91 +
 src/framing/tests/framesync64_autotest.c           |   92 +
 src/framing/tests/qdetector_cccf_autotest.c        |  308 +
 src/framing/tests/qpacketmodem_autotest.c          |  142 +
 src/framing/tests/qpilotsync_autotest.c            |  184 +
 src/libliquid.c                                    |   40 +
 src/math/bench/polyfit_benchmark.c                 |   71 +
 src/math/src/math.bessel.c                         |  161 +
 src/math/src/math.c                                |  184 +
 src/math/src/math.complex.c                        |  116 +
 src/math/src/math.gamma.c                          |  158 +
 src/math/src/math.trig.c                           |   81 +
 src/math/src/modular_arithmetic.c                  |  217 +
 src/math/src/poly.c                                |   45 +
 src/math/src/poly.common.c                         |   92 +
 src/math/src/poly.expand.c                         |  236 +
 src/math/src/poly.findroots.c                      |  330 +
 src/math/src/poly.lagrange.c                       |  153 +
 src/math/src/polyc.c                               |   45 +
 src/math/src/polycf.c                              |   45 +
 src/math/src/polyf.c                               |   45 +
 src/math/src/windows.c                             |  349 +
 src/math/tests/kbd_autotest.c                      |   59 +
 src/math/tests/math_autotest.c                     |   82 +
 src/math/tests/math_bessel_autotest.c              |  170 +
 src/math/tests/math_complex_autotest.c             |  347 +
 src/math/tests/math_gamma_autotest.c               |  207 +
 src/math/tests/polynomial_autotest.c               |  406 ++
 src/matrix/bench/matrixf_inv_benchmark.c           |   66 +
 src/matrix/bench/matrixf_linsolve_benchmark.c      |   72 +
 src/matrix/bench/matrixf_mul_benchmark.c           |   70 +
 src/matrix/bench/smatrixf_mul_benchmark.c          |   96 +
 src/matrix/src/matrix.base.c                       |   75 +
 src/matrix/src/matrix.c                            |   51 +
 src/matrix/src/matrix.cgsolve.c                    |  183 +
 src/matrix/src/matrix.chol.c                       |  102 +
 src/matrix/src/matrix.gramschmidt.c                |  127 +
 src/matrix/src/matrix.inv.c                        |  166 +
 src/matrix/src/matrix.linsolve.c                   |   86 +
 src/matrix/src/matrix.ludecomp.c                   |  142 +
 src/matrix/src/matrix.math.c                       |  369 ++
 src/matrix/src/matrix.qrdecomp.c                   |  112 +
 src/matrix/src/matrixc.c                           |   53 +
 src/matrix/src/matrixcf.c                          |   53 +
 src/matrix/src/matrixf.c                           |   51 +
 src/matrix/src/smatrix.c                           |  606 ++
 src/matrix/src/smatrix.common.c                    |   43 +
 src/matrix/src/smatrixb.c                          |  112 +
 src/matrix/src/smatrixf.c                          |   48 +
 src/matrix/src/smatrixi.c                          |   49 +
 src/matrix/tests/data/README.md                    |    7 +
 src/matrix/tests/data/matrixcf_data_add.c          |   97 +
 src/matrix/tests/data/matrixcf_data_aug.c          |  107 +
 src/matrix/tests/data/matrixcf_data_chol.c         |   66 +
 src/matrix/tests/data/matrixcf_data_inv.c          |   84 +
 src/matrix/tests/data/matrixcf_data_linsolve.c     |   72 +
 src/matrix/tests/data/matrixcf_data_ludecomp.c     |   95 +
 src/matrix/tests/data/matrixcf_data_mul.c          |   84 +
 src/matrix/tests/data/matrixcf_data_qrdecomp.c     |   85 +
 src/matrix/tests/data/matrixcf_data_transmul.c     |  145 +
 src/matrix/tests/data/matrixf_data_add.c           |   97 +
 src/matrix/tests/data/matrixf_data_aug.c           |  107 +
 src/matrix/tests/data/matrixf_data_cgsolve.c       |  117 +
 src/matrix/tests/data/matrixf_data_chol.c          |   66 +
 src/matrix/tests/data/matrixf_data_gramschmidt.c   |   58 +
 src/matrix/tests/data/matrixf_data_inv.c           |   84 +
 src/matrix/tests/data/matrixf_data_linsolve.c      |   72 +
 src/matrix/tests/data/matrixf_data_ludecomp.c      |   95 +
 src/matrix/tests/data/matrixf_data_mul.c           |   84 +
 src/matrix/tests/data/matrixf_data_qrdecomp.c      |   85 +
 src/matrix/tests/data/matrixf_data_transmul.c      |  145 +
 src/matrix/tests/gendata_write_autotests.m         |  350 +
 src/matrix/tests/gendata_write_header.m            |   39 +
 src/matrix/tests/gendata_write_matrix.m            |   63 +
 src/matrix/tests/matrix_data.h                     |  136 +
 src/matrix/tests/matrixcf_autotest.c               |  407 ++
 src/matrix/tests/matrixf_autotest.c                |  422 ++
 src/matrix/tests/smatrixb_autotest.c               |  367 ++
 src/matrix/tests/smatrixf_autotest.c               |  127 +
 src/matrix/tests/smatrixi_autotest.c               |  123 +
 src/modem/bench/freqdem_benchmark.c                |   78 +
 src/modem/bench/freqmod_benchmark.c                |   81 +
 src/modem/bench/fskdem_benchmark.c                 |  103 +
 src/modem/bench/fskmod_benchmark.c                 |   99 +
 src/modem/bench/gmskmodem_benchmark.c              |   95 +
 src/modem/bench/modem_demodsoft_benchmark.c        |  161 +
 src/modem/bench/modem_demodulate_benchmark.c       |  162 +
 src/modem/bench/modem_modulate_benchmark.c         |  141 +
 src/modem/src/ampmodem.c                           |  324 +
 src/modem/src/cpfskdem.c                           |  460 ++
 src/modem/src/cpfskmod.c                           |  311 +
 src/modem/src/freqdem.c                            |  119 +
 src/modem/src/freqmod.c                            |  139 +
 src/modem/src/fskdem.c                             |  239 +
 src/modem/src/fskmod.c                             |  147 +
 src/modem/src/gmskdem.c                            |  322 +
 src/modem/src/gmskmod.c                            |  152 +
 src/modem/src/modem_apsk.c                         |  190 +
 src/modem/src/modem_apsk_const.c                   |  249 +
 src/modem/src/modem_arb.c                          |  356 +
 src/modem/src/modem_arb_const.c                    |  597 ++
 src/modem/src/modem_ask.c                          |   98 +
 src/modem/src/modem_bpsk.c                         |   95 +
 src/modem/src/modem_common.c                       |  711 ++
 src/modem/src/modem_dpsk.c                         |  117 +
 src/modem/src/modem_ook.c                          |   72 +
 src/modem/src/modem_psk.c                          |  113 +
 src/modem/src/modem_qam.c                          |  145 +
 src/modem/src/modem_qpsk.c                         |  109 +
 src/modem/src/modem_sqam128.c                      |  121 +
 src/modem/src/modem_sqam32.c                       |  120 +
 src/modem/src/modem_utilities.c                    |  312 +
 src/modem/src/modemf.c                             |   64 +
 src/modem/tests/cpfskmodem_autotest.c              |  120 +
 src/modem/tests/freqmodem_autotest.c               |   71 +
 src/modem/tests/fskmodem_autotest.c                |   82 +
 src/modem/tests/modem_autotest.c                   |  118 +
 src/modem/tests/modem_demodsoft_autotest.c         |  130 +
 src/modem/tests/modem_demodstats_autotest.c        |  159 +
 src/multichannel/bench/firpfbch2_crcf_benchmark.c  |   85 +
 src/multichannel/bench/firpfbch_crcf_benchmark.c   |   87 +
 .../bench/ofdmframesync_acquire_benchmark.c        |  105 +
 .../bench/ofdmframesync_rxsymbol_benchmark.c       |  113 +
 src/multichannel/ofdmoqam.readme.txt               |   51 +
 src/multichannel/readme.ofdm.txt                   |  116 +
 src/multichannel/src/firpfbch.c                    |  411 ++
 src/multichannel/src/firpfbch2.c                   |  358 +
 src/multichannel/src/firpfbch_cccf.c               |   53 +
 src/multichannel/src/firpfbch_crcf.c               |   55 +
 src/multichannel/src/firpfbch_old.c                |  313 +
 src/multichannel/src/ofdmframe.common.c            |  282 +
 src/multichannel/src/ofdmframegen.c                |  362 ++
 src/multichannel/src/ofdmframesync.c               | 1303 ++++
 src/multichannel/tests/firpfbch2_crcf_autotest.c   |   99 +
 .../tests/firpfbch_crcf_analyzer_autotest.c        |  152 +
 .../tests/firpfbch_crcf_synthesizer_autotest.c     |  145 +
 src/multichannel/tests/ofdmframesync_autotest.c    |  145 +
 src/nco/bench/nco_benchmark.c                      |  101 +
 src/nco/bench/vco_benchmark.c                      |   98 +
 src/nco/src/nco.c                                  |  385 ++
 src/nco/src/nco.utilities.c                        |   66 +
 src/nco/src/nco_crcf.c                             |   36 +
 src/nco/tests/data/nco_sincos_fsqrt1_2.c           |  286 +
 src/nco/tests/data/nco_sincos_fsqrt1_3.c           |  286 +
 src/nco/tests/data/nco_sincos_fsqrt1_5.c           |  286 +
 src/nco/tests/data/nco_sincos_fsqrt1_7.c           |  286 +
 src/nco/tests/gen_nco_data.m                       |   61 +
 src/nco/tests/nco_crcf_frequency_autotest.c        |  103 +
 src/nco/tests/nco_crcf_phase_autotest.c            |  260 +
 src/nco/tests/nco_crcf_pll_autotest.c              |  147 +
 src/nco/tests/unwrap_phase_autotest.c              |   64 +
 src/optim/src/chromosome.c                         |  322 +
 src/optim/src/gasearch.c                           |  366 ++
 src/optim/src/gradsearch.c                         |  325 +
 src/optim/src/optim.common.c                       |   76 +
 src/optim/src/optim.h                              |   51 +
 src/optim/src/qnsearch.c                           |  274 +
 src/optim/src/utilities.c                          |  140 +
 src/optim/tests/gradsearch_autotest.c              |  160 +
 src/quantization/bench/compander_benchmark.c       |   73 +
 src/quantization/bench/quantizer_benchmark.c       |   71 +
 src/quantization/src/compand.c                     |   92 +
 src/quantization/src/quantizer.c                   |  102 +
 src/quantization/src/quantizer.inline.c            |   86 +
 src/quantization/src/quantizercf.c                 |   37 +
 src/quantization/src/quantizerf.c                  |   37 +
 src/quantization/tests/compand_autotest.c          |   80 +
 src/quantization/tests/quantize_autotest.c         |   55 +
 src/random/bench/random_benchmark.c                |  155 +
 src/random/src/rand.c                              |   54 +
 src/random/src/randexp.c                           |   83 +
 src/random/src/randgamma.c                         |  180 +
 src/random/src/randn.c                             |  107 +
 src/random/src/randnakm.c                          |  113 +
 src/random/src/randricek.c                         |  113 +
 src/random/src/randweib.c                          |  101 +
 src/random/src/scramble.c                          |  100 +
 src/random/tests/random_autotest.c                 |  137 +
 src/random/tests/scramble_autotest.c               |  136 +
 src/sequence/bench/bsequence_benchmark.c           |   71 +
 src/sequence/src/bsequence.c                       |  338 +
 src/sequence/src/msequence.c                       |  243 +
 src/sequence/tests/bsequence_autotest.c            |  204 +
 src/sequence/tests/complementary_codes_autotest.c  |   76 +
 src/sequence/tests/msequence_autotest.c            |   94 +
 src/utility/src/bshift_array.c                     |  162 +
 src/utility/src/byte_utilities.c                   |  235 +
 src/utility/src/msb_index.c                        |  142 +
 src/utility/src/msb_index.x86.s                    |   13 +
 src/utility/src/pack_bytes.c                       |  340 +
 src/utility/src/shift_array.c                      |  153 +
 src/utility/tests/bdotprod_gentest.m               |   20 +
 src/utility/tests/bshift_array_autotest.c          |  218 +
 src/utility/tests/count_bits_autotest.c            |  185 +
 src/utility/tests/pack_bytes_autotest.c            |  295 +
 src/utility/tests/shift_array_autotest.c           |  181 +
 src/vector/src/vector_add.c                        |   84 +
 src/vector/src/vector_mul.c                        |   84 +
 src/vector/src/vector_norm.c                       |   94 +
 src/vector/src/vector_trig.c                       |  143 +
 src/vector/src/vectorcf_add.port.c                 |   34 +
 src/vector/src/vectorcf_mul.port.c                 |   34 +
 src/vector/src/vectorcf_norm.port.c                |   34 +
 src/vector/src/vectorcf_trig.port.c                |   36 +
 src/vector/src/vectorf_add.port.c                  |   34 +
 src/vector/src/vectorf_mul.port.c                  |   34 +
 src/vector/src/vectorf_norm.port.c                 |   34 +
 src/vector/src/vectorf_trig.port.c                 |   36 +
 813 files changed, 155948 insertions(+)

diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..704946b
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,108 @@
+
+Major improvements since v1.2.0
+  * documentation
+    - added script to auto-generate code listings when pygmentize
+      is unavailable (not as good, but still functional)
+  * dotprod
+    - adding method to compute x^T * x of a vector (sum of squares)
+  * fft
+    - general speed improvements for one-dimensional FFTs
+  * filter
+    - add linear interpolation for arbitrary resamp output
+    - added autotests for validating performance of both the
+      resamp and msresamp objects
+    - added new fftfilt family of objects to realize linear filter
+      with fast Fourier transforms
+  * framing
+    - adding generic callback function definition for all framing
+      structures
+    - adding pre-demodulator synchronizer/detector
+    - moved interleaver and packetizer objects to `fec` module
+    - restructuring frame[gen|sync]64 and flexframe[gen|sync]
+      objects with vastly improved performance and reliability
+  * matrix
+    - adding smatrix family of objects (sparse matrices)
+    - improving linear solver methods (roughly doubled speed)
+  * modem
+    - re-organizing internal modem code (no interface change)
+  * multicarrier
+    - adding OFDM framing option for window tapering
+    - simplfying OFDM framing for generating preamble symbols (all
+      generated OFDM symbols are the same length)
+    - adding run-time option for debugging ofdmframesync
+  * optim
+    - gradsearch (gradient search) uses internal linesearch for
+      significant speed increase and better reliability
+    - gradsearch interface greatly simplified
+  * vector
+    - new module to simplify basic vector operations
+
+Major improvements for v1.2.0
+  * dotprod
+    - leveraging SIMD extensions for vector operations (SSE2, SSE3)
+  * fft
+    - completely re-structured internal transform strategy including
+      Cooley-Tukey mixed-radix algorithm, Rader algorithm for FFTs of
+      prime length, and specific codelets for small-size transforms.
+  * math
+    - new modular arithmetic methods (prime factor, totient, etc.)
+  * modem
+    - new API creates linear modem objects with one argument, e.g.
+      LIQUID_MODEM_QAM16
+    - new type definitions for analog modems
+
+Major improvements for v1.1.0
+  * build
+    - simplifying build environment by explicitly defining object
+      dependencies (no longer auto-generated; much faster now)
+  * documentation
+    - new tutorials (ofdmflexframe)
+    - sections on new objects/methods (msresamp, gmsk filter design,
+      soft-decision demodulation)
+    - adding useful figures (polyfit-lagrange, symsync)
+    - adding BER performance plots for new FEC schemes
+    - adding BER performance plots for sqam32 and sqam128
+  * agc
+    - fixing scaling issues
+    - improving computation speed
+    - simplifying interface with a single, unified design model
+  * equalization
+    - adding support for decision-directed equalizers
+  * fec
+    - adding soft decoding (when available) for forward error-correction
+      schemes; generally improves performance by about 2 dB Eb/N0
+    - adding half-rate Golay(24,12) code
+    - adding SEC-DED codes: (22,16), (39,32), (72,64)
+  * filter
+    - firdes: adding Nyquist prototyping design method
+    - firdes: adding new GMSK receive filter design method
+    - interp: porting to efficient polyphase filterbank implementation,
+      adding prototype create() method
+    - adding multi-stage resampler for efficient decimation and
+      interpolation
+  * framing
+    - adding ofdmflexframe family of objects capable of defining which
+      subcarriers are nulls/pilots/data, and easily loading data into
+      frames. Very similar to 'flexframe' in usage.
+    - supporting soft packet decoding (interleaving, etc.)
+    - adding gmskframe generator and synchronizer object; simple,
+      reliable
+  * matrix
+    - adding Cholesky factorization A = R^T * R (for positive definite
+      matrices)
+    - adding conjugate gradient solver (for positive definite matrices)
+  * modem
+    - adding simple on/off keying (OOK) type
+    - adding 256-APSK type (6,18,32,36,46,54,64)
+    - adding 'square' (cross) 32-, 128-QAM types
+    - adding 'optimal' 64-, 128-, and 256-QAM constellations
+    - improved speed of most schemes' modulate/demodulate
+      implementations
+    - adding soft-decision (log-likelihood ratio) demodulation
+    - adding GMSK modulation/demodulation with improved filter design
+  * multicarrier
+    - ofdmframe: improving synchronization and reliability in
+      interference environments, enabling squelch, improving
+      equalization
+  * optim
+    - simplified interface to gradient search
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ba884cb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 - 2016 Joseph Gaeddert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fa83de6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,156 @@
+
+liquid-dsp
+==========
+
+Software-Defined Radio Digital Signal Processing Library
+
+liquid-dsp is a free and open-source digital signal processing (DSP)
+library designed specifically for software-defined radios on embedded
+platforms. The aim is to provide a lightweight DSP library that does not
+rely on a myriad of external dependencies or proprietary and otherwise
+cumbersome frameworks. All signal processing elements are designed to be
+flexible, scalable, and dynamic, including filters, filter design,
+oscillators, modems, synchronizers, and complex mathematical operations.
+
+For more information, please refer to the documentation:
+
+  * online HTML version: [http://liquidsdr.org/doc](http://liquidsdr.org/doc)
+  * pre-compiled `.pdf` version: [liquid.pdf](http://liquidsdr.org/downloads/liquid.pdf) (4.4 MB).
+
+Installation and Dependencies
+-----------------------------
+
+liquid-dsp only relies on `libc` and `libm` (standard C and math)
+libraries to run; however liquid will take advantage of other packages
+(such as [FFTW](http://www.fftw.org)) if they are available.
+
+### Getting the source code ###
+
+There are two primary ways of obtaining the source code:
+
+1. Clone the entire [repository](http://github.com/jgaeddert/liquid-dsp)
+   (recommended)
+        
+        $ git clone git://github.com/jgaeddert/liquid-dsp.git
+
+2. or download the [tarball](http://liquidsdr.org/downloads/liquid-dsp-1.2.0.tar.gz)
+   (2.6 MB), validate the checksum, and unpack 
+
+        $ wget http://liquidsdr.org/downloads/liquid-dsp-1.2.0.tar.gz
+        $ wget http://liquidsdr.org/downloads/liquid-dsp.md5
+        $ md5sum --check liquid-dsp.md5
+        $ tar -xf liquid-dsp-1.2.0.tar.gz
+
+### Installation ###
+
+Once you have obtained a copy of the source code, you can now build the
+DSP library (NOTE: if you chose to clone the repository, you will need
+to also run the additional `./bootstrap.sh` script before configuring):
+
+    $ ./bootstrap.sh     # <- only if you cloned the Git repo
+    $ ./configure
+    $ make
+    $ sudo make install
+
+If you are installing on Linux for the first time, you will also need
+to rebind your dynamic libraries with `sudo ldconfig` to make the
+shared object available.
+
+If you decide that you want to remove the installed DSP library, simply
+run
+
+    $ sudo make uninstall
+
+### Run all test scripts ###
+
+Source code validation is a critical step in any software library,
+particulary for verifying the portability of code to different
+processors and platforms. Packaged with liquid-dsp are a number of
+automatic test scripts to validate the correctness of the source code.
+The test scripts are located under each module's `tests/` directory and
+take the form of a C source file. liquid includes a framework for
+compiling, linking, and running the tests, and can be invoked with the
+make target `check`, viz.
+
+    $ make check
+
+### Examples ###
+
+Nearly all signal processing elements have a corresponding example in
+the `examples/` directory.  Most example scripts generate an output
+`.m` file for plotting with [GNU octave](http://www.gnu.org/software/octave/)
+All examples are built as stand-alone programs and can be compiled with
+the make target `examples`:
+
+    $ make examples
+
+Sometimes, however, it is useful to build one example individually.
+This can be accomplished by directly targeting its binary
+(e.g. `make examples/modem_example`). The example then can be run at the
+command line, viz. `./examples/modem_example`.
+
+### Benchmarking tool ###
+
+Packaged with liquid are benchmarks to determine the speed each signal
+processing element can run on your machine. Initially the tool provides
+an estimate of the processor's clock frequency and will then estimate
+the number of trials so that each benchmark will take between 50 and
+500 ms to run. You can build and run the benchmark program with the
+following command:
+
+    $ make bench
+
+Available Modules
+-----------------
+
+  * _agc_: automatic gain control, received signal strength
+  * _audio_: source audio encoders/decoders: cvsd, filterbanks
+  * _buffer_: internal buffering, circular/static, ports (threaded)
+  * _channel_: additive noise, multi-path fading, carrier phase/frequency
+        offsets, timing phase/rate offsets
+  * _dotprod_: inner dot products (real, complex), vector sum of squares
+  * _equalization_: adaptive equalizers: least mean-squares, recursive
+        least squares, semi-blind
+  * _fec_: basic forward error correction codes including several
+        Hamming codes, single error correction/double error detection,
+        Golay block code, as well as several checksums and cyclic
+        redundancy checks, interleaving, soft decoding
+  * _fft_: fast Fourier transforms (arbitrary length), discrete sin/cos
+        transforms
+  * _filter_: finite/infinite impulse response, polyphase, hilbert,
+        interpolation, decimation, filter design, resampling, symbol
+        timing recovery
+  * _framing_: flexible framing structures for amazingly easy packet
+        software radio; dynamically adjust modulation and coding on the
+        fly with single- and multi-carrier framing structures
+  * _math_: transcendental functions not in the C standard library
+        (gamma, besseli, etc.), polynomial operations (curve-fitting,
+        root-finding, etc.)
+  * _matrix_: basic math, LU/QR/Cholesky factorization, inversion,
+        Gauss elimination, Gram-Schmidt decomposition, linear solver,
+        sparse matrix representation
+  * _modem_: modulate, demodulate, PSK, differential PSK, QAM, optimal
+        QAM, as well as analog and non-linear digital modulations GMSK)
+  * _multichannel_: filterbank channelizers, OFDM
+  * _nco_: numerically-controlled oscillator: mixing, frequency
+        synthesis, phase-locked loops
+  * _optim_: (non-linear optimization) Newton-Raphson, evoluationary
+        algorithms, gradient descent, line search
+  * _quantization_: analog/digital converters, compression/expansion
+  * _random_: (random number generators) uniform, exponential, gamma,
+        Nakagami-m, Gauss, Rice-K, Weibull
+  * _sequence_: linear feedback shift registers, complementary codes,
+        maximal-length sequences
+  * _utility_: useful miscellany, mostly bit manipulation (shifting,
+        packing, and unpacking of arrays)
+  * _vector_: generic vector operations
+
+### License ###
+
+liquid projects are released under the X11/MIT license.
+Short version: this code is copyrighted to me (Joseph D. Gaeddert),
+I give you permission to do wantever you want with it except remove my name
+from the credits. See the LICENSE file or
+[http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)
+for specific terms.
+
diff --git a/TROUBLESHOOTING b/TROUBLESHOOTING
new file mode 100644
index 0000000..05cccd9
--- /dev/null
+++ b/TROUBLESHOOTING
@@ -0,0 +1,73 @@
+=========================================================================
+ Troubleshooting
+=========================================================================
+
+Here is a list of common problems and potential solutions.
+
+=========================================================================
+ Q1     When I try to compile I get an error like
+        "internal compiler error: in gimplify_expr, at gimplify.c:7153"
+
+ A1(a)  This is a bug with gcc v4.5.1 and v4.5.2 dealing with complex
+        data type, e.g. "float complex"
+        (see: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47150).
+        The best option is to update your compiler to a more recent
+        version, revert to an older version, or apply a patch.
+      
+ A1(b)  Alternatively, if you are observing this error with ellip.c you
+        can probably comment out the offending lines; however this will
+        render the elliptic filter generator in liquid-dsp useless. This
+        is probably the fastest solution if you aren't concerned with
+        generating elliptic filters.
+
+=========================================================================
+ Q2     When compiling I get linker errors:
+        /liquid-dsp/src/fec/src/fec_conv.c:210: undefined reference to `create_viterbi27'
+        /liquid-dsp/src/fec/src/fec_conv.c:210: undefined reference to `init_viterbi27'
+        /liquid-dsp/src/fec/src/fec_conv.c:210: undefined reference to `update_viterbi27_blk'
+        ...
+
+ NOTE   Issue should be resolved after version 1.2.0 (commit 2f6ed1c9d39f4)
+
+ A2     This is because the linker cannot find the libfec library.
+        The 'configure' script checks to make sure that the fec library
+        is available before compiling. If found the additional forward
+        error-correction codecs (e.g. convolutional) will be built and
+        used. If the headers and library are not found, then the
+        liquid-dsp build environment will ignore these. However if, for
+        example, the fec headers are found but the libfec is not, the
+        build script it will try to build the additional forward error-
+        correction codecs without linking to the proper libraries. This
+        is a bug with the current version of liquid-dsp. The quick fix
+        is to run "./configure" and then edit "config.h" to ignore the
+        fec headers.
+
+        Open "config.h" and change the line (approx. 13) from
+          #define HAVE_FEC_H 1
+        to
+          /* #undef HAVE_FEC_H */
+        This will disable building the additional FEC codecs (such as
+        convolutional) but will still build the internal codes (such as
+        the Hamming codes). Now run "make" as normal.
+
+=========================================================================
+Q3      When I try compiling on OS 10.9 I get the following error when trying
+        to run "./configure":
+
+        configure: error: Need standard c library!
+
+        or 
+
+        configure: error: Could not use standard c library!
+
+A3      This is likely a result of using Apple's default build of gcc (LLVM
+        version 5.0) which by default uses extremely strict checking for
+        compiling. Because the script generated by configure is pretty sloppy,
+        LLVM coughs when trying to build it. The simple fix is to update the
+        repository and specify a different compiler by setting your
+        environment variable CC, viz
+
+        $ export CC="/usr/local/bin/gcc-4.8"
+
+        I know this is a work-around; I'm working on a permanent fix.
+
diff --git a/autotest/autotest.c b/autotest/autotest.c
new file mode 100644
index 0000000..3de6450
--- /dev/null
+++ b/autotest/autotest.c
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// autotest.c
+//
+// This file is used in conjunction with autotest_include.h (generated with
+// autotest_gen.py) to produce an executable for automatically testing the
+// various signal processing algorithms in liquid.
+
+// default include headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include "autotest/autotest.h"
+
+void usage()
+{
+    // help
+    printf("Usage: xautotest [OPTION]\n");
+    printf("Execute autotest scripts for liquid-dsp library.\n");
+    printf("  -h            display this help and exit\n");
+    printf("  -t <id>       run specific test\n");
+    printf("  -p <id>       run specific package\n");
+    printf("  -r            run all tests, random order\n");
+    printf("  -L            lists all scripts\n");
+    printf("  -l            lists all packages\n");
+    printf("  -x            stop on fail\n");
+    printf("  -s <string>   run all tests matching search string\n");
+    printf("  -v            verbose\n");
+    printf("  -q            quiet\n");
+    printf("  -o <filename> output file (json)\n");
+}
+
+// define autotest function pointer
+typedef void(autotest_function_t) (void);
+
+// define autotest_t
+typedef struct {
+    unsigned int id;                // test identification
+    autotest_function_t * api;      // test function, e.g. autotest_modem()
+    const char* name;               // test name
+    long unsigned int num_checks;   // number of checks that were run for this test
+    long unsigned int num_passed;   // number of checks that passed
+    long unsigned int num_failed;   // number of checks that failed
+    long unsigned int num_warnings; // number of warnings 
+    float percent_passed;           // percent of checks that passed
+    int executed;                   // was the test executed?
+    int pass;                       // did the test pass? (i.e. no failures)
+} autotest_t;
+
+// define package_t
+typedef struct {
+    unsigned int id;            // package identification
+    unsigned int index;         // index of first autotest
+    unsigned int num_scripts;   // number of tests in package
+    const char* name;           // package name
+    int executed;               // were any tests executed?
+} package_t;
+
+// include auto-generated autotest header
+//
+// defines the following symbols:
+//   #define AUTOSCRIPT_VERSION
+//   #define NUM_AUTOSCRIPTS
+//   autotest_t scripts[NUM_AUTOSCRIPTS]
+//   #define NUM_PACKAGES
+//   struct package_t packages[NUM_PACKAGES]
+#include "../autotest_include.h"
+
+// 
+// helper functions:
+//
+
+// execute a specific test
+void execute_autotest(autotest_t * _test, int _verbose);
+
+// execute a specific package
+void execute_package(package_t * _p, int _verbose);
+
+// execute a specific package if string matches
+void execute_package_search(package_t * _p, char * _str, int _verbose);
+
+// print all autotest results
+void print_autotest_results(autotest_t * _test);
+
+// print the results of a particular package
+void print_package_results(package_t * _p);
+
+// print all unstable tests (those which failed or gave warnings)
+void print_unstable_tests(void);
+
+// print list of tests
+void print_test_list(void);
+
+// print list of packages
+void print_package_list(void);
+
+// print basic autotest results to stdout
+int export_results(char * _filename);
+
+// main function
+int main(int argc, char *argv[])
+{
+    // options
+    enum {RUN_ALL,              // run all tests
+          RUN_ALL_RANDOM,       // run all tests (random order)
+          RUN_SINGLE_TEST,      // run just a single test
+          RUN_SINGLE_PACKAGE,   // run just a single package
+          RUN_SEARCH
+    } mode = RUN_ALL;
+
+    // set defaults
+    unsigned int autotest_id        = 0;
+    unsigned int package_id         = 0;
+    int          verbose            = 1;
+    int          stop_on_fail       = 0;
+    int          rseed              = 0;
+    char         search_string[128] = "";
+    char         filename[256]      = "";
+
+    unsigned int i;
+
+    // get input options
+    int d;
+    while((d = getopt(argc,argv,"ht:p:rLlxs:vqo:")) != EOF){
+        switch (d) {
+        case 'h':
+            usage();
+            return 0;
+        case 't':
+            autotest_id = atoi(optarg);
+            mode = RUN_SINGLE_TEST;
+            break;
+        case 'p':
+            package_id = atoi(optarg);
+            mode = RUN_SINGLE_PACKAGE;
+            break;
+        case 'r':
+            mode = RUN_ALL_RANDOM;
+            break;
+        case 'L':
+            // list packages, scripts and exit
+            print_test_list();
+            return 0;
+        case 'l':
+            // list only packages and exit
+            print_package_list();
+            return 0;
+        case 'x':
+            stop_on_fail = 1;
+            break;
+        case 's':
+            mode = RUN_SEARCH;
+            strncpy(search_string, optarg, 128);
+            search_string[127] = '\0';
+            break;
+        case 'v':
+            verbose = 1;
+            liquid_autotest_verbose = 1;
+            break;
+        case 'q':
+            verbose = 0;
+            liquid_autotest_verbose = 0;
+            break;
+        case 'o':
+            strncpy(filename,optarg,255);
+            filename[255] = '\0';
+            break;
+        default:
+            return 1;
+        }
+    }
+
+    // validate results
+    if (autotest_id >= NUM_AUTOSCRIPTS) {
+        printf("error, cannot run autotest %u; index exceeded\n", autotest_id);
+        return -1;
+    } else if (package_id >= NUM_PACKAGES) {
+        printf("error, cannot run package %u; index exceeded\n", package_id);
+        return -1;
+    }
+
+    unsigned int n=0;
+    switch (mode) {
+    case RUN_ALL:
+        for (i=0; i<NUM_PACKAGES; i++) {
+            execute_package( &packages[i], verbose );
+
+            n++;
+            if (stop_on_fail && liquid_autotest_num_failed > 0)
+                break;
+        }
+
+        for (i=0; i<n; i++) {
+            if (verbose)
+                print_package_results( &packages[i] );
+        }
+        break;
+    case RUN_ALL_RANDOM:
+        // initialize with large random number
+        i = (rseed + 8191) % NUM_AUTOSCRIPTS;
+
+        while (n < NUM_AUTOSCRIPTS) {
+            i = (i + 524287) % NUM_AUTOSCRIPTS;
+            while (scripts[i].executed) {
+                i++;
+                if (i >= NUM_AUTOSCRIPTS)
+                    i %= NUM_AUTOSCRIPTS;
+            }
+
+            printf("executing test %4u (%4u / %4u)\n", i, n+1, NUM_AUTOSCRIPTS);
+            execute_autotest( &scripts[i], verbose );
+
+            n++;
+            if (stop_on_fail && liquid_autotest_num_failed > 0)
+                break;
+        }
+
+        for (i=0; i<n; i++) {
+            if (verbose)
+                print_autotest_results( &scripts[i] );
+        }
+        break;
+    case RUN_SINGLE_TEST:
+        execute_autotest( &scripts[autotest_id], verbose );
+        if (verbose)
+            print_autotest_results( &scripts[autotest_id] );
+        break;
+    case RUN_SINGLE_PACKAGE:
+        execute_package( &packages[package_id], verbose );
+        if (verbose)
+            print_package_results( &packages[package_id] );
+        break;
+    case RUN_SEARCH:
+        printf("running all scripts matching '%s'...\n", search_string);
+
+        // search all packages
+        for (i=0; i<NUM_PACKAGES; i++)
+            execute_package_search( &packages[i], search_string, verbose);
+
+        // print results
+        for (i=0; i<NUM_PACKAGES; i++) {
+            if (verbose && packages[i].executed)
+                print_package_results( &packages[i] );
+        }
+        break;
+    }
+
+    if (liquid_autotest_verbose)
+        print_unstable_tests();
+
+    autotest_print_results();
+
+    // export results
+    if (strcmp(filename,"")!=0)
+        export_results(filename);
+
+    return 0;
+}
+
+// execute a specific autotest
+//  _test       :   pointer to autotest object
+//  _verbose    :   verbose output flag
+void execute_autotest(autotest_t * _test,
+                      int _verbose)
+{
+    unsigned long int autotest_num_passed_init = liquid_autotest_num_passed;
+    unsigned long int autotest_num_failed_init = liquid_autotest_num_failed;
+    unsigned long int autotest_num_warnings_init = liquid_autotest_num_warnings;
+
+    // execute test
+    _test->api();
+
+    _test->num_passed = liquid_autotest_num_passed - autotest_num_passed_init;
+    _test->num_failed = liquid_autotest_num_failed - autotest_num_failed_init;
+    _test->num_warnings = liquid_autotest_num_warnings - autotest_num_warnings_init;
+    _test->num_checks = _test->num_passed + _test->num_failed;
+    _test->pass = (_test->num_failed==0) ? 1 : 0;
+    if (_test->num_checks > 0)
+        _test->percent_passed = 100.0f * (float) (_test->num_passed) / (float) (_test->num_checks);
+    else
+        _test->percent_passed = 0.0f;
+
+    _test->executed = 1;
+
+    //if (_verbose)
+    //    print_autotest_results(_test);
+}
+
+// execute a specific package
+//  _p          :   pointer to package object
+//  _verbose    :   verbose output flag
+void execute_package(package_t * _p,
+                     int _verbose)
+{
+    if (_verbose)
+        printf("%u: %s\n", _p->id, _p->name);
+    
+    unsigned int i;
+    for (i=0; i<_p->num_scripts; i++) {
+        execute_autotest( &scripts[ i + _p->index ], _verbose );
+    }
+    
+    _p->executed = 1;
+}
+
+// execute a specific package if string matches
+void execute_package_search(package_t * _p, char * _str, int _verbose)
+{
+    // see if search string matches autotest name
+    if (strstr(_p->name, _str) != NULL) {
+        // run the package
+        execute_package(_p, _verbose);
+    } else {
+
+        unsigned int i;
+        unsigned int i0 = _p->index;
+        unsigned int i1 = _p->num_scripts + i0;
+        for (i=i0; i<i1; i++) {
+            // see if search string matches autotest name
+            if (strstr(scripts[i].name, _str) != NULL) {
+                // run the autotest
+                execute_autotest( &scripts[i], _verbose );
+                _p->executed = 1;
+            }
+        }
+    }
+}
+
+// print results of a particular test
+void print_autotest_results(autotest_t * _test)
+{
+    if (!_test->executed)
+        printf("    %3u :   IGNORED ", _test->id);
+    else if (_test->pass)
+        printf("    %3u :   PASS    ", _test->id);
+    else
+        printf("    %3u : <<FAIL>>  ", _test->id);
+
+    printf("passed %4lu / %4lu checks (%5.1f%%) : %s\n",
+            _test->num_passed,
+            _test->num_checks,
+            _test->percent_passed,
+            _test->name);
+}
+
+// print results of a particular package
+void print_package_results(package_t * _p)
+{
+    unsigned int i;
+    printf("%u: %s:\n", _p->id, _p->name);
+    for (i=_p->index; i<(_p->index+_p->num_scripts); i++) {
+        //if ( scripts[i].executed ) // only print scripts that were executed
+        print_autotest_results( &scripts[i] );
+    }
+
+    printf("\n");
+}
+
+// print all unstable tests (those which failed or gave warnings)
+void print_unstable_tests(void)
+{
+    if (liquid_autotest_num_failed == 0 &&
+        liquid_autotest_num_warnings == 0)
+    {
+        return;
+    }
+
+    printf("==================================\n");
+    printf(" UNSTABLE TESTS:\n");
+    unsigned int t;
+    for (t=0; t<NUM_AUTOSCRIPTS; t++) {
+        if (scripts[t].executed) {
+            if (!scripts[t].pass) {
+                printf("    %3u : <<FAIL>> %s\n", scripts[t].id,
+                                                  scripts[t].name);
+            }
+            
+            if (scripts[t].num_warnings > 0) {
+                printf("    %3u : %4lu warnings %s\n", scripts[t].id,
+                                                       scripts[t].num_warnings,
+                                                       scripts[t].name);
+            }
+        }
+    }
+}
+
+void print_test_list(void)
+{
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<NUM_PACKAGES; i++) {
+        printf("%u: %s\n", packages[i].id, packages[i].name);
+        for (j=packages[i].index; j<packages[i].num_scripts+packages[i].index; j++)
+            printf("    %u: %s\n", scripts[j].id, scripts[j].name);
+    }
+}
+
+void print_package_list(void)
+{
+    unsigned int i;
+    for (i=0; i<NUM_PACKAGES; i++)
+        printf("%u: %s\n", packages[i].id, packages[i].name);
+}
+
+// print basic autotest results to stdout
+int export_results(char * _filename)
+{
+    // try to open file for writing
+    FILE * fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: export_results(), could not open '%s' for writing\n", _filename);
+        return -1;
+    }
+
+    // print header
+    fprintf(fid,"{\n");
+    fprintf(fid,"  \"build-info\" : {\n");
+    fprintf(fid,"  },\n");
+    fprintf(fid,"  \"tests\" : [\n");
+
+    // print each individual test as opposed to package
+    unsigned int i;
+    for (i=0; i<NUM_AUTOSCRIPTS; i++) {
+        fprintf(fid,"    {\"id\":%3u, \"pass\":%s, \"num_checks\":%4lu, \"num_passed\":%4lu, \"name\":\"%s\"}%s\n",
+                scripts[i].id,
+                scripts[i].num_failed == 0 ? "true" : "false",
+                scripts[i].num_checks,
+                scripts[i].num_passed,
+                scripts[i].name,
+                i==NUM_AUTOSCRIPTS-1 ? "" : ",");
+    }
+
+    fprintf(fid,"  ]\n");
+    fprintf(fid,"}\n");
+    fclose(fid);
+
+    if (liquid_autotest_verbose)
+        printf("output .json results written to %s\n", _filename);
+
+    // everything is fine
+    return 0;
+}
+
diff --git a/autotest/autotest.h b/autotest/autotest.h
new file mode 100644
index 0000000..c35b89c
--- /dev/null
+++ b/autotest/autotest.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Lightweight autotest header
+//
+// Similar to CxxTest, but written for liquid DSP code in C
+//
+
+#ifndef __LIQUID_AUTOTEST_H__
+#define __LIQUID_AUTOTEST_H__
+
+#include <stdio.h>
+#include <math.h>
+#include <inttypes.h>
+
+// total number of checks invoked
+extern unsigned long int liquid_autotest_num_checks;
+
+// total number of checks which passed
+extern unsigned long int liquid_autotest_num_passed;
+
+// total number of checks which failed
+extern unsigned long int liquid_autotest_num_failed;
+
+// total number of warnings
+extern unsigned long int liquid_autotest_num_warnings;
+
+// verbosity flag
+extern int liquid_autotest_verbose;
+
+// fail test
+// increment liquid_autotest_num_checks
+// increment liquid_autotest_num_failed
+void liquid_autotest_failed();
+
+// pass test
+// increment liquid_autotest_num_checks
+// increment liquid_autotest_num_passed
+void liquid_autotest_passed();
+
+// fail test, given expression
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _exprL      :   left side of expression (string)
+//  _valueL     :   left side of expression (value)
+//  _qualifier  :   expression qualifier
+//  _exprR      :   right side of expression (string)
+//  _valueR     :   right side of expression (value)
+void liquid_autotest_failed_expr(const char * _file,
+                                 unsigned int _line,
+                                 const char * _exprL,
+                                 double _valueL,
+                                 const char * _qualifier,
+                                 const char * _exprR,
+                                 double _valueR);
+
+// fail test with message
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _message    :   message string
+void liquid_autotest_failed_msg(const char * _file,
+                                unsigned int _line,
+                                const char * _message);
+
+// print basic autotest results to stdout
+void autotest_print_results(void);
+
+// export results to .json file
+int autotest_export_results(char * _filename);
+
+
+// print warning to stderr
+// increment liquid_autotest_num_warnings
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _message    :   message string
+void liquid_autotest_warn(const char * _file,
+                          unsigned int _line,
+                          const char * _message);
+
+
+// contend that data in two arrays are identical
+//  _x      :   input array [size: _n x 1]
+//  _y      :   input array [size: _n x 1]
+//  _n      :   input array size
+int liquid_autotest_same_data(unsigned char * _x,
+                              unsigned char * _y,
+                              unsigned int _n);
+
+// print array to standard out
+//  _x      :   input array [size: _n x 1]
+//  _n      :   input array size
+void liquid_autotest_print_array(unsigned char * _x,
+                                 unsigned int _n);
+
+// CONTEND_EQUALITY
+#define TEST_EQUALITY(F,L,EX,X,EY,Y)                                \
+{                                                                   \
+    if ((X)!=(Y))                                                   \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,EX,X,"==",EY,Y);            \
+    } else {                                                        \
+         liquid_autotest_passed();                                  \
+    }                                                               \
+}
+#define CONTEND_EQUALITY_FL(F,L,X,Y)      TEST_EQUALITY(F,L,#X,(X),#Y,(Y))
+#define CONTEND_EQUALITY(X,Y)             CONTEND_EQUALITY_FL(__FILE__,__LINE__,X,Y)
+
+// CONTEND_INEQUALITY
+#define TEST_INEQUALITY(F,L,EX,X,EY,Y)                              \
+{                                                                   \
+    if ((X)==(Y))                                                   \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,EX,X,"!=",EY,Y);            \
+    } else {                                                        \
+        liquid_autotest_passed();                                   \
+    }                                                               \
+}
+#define CONTEND_INEQUALITY_FL(F,L,X,Y)    TEST_INEQUALITY(F,L,#X,(X),#Y,(Y))
+#define CONTEND_INEQUALITY(X,Y)           CONTEND_INEQUALITY_FL(__FILE__,__LINE__,X,Y)
+
+// CONTEND_GREATER_THAN
+#define TEST_GREATER_THAN(F,L,EX,X,EY,Y)                            \
+{                                                                   \
+    if ((X)<=(Y))                                                   \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,EX,X,">",EY,Y);             \
+    } else {                                                        \
+        liquid_autotest_passed();                                   \
+    }                                                               \
+}
+#define CONTEND_GREATER_THAN_FL(F,L,X,Y)  TEST_GREATER_THAN(F,L,#X,(X),#Y,(Y))
+#define CONTEND_GREATER_THAN(X,Y)         CONTEND_GREATER_THAN_FL(__FILE__,__LINE__,X,Y)
+
+// CONTEND_LESS_THAN
+#define TEST_LESS_THAN(F,L,EX,X,EY,Y)                               \
+{                                                                   \
+    if ((X)>=(Y))                                                   \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,EX,X,">",EY,Y);             \
+    } else {                                                        \
+        liquid_autotest_passed();                                   \
+    }                                                               \
+}
+#define CONTEND_LESS_THAN_FL(F,L,X,Y)     TEST_LESS_THAN(F,L,#X,(X),#Y,(Y))
+#define CONTEND_LESS_THAN(X,Y)            CONTEND_LESS_THAN_FL(__FILE__,__LINE__,X,Y)
+
+// CONTEND_DELTA
+#define TEST_DELTA(F,L,EX,X,EY,Y,ED,D)                              \
+{                                                                   \
+    if (fabs((X)-(Y))>(D))                                          \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,"abs(" #X "-" #Y ")",       \
+                                    fabs(X-Y),"<",ED,D);            \
+    } else {                                                        \
+        liquid_autotest_passed();                                   \
+    }                                                               \
+}
+#define CONTEND_DELTA_FL(F,L,X,Y,D)       TEST_DELTA(F,L,#X,(X),#Y,(Y),#D,(D))
+#define CONTEND_DELTA(X,Y,D)              CONTEND_DELTA_FL(__FILE__,__LINE__,X,Y,D)
+
+// CONTEND_EXPRESSION
+#define TEST_EXPRESSION(F,L,EX,X)                                   \
+{                                                                   \
+    if (!X)                                                         \
+    {                                                               \
+        liquid_autotest_failed_expr(F,L,#X,(X),"is","1",1);         \
+    } else {                                                        \
+        liquid_autotest_passed();                                   \
+    }                                                               \
+}
+#define CONTEND_EXPRESSION_FL(F,L,X)      TEST_EXPRESSION(F,L,#X,(X))
+#define CONTEND_EXPRESSION(X)             CONTEND_EXPRESSION_FL(__FILE__,__LINE__,X)
+
+// CONTEND_SAME_DATA
+#define TEST_SAME_DATA(F,L,EX,X,EY,Y,EN,N)                                      \
+{                                                                               \
+    if (!liquid_autotest_same_data((uint8_t*)(X),(uint8_t*)(Y),(N)))            \
+    {                                                                           \
+        liquid_autotest_failed_msg(F,L,EX "[] != " EY "[] for " EN " bytes");   \
+        if (liquid_autotest_verbose)                                            \
+        {                                                                       \
+            liquid_autotest_print_array((uint8_t*)(X),N);                       \
+            liquid_autotest_print_array((uint8_t*)(Y),N);                       \
+        }                                                                       \
+    } else {                                                                    \
+        liquid_autotest_passed();                                               \
+    }                                                                           \
+}
+#define CONTEND_SAME_DATA_FL(F,L,X,Y,N)  TEST_SAME_DATA(F,L,#X,(X),#Y,(Y),#N,(N))
+#define CONTEND_SAME_DATA(X,Y,N)         CONTEND_SAME_DATA_FL(__FILE__,__LINE__,X,Y,N)
+
+
+// AUTOTEST WARN
+#define AUTOTEST_WARN_FL(F,L,MSG)      liquid_autotest_warn(F,L,#MSG)
+#define AUTOTEST_WARN(MSG)             AUTOTEST_WARN_FL(__FILE__,__LINE__,MSG)
+
+// AUTOTEST PASS
+#define AUTOTEST_PASS_FL(F,L)          liquid_autotest_passed()
+#define AUTOTEST_PASS()                AUTOTEST_PASS_FL(__FILE__,__LINE__)
+
+// AUTOTEST FAIL
+#define AUTOTEST_FAIL_FL(F,L,MSG)      liquid_autotest_failed_msg(F,L,MSG)
+#define AUTOTEST_FAIL(MSG)             AUTOTEST_FAIL_FL(__FILE__,__LINE__,MSG)
+
+#endif // __LIQUID_AUTOTEST_H__
+
diff --git a/autotest/autotestlib.c b/autotest/autotestlib.c
new file mode 100644
index 0000000..67b9d2b
--- /dev/null
+++ b/autotest/autotestlib.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotestlib.c
+//
+
+// default include headers
+#include <stdio.h>
+#include <stdlib.h>
+#include "autotest/autotest.h"
+
+// total number of checks invoked
+unsigned long int liquid_autotest_num_checks=0;
+
+// total number of checks which passed
+unsigned long int liquid_autotest_num_passed=0;
+
+// total number of checks which failed
+unsigned long int liquid_autotest_num_failed=0;
+
+// total number of warnings
+unsigned long int liquid_autotest_num_warnings=0;
+
+// verbosity flag
+int liquid_autotest_verbose = 1;
+
+// fail test
+// increment liquid_autotest_num_checks
+// increment liquid_autotest_num_failed
+void liquid_autotest_failed()
+{
+    liquid_autotest_num_checks++;
+    liquid_autotest_num_failed++;
+}
+
+// pass test
+// increment liquid_autotest_num_checks
+// increment liquid_autotest_num_passed
+void liquid_autotest_passed()
+{
+    liquid_autotest_num_checks++;
+    liquid_autotest_num_passed++;
+}
+
+// fail test, given expression
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _exprL      :   left side of expression (string)
+//  _valueL     :   left side of expression (value)
+//  _qualifier  :   expression qualifier
+//  _exprR      :   right side of expression (string)
+//  _valueR     :   right side of expression (value)
+void liquid_autotest_failed_expr(const char * _file,
+                                 unsigned int _line,
+                                 const char * _exprL,
+                                 double _valueL,
+                                 const char * _qualifier,
+                                 const char * _exprR,
+                                 double _valueR)
+{
+    if (liquid_autotest_verbose) {
+        printf("  TEST FAILED: %s line %u : expected %s (%0.2E) %s %s (%0.2E)\n",
+                _file, _line, _exprL, _valueL, _qualifier, _exprR, _valueR);
+    }
+    liquid_autotest_failed();
+}
+
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _message    :   message string
+void liquid_autotest_failed_msg(const char * _file,
+                                unsigned int _line,
+                                const char * _message)
+{
+    if (liquid_autotest_verbose)
+        printf("  TEST FAILED: %s line %u : %s\n", _file, _line, _message);
+    liquid_autotest_failed();
+}
+
+// print basic autotest results to stdout
+void autotest_print_results(void)
+{
+    if (liquid_autotest_num_warnings > 0) {
+        printf("==================================\n");
+        printf(" WARNINGS : %-lu\n", liquid_autotest_num_warnings);
+    }
+
+    printf("==================================\n");
+    if (liquid_autotest_num_checks==0) {
+        printf(" NO CHECKS RUN\n");
+    } else if (liquid_autotest_num_failed==0) {
+        printf(" PASSED ALL %lu CHECKS\n", liquid_autotest_num_passed);
+    } else {
+        // compute and print percentage of failed tests
+        double percent_failed = (double) liquid_autotest_num_failed /
+                                (double) liquid_autotest_num_checks;
+        printf(" FAILED %lu / %lu CHECKS (%7.2f%%)\n",
+                liquid_autotest_num_failed,
+                liquid_autotest_num_checks,
+                100.0*percent_failed);
+    }
+    printf("==================================\n");
+}
+
+// print warning to stderr
+// increment liquid_autotest_num_warnings
+//  _file       :   filename (string)
+//  _line       :   line number of test
+//  _message    :   message string
+void liquid_autotest_warn(const char * _file,
+                          unsigned int _line,
+                         const char * _message)
+{
+    if (liquid_autotest_verbose)
+        fprintf(stderr,"  WARNING: %s line %u : %s\n", _file, _line, _message);
+
+    liquid_autotest_num_warnings++;
+}
+
+// contend that data in two arrays are identical
+//  _x      :   input array [size: _n x 1]
+//  _y      :   input array [size: _n x 1]
+//  _n      :   input array size
+int liquid_autotest_same_data(unsigned char * _x,
+                              unsigned char * _y,
+                              unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        if (_x[i] != _y[i])
+            return 0;
+    }
+    return 1;
+}
+
+// print array to standard out
+//  _x      :   input array [size: _n x 1]
+//  _n      :   input array size
+void liquid_autotest_print_array(unsigned char * _x,
+                                 unsigned int _n)
+{
+    unsigned int i;
+    printf("   {");
+    for (i=0; i<_n; i++) {
+        printf("%.2x, ", (unsigned int)(_x[i]));
+        if ( ((i+1)%16 == 0) && (i != (_n-1)) )
+            printf("\n    ");
+    }
+    printf("}\n");
+}
+
diff --git a/autotest/null_autotest.c b/autotest/null_autotest.c
new file mode 100644
index 0000000..4754a13
--- /dev/null
+++ b/autotest/null_autotest.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest.h"
+
+void autotest_null()
+{
+    // pass unconditionally
+    AUTOTEST_PASS();
+}
+
diff --git a/bench/bench.c b/bench/bench.c
new file mode 100644
index 0000000..771c54e
--- /dev/null
+++ b/bench/bench.c
@@ -0,0 +1,476 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// bench.c
+//
+// This file is used in conjunction with benchinclude.h (generated with
+// benchmarkgen.py) to produce an executable for benchmarking the various
+// signal processing algorithms in liquid.
+//
+
+
+// default include headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <math.h>
+#include <sys/resource.h>
+
+// define benchmark function pointer
+typedef void(benchmark_function_t) (
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations);
+
+// define benchmark_t
+typedef struct {
+    unsigned int id;
+    benchmark_function_t * api;
+    const char* name;
+    unsigned int name_len;
+    unsigned int num_trials;
+    float extime;
+    float rate;
+    float cycles_per_trial;
+} benchmark_t;
+
+// define package_t
+typedef struct {
+    unsigned int id;            // package identification
+    unsigned int index;         // index of first benchmark
+    unsigned int num_scripts;   // number of tests in package
+    const char* name;           // package name
+} package_t;
+
+// include auto-generated benchmark header
+//
+// defines the following symbols:
+//   #define AUTOSCRIPT_VERSION
+//   #define NUM_AUTOSCRIPTS
+//   benchmark_t scripts[NUM_AUTOSCRIPTS]
+//   #define NUM_PACKAGES
+//   package_t packages[NUM_PACKAGES]
+#include "benchmark_include.h"
+
+// helper functions:
+void estimate_cpu_clock(void);
+void set_num_trials_from_cpu_speed(void);
+void execute_benchmark(benchmark_t* _benchmark, int _verbose);
+void execute_package(package_t* _package, int _verbose);
+
+char convert_units(float * _s);
+void print_benchmark_results(benchmark_t* _benchmark);
+void print_package_results(package_t* _package);
+double calculate_execution_time(struct rusage, struct rusage);
+
+unsigned long int num_base_trials = 1<<12;
+float cpu_clock = 1.0f; // cpu clock speed (Hz)
+float runtime=0.100f;   // minimum run time (s)
+
+FILE * fid; // output file id
+void output_benchmark_to_file(FILE * _fid, benchmark_t * _benchmark);
+
+void usage()
+{
+    // help
+    printf("Usage: benchmark [OPTION]\n");
+    printf("Execute benchmark scripts for liquid-dsp library.\n");
+    printf("  -h,-u         display this help and exit\n");
+    printf("  -v            verbose\n");
+    printf("  -q            quiet\n");
+    printf("  -e            estimate cpu clock frequency and exit\n");
+    printf("  -c            set cpu clock frequency (Hz)\n");
+    printf("  -n[COUNT]     set number of base trials\n");
+    printf("  -p[ID]        run specific package\n");
+    printf("  -b[ID]        run specific benchmark\n");
+    printf("  -t[SECONDS]   set minimum execution time (s)\n");
+    printf("  -l            list available packages\n");
+    printf("  -L            list all available scripts\n");
+    printf("  -s[STRING]    run all packages/benchmarks matching search string\n");
+    printf("  -o[FILENAME]  export output\n");
+}
+
+// main function
+int main(int argc, char *argv[])
+{
+    // initialize timing variables
+    unsigned int i, j;
+
+    // options
+    enum {RUN_ALL,
+          RUN_SINGLE_BENCH,
+          RUN_SINGLE_PACKAGE,
+          RUN_SEARCH,
+    } mode = RUN_ALL;
+    unsigned int benchmark_id = 0;
+    unsigned int package_id = 0;
+    int verbose = 1;
+    int autoscale = 1;
+    int cpu_clock_detect = 1;
+    int output_to_file = 0;
+    char filename[128];
+    char search_string[128];
+
+    // get input options
+    int d;
+    while((d = getopt(argc,argv,"uhvqec:n:b:p:t:lLs:o:")) != EOF){
+        switch (d) {
+        case 'u':
+        case 'h':   usage();        return 0;
+        case 'v':   verbose = 1;    break;
+        case 'q':   verbose = 0;    break;
+        case 'e':
+            estimate_cpu_clock();
+            return 0;
+        case 'c':
+            cpu_clock = atof(optarg);
+            if (cpu_clock < 0) {
+                printf("error: cpu clock speed is negative (%f)\n", cpu_clock);
+                return -1;
+            }
+            cpu_clock_detect = 0;
+            break;
+        case 'n':
+            num_base_trials = atoi(optarg);
+            autoscale = 0;
+            break;
+        case 'b':
+            benchmark_id = atoi(optarg);
+            if (benchmark_id >= NUM_AUTOSCRIPTS) {
+                printf("error, cannot run benchmark %u; index exceeded\n", benchmark_id);
+                return -1;
+            } else {
+                mode = RUN_SINGLE_BENCH;
+            }
+            break;
+        case 'p':
+            package_id = atoi(optarg);
+            if (package_id >= NUM_PACKAGES) {
+                printf("error, cannot run package %u; index exceeded\n", package_id);
+                return -1;
+            } else {
+                mode = RUN_SINGLE_PACKAGE;
+            }
+            break;
+        case 't':
+            runtime = atof(optarg);
+            if (runtime < 1e-3f)     runtime = 1e-3f;
+            else if (runtime > 10.f) runtime = 10.0f;
+            printf("minimum runtime: %d ms\n", (int) roundf(runtime*1e3));
+            break;
+        case 'l':
+            // list only packages and exit
+            for (i=0; i<NUM_PACKAGES; i++)
+                printf("%u: %s\n", packages[i].id, packages[i].name);
+            return 0;
+        case 'L':
+            // list packages, scripts and exit
+            for (i=0; i<NUM_PACKAGES; i++) {
+                printf("%u: %s\n", packages[i].id, packages[i].name);
+                for (j=packages[i].index; j<packages[i].num_scripts+packages[i].index; j++)
+                    printf("    %-3u: %-22s\n", scripts[j].id, scripts[j].name);
+            }
+            return 0;
+        case 's':
+            mode = RUN_SEARCH;
+            strncpy(search_string, optarg, 128);
+            search_string[127] = '\0';
+            break;
+        case 'o':
+            output_to_file = 1;
+            strcpy(filename, optarg);
+            break;
+        default:
+            usage();
+            return 0;
+        }
+    }
+
+    // run empty loop; a bug was found that sometimes the first package run
+    // resulted in a longer execution time than what the benchmark really
+    // reflected.  This loop prevents that from happening.
+    for (i=0; i<1e6; i++) {
+        // do nothing
+    }
+
+    if (cpu_clock_detect)
+        estimate_cpu_clock();
+
+    if (autoscale)
+        set_num_trials_from_cpu_speed();
+
+    switch (mode) {
+    case RUN_ALL:
+        for (i=0; i<NUM_PACKAGES; i++)
+            execute_package( &packages[i], verbose );
+
+        //for (i=0; i<NUM_PACKAGES; i++)
+        //    print_package_results( &packages[i] );
+        break;
+    case RUN_SINGLE_BENCH:
+        execute_benchmark( &scripts[benchmark_id], verbose );
+        //print_benchmark_results( &scripts[benchmark_id] );
+        break;
+    case RUN_SINGLE_PACKAGE:
+        execute_package( &packages[package_id], verbose );
+        //print_package_results( &packages[package_id] );
+        break;
+    case RUN_SEARCH:
+        printf("running all packages and benchmarks matching '%s'...\n", search_string);
+        for (i=0; i<NUM_PACKAGES; i++) {
+            // see if search string matches package name
+            if (strstr(packages[i].name, search_string) != NULL) {
+                // run the package
+                execute_package( &packages[i], verbose );
+            }
+        }
+        printf("running all remaining scripts matching '%s'...\n", search_string);
+        for (i=0; i<NUM_AUTOSCRIPTS; i++) {
+            // see if search string matches benchmark name
+            if (strstr(scripts[i].name, search_string) != NULL && scripts[i].num_trials == 0) {
+                // run the benchmark
+                execute_benchmark( &scripts[i], verbose );
+            }
+        }
+        break;
+    default:
+        fprintf(stderr,"invalid mode\n");
+        exit(1);
+    }
+
+    if (output_to_file) {
+        fid = fopen(filename,"w");
+        if (!fid) {
+            printf("error: could not open file %s for writing\n", filename);
+            return 1;
+        }
+
+        // print header
+        fprintf(fid,"# %s : auto-generated file (autoscript version %s)\n", filename, AUTOSCRIPT_VERSION);
+        fprintf(fid,"#\n");
+        fprintf(fid,"# invoked as:\n");
+        fprintf(fid,"#   ");
+        for (i=0; i<argc; i++)
+            fprintf(fid," %s", argv[i]);
+        fprintf(fid,"\n");
+        fprintf(fid,"#\n");
+        fprintf(fid,"# properties:\n");
+        fprintf(fid,"#  verbose             :   %s\n", verbose ? "true" : "false");
+        fprintf(fid,"#  autoscale           :   %s\n", autoscale ? "true" : "false");
+        fprintf(fid,"#  cpu_clock_detect    :   %s\n", cpu_clock_detect ? "true" : "false");
+        fprintf(fid,"#  search string       :   '%s'\n", mode == RUN_SEARCH ? search_string : "");
+        fprintf(fid,"#  runtime             :   %12.8f s\n", runtime);
+        fprintf(fid,"#  cpu_clock           :   %e Hz\n", cpu_clock);
+        fprintf(fid,"#  cpu_clock determined:   %s\n", cpu_clock_detect ? "estimated" : "specified");
+        fprintf(fid,"#  num_trials          :   %lu\n", num_base_trials);
+        fprintf(fid,"#\n");
+        fprintf(fid,"# %-5s %-30s %12s %12s %12s %12s\n",
+                "id", "name", "num trials", "ex.time [s]", "rate [t/s]", "[cycles/t]");
+
+        for (i=0; i<NUM_AUTOSCRIPTS; i++) {
+            if (scripts[i].num_trials > 0)
+                output_benchmark_to_file(fid, &scripts[i]);
+        }
+
+        fclose(fid);
+        printf("results written to %s\n", filename);
+    }
+
+    return 0;
+}
+
+// run basic benchmark to estimate CPU clock frequency
+void estimate_cpu_clock(void)
+{
+    printf("  estimating cpu clock frequency...\n");
+    unsigned long int i, n = 1<<4;
+    struct rusage start, finish;
+    double extime;
+    
+    // run trials until execution time threshold is exceeded
+    do {
+        // trials
+        n <<= 1;
+
+        // NOTE: Smart compilers will realize that this loop doesn't really do
+        //       anything, so they won't actually compute anything. We need to
+        //       actually do something interesting here to trick the compiler
+        //       into actually crunching these numbers, and then later display
+        //       the results, even if they're meaningless
+        unsigned int k = 366001;    // large prime number
+        unsigned int g = 184903;    // another large prime number
+        unsigned int s = 1;
+        getrusage(RUSAGE_SELF, &start);
+        for (i=0; i<n; i++) {
+            // perform mindless task
+            s = (s*k) % g;
+        }
+        getrusage(RUSAGE_SELF, &finish);
+
+        extime = calculate_execution_time(start, finish);
+
+        // print results to screen
+        // NOTE: it is necessary to do something with the variable 's' so that
+        //       the compiler will actually run the above loop
+        printf("%12lu trials in %8.3f ms, s = %6u\n", n, extime*1e3, s);
+    } while (extime < 0.5 && n < (1<<28));
+
+    // estimate cpu clock frequency
+    cpu_clock = 9.5 * n / extime;
+
+    printf("  performed %ld trials in %5.1f ms\n", n, extime * 1e3);
+    
+    float clock_format = cpu_clock;
+    char clock_units = convert_units(&clock_format);
+    printf("  estimated clock speed: %7.3f %cHz\n", clock_format, clock_units);
+}
+
+void set_num_trials_from_cpu_speed(void)
+{
+    unsigned long int min_trials = 256;
+    num_base_trials = (unsigned long int) ( cpu_clock / 10e3 );
+    num_base_trials = (num_base_trials < min_trials) ? min_trials : num_base_trials;
+
+    printf("  setting number of base trials to %ld\n", num_base_trials);
+}
+
+void execute_benchmark(benchmark_t* _benchmark, int _verbose)
+{
+    unsigned long int n = num_base_trials;
+    struct rusage start, finish;
+
+    unsigned int num_attempts = 0;
+    unsigned long int num_trials;
+    do {
+        // increment number of attempts
+        num_attempts++;
+
+        // set number of trials and run benchmark
+        num_trials = n;
+        _benchmark->api(&start, &finish, &num_trials);
+        _benchmark->extime = calculate_execution_time(start, finish);
+
+        // check exit criteria
+        if (_benchmark->extime >= runtime) {
+            break;
+        } else if (num_attempts == 30) {
+            fprintf(stderr,"warning: benchmark could not execute over minimum run time\n");
+            break;
+        } else {
+            // increase number of trials
+            n *= 2;
+        }
+    } while (1);
+
+    _benchmark->num_trials = num_trials;
+    _benchmark->rate = (float)(_benchmark->num_trials) / _benchmark->extime;
+    _benchmark->cycles_per_trial = cpu_clock / (_benchmark->rate);
+
+    if (_verbose)
+        print_benchmark_results(_benchmark);
+}
+
+void execute_package(package_t* _package, int _verbose)
+{
+    if (_verbose)
+        printf("%u: %s\n", _package->id, _package->name);
+    
+    unsigned int i;
+    for (i=0; i<_package->num_scripts; i++) {
+        execute_benchmark( &scripts[ i + _package->index ], _verbose );
+    }
+}
+
+// convert raw value into metric units,
+//   example: "0.01397s" -> "13.97 ms"
+char convert_units(float * _v)
+{
+    char unit;
+    if (*_v < 1e-9)     {   (*_v) *= 1e12;  unit = 'p';}
+    else if (*_v < 1e-6){   (*_v) *= 1e9;   unit = 'n';}
+    else if (*_v < 1e-3){   (*_v) *= 1e6;   unit = 'u';}
+    else if (*_v < 1e+0){   (*_v) *= 1e3;   unit = 'm';}
+    else if (*_v < 1e3) {   (*_v) *= 1e+0;  unit = ' ';}
+    else if (*_v < 1e6) {   (*_v) *= 1e-3;  unit = 'k';}
+    else if (*_v < 1e9) {   (*_v) *= 1e-6;  unit = 'M';}
+    else if (*_v < 1e12){   (*_v) *= 1e-9;  unit = 'G';}
+    else                {   (*_v) *= 1e-12; unit = 'T';}
+
+    return unit;
+}
+
+void print_benchmark_results(benchmark_t* _b)
+{
+    // format trials (iterations)
+    float trials_format = (float)(_b->num_trials);
+    char trials_units = convert_units(&trials_format);
+
+    // format time (seconds)
+    float extime_format = _b->extime;
+    char extime_units = convert_units(&extime_format);
+
+    // format rate (trials/second)
+    float rate_format = _b->rate;
+    char rate_units = convert_units(&rate_format);
+
+    // format processor efficiency (cycles/trial)
+    float cycles_format = _b->cycles_per_trial;
+    char cycles_units = convert_units(&cycles_format);
+
+    printf("  %-3u: %-30s: %6.2f %c trials / %6.2f %cs (%6.2f %c t/s, %6.2f %c c/t)\n",
+        _b->id, _b->name,
+        trials_format, trials_units,
+        extime_format, extime_units,
+        rate_format, rate_units,
+        cycles_format, cycles_units);
+}
+
+void print_package_results(package_t* _package)
+{
+    unsigned int i;
+    printf("%u: %s:\n", _package->id, _package->name);
+    for (i=_package->index; i<(_package->index+_package->num_scripts); i++)
+        print_benchmark_results( &scripts[i] );
+
+    printf("\n");
+}
+
+double calculate_execution_time(struct rusage _start, struct rusage _finish)
+{
+    return _finish.ru_utime.tv_sec - _start.ru_utime.tv_sec
+        + 1e-6*(_finish.ru_utime.tv_usec - _start.ru_utime.tv_usec)
+        + _finish.ru_stime.tv_sec - _start.ru_stime.tv_sec
+        + 1e-6*(_finish.ru_stime.tv_usec - _start.ru_stime.tv_usec);
+}
+
+void output_benchmark_to_file(FILE * _fid, benchmark_t * _benchmark)
+{
+    fprintf(_fid,"  %-5u %-30s %12u %12.4e %12.4e %12.4e\n",
+                 _benchmark->id,
+                 _benchmark->name,
+                 _benchmark->num_trials,
+                 _benchmark->extime,
+                 _benchmark->rate,
+                 _benchmark->cycles_per_trial);
+}
+
diff --git a/bench/example_benchmark.h b/bench/example_benchmark.h
new file mode 100644
index 0000000..3973a89
--- /dev/null
+++ b/bench/example_benchmark.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Example of a benchmark header
+//
+
+#include <sys/resource.h>
+#include <math.h>
+
+// strings parsed by benchmarkgen.py
+const char * mybench_opts[3] = {
+    "opt1a opt1b",
+    "opt2a opt2b opt2c",
+    "opt3a opt3b opt3c"
+};
+
+
+void benchmark_mybench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+//    unsigned int argc,
+//    char *argv[])
+{
+    // DSP initiazation goes here
+
+    unsigned int i;
+    float x, y, theta;
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // DSP execution goes here
+        x = cosf(M_PI/2.0f);
+        y = sinf(M_PI/2.0f);
+        theta = atan2(y,x);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // DSP cleanup goes here
+}
+
+
diff --git a/bench/fftbench.c b/bench/fftbench.c
new file mode 100644
index 0000000..bc9c529
--- /dev/null
+++ b/bench/fftbench.c
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftbench.c : benchmark fft algorithms
+//
+
+
+// default include headers
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <sys/resource.h>
+
+#include <fftw3.h>
+#include "liquid.h"
+
+void usage()
+{
+    // help
+    printf("Usage: benchmark [OPTION]\n");
+    printf("Execute benchmark scripts for liquid-dsp library.\n");
+    printf("  -h            display this help and exit\n");
+    printf("  -v/q          verbose/quiet\n");
+    printf("  -t[SECONDS]   set minimum execution time (s)\n");
+    printf("  -o[FILENAME]  export output\n");
+    printf("  -n[NFFT_MIN]  minimum FFT size (benchmark single FFT)\n");
+    printf("  -N[NFFT_MAX]  maximum FFT size\n");
+    printf("  -m[MODE]      mode: all, radix2, composite, prime, fftwbench, single\n");
+    printf("  -l[library]   library: float, fftw\n");
+}
+
+// benchmark structure
+struct benchmark_s {
+    // fft options
+    unsigned int nfft;          // FFT size
+    int direction;              // FFT direction
+    int flags;                  // FFT flags/method
+
+    // benchmark results
+    unsigned int num_trials;    // number of trials
+    float extime;               // execution time
+
+    // derived values
+    float time_per_trial;       // execution time per trial
+    float flops;                // computation bandwidth
+};
+
+typedef enum {
+    LIB_FLOAT=0,
+    LIB_FFTW,
+} library_t;
+    
+// simulation structure
+struct fftbench_s {
+    enum {RUN_ALL=0,
+          RUN_RADIX2,
+          RUN_COMPOSITE,
+          RUN_PRIME,
+          RUN_FFTWBENCH,
+          RUN_SINGLE,
+    } mode;
+
+    // library version
+    library_t library;
+    
+    int verbose;
+    float runtime;   // minimum run time (s)
+
+    // min/max sizes for other modes
+    unsigned int nfft_min;  // minimum FFT size (also, size for RUN_SINGLE mode)
+    unsigned int nfft_max;  // maximum FFT size
+
+    // output file
+    char filename[128];     // output filename
+    FILE * fid;             // output file pointer
+    int output_to_file;     // output file write flag
+};
+
+// helper functions:
+char convert_units(float * _s);
+double calculate_execution_time(struct rusage, struct rusage);
+
+// run all benchmarks
+void fftbench_execute(struct fftbench_s * _fftbench);
+
+// execute single benchmark
+void execute_benchmark_fft(struct benchmark_s * _benchmark,
+                           float                _runtime,
+                           library_t            _library);
+
+// main benchmark script (floating-point precision)
+void benchmark_fft(struct rusage *      _start,
+                   struct rusage *      _finish,
+                   struct benchmark_s * _benchmark);
+
+// main benchmark script (FFTW)
+void benchmark_fftw(struct rusage *      _start,
+                    struct rusage *      _finish,
+                    struct benchmark_s * _benchmark);
+
+void benchmark_print_to_file(FILE * _fid,
+                              struct benchmark_s * _benchmark);
+
+void benchmark_print(struct benchmark_s * _benchmark);
+
+// main function
+int main(int argc, char *argv[])
+{
+    // options
+    struct fftbench_s fftbench;
+    fftbench.mode       = RUN_RADIX2;
+    fftbench.library    = LIB_FLOAT;
+    fftbench.verbose    = 1;
+    fftbench.runtime    = 0.1f;
+    fftbench.nfft_min   = 2;
+    fftbench.nfft_max   = 1024;
+    fftbench.filename[0]= '\0';
+    fftbench.fid        = NULL;
+    fftbench.output_to_file = 0;
+
+    // get input options
+    int d;
+    while((d = getopt(argc,argv,"hvqn:N:t:o:m:l:")) != EOF){
+        switch (d) {
+        case 'h':   usage();        return 0;
+        case 'v':   fftbench.verbose = 1;    break;
+        case 'q':   fftbench.verbose = 0;    break;
+        case 'n':
+            fftbench.nfft_min = atoi(optarg);
+            break;
+        case 'N':
+            fftbench.nfft_max = atoi(optarg);
+            break;
+        case 't':
+            fftbench.runtime = atof(optarg);
+            if (fftbench.runtime < 1e-3f)     fftbench.runtime = 1e-3f;
+            else if (fftbench.runtime > 10.f) fftbench.runtime = 10.0f;
+            printf("minimum runtime: %d ms\n", (int) roundf(fftbench.runtime*1e3));
+            break;
+        case 'o':
+            fftbench.output_to_file = 1;
+            strcpy(fftbench.filename, optarg);
+            break;
+        case 'm':
+            if      (strcmp(optarg,"all")==0)       fftbench.mode = RUN_ALL;
+            else if (strcmp(optarg,"radix2")==0)    fftbench.mode = RUN_RADIX2;
+            else if (strcmp(optarg,"composite")==0) fftbench.mode = RUN_COMPOSITE;
+            else if (strcmp(optarg,"prime")==0)     fftbench.mode = RUN_PRIME;
+            else if (strcmp(optarg,"fftwbench")==0) fftbench.mode = RUN_FFTWBENCH;
+            else if (strcmp(optarg,"single")==0)    fftbench.mode = RUN_SINGLE;
+            else {
+                fprintf(stderr,"error: %s, unknown mode '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'l':
+            if      (strcmp(optarg,"float")==0)     fftbench.library = LIB_FLOAT;
+            else if (strcmp(optarg,"fftw")==0)      fftbench.library = LIB_FFTW;
+            else {
+                fprintf(stderr,"error: %s, unknown library option '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        default:
+            usage();
+            return 0;
+        }
+    }
+
+    // run empty loop; a bug was found that sometimes the first package run
+    // resulted in a longer execution time than what the benchmark really
+    // reflected.  This loop prevents that from happening.
+    unsigned int i;
+    for (i=0; i<1e6; i++) {
+        // do nothing
+    }
+    
+    // open output file (if applicable)
+    if (fftbench.output_to_file) {
+        fftbench.fid = fopen(fftbench.filename,"w");
+        if (!fftbench.fid) {
+            fprintf(stderr,"error: %s, could not open file '%s' for writing\n", argv[0], fftbench.filename);
+            exit(1);
+        }
+        FILE * fid = fftbench.fid;
+
+        // print header
+        fprintf(fid,"# %s : auto-generated file\n", fftbench.filename);
+        fprintf(fid,"#\n");
+        fprintf(fid,"# invoked as:\n");
+        fprintf(fid,"#   ");
+        for (i=0; i<argc; i++)
+            fprintf(fid," %s", argv[i]);
+        fprintf(fid,"\n");
+        fprintf(fid,"#\n");
+        fprintf(fid,"# properties:\n");
+        fprintf(fid,"#  verbose             :   %s\n", fftbench.verbose ? "true" : "false");
+        fprintf(fid,"#  runtime             :   %12.8f s\n", fftbench.runtime);
+        fprintf(fid,"#  mode                :   \n");
+        fprintf(fid,"#\n");
+        fprintf(fid,"# %12s %12s %12s %12s %12s\n",
+                "nfft", "num trials", "ex. time", "us/trial", "M-flops");
+    }
+
+    // run benchmarks
+    fftbench_execute(&fftbench);
+
+    if (fftbench.output_to_file) {
+        fclose(fftbench.fid);
+        printf("results written to %s\n", fftbench.filename);
+    }
+
+    return 0;
+}
+
+// convert raw value into metric units,
+//   example: "0.01397s" -> "13.97 ms"
+char convert_units(float * _v)
+{
+    char unit;
+    if (*_v < 1e-9)     {   (*_v) *= 1e12;  unit = 'p';}
+    else if (*_v < 1e-6){   (*_v) *= 1e9;   unit = 'n';}
+    else if (*_v < 1e-3){   (*_v) *= 1e6;   unit = 'u';}
+    else if (*_v < 1e+0){   (*_v) *= 1e3;   unit = 'm';}
+    else if (*_v < 1e3) {   (*_v) *= 1e+0;  unit = ' ';}
+    else if (*_v < 1e6) {   (*_v) *= 1e-3;  unit = 'k';}
+    else if (*_v < 1e9) {   (*_v) *= 1e-6;  unit = 'M';}
+    else if (*_v < 1e12){   (*_v) *= 1e-9;  unit = 'G';}
+    else                {   (*_v) *= 1e-12; unit = 'T';}
+
+    return unit;
+}
+
+double calculate_execution_time(struct rusage _start, struct rusage _finish)
+{
+    return _finish.ru_utime.tv_sec - _start.ru_utime.tv_sec
+        + 1e-6*(_finish.ru_utime.tv_usec - _start.ru_utime.tv_usec)
+        + _finish.ru_stime.tv_sec - _start.ru_stime.tv_sec
+        + 1e-6*(_finish.ru_stime.tv_usec - _start.ru_stime.tv_usec);
+}
+
+// run all benchmarks
+void fftbench_execute(struct fftbench_s * _fftbench)
+{
+    // validate input
+    if (_fftbench->nfft_min > _fftbench->nfft_max && _fftbench->mode != RUN_SINGLE) {
+        fprintf(stderr,"execute_benchmarks_composite(), nfft_min cannot be greater than nfft_max\n");
+        exit(1);
+    } else if (_fftbench->runtime <= 0.0f) {
+        fprintf(stderr,"execute_benchmarks_composite(), runtime must be greater than zero\n");
+        exit(1);
+    }
+
+    // create benchmark structure
+    struct benchmark_s benchmark;
+
+    if (_fftbench->mode == RUN_SINGLE) {
+        // run single benchmark and exit
+
+        // initialize benchmark structure
+        benchmark.nfft       = _fftbench->nfft_min;
+        benchmark.direction  = LIQUID_FFT_FORWARD;
+        benchmark.num_trials = 1;
+        benchmark.flags      = 0;
+        benchmark.extime     = 0.0f;
+        benchmark.flops      = 0.0f;
+
+        // run the benchmark
+        execute_benchmark_fft(&benchmark, _fftbench->runtime, _fftbench->library);
+        benchmark_print(&benchmark);
+        return;
+    } else if (_fftbench->mode == RUN_FFTWBENCH) {
+        printf("running composite FFTs from FFTW benchmark\n");
+        unsigned int nfftw[18] = {6,9,12,15,18,24,36,80,108,210,504,
+                                  1000,1960,4725,10368,27000,75600,165375};
+        unsigned int i;
+        for (i=0; i<18; i++) {
+            // initialize benchmark structure
+            benchmark.nfft       = nfftw[i];
+            benchmark.direction  = LIQUID_FFT_FORWARD;
+            benchmark.num_trials = 1;
+            benchmark.flags      = 0;
+
+            // run the benchmark
+            execute_benchmark_fft(&benchmark, _fftbench->runtime, _fftbench->library);
+            benchmark_print(&benchmark);
+
+            if (_fftbench->output_to_file)
+                benchmark_print_to_file(_fftbench->fid, &benchmark);
+        }
+        return;
+    } else if (_fftbench->mode == RUN_RADIX2) {
+        printf("running all power-of-two FFTs from %u to %u:\n",
+            _fftbench->nfft_min,
+            _fftbench->nfft_max);
+
+        unsigned int nfft = 1 << liquid_nextpow2(_fftbench->nfft_min);
+        while ( nfft <= _fftbench->nfft_max) {
+
+            // initialize benchmark structure
+            benchmark.nfft       = nfft;
+            benchmark.direction  = LIQUID_FFT_FORWARD;
+            benchmark.num_trials = 1;
+            benchmark.flags      = 0;
+
+            // run the benchmark
+            execute_benchmark_fft(&benchmark, _fftbench->runtime, _fftbench->library);
+            benchmark_print(&benchmark);
+
+            if (_fftbench->output_to_file)
+                benchmark_print_to_file(_fftbench->fid, &benchmark);
+
+            nfft *= 2;
+        };
+        return;
+    }
+        
+    printf("running ");
+    switch (_fftbench->mode) {
+    case RUN_ALL:       printf("all");              break;
+    case RUN_COMPOSITE: printf("all composite");    break;
+    case RUN_PRIME:     printf("all prime");        break;
+    case RUN_SINGLE:
+    default:;
+    }
+    printf(" FFTs from %u to %u:\n",
+        _fftbench->nfft_min,
+        _fftbench->nfft_max);
+
+    unsigned int nfft;
+    for (nfft=_fftbench->nfft_min; nfft<=_fftbench->nfft_max; nfft++) {
+        int isprime  = liquid_is_prime(nfft);
+        int isradix2 = (1 << liquid_nextpow2(nfft))==nfft ? 1 : 0;
+
+        // check run mode
+        if (_fftbench->mode == RUN_COMPOSITE && (isprime || isradix2))
+            continue;
+        if (_fftbench->mode == RUN_PRIME && !isprime)
+            continue;
+
+        // run the transform
+        benchmark.nfft       = nfft;
+        benchmark.direction  = LIQUID_FFT_FORWARD;
+        benchmark.num_trials = 1;
+        benchmark.flags      = 0;
+        benchmark.extime     = 0.0f;
+        benchmark.flops      = 0.0f;
+        execute_benchmark_fft(&benchmark, _fftbench->runtime, _fftbench->library);
+
+        if (_fftbench->verbose)
+            benchmark_print(&benchmark);
+
+        if (_fftbench->output_to_file)
+            benchmark_print_to_file(_fftbench->fid, &benchmark);
+    }
+}
+
+// execute single benchmark
+void execute_benchmark_fft(struct benchmark_s * _benchmark,
+                           float                _runtime,
+                           library_t            _library)
+{
+    unsigned long int n = _benchmark->num_trials;
+    struct rusage start, finish;
+
+    unsigned int num_attempts = 0;
+    do {
+        // increment number of attempts
+        num_attempts++;
+
+        // set number of trials and run benchmark
+        _benchmark->num_trials = n;
+
+        // run appropriate library
+        switch (_library) {
+        case LIB_FLOAT:
+            benchmark_fft(&start, &finish, _benchmark);
+            break;
+        case LIB_FFTW:
+            benchmark_fftw(&start, &finish, _benchmark);
+            break;
+        default:
+            fprintf(stderr,"error: execute_benchmark_fft(), invalid library\n");
+            exit(1);
+        }
+
+        // calculate execution time
+        _benchmark->extime = calculate_execution_time(start, finish);
+
+        // check exit criteria
+        if (_benchmark->extime >= _runtime) {
+            break;
+        } else if (num_attempts == 30) {
+            fprintf(stderr,"warning: benchmark could not execute over minimum run time\n");
+            break;
+        } else {
+            // increase number of trials
+            n *= 2;
+        }
+    } while (1);
+
+    // compute time per trial
+    _benchmark->time_per_trial = _benchmark->extime / (float)_benchmark->num_trials;
+
+    // computational bandwidth (see: http://www.fftw.org/speed/)
+    _benchmark->flops = 5.0f * _benchmark->nfft * log2f(_benchmark->nfft) / _benchmark->time_per_trial;
+}
+
+// main benchmark script
+void benchmark_fft(struct rusage *      _start,
+                   struct rusage *      _finish,
+                   struct benchmark_s * _benchmark)
+{
+    // initialize arrays, plan
+    float complex * x = (float complex *) malloc((_benchmark->nfft)*sizeof(float complex));
+    float complex * y = (float complex *) malloc((_benchmark->nfft)*sizeof(float complex));
+    fftplan q = fft_create_plan(_benchmark->nfft,
+                                x, y,
+                                _benchmark->direction,
+                                _benchmark->flags);
+    
+    unsigned long int i;
+
+    // initialize input with random values
+    for (i=0; i<_benchmark->nfft; i++)
+        x[i] = randnf() + randnf()*_Complex_I;
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    unsigned int num_iterations = _benchmark->num_trials;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<num_iterations; i++) {
+        fft_execute(q);
+        fft_execute(q);
+        fft_execute(q);
+        fft_execute(q);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // set actual number of iterations in result
+    _benchmark->num_trials = num_iterations * 4;
+
+    fft_destroy_plan(q);
+    free(x);
+    free(y);
+}
+
+// main benchmark script (FFTW)
+void benchmark_fftw(struct rusage *      _start,
+                    struct rusage *      _finish,
+                    struct benchmark_s * _benchmark)
+{
+    // initialize arrays, plan
+    float complex * x = (float complex *) malloc((_benchmark->nfft)*sizeof(float complex));
+    float complex * y = (float complex *) malloc((_benchmark->nfft)*sizeof(float complex));
+    fftwf_plan q = fftwf_plan_dft_1d(_benchmark->nfft,
+                                     x, y,
+                                     //_benchmark->direction,
+                                     FFTW_FORWARD,
+                                     FFTW_ESTIMATE);
+    
+    unsigned long int i;
+
+    // initialize input with random values
+    for (i=0; i<_benchmark->nfft; i++)
+        x[i] = randnf() + randnf()*_Complex_I;
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    unsigned int num_iterations = _benchmark->num_trials;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<num_iterations; i++) {
+        fftwf_execute(q);
+        fftwf_execute(q);
+        fftwf_execute(q);
+        fftwf_execute(q);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // set actual number of iterations in result
+    _benchmark->num_trials = num_iterations * 4;
+
+    fftwf_destroy_plan(q);
+    free(x);
+    free(y);
+}
+
+void benchmark_print_to_file(FILE * _fid,
+                             struct benchmark_s * _benchmark)
+{
+    fprintf(_fid,"  %12u %12u %12.4e %12.6f %12.3f\n",
+            _benchmark->nfft,
+            _benchmark->num_trials,
+            _benchmark->extime,
+            _benchmark->time_per_trial * 1e6f,
+            _benchmark->flops * 1e-6f);
+}
+
+void benchmark_print(struct benchmark_s * _benchmark)
+{
+    // format time/trial
+    float time_format = _benchmark->time_per_trial;
+    char time_units = convert_units(&time_format);
+
+    printf("  %12u: %12u trials / %10.3f ms (%10.3f %cs/t) > %10.3f M flops\n",
+            _benchmark->nfft,
+            _benchmark->num_trials,
+            _benchmark->extime * 1e3f,
+            time_format, time_units,
+            _benchmark->flops * 1e-6f);
+}
+
diff --git a/bench/main.c b/bench/main.c
new file mode 100644
index 0000000..cc19615
--- /dev/null
+++ b/bench/main.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// bench/main.c
+// 
+// Benchmark generator script
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "benchmarkgen.h"
+
+int main(int argc, char*argv[])
+{
+    // create benchmarkgen object
+    benchmarkgen q = benchmarkgen_create();
+
+    // parse files
+    int i;
+    for (i=1; i<argc; i++) {
+        benchmarkgen_parse(q,argv[i]);
+    }
+
+    // print results
+    benchmarkgen_print(q);
+
+    // destroy benchmarkgen object
+    benchmarkgen_destroy(q);
+
+    return 0;
+}
+
diff --git a/bench/null_benchmark.c b/bench/null_benchmark.c
new file mode 100644
index 0000000..c98dc63
--- /dev/null
+++ b/bench/null_benchmark.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+
+// null benchmark
+void benchmark_null(struct rusage *_start,
+                    struct rusage *_finish,
+                    unsigned long int *_num_iterations)
+{
+    unsigned long int i;
+    *_num_iterations *= 100;
+
+    getrusage(RUSAGE_SELF, _start);
+    unsigned int x = 0;
+    for (i=0; i<*_num_iterations; i++) {
+        // perform mindless task
+        x <<= 1;
+        x |= 1;
+        x &= 0xff;
+        x ^= 0xff;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+}
+
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..6d131c8
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Copyright (c) 2007 - 2016 Joseph Gaeddert
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# 
+# bootstrap.sh
+#
+# This is the bootstrapping script to auto-generate a configure
+# script for checking build environments, etc.
+#
+
+rm -f config.cache aclocal.m4
+aclocal
+autoconf
+autoheader
+#automake --foreign --add-missing
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..100cdd7
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,249 @@
+# Copyright (c) 2007 - 2016 Joseph Gaeddert
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# 
+# liquid-dsp library configure
+# Process with autoconf to generate configure script
+#
+
+AC_INIT([liquid-dsp],[1.2.0],[support at liquidsdr.org])
+AC_CONFIG_SRCDIR([src/libliquid.c])
+AC_CONFIG_MACRO_DIR([scripts])
+
+# permit auxiliary scripts directory (e.g. config.sub, config.guess, install-sh)
+AC_CONFIG_AUX_DIR(scripts/)
+
+# Specify 'C' language
+AC_LANG(C)
+
+# Autoheader
+AH_TEMPLATE([LIQUID_FFTOVERRIDE],  [Force internal FFT even if libfftw is available])
+AH_TEMPLATE([LIQUID_SIMDOVERRIDE], [Force overriding of SIMD (use portable C code)])
+
+AC_CONFIG_HEADER(config.h)
+AH_TOP([
+#ifndef __LIQUID_CONFIG_H__
+#define __LIQUID_CONFIG_H__
+])
+AH_BOTTOM([
+#endif // __LIQUID_CONFIG_H__
+])
+
+# Configure options
+
+AC_ARG_ENABLE(debug,
+    AS_HELP_STRING([--enable-debug],[debug]),
+    [DEBUG_OPTION="-DDEBUG"],
+    [DEBUG_OPTION=""]
+)
+
+AC_ARG_ENABLE(simdoverride,
+    AS_HELP_STRING([--enable-simdoverride],[use portable C code for dotprod, etc. even if SIMD extensions are available]),
+    [AC_DEFINE(LIQUID_SIMDOVERRIDE)],
+    [],
+)
+
+AC_ARG_ENABLE(fftoverride,
+    AS_HELP_STRING([--enable-fftoverride],[use internal fft even if libfftw is available]),
+    [AC_DEFINE(LIQUID_FFTOVERRIDE)],
+    [],
+)
+
+# Check for necessary programs
+AC_PROG_CC
+AC_PROG_SED
+AC_PROG_GREP
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+
+# Check for necessary libraries, library functions
+AC_FUNC_ERROR_AT_LINE
+AC_FUNC_MALLOC
+AC_FUNC_REALLOC
+
+# AC_CHECK_LIB (library, function, [action-if-found], [action-if-not-found], [other-libraries])
+AC_CHECK_LIB([c],[main], [],[AC_MSG_ERROR(Could not use standard C library)],   [])
+AC_CHECK_LIB([m],[main], [],[AC_MSG_ERROR(Could not use standard math library)],[])
+
+# AC_CHECK_FUNC(function, [action-if-found], [action-if-not-found])
+AC_CHECK_FUNC([malloc],  [],[AC_MSG_ERROR(Could not use malloc())])
+AC_CHECK_FUNC([realloc], [],[AC_MSG_ERROR(Could not use realloc())],)
+AC_CHECK_FUNC([free],    [],[AC_MSG_ERROR(Could not use free())],)
+AC_CHECK_FUNC([memset],  [],[AC_MSG_ERROR(Could not use memset())],)
+AC_CHECK_FUNC([memmove], [],[AC_MSG_ERROR(Could not use memove())],)
+
+AC_CHECK_FUNC([sinf],    [],[AC_MSG_ERROR(Could not use sinf())],)
+AC_CHECK_FUNC([cosf],    [],[AC_MSG_ERROR(Could not use cosf())],)
+AC_CHECK_FUNC([expf],    [],[AC_MSG_ERROR(Could not use expf())],)
+AC_CHECK_FUNC([cargf],   [],[AC_MSG_ERROR(Could not use cargf())],)
+AC_CHECK_FUNC([cexpf],   [],[AC_MSG_ERROR(Could not use cexpf())],)
+AC_CHECK_FUNC([crealf],  [],[AC_MSG_ERROR(Could not use crealf())],)
+AC_CHECK_FUNC([cimagf],  [],[AC_MSG_ERROR(Could not use cimagf())],)
+AC_CHECK_FUNC([sqrtf],   [],[AC_MSG_ERROR(Could not use sqrtf())],)
+
+# Check for necessary header files
+AC_CHECK_HEADERS([stdio.h stdlib.h complex.h string.h getopt.h sys/resource.h float.h inttypes.h limits.h stdlib.h string.h unistd.h])
+if test -z "$HAVE_stdio.h"
+then
+    AC_MSG_ERROR([Need stdio.h!])
+fi
+
+# Check for optional header files, libraries, programs
+AC_CHECK_HEADERS(fec.h fftw3.h)
+AC_CHECK_LIB([fftw3f], [fftwf_plan_dft_1d], [],
+             [AC_MSG_WARN(fftw3 library useful but not required)],
+             [])
+AC_CHECK_LIB([fec], [create_viterbi27], [],
+             [AC_MSG_WARN(fec library useful but not required)],
+             [])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_C_INLINE
+AC_TYPE_SIZE_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT8_T
+
+# Check size of certain variables
+AC_CHECK_SIZEOF(int)
+AC_CHECK_SIZEOF(unsigned int)
+
+# AX_GCC_ARCHFLAG([PORTABLE?], [ACTION-SUCCESS], [ACTION-FAILURE])
+# Try to guess the "native" architecture of the target to use with gcc's
+# -march or -mtune flags. Default success action adds $ax_cv_gcc_archflag to
+# $CFLAGS (sets to "unknown" on failure).
+#AX_GCC_ARCHFLAG([no],[],[])
+
+# get canonical target architecture
+AC_CANONICAL_TARGET
+
+# override SIMD
+if test "${enable_simdoverride+set}" = set; then
+    # portable C version
+    MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.o \
+                   src/dotprod/src/dotprod_crcf.o \
+                   src/dotprod/src/dotprod_rrrf.o \
+                   src/dotprod/src/sumsq.o"
+    ARCH_OPTION=""
+else
+    # Check canonical system
+    case $target_cpu in
+    i386|i486|i586|i686|x86|x86_64)
+        # check for MMX/SSE/AVX CPU extensions and intrinsics headers
+        #   MMX     :   mmintrin.h
+        #   SSE     :   xmmintrin.h
+        #   SSE2    :   emmintrin.h
+        #   SSE3    :   pmmintrin.h
+        #   SSSE3   :   tmmintrin.h
+        #   SSE4.1/2:   smmintrin.h
+        #   AVX     :   immintrin.h
+        AX_EXT
+
+        if [ test "$ax_cv_have_sse41_ext" = yes && test "$ac_cv_header_smmintrin_h" = yes ]; then
+            # SSE4.1/2 extensions
+            MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.mmx.o \
+                           src/dotprod/src/dotprod_crcf.mmx.o \
+                           src/dotprod/src/dotprod_rrrf.mmx.o \
+                           src/dotprod/src/sumsq.mmx.o"
+        elif [ test "$ax_cv_have_sse2_ext" = yes && test "$ac_cv_header_emmintrin_h" = yes ]; then
+            # SSE2 extensions
+            MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.mmx.o \
+                           src/dotprod/src/dotprod_crcf.mmx.o \
+                           src/dotprod/src/dotprod_rrrf.mmx.o \
+                           src/dotprod/src/sumsq.mmx.o"
+        else
+            # portable C version
+            MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.o \
+                           src/dotprod/src/dotprod_crcf.o \
+                           src/dotprod/src/dotprod_rrrf.o \
+                           src/dotprod/src/sumsq.o"
+        fi;;
+    powerpc*)
+        MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.o \
+                       src/dotprod/src/dotprod_rrrf.av.o \
+                       src/dotprod/src/dotprod_crcf.av.o \
+                       src/dotprod/src/sumsq.o"
+        ARCH_OPTION="-fno-common -faltivec";;
+    armv1*|armv2*|armv3*|armv4*|armv5*|armv6*)
+        # assume neon instructions are NOT available
+        MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.o \
+                       src/dotprod/src/dotprod_crcf.o \
+                       src/dotprod/src/dotprod_rrrf.o \
+                       src/dotprod/src/sumsq.o"
+        ARCH_OPTION="-ffast-math";;
+    armv7*|armv8*)
+        # assume neon instructions are available
+        # TODO: check for Neon availability
+
+        # ARM architecture : use neon extensions
+        MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.neon.o \
+                       src/dotprod/src/dotprod_crcf.neon.o \
+                       src/dotprod/src/dotprod_rrrf.neon.o \
+                       src/dotprod/src/sumsq.o"
+        # TODO: check these flags
+        #ARCH_OPTION="-ffast-math -mcpu=cortex-a8 -mfloat-abi=softfp -mfpu=neon";;
+        ARCH_OPTION="-ffast-math -mcpu=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4";;
+    *)
+        # unknown architecture : use portable C version
+        MLIBS_DOTPROD="src/dotprod/src/dotprod_cccf.o \
+                       src/dotprod/src/dotprod_crcf.o \
+                       src/dotprod/src/dotprod_rrrf.o \
+                       src/dotprod/src/sumsq.o"
+        ARCH_OPTION="";;
+    esac
+fi
+
+
+# for now all vector operations are portable C versions
+MLIBS_VECTOR="src/vector/src/vectorf_add.port.o   \
+              src/vector/src/vectorf_norm.port.o  \
+              src/vector/src/vectorf_mul.port.o   \
+              src/vector/src/vectorf_trig.port.o  \
+              src/vector/src/vectorcf_add.port.o  \
+              src/vector/src/vectorcf_norm.port.o \
+              src/vector/src/vectorcf_mul.port.o  \
+              src/vector/src/vectorcf_trig.port.o"
+
+case $target_os in
+darwin*)
+    SH_LIB=libliquid.dylib
+    REBIND=""
+    ;;
+*)
+    SH_LIB=libliquid.so
+    REBIND=ldconfig
+    ;;
+esac
+
+#
+# autoconf variable substitutions
+#
+AC_SUBST(LIBS)                      # shared libraries (-lc, -lm, etc.)
+AC_SUBST(MLIBS_DOTPROD)             # 
+AC_SUBST(MLIBS_VECTOR)              #
+
+AC_SUBST(SH_LIB)                    # output shared library target
+AC_SUBST(REBIND)                    # rebinding tool (e.g. ldconfig)
+AC_SUBST(ARCH_OPTION)               # compiler architecture option
+
+AC_SUBST(DEBUG_OPTION)              # debug option
+AC_SUBST(CLIB)                      # C library linkage (e.g. '-lc')
+
+AC_CONFIG_FILES([makefile])
+AC_OUTPUT
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..a470197
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,635 @@
+
+liquid-dsp examples
+===================
+
+This directory contains all the examples for interfacing the liquid modules.
+
+ * `agc_crcf_example.c`:
+    Automatic gain control example demonstrating its transient response.
+
+ * `agc_crcf_qpsk_example.c`:
+    Automatic gain control test for data signals with fluctuating signal
+    levels.  QPSK modulation introduces periodic random zero-crossings which
+    gives instantaneous amplitude levels near zero.  This example tests the
+    response of the AGC to these types of signals.
+
+ * `ampmodem_example.c`:
+    Tests simple modulation/demodulation of the ampmodem (analog amplitude
+    modulator/demodulator) with noise, carrier phase, and carrier frequency
+    offsets.
+
+ * `asgramcf_example.c`:
+    ASCII spectrogram example for complex inputs. This example demonstrates
+    the functionality of the ASCII spectrogram. A sweeping complex sinusoid
+    is generated and the resulting spectral periodogram is printed to the
+    screen.
+
+ * `asgramf_example.c`:
+    ASCII spectrogram example for real-valued input. This example demonstrates
+    the functionality of the ASCII spectrogram for real-valued input siganls.
+    A cosine signal with time-varying frequency is generated and the resulting
+    spectral periodogram is printed to the screen. Because the time signal has
+    no complex component, its spectrum is symmetric.
+
+ * `autocorr_cccf_example.c`:
+    This example demonstrates the autocorr (auto-correlation) object
+    functionality.  A random time-domain sequence is generated which exhibits
+    time-domain repetitions (auto-correlation properties), for example:
+    abcdabcdabcd....abcd.  The sequence is pushed through the autocorr object,
+    and the results are written to an output file. The command-line arguments
+    allow the user to experiment with the sequence length, number of sequence
+    repetitions, and properties of the auto-correlator, as well as signal-to-
+    noise ratio.
+
+ * `bpacketsync_example.c`:
+
+ * `bpresync_example.c`:
+    This example demonstrates the binary pre-demodulator synchronizer. A random
+    binary sequence is generated, modulated with BPSK, and then interpolated.
+    The resulting sequence is used to generate a bpresync object which in turn
+    is used to detect a signal in the presence of carrier frequency and timing
+    offsets and additive white Gauss noise.
+
+ * `bsequence_example.c`:
+    This example demonstrates the interface to the bsequence (binary sequence)
+    object.  The bsequence object acts like a buffer of bits which are stored
+    and manipulated efficiently in memory.
+
+ * `bufferf_example.c`:
+
+ * `cgsolve_example.c`:
+    Solve linear system of equations `Ax = b` using the conjugate-
+    gradient method where A is a symmetric positive-definite matrix.
+    Compare speed to matrixf_linsolve() for same system.
+
+ * `chromosome_example.c`:
+
+ * `compand_cf_example.c`:
+
+ * `compand_example.c`:
+    This example demonstrates the interface to the compand function
+    (compression, expansion).  The compander is typically used with the
+    quantizer to increase the dynamic range of the converter, particularly for
+    low-level signals.  The transfer function is computed (emperically) and
+    printed to the screen.
+
+ * `complementary_codes_example.c`:
+    This example demonstrates how to generate complementary binary codes
+    in liquid.  A pair of codes is generated using the bsequence
+    interface, their auto-correlations are computed, and the result is
+    summed and printed to the screen.  The results are also printed to
+    an output file, which plots the sequences and their
+    auto-correlations.
+
+    SEE ALSO: `bsequence_example.c`
+              `msequence_example.c`
+
+ * `cpfskmodem_example.c`:
+
+ * `conversion_example.c`:
+    
+    This example demonstrates conversion from complex baseband to a real-valued
+    signal, and then down-conversion back to complex baseband while removing the
+    negative image.
+    
+     STEP 1: A signal is generated at complex baseband consisting of narrow-band
+             filtered noise and an offset tone (to show asymmetry in the transmit
+             spectrum).
+    
+     STEP 2: The signal is mixed up to a carrier 'fc' (relative to the sampling
+             frequency) and the real-component of the result is retained. This is
+             the DAC output. The spectrum of this signal has two images: one at
+             +fc, the other at -fc.
+    
+     STEP 3: The DAC output is mixed back down to complex baseband and the lower
+             image is (mostly) filtered off. Reminants of the lower frequency
+             component are still visible due to the wide-band and low-order
+             filter on the receiver. The received complex baseband signal also
+             has a reduction in power by 2 because half the signal's energy (the
+             negative image) is filtered off.
+    
+ * `crc_example.c`:
+    Cyclic redundancy check (CRC) example.  This example demonstrates how a
+    CRC can be used to validate data received through un-reliable means (e.g.
+    a noisy channel).  A CRC is, in essence, a strong algebraic error
+    detection code that computes a key on a block of data using base-2
+    polynomials.  Also available is a checksum for data validation.
+
+    SEE ALSO: `fec_example.c`
+
+ * `cvsd_example.c`:
+    Continuously-variable slope delta example, sinusoidal input. This example
+    demonstrates the CVSD audio encoder interface, and its response to a
+    sinusoidal input.  The output distortion ratio is computed, and the
+    time-domain results are written to a file.
+
+ * `dotprod_cccf_example.c`:
+    This example demonstrates the interface to the complex
+    floating-point dot product object (dotprod_cccf).
+
+ * `dotprod_rrrf_example.c`:
+    This example demonstrates the interface to the floating-point dot
+    product object (dotprod_rrrf).
+
+ * `eqlms_cccf_blind_example.c`:
+    This example tests the least mean-squares (LMS) equalizer (EQ) on a
+    signal with an unknown modulation and carrier frequency offset. That
+    is, the equalization is done completely blind of the modulation
+    scheme or its underlying data set. The error estimate assumes a
+    constant modulus linear modulation scheme. This works surprisingly
+    well even more amplitude-modulated signals, e.g. 'qam16'.
+
+ * `eqlms_cccf_block_example.c`:
+    This example tests the least mean-squares (LMS) equalizer (EQ) on a
+    signal with an unknown modulation and carrier frequency offset.
+    Equalization is performed blind on a block of samples and the reulting
+    constellation is output to a file for plotting.
+
+ * `eqlms_cccf_decisiondirected_example.c`:
+    Tests least mean-squares (LMS) equalizer (EQ) on a signal with a known
+    linear modulation scheme, but unknown data. The equalizer is updated
+    using decision-directed demodulator output samples.
+
+ * `eqlms_cccf_example.c`:
+ * `eqrls_cccf_example.c`:
+ * `fct_example.c`:
+
+ * `fec_example.c`:
+    This example demonstrates the interface for forward error-correction (FEC)
+    codes.  A buffer of data bytes is encoded and corrupted with several
+    errors.  The decoder then attempts to recover the original data set.  The
+    user may select the FEC scheme from the command-line interface.
+
+    SEE ALSO: `crc_example.c`
+              `checksum_example.c`
+              `packetizer_example.c`
+
+ * `fec_soft_example.c`:
+    This example demonstrates the interface for forward error-correction
+    (FEC) codes with soft-decision decoding.  A buffer of data bytes is
+    encoded before the data are corrupted with at least one error and
+    noise. The decoder then attempts to recover the original data set
+    from the soft input bits.  The user may select the FEC scheme from
+    the command-line interface.
+
+    SEE ALSO: `fec_example.c`
+              `packetizer_soft_example.c`
+
+ * `fft_example.c`:
+    This example demonstrates the interface to the fast discrete Fourier
+    transform (FFT).
+
+    SEE ALSO: `mdct_example.c`
+              `fct_example.c`
+
+  * `fftfilt_crcf_example.c`
+
+    Complex FFT-based finite impulse response filter example. This example
+    demonstrates the functionality of firfilt by designing a low-order 
+    prototype and using it to filter a noisy signal.  The filter coefficients
+    are  real, but the input and output arrays are complex. The filter order
+    and cutoff frequency are specified at the beginning, and the result is
+    compared to the regular corresponding firfilt_crcf output.
+
+    SEE ALSO: `firfilt_crcf_example.c`
+
+ * `firdecim_crcf_example.c`:
+    This example demonstrates the interface to the firdecim (finite
+    impulse response decimator) family of objects.
+    Data symbols are generated and then interpolated according to a
+    finite impulse response square-root Nyquist filter.  The resulting
+    sequence is then decimated with the same filter, matched to the
+    interpolator.
+
+    SEE ALSO: `firinterp_crcf_example.c`
+
+ * `firdes_kaiser_example.c`:
+    This example demonstrates finite impulse response filter design using a
+    Kaiser window.
+    
+    SEE ALSO: `firdespm_example.c`
+
+ * `firdespm_example.c`:
+    This example demonstrates finite impulse response filter design using the
+    Parks-McClellan algorithm.
+
+    SEE ALSO: `firdes_kaiser_example.c`
+
+ * `firfarrow_rrrf_sine_example.c`:
+
+ * `firfilt_rrrf_example.c`:
+
+ * `firfilt_cccf_example.c`:
+    This example demonstrates the finite impulse response (FIR) filter
+    with complex coefficients as a cross-correlator between transmitted
+    and received sequences.
+
+ * `firfilt_crcf_example.c`:
+    Complex finite impulse response filter example. Demonstrates the 
+    functionality of firfilt by designing a low-order prototype and using it 
+    to filter a noisy signal.  The filter coefficients are real, but the 
+    input and output arrays are complex. The filter order and cutoff 
+    frequency are specified at the beginning.
+
+ * `firhilb_decim_example.c`:
+    Hilbert transform: 2:1 real-to-complex decimator.  This example
+    demonstrates the functionality of firhilb (finite impulse response Hilbert
+    transform) decimator which converts a real time series into a complex one
+    with half the number of samples.  The input is a real-valued sinusoid of N
+    samples. The output is a complex-valued sinusoid of N/2 samples.
+
+    SEE ALSO: `firhilb_interp_example.c`
+
+ * `firhilb_example.c`:
+
+ * `firhilb_interp_example.c`:
+    Hilbert transform: 1:2 complex-to-real interpolator.  This example
+    demonstrates the functionality of firhilb (finite impulse response Hilbert
+    transform) interpolator which converts a complex time series into a real
+    one with twice the number of samples.  The input is a complex-valued
+    sinusoid of N samples. The output is a real-valued sinusoid of 2*N
+    samples.
+
+    SEE ALSO: `firhilb_decim_example.c`
+
+ * `firpfbch2_crcf_example.c`:
+    Example of the finite impulse response (FIR) polyphase filterbank
+    (PFB) channelizer with an output rate of 2 Fs / M as an (almost)
+    perfect reconstructive system.
+
+ * `firinterp_crcf_example.c`:
+    This example demonstrates the interp object (interpolator)
+    interface. Data symbols are generated and then interpolated
+    according to a finite impulse response Nyquist filter.
+
+ * `firpfbch_crcf_analysis_example.c`:
+    Example of the analysis channelizer filterbank. The input signal is
+    comprised of several signals spanning different frequency bands. The
+    channelizer downconverts each to baseband (maximally decimated), and
+    the resulting spectrum of each is plotted.
+
+ * `firpfbch_crcf_example.c`:
+    Finite impulse response (FIR) polyphase filter bank (PFB)
+    channelizer example.  This example demonstrates the functionality of
+    the polyphase filter bank channelizer and how its output is
+    mathematically equivalent to a series of parallel down-converters
+    (mixers/decimators). Both the synthesis and analysis filter banks
+    are presented.
+
+ * `firpfbch_crcf_synthesis_example.c`:
+    Example of the synthesis channelizer filterbank.  Random symbols are
+    generated and loaded into the bins of the channelizer and the
+    time-domain signal is synthesized.  Subcarriers around the band
+    edges are disabled as well as those near 0.25 to demonstrate the
+    synthesizer's ability to efficiently notch the spectrum. The results
+    are printed to a file for plotting.
+
+ * `flexframesync_example.c`:
+    This example demonstrates the basic interface to the flexframegen and
+    flexframesync objects used to completely encapsulate raw data bytes
+    into frame samples (nearly) ready for over-the-air transmission. A
+    14-byte header and variable length payload are encoded into baseband
+    symbols using the flexframegen object.  The resulting symbols are
+    interpolated using a root-Nyquist filter and the resulting samples are
+    then fed into the flexframesync object which attempts to decode the
+    frame. Whenever frame is found and properly decoded, its callback
+    function is invoked.
+
+ * `flexframesync_reconfig_example.c`:
+    Demonstrates the reconfigurability of the flexframegen and
+    flexframesync objects.
+
+ * `framesync64_example.c`:
+    This example demonstrates the interfaces to the framegen64 and
+    framesync64 objects used to completely encapsulate data for
+    over-the-air transmission.  A 24-byte header and 64-byte payload are
+    encoded, modulated, and interpolated using the framegen64 object.
+    The resulting complex baseband samples are corrupted with noise and
+    moderate carrier frequency and phase offsets before the framesync64
+    object attempts to decode the frame.  The resulting data are
+    compared to the original to validate correctness.
+
+    SEE ALSO: `flexframesync_example.c`
+
+ * `freqmodem_example.c`:
+
+ * `fskmodem_example.c`:
+    This example demostrates the M-ary frequency-shift keying
+    (MFSK) modem in liquid. A message signal is modulated and the
+    resulting signal is recovered using a demodulator object.
+
+ * `gasearch_example.c`:
+
+ * `gasearch_knapsack_example.c`:
+
+ * `gmskmodem_example.c`:
+
+ * `gradsearch_example.c`:
+
+ * `gradsearch_datafit_example.c`:
+    Fit 3-parameter curve to sampled data set in the minimum
+    mean-squared error sense.
+
+ * `iirdes_analog_example.c`:
+    Tests infinite impulse reponse (IIR) analog filter design. While this
+    example seems purely academic as IIR filters used in liquid are all
+    digital, it is important to realize that they are all derived from their
+    analog counterparts. This example serves to check the response of the
+    analog filters to ensure they are correct.  The results of design are
+    written to a file.
+
+    SEE ALSO: `iirdes_example.c`
+              `iirfilt_crcf_example.c`
+
+ * `iirdes_example.c`:
+    Tests infinite impulse reponse (IIR) digital filter design.
+
+    SEE ALSO: `iirdes_analog_example.c`
+              `iirfilt_crcf_example.c`
+
+ * `iirdes_pll_example.c`:
+    This example demonstrates 2nd-order IIR phase-locked loop filter
+    design with a practical simulation.
+
+    SEE ALSO: `nco_pll_example.c`
+              `nco_pll_modem_example.c`
+
+ * `iirfilt_cccf_example.c`:
+    Complex infinite impulse response filter example. Demonstrates the
+    functionality of iirfilt with complex coefficients by designing a
+    filter with specified parameters and then filters noise.
+
+ * `iirfilt_crcf_example.c`:
+    Complex infinite impulse response filter example. Demonstrates the
+    functionality of iirfilt by designing a low-order prototype (e.g.
+    Butterworth) and using it to filter a noisy signal.  The filter
+    coefficients are real, but the input and output arrays are complex.  The
+    filter order and cutoff frequency are specified at the beginning.
+
+ * `iirinterp_crcf_example.c`:
+    This example demonstrates the iirinterp object (IIR interpolator)
+    interface.
+
+ * `interleaver_example.c`:
+    This example demonstrates the functionality of the liquid interleaver
+    object.  Interleavers serve to distribute  grouped bit errors evenly
+    throughout a block of data. This aids certain forward error-correction
+    codes in correcting bit errors.  In this example, data bits are
+    interleaved and de-interleaved; the resulting sequence is validated to
+    match the original.
+
+    SEE ALSO: `packetizer_example.c`
+
+ * `interleaver_scatterplot_example.c`:
+
+ * `interleaver_soft_example.c`:
+
+ * `kbd_window_example.c`:
+
+ * `lpc_example.c`:
+    This example demonstrates linear prediction in liquid. An input signal
+    is generated which exhibits a strong temporal correlation. The linear
+    predictor generates an approximating all-pole filter which minimizes
+    the squared error between the prediction and the actual output.
+
+ * `matched_filter_example.c`:
+
+ * `math_lngamma_example.c`:
+    Demonstrates accuracy of lngamma function.
+
+ * `mdct_example.c`:
+
+ * `modem_arb_example.c`:
+    This example demonstrates the functionality of the arbitrary modem, a
+    digital modulator/demodulator object with signal constellation points
+    chosen arbitrarily.  A simple bit-error rate simulation is then run to
+    test the performance of the modem.  The results are written to a file.
+
+    SEE ALSO: `modem_example.c`
+
+ * `modem_example.c`:
+    This example demonstates the digital modulator/demodulator (modem) object.
+    Data symbols are modulated into complex samples which are then demodulated
+    without noise or phase offsets.  The user may select the modulation scheme
+    via the command-line interface.
+
+    SEE ALSO: `modem_arb_example.c`
+
+ * `modem_soft_example.c`:
+    This example demonstates soft demodulation of linear
+    modulation schemes.
+
+ * `modular_arithmetic_example.c`:
+    This example demonstates some modular arithmetic functions.
+
+ * `msequence_example.c`:
+    This example demonstrates the auto-correlation properties of a
+    maximal-length sequence (m-sequence).  An m-sequence of a certain length
+    is used to generate two binary sequences (buffers) which are then
+    cross-correlated.  The resulting correlation produces -1 for all values
+    except at index zero, where the sequences align.
+
+    SEE ALSO: `bsequence_example.c`
+
+ * `msourcecf_example.c`:
+    This example demonstrates generating multiple signal sources simultaneously
+    for testing using the msource (multi-source) family of objects.
+
+ * `msresamp_crcf_example.c`:
+    Demonstration of the multi-stage arbitrary resampler.
+
+ * `msresamp2_crcf_example.c`:
+    Demonstration of the multi-stage half-band resampler.
+
+ * `nco_example.c`:
+    This example demonstrates the most basic functionality of the
+    numerically-controlled oscillator (NCO) object.
+
+    SEE ALSO: `nco_pll_example.c`
+              `nco_pll_modem_example.c`
+
+ * `nco_pll_example.c`:
+    This example demonstrates how the use the nco/pll object
+    (numerically-controlled oscillator with phase-locked loop) interface for
+    tracking to a complex sinusoid.  The loop bandwidth, phase offset, and
+    other parameter can be specified via the command-line interface.
+
+    SEE ALSO: `nco_example.c`
+              `nco_pll_modem_example.c`
+
+ * `nco_pll_modem_example.c`:
+    This example demonstrates how the nco/pll object (numerically-controlled
+    oscillator with phase-locked loop) can be used for carrier frequency
+    recovery in digital modems.  The modem type, SNR, and other parameters are
+    specified via the command-line interface.
+
+    SEE ALSO: `nco_example.c`
+              `nco_pll_example.c`
+
+ * `nyquist_filter_example.c`:
+ * `ofdmflexframesync_example.c`:
+ * `ofdmframegen_example.c`:
+ * `ofdmframesync_example.c`:
+
+ * `packetizer_example.c`:
+    Demonstrates the functionality of the packetizer object.  Data are encoded
+    using two forward error-correction schemes (an inner and outer code)
+    before data errors are introduced.  The decoder then tries to recover the
+    original data message.
+
+    SEE ALSO: `fec_example.c`
+              `crc_example.c`
+
+ * `packetizer_soft_example.c`:
+    This example demonstrates the functionality of the packetizer object
+    for soft-decision decoding.  Data are encoded using two forward error-
+    correction schemes (an inner and outer code) before noise and data
+    errors are added. The decoder then tries to recover the original data
+    message. Only the outer code uses soft-decision decoding.
+
+    SEE ALSO: `fec_soft_example.c`
+              `packetizer_example.c`
+
+ * `pll_example.c`:
+    Demonstrates a basic phase-locked loop to track the phase of a
+    complex sinusoid.
+
+ * `poly_findroots_example.c`:
+
+ * `polyfit_example.c`:
+    Test polynomial fit to sample data.
+
+    SEE ALSO: `polyfit_lagrange_example.c`
+
+ * `polyfit_lagrange_example.c`:
+    Test exact polynomial fit to sample data using Lagrange interpolating
+    polynomials.
+
+    SEE ALSO: `polyfit_example.c`
+
+ * `qdetector_cccf_example.c`:
+    This example demonstrates the functionality of the qdetector object
+    to detect an arbitrary signal in time in the presence of noise,
+    carrier frequency/phase offsets, and fractional-sample timing
+    offsets.
+
+ * `qpacketmodem_example.c`:
+    This example demonstrates the basic packet modem encoder/decoder
+    operation. A packet of data is encoded and modulated into symbols,
+    channel noise is added, and the resulting packet is demodulated
+    and decoded.
+
+ * `qnsearch_example.c`:
+
+ * `quantize_example.c`:
+
+ * `random_histogram_example.c`:
+    This example tests the random number generators for different
+    distributions.
+
+ * `repack_bytes_example.c`:
+    This example demonstrates the repack_bytes() interface by packing a
+    sequence of three 3-bit symbols into five 2-bit symbols.  The
+    results are printed to the screen.  Because the total number of bits
+    in the input is 9 and not evenly divisible by 2, the last of the 5
+    output symbols has a zero explicitly padded to the end.
+
+  * `resamp2_cccf_example.c`
+    This example demonstrates the halfband resampler cenetered at the
+    quarter sample rate to split the signal into positive and negative
+    frequency bands. Two distinct narrow-band signals are generated; one
+    at a positive frequency and one at a negative frequency. The resamp2
+    object is run as a filter to separate the two about the zero-
+    frequency center point.
+
+ * `resamp2_crcf_example.c`:
+    This example demonstrates the halfband resampler running as both an
+    interpolator and a decimator. A narrow-band signal is first
+    interpolated by a factor of 2, and then decimated. The resulting RMS
+     error between the final signal and original is computed and printed
+    to the screen.
+
+ * `resamp2_crcf_decim_example.c`:
+    Halfband decimator.  This example demonstrates the interface to the
+    decimating halfband resampler.  A low-frequency input sinusoid is
+    generated and fed into the decimator two samples at a time, producing one
+    output at each iteration.  The results are written to an output file.
+
+    SEE ALSO: `resamp2_crcf_interp_example.c`
+              `decim_rrrf_example.c`
+
+ * `resamp2_crcf_filter_example.c`:
+    Halfband (two-channel) filterbank example. This example demonstrates
+    the analyzer/synthesizer execute() methods for the resamp2_xxxt
+    family of objects.
+
+    NOTE: The filterbank is not a perfect reconstruction filter; a
+          significant amount of distortion occurs in the transition band
+          of the half-band filters.
+
+ * `resamp2_crcf_interp_example.c`:
+    Halfband interpolator.  This example demonstrates the interface to the
+    interpolating halfband resampler.  A low-frequency input sinusoid is
+    generated and fed into the interpolator one sample at a time, producing
+    two outputs at each iteration.  The results are written to an output file.
+
+    SEE ALSO: `resamp2_crcf_decim_example.c`
+              `interp_crcf_example.c`
+
+ * `resamp_crcf_example.c`:
+
+ * `scramble_example.c`:
+    Data-scrambling example.  Physical layer synchronization of received
+    waveforms relies on independent and identically distributed underlying
+    data symbols.  If the message sequence, however, is '00000....' and the
+    modulation scheme is BPSK, the synchronizer probably won't be able to
+    recover the symbol timing.  It is imperative to increase the entropy of
+    the data for this to happen.  The data scrambler routine attempts to
+    'whiten' the data sequence with a bit mask in order to achieve maximum
+    entropy.  This example demonstrates the interface.
+
+ * `smatrix_example.c`:
+
+ * `spgramcf_example.c`:
+   Spectral periodogram example with complex inputs.
+
+ * `spgramf_example.c`:
+   Spectral periodogram example with real inputs.
+
+ * `symsync_crcf_example.c`:
+    This example demonstrates the basic principles of the symbol timing
+    recovery family of objects, specifically symsync_crcf. A set of random
+    QPSK symbols are generated and interpolated with a timing offset. The
+    resulting signal is run through the symsync_crcf object which applies a
+    matched filter and recovers timing producing a clean constellation.
+
+ * `symsync_crcf_full_example.c`:
+    This example extends that of `symsync_crcf_example.c` by including options
+    for simulating a timing rate offset in addition to just a timing phase
+    error. The resulting output file shows not just the constellation but the
+    time domain sequence as well as the timing phase estimate over time.
+
+ * `symsync_crcf_kaiser_example.c`:
+    This is a simplified example of the symync family of objects to show how
+    symbol timing can be recovered after the matched filter output.
+
+  * `symtrack_cccf_example.c`:
+    
+    This example demonstrates how to recover data symbols using the symtrack
+    object. A stream of modulated and interpolated symbols are generated using
+    the symstream object. The resulting samples are passed through a channel
+    to add various impairments. The symtrack object recovers timing, carrier,
+    and other information imparted by the channel and returns data symbols
+    ready for demodulation.
+
+ * `wdelayf_example.c`:
+
+ * `windowf_example.c`:
+    This example demonstrates the functionality of a window buffer (also
+    known as a circular or ring buffer) of floating-point values.
+    Values are written to and read from the buffer using several
+    different methods.
+
+    SEE ALSO: `bufferf_example.c`
+              `wdelayf_example.c`
+
diff --git a/examples/agc_crcf_example.c b/examples/agc_crcf_example.c
new file mode 100644
index 0000000..845eebb
--- /dev/null
+++ b/examples/agc_crcf_example.c
@@ -0,0 +1,129 @@
+//
+// agc_crcf_example.c
+//
+// Automatic gain control example demonstrating its transient
+// response.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "agc_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("agc_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  n     : number of samples, n >=100, default: 2000\n");
+    printf("  b     : AGC bandwidth,     b >=  0, default: 0.01\n");
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    float        bt          = 0.01f;   // agc loop bandwidth
+    float        gamma       = 0.001f;  // initial signal level
+    unsigned int num_samples = 2000;    // number of samples
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"hn:N:s:b:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': num_samples = atoi(optarg);   break;
+        case 'b': bt          = atof(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (bt < 0.0f) {
+        fprintf(stderr,"error: %s, bandwidth must be positive\n", argv[0]);
+        exit(1);
+    } else if (num_samples == 0) {
+        fprintf(stderr,"error: %s, number of samples must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+    
+    unsigned int i;
+
+    // create objects
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q, bt);
+
+    float complex x[num_samples];   // input
+    float complex y[num_samples];   // output
+    float rssi[num_samples];        // received signal strength
+
+    // print info
+    printf("automatic gain control // loop bandwidth: %4.2e\n",bt);
+
+    // generate signal
+    for (i=0; i<num_samples; i++)
+        x[i] = gamma * cexpf(_Complex_I*2*M_PI*0.0193f*i);
+
+    // run agc
+    for (i=0; i<num_samples; i++) {
+        // apply gain
+        agc_crcf_execute(q, x[i], &y[i]);
+
+        // retrieve signal level [dB]
+        rssi[i] = agc_crcf_get_rssi(q);
+    }
+
+    // destroy AGC object
+    agc_crcf_destroy(q);
+
+    // 
+    // export results
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    if (!fid) {
+        fprintf(stderr,"error: %s, could not open '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+    fprintf(fid,"%% %s: auto-generated file\n\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"n = %u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"rssi(%4u)  = %12.4e;\n", i+1, rssi[i]);
+    }
+
+    // plot results
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t, real(x), '-', 'Color',[0 0.2 0.5],...\n");
+    fprintf(fid,"       t, imag(x), '-', 'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('input');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(t, real(y), '-', 'Color',[0 0.2 0.5],...\n");
+    fprintf(fid,"       t, imag(y), '-', 'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('output');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t,rssi,'-','LineWidth',1.2,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('rssi [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/agc_crcf_qpsk_example.c b/examples/agc_crcf_qpsk_example.c
new file mode 100644
index 0000000..a8b498b
--- /dev/null
+++ b/examples/agc_crcf_qpsk_example.c
@@ -0,0 +1,128 @@
+//
+// agc_crcf_qpsk_example.c
+//
+// Automatic gain control test for data signals with fluctuating signal
+// levels.  QPSK modulation introduces periodic random zero-crossings
+// which gives instantaneous amplitude levels near zero.  This example
+// tests the response of the AGC to these types of signals.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "agc_crcf_qpsk_example.m"
+
+int main(int argc, char*argv[])
+{
+    // options
+    float        noise_floor= -40.0f;   // noise floor [dB]
+    float        SNRdB      = 20.0f;    // signal-to-noise ratio [dB]
+    float        bt         = 0.05f;    // loop bandwidth
+    unsigned int num_symbols= 100;      // number of iterations
+    unsigned int d          = 5;        // print every d iterations
+
+    unsigned int k          = 2;        // interpolation factor (samples/symbol)
+    unsigned int m          = 3;        // filter delay (symbols)
+    float        beta       = 0.3f;     // filter excess bandwidth factor
+    float        dt         = 0.0f;     // filter fractional sample delay
+
+    // derived values
+    unsigned int num_samples=num_symbols*k;
+    float gamma = powf(10.0f, (SNRdB+noise_floor)/20.0f);   // channel gain
+    float nstd = powf(10.0f, noise_floor / 20.0f);
+
+    // arrays
+    float complex x[num_samples];
+    float complex y[num_samples];
+    float rssi[num_samples];
+
+    // create objects
+    modem mod = modem_create(LIQUID_MODEM_QPSK);
+    firinterp_crcf interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_RRC,k,m,beta,dt);
+    agc_crcf p = agc_crcf_create();
+    agc_crcf_set_bandwidth(p, bt);
+
+    unsigned int i;
+
+    // print info
+    printf("automatic gain control // loop bandwidth: %4.2e\n",bt);
+
+    unsigned int sym;
+    float complex s;
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        sym = modem_gen_rand_sym(mod);
+        modem_modulate(mod, sym, &s);
+        s *= gamma;
+
+        firinterp_crcf_execute(interp, s, &x[i*k]);
+    }
+
+    // add noise
+    for (i=0; i<num_samples; i++)
+        x[i] += nstd*(randnf() + _Complex_I*randnf()) * M_SQRT1_2;
+
+    // run agc
+    for (i=0; i<num_samples; i++) {
+        agc_crcf_execute(p, x[i], &y[i]);
+
+        rssi[i] = agc_crcf_get_rssi(p);
+    }
+
+    // destroy objects
+    modem_destroy(mod);
+    agc_crcf_destroy(p);
+    firinterp_crcf_destroy(interp);
+
+    // print results to screen
+    printf("received signal strength indication (rssi):\n");
+    for (i=0; i<num_samples; i+=d) {
+        printf("%4u : %8.2f\n", i, rssi[i]);
+    }
+
+
+    // 
+    // export results
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    if (!fid) {
+        fprintf(stderr,"error: %s, could not open '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+    fprintf(fid,"%% %s: auto-generated file\n\n",OUTPUT_FILENAME);
+    fprintf(fid,"n = %u;\n", num_samples);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+
+    // print results
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"   x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"   y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"rssi(%4u) = %12.4e;\n", i+1, rssi[i]);
+    }
+
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"n = length(x);\n");
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,rssi,'-k','LineWidth',2);\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('rssi [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(y),t,imag(y));\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('agc output');\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/agc_rrrf_example.c b/examples/agc_rrrf_example.c
new file mode 100644
index 0000000..c12c843
--- /dev/null
+++ b/examples/agc_rrrf_example.c
@@ -0,0 +1,123 @@
+//
+// agc_rrrf_example.c
+//
+// Automatic gain control example demonstrating its transient
+// response.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "agc_rrrf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("agc_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  n     : number of samples, n >=100, default: 2000\n");
+    printf("  b     : AGC bandwidth,     b >=  0, default: 0.01\n");
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    float        bt          = 0.01f;   // agc loop bandwidth
+    float        gamma       = 0.001f;  // initial signal level
+    unsigned int num_samples = 2000;    // number of samples
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"hn:N:s:b:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': num_samples = atoi(optarg);   break;
+        case 'b': bt          = atof(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (bt < 0.0f) {
+        fprintf(stderr,"error: %s, bandwidth must be positive\n", argv[0]);
+        exit(1);
+    } else if (num_samples == 0) {
+        fprintf(stderr,"error: %s, number of samples must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+    
+    unsigned int i;
+
+    // create objects
+    agc_rrrf q = agc_rrrf_create();
+    agc_rrrf_set_bandwidth(q, bt);
+
+    float x[num_samples];       // input
+    float y[num_samples];       // output
+    float rssi[num_samples];    // received signal strength
+
+    // print info
+    printf("automatic gain control // loop bandwidth: %4.2e\n",bt);
+
+    // generate signal
+    for (i=0; i<num_samples; i++)
+        x[i] = gamma * cosf(2*M_PI*0.093f*i);
+
+    // run agc
+    for (i=0; i<num_samples; i++) {
+        agc_rrrf_execute(q, x[i], &y[i]);
+        rssi[i] = agc_rrrf_get_rssi(q);
+    }
+
+    // destroy AGC object
+    agc_rrrf_destroy(q);
+
+    // 
+    // export results
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    if (!fid) {
+        fprintf(stderr,"error: %s, could not open '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+    fprintf(fid,"%% %s: auto-generated file\n\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"n = %u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e;\n", i+1, x[i]);
+        fprintf(fid,"y(%4u) = %12.4e;\n", i+1, y[i]);
+        fprintf(fid,"rssi(%4u)  = %12.4e;\n", i+1, rssi[i]);
+    }
+
+    // plot results
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t,x, '-','Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('input');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(t,y, '-','Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('output');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t,rssi,'-','LineWidth',1.2,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('rssi [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/ampmodem_example.c b/examples/ampmodem_example.c
new file mode 100644
index 0000000..a744b26
--- /dev/null
+++ b/examples/ampmodem_example.c
@@ -0,0 +1,170 @@
+// 
+// ampmodem_test.c
+//
+// Tests simple modulation/demodulation of the ampmodem (analog
+// amplitude modulator/demodulator) with noise, carrier phase,
+// and carrier frequency offsets.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ampmodem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("ampmodem_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  f     : frequency offset, default: 0.02\n");
+    printf("  p     : phase offset, default: -pi/4\n");
+    printf("  n     : number of samples, default: 256\n");
+    printf("  S     : SNR [dB], default: 20\n");
+    printf("  t     : AM type (dsb/usb/lsb), default: dsb\n");
+    printf("  s     : suppress the carrier, default: off\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float mod_index = 0.1f;         // modulation index (bandwidth)
+    float fc = 0.0f;                // AM carrier
+    float cfo = 0.02f;              // carrier frequency offset
+    float cpo = -M_PI / 4.0f;       // carrier phase offset
+    unsigned int num_samples = 256; // number of samples
+    float SNRdB = 30.0f;            // signal-to-noise ratio [dB]
+    liquid_ampmodem_type type = LIQUID_AMPMODEM_USB;
+    int suppressed_carrier = 0;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhf:p:n:S:t:s")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 'f':   cfo = atof(optarg); break;
+        case 'p':   cpo = atof(optarg); break;
+        case 'n':   num_samples = atoi(optarg); break;
+        case 'S':   SNRdB = atof(optarg);       break;
+        case 't':
+            if (strcmp(optarg,"dsb")==0) {
+                type = LIQUID_AMPMODEM_DSB;
+            } else if (strcmp(optarg,"usb")==0) {
+                type = LIQUID_AMPMODEM_USB;
+            } else if (strcmp(optarg,"lsb")==0) {
+                type = LIQUID_AMPMODEM_LSB;
+            } else {
+                fprintf(stderr,"error: %s, invalid AM type: %s\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        case 's':   suppressed_carrier = 1; break;
+        default:
+            usage();
+            return 1;
+        }
+    }
+
+    // create mod/demod objects
+    ampmodem mod   = ampmodem_create(mod_index, fc, type, suppressed_carrier);
+    ampmodem demod = ampmodem_create(mod_index, fc, type, suppressed_carrier);
+    ampmodem_print(mod);
+
+    unsigned int i;
+    float x[num_samples];
+    float complex y[num_samples];
+    float z[num_samples];
+
+    // generate 'audio' signal
+    unsigned int h_len = 21;
+    float h[h_len];
+    liquid_firdes_kaiser(h_len, 0.03f, -40.0f, 0.0f, h);
+    firfilt_rrrf faudio = firfilt_rrrf_create(h,h_len);
+    for (i=0; i<num_samples; i++) {
+        // push random sample
+        firfilt_rrrf_push(faudio, 0.3f*randnf());
+        firfilt_rrrf_execute(faudio, &x[i]);
+
+        // clip
+        x[i] = tanhf(x[i]);
+
+        // add frequency offset (not centered at zero)
+        //x[i] *= sinf(2*M_PI*i*0.13f);
+
+        // apply window
+        //x[i] *= hamming(i,num_samples);
+    }
+    firfilt_rrrf_destroy(faudio);
+
+    // modulate signal
+    for (i=0; i<num_samples; i++)
+        ampmodem_modulate(mod, x[i], &y[i]);
+
+    // add channel impairments
+    float nstd = powf(10.0f,-SNRdB/20.0f);
+    for (i=0; i<num_samples; i++) {
+        y[i] *= cexpf(_Complex_I*(2*M_PI*cfo*i + cpo));
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // demodulate signal
+    for (i=0; i<num_samples; i++)
+        ampmodem_demodulate(demod, y[i], &z[i]);
+
+    // destroy objects
+    ampmodem_destroy(mod);
+    ampmodem_destroy(demod);
+
+    // compute demodulation error
+    unsigned int delay = (type == LIQUID_AMPMODEM_DSB) ? 0 : 18; // fixed delay
+    float rmse = 0.0f;
+    for (i=delay; i<num_samples; i++)
+        rmse += (x[i-delay] - z[i]) * (x[i-delay] - z[i]);
+    rmse = sqrtf( rmse / (float)(num_samples-delay) );
+    printf("rms error : %12.8f dB\n", 10*log10f(rmse));
+
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n=%u;\n",num_samples);
+    fprintf(fid,"delay=%u;\n", delay);
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%3u) = %12.4e;\n", i+1, x[i]);
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%3u) = %12.4e;\n", i+1, z[i]);
+    }
+    // plot results
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,x,t-delay,z);\n");
+    fprintf(fid,"axis([-delay n -1.2 1.2]);\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('signal');\n");
+    fprintf(fid,"legend('original','demodulated',1);\n");
+    fprintf(fid,"grid on;\n");
+    // spectrum
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y,nfft))));\n");
+    fprintf(fid,"Y = Y - max(Y);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Y);\n");
+    fprintf(fid,"axis([-0.5 0.5 -60 10]);\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/asgramcf_example.c b/examples/asgramcf_example.c
new file mode 100644
index 0000000..67fd505
--- /dev/null
+++ b/examples/asgramcf_example.c
@@ -0,0 +1,82 @@
+//
+// asgramcf_example.c
+//
+// ASCII spectrogram example for complex inputs. This example demonstrates
+// the functionality of the ASCII spectrogram. A sweeping complex sinusoid
+// is generated and the resulting spectral periodogram is printed to the
+// screen.
+//
+
+#include <unistd.h> // usleep
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.h"
+
+int main() {
+    // options
+    unsigned int nfft        =   64;    // transform size
+    unsigned int num_frames  =  200;    // total number of frames
+    unsigned int msdelay     =   50;    // delay between transforms [ms]
+    float        noise_floor = -40.0f;  // noise floor
+
+    // initialize objects
+    asgramcf q = asgramcf_create(nfft);
+    asgramcf_set_scale(q, noise_floor+15.0f, 5.0f);
+
+    unsigned int i;
+    unsigned int n;
+    float theta  = 0.0f;    // current instantaneous phase
+    float dtheta = 0.0f;    // current instantaneous frequency
+    float phi    = 0.0f;    // phase of sinusoidal frequency drift
+    float dphi   = 0.003f;  // frequency of sinusoidal frequency drift
+
+    float complex x[nfft];
+    float maxval;
+    float maxfreq;
+    char ascii[nfft+1];
+    ascii[nfft] = '\0'; // append null character to end of string
+    float nstd = powf(10.0f,noise_floor/20.0f);  // noise standard deviation
+    for (n=0; n<num_frames; n++) {
+        // generate a frame of data samples
+        for (i=0; i<nfft; i++) {
+            // complex exponential
+            x[i] = cexpf(_Complex_I*theta);
+
+            // add noise to signal
+            x[i] += nstd * (randnf() + _Complex_I*randnf()) * M_SQRT1_2;
+
+            // adjust frequency and phase
+            theta  += dtheta;
+            dtheta =  0.9f*M_PI*sinf(phi) * hamming(n, num_frames);
+            phi    += dphi;
+        }
+
+        // write block of samples to the spectrogram object
+        asgramcf_write(q, x, nfft);
+
+        // execute the spectrogram
+        asgramcf_execute(q, ascii, &maxval, &maxfreq);
+
+        // print the spectrogram to stdout
+        printf(" > %s < pk%5.1f dB [%5.2f]\n", ascii, maxval, maxfreq);
+
+        // optional: find peak and print arrow pointing to peak
+        int peak_index = (int)( (maxfreq+0.5) * nfft );
+        peak_index = peak_index < 0      ? 0      : peak_index;
+        peak_index = peak_index > nfft-1 ? nfft-1 : peak_index;
+        memset(ascii, ' ', nfft);   // clear ascii array with spaces
+        ascii[peak_index] = '^';    // set peak index to caret character
+        printf(" > %s < peak location\r", ascii);
+        fflush(stdout);
+
+        // sleep for some time before generating the next frame
+        usleep(msdelay*1000);
+    }
+
+    asgramcf_destroy(q);
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/asgramf_example.c b/examples/asgramf_example.c
new file mode 100644
index 0000000..56fb503
--- /dev/null
+++ b/examples/asgramf_example.c
@@ -0,0 +1,62 @@
+//
+// asgramf_example.c
+//
+// ASCII spectrogram example for real-valued input. This example demonstrates
+// the functionality of the ASCII spectrogram for real-valued input siganls.
+// A cosine signal with time-varying frequency is generated and the resulting
+// spectral periodogram is printed to the screen. Because the time signal has
+// no complex component, its spectrum is symmetric.
+//
+
+#include <unistd.h> // usleep
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.h"
+
+int main() {
+    // options
+    unsigned int nfft        =   64;    // transform size
+    unsigned int num_frames  =  200;    // total number of frames
+    unsigned int msdelay     =   50;    // delay between transforms [ms]
+    float        noise_floor = -40.0f;  // noise floor
+
+    // initialize objects
+    asgramf q = asgramf_create(nfft);
+    asgramf_set_scale(q, noise_floor+15.0f, 5.0f);
+
+    unsigned int i;
+    unsigned int n;
+    float theta  = 0.0f;    // current instantaneous phase
+    float dtheta = 0.0f;    // current instantaneous frequency
+    float phi    = 0.0f;    // phase of sinusoidal frequency drift
+    float dphi   = 0.003f;  // frequency of sinusoidal frequency drift
+
+    float nstd = powf(10.0f,noise_floor/20.0f);  // noise standard deviation
+    for (n=0; n<num_frames; n++) {
+        // generate a frame of data samples
+        for (i=0; i<nfft; i++) {
+            // cosine wave of time-varying frequency with noise
+            float x = cosf(theta) + nstd*randnf();
+
+            // push sample into spectrogram object
+            asgramf_push(q, x);
+
+            // adjust frequency and phase
+            theta  += dtheta;
+            dtheta =  0.5f*M_PI + 0.4f*M_PI*sinf(phi) * hamming(n, num_frames);
+            phi    += dphi;
+        }
+
+        // print the spectrogram to stdout
+        asgramf_print(q);
+
+        // sleep for some time before generating the next frame
+        usleep(msdelay*1000);
+    }
+
+    asgramf_destroy(q);
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/autocorr_cccf_example.c b/examples/autocorr_cccf_example.c
new file mode 100644
index 0000000..f536832
--- /dev/null
+++ b/examples/autocorr_cccf_example.c
@@ -0,0 +1,159 @@
+//
+// autocorr_cccf_example.c
+//
+// This example demonstrates the autocorr (auto-correlation) object
+// functionality.  A random time-domain sequence is generated which
+// exhibits time-domain repetitions (auto-correlation properties),
+// for example:  abcdabcdabcd....abcd.  The sequence is pushed through
+// the autocorr object, and the results are written to an output file.
+// The command-line arguments allow the user to experiment with the
+// sequence length, number of sequence repetitions, and properties of
+// the auto-correlator, as well as signal-to-noise ratio.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "autocorr_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("autocorr_cccf_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  m     : sequence length, default: 32\n");
+    printf("  n     : number of sequences (repetitions), default: 8\n");
+    printf("  w     : autocorr window length, default: 64\n");
+    printf("  d     : autocorr delay length (multiple of 's'), default: 32\n");
+    printf("  e     : normalize by energy? default: off\n");
+    printf("  s     : SNR, signal-to-noise ratio [dB], default: 20\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int sequence_len = 32;     // short sequence length
+    unsigned int n = 8;                 // number short sequences (repetition length)
+    unsigned int window_size = 64;      // autocorr window size
+    unsigned int delay = sequence_len;  // autocorr delay (multiple of 's')
+    int normalize_by_energy = 0;        // normalize output by E{x^2}?
+    float SNRdB=20.0f;                  // signal-to-noise ratio (dB)
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhm:n:w:d:es:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'm': sequence_len = atof(optarg);  break;
+        case 'n': n = atof(optarg);             break;
+        case 'w': window_size = atof(optarg);   break;
+        case 'd': delay = atof(optarg);         break;
+        case 'e': normalize_by_energy = 1;      break;
+        case 's': SNRdB = atoi(optarg);         break;
+            usage();
+            return 1;
+        }
+    }
+
+
+    // derived values
+    unsigned int num_samples = sequence_len*(n+2); // pad end w/ zeros
+
+    // data arrays
+    float complex sequence[sequence_len];   // short sequence
+    float complex x[num_samples];           // autocorr input sequence
+    float complex rxx[num_samples];         // autocorr output
+
+    // generate objects
+    autocorr_cccf q = autocorr_cccf_create(window_size,delay);
+
+    unsigned int i;
+
+    // generate random training sequence using QPSK symbols
+    modem mod = modem_create(LIQUID_MODEM_QPSK);
+    for (i=0; i<sequence_len; i++)
+        modem_modulate(mod, rand()%4, &sequence[i]);
+    modem_destroy(mod);
+
+    // write training sequence 'n' times, followed by zeros
+    unsigned int t=0;
+    for (i=0; i<n; i++) {
+        // copy sequence
+        memmove(&x[t], sequence, sequence_len*sizeof(float complex));
+
+        t += sequence_len;
+    }
+
+    // pad end with zeros
+    for (i=t; i<num_samples; i++)
+        x[i] = 0;
+
+    // add noise
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++)
+        cawgn(&x[i],nstd);
+        
+    // compute auto-correlation
+    for (i=0; i<num_samples; i++) {
+        autocorr_cccf_push(q,x[i]);
+        autocorr_cccf_execute(q,&rxx[i]);
+
+        // normalize by energy
+        if (normalize_by_energy)
+            rxx[i] /= autocorr_cccf_get_energy(q);
+    }
+
+    // find peak
+    float complex rxx_peak = 0;
+    for (i=0; i<num_samples; i++) {
+        if (i==0 || cabsf(rxx[i]) > cabsf(rxx_peak))
+            rxx_peak = rxx[i];
+    }
+    printf("peak auto-correlation : %12.8f, angle %12.8f\n", cabsf(rxx_peak),
+                                                             cargf(rxx_peak));
+
+
+    // destroy allocated objects
+    autocorr_cccf_destroy(q);
+
+    // 
+    // write results to file
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    // write signal to output file
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n",i+1,crealf(x[i]),cimagf(x[i]));
+
+        fprintf(fid,"rxx(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rxx[i]), cimagf(rxx[i]));
+    }
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t=1:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(x),t,imag(x));\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('received signal');\n");
+    fprintf(fid,"legend('real','imag',0);\n");
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,abs(rxx));\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('auto-correlation magnitude');\n");
+
+    fprintf(fid,"\n\n");
+    fclose(fid);
+    printf("data written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/bpacketsync_example.c b/examples/bpacketsync_example.c
new file mode 100644
index 0000000..8b2224f
--- /dev/null
+++ b/examples/bpacketsync_example.c
@@ -0,0 +1,173 @@
+//
+// bpacketsync_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+int callback(unsigned char *  _payload,
+             int              _payload_valid,
+             unsigned int     _payload_len,
+             framesyncstats_s _stats,
+             void *           _userdata)
+{
+    printf("callback invoked, payload (%u bytes) : %s\n",
+            _payload_len,
+            _payload_valid ? "valid" : "INVALID!");
+
+    // copy data if valid
+    if (_payload_valid) {
+        unsigned char * msg_dec = (unsigned char*) _userdata;
+
+        memmove(msg_dec, _payload, _payload_len*sizeof(unsigned char));
+    }
+
+    return 0;
+}
+
+// print usage/help message
+void usage()
+{
+    printf("bpacketsync_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : input data size (number of uncoded bytes): 8 default\n");
+    printf("  e     : bit error rate of channel, default: 0\n");
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+}
+
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int n=8;                       // original data message length
+    crc_scheme check = LIQUID_CRC_32;       // data integrity check
+    fec_scheme fec0 = LIQUID_FEC_HAMMING74; // inner code
+    fec_scheme fec1 = LIQUID_FEC_NONE;      // outer code
+    float bit_error_rate = 0.0f;            // bit error rate
+
+    // read command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:e:v:c:k:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n': n = atoi(optarg);     break;
+        case 'e': bit_error_rate = atof(optarg);     break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            // inner FEC scheme
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported inner FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            // outer FEC scheme
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported outer FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (n == 0) {
+        fprintf(stderr,"error: %s, packet length must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (bit_error_rate < 0.0f || bit_error_rate > 1.0f) {
+        fprintf(stderr,"error: %s, channel bit error rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // create packet generator
+    bpacketgen pg = bpacketgen_create(0, n, check, fec0, fec1);
+    bpacketgen_print(pg);
+
+    unsigned int i;
+
+    // compute packet length
+    unsigned int k = bpacketgen_get_packet_len(pg);
+
+    // initialize arrays
+    unsigned char msg_org[n];   // original message
+    unsigned char msg_enc[k];   // encoded message
+    unsigned char msg_rec[k+1]; // recieved message
+    unsigned char msg_dec[n];   // decoded message
+
+    // create packet synchronizer
+    bpacketsync ps = bpacketsync_create(0, callback, (void*)msg_dec);
+
+    // initialize original data message
+    for (i=0; i<n; i++)
+        msg_org[i] = rand() % 256;
+
+    // 
+    // encode packet
+    //
+    bpacketgen_encode(pg,msg_org,msg_enc);
+
+    // 
+    // channel
+    //
+    // add delay
+    msg_rec[0] = rand() & 0xff; // initialize first byte as random
+    memmove(&msg_rec[1], msg_enc, k*sizeof(unsigned char));
+    liquid_lbshift(msg_rec, (k+1)*sizeof(unsigned char), rand()%8); // random shift
+    // add random errors
+    for (i=0; i<k+1; i++) {
+        unsigned int j;
+        for (j=0; j<8; j++) {
+            if (randf() < bit_error_rate)
+                msg_rec[i] ^= 1 << (8-j-1);
+        }
+    }
+
+    // 
+    // run packet synchronizer
+    //
+
+    // push random bits through synchronizer
+    for (i=0; i<100; i++)
+        bpacketsync_execute_byte(ps, rand() & 0xff);
+
+    // push packet through synchronizer
+    for (i=0; i<k+1; i++)
+        bpacketsync_execute_byte(ps, msg_rec[i]);
+
+    // 
+    // count errors
+    //
+    unsigned int num_bit_errors = 0;
+    for (i=0; i<n; i++)
+        num_bit_errors += count_bit_errors(msg_org[i], msg_dec[i]);
+    printf("number of bit errors received:    %4u / %4u\n", num_bit_errors, n*8);
+
+    // clean up allocated objects
+    bpacketgen_destroy(pg);
+    bpacketsync_destroy(ps);
+
+    return 0;
+}
+
diff --git a/examples/bpresync_example.c b/examples/bpresync_example.c
new file mode 100644
index 0000000..75a78cf
--- /dev/null
+++ b/examples/bpresync_example.c
@@ -0,0 +1,197 @@
+// 
+// bpresync_example.c
+//
+// This example demonstrates the binary pre-demodulator synchronizer. A random
+// binary sequence is generated, modulated with BPSK, and then interpolated.
+// The resulting sequence is used to generate a bpresync object which in turn
+// is used to detect a signal in the presence of carrier frequency and timing
+// offsets and additive white Gauss noise.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "bpresync_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("bpresync_example -- test binary pre-demodulation synchronization\n");
+    printf("options (default values in <>):\n");
+    printf("  h     : print usage/help\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter delay [symbols], default: 5\n");
+    printf("  n     : number of data symbols, default: 64\n");
+    printf("  b     : bandwidth-time product beta in (0,1), default: 0.3\n");
+    printf("  F     : carrier frequency offset, default: 0.02\n");
+    printf("  t     : fractional sample offset dt in [-0.5, 0.5], default: 0\n");
+    printf("  S     : SNR [dB], default: 20\n");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int k=2;                   // filter samples/symbol
+    unsigned int m=5;                   // filter delay (symbols)
+    float beta=0.3f;                    // bandwidth-time product
+    float dt = 0.0f;                    // fractional sample timing offset
+    unsigned int num_sync_symbols = 64; // number of synchronization symbols
+    float SNRdB = 20.0f;                // signal-to-noise ratio [dB]
+    float dphi = 0.02f;                 // carrier frequency offset
+    float phi  = 2*M_PI*randf();        // carrier phase offset
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:n:b:t:F:t:S:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                          return 0;
+        case 'k': k = atoi(optarg);                 break;
+        case 'm': m = atoi(optarg);                 break;
+        case 'n': num_sync_symbols = atoi(optarg);  break;
+        case 'b': beta = atof(optarg);              break;
+        case 'F': dphi = atof(optarg);              break;
+        case 't': dt = atof(optarg);                break;
+        case 'S': SNRdB = atof(optarg);             break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, bandwidth-time product must be in (0,1)\n", argv[0]);
+        exit(1);
+    } else if (dt < -0.5f || dt > 0.5f) {
+        fprintf(stderr,"error: %s, fractional sample offset must be in [-0.5,0.5]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_symbols = num_sync_symbols + 2*m + 10;
+    unsigned int num_samples = k*num_symbols;
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    float complex seq[num_sync_symbols];    // synchronization pattern (symbols)
+    float complex s0[k*num_sync_symbols];   // synchronization pattern (samples)
+    float complex x[num_samples];           // transmitted signal
+    float complex y[num_samples];           // received signal
+    float complex rxy[num_samples];         // pre-demod correlation output
+    float dphi_hat[num_samples];            // carrier offset estimate
+
+    // create transmit/receive interpolator/decimator
+    firinterp_crcf interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_RRC,k,m,beta,dt);
+
+    // generate synchronization pattern (BPSK) and interpolate
+    for (i=0; i<num_sync_symbols + 2*m; i++) {
+        float complex sym = 0.0f;
+    
+        if (i < num_sync_symbols) {
+            sym = rand() % 2 ? -1.0f : 1.0f;
+            seq[i] = sym;
+        }
+
+        if (i < 2*m) firinterp_crcf_execute(interp, sym, s0);
+        else         firinterp_crcf_execute(interp, sym, &s0[k*(i-2*m)]);
+    }
+
+    // reset interpolator
+    firinterp_crcf_reset(interp);
+
+    // interpolate input
+    for (i=0; i<num_symbols; i++) {
+        float complex sym = i < num_sync_symbols ? seq[i] : 0.0f;
+
+        firinterp_crcf_execute(interp, sym, &x[k*i]);
+    }
+
+    // push through channel
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i]*cexpf(_Complex_I*(dphi*i + phi)) + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+    // create cross-correlator
+    bpresync_cccf sync = bpresync_cccf_create(s0, k*num_sync_symbols, 0.05f, 11);
+    bpresync_cccf_print(sync);
+
+    // push signal through cross-correlator
+    float rxy_max  = 0.0f;  // maximum cross-correlation
+    float dphi_est = 0.0f;  // carrier frequency offset estimate
+    int delay_est  = 0;     // delay estimate
+    for (i=0; i<num_samples; i++) {
+        
+        // correlate
+        bpresync_cccf_push(sync, y[i]);
+        bpresync_cccf_correlate(sync, &rxy[i], &dphi_hat[i]);
+
+        // detect...
+        if (cabsf(rxy[i]) > 0.6f) {
+            printf("****** preamble found, rxy = %12.8f (dphi-hat: %12.8f), i=%3u ******\n",
+                    cabsf(rxy[i]), dphi_hat[i], i);
+        }
+        
+        // retain maximum
+        if (cabsf(rxy[i]) > rxy_max) {
+            rxy_max   = cabsf(rxy[i]);
+            dphi_est  = dphi_hat[i];
+            delay_est = (int)i - (int)2*k*m + 1;
+        }
+    }
+
+    // destroy objects
+    firinterp_crcf_destroy(interp);
+    bpresync_cccf_destroy(sync);
+    
+    // print results
+    printf("\n");
+    printf("rxy (max) : %12.8f\n", rxy_max);
+    printf("dphi est. : %12.8f ,error=%12.8f\n",      dphi_est, dphi-dphi_est);
+    printf("delay est.: %12d ,error=%3d sample(s)\n", delay_est, k*num_sync_symbols - delay_est);
+    printf("\n");
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"k           = %u;\n", k);
+
+    fprintf(fid,"x   = zeros(1,num_samples);\n");
+    fprintf(fid,"y   = zeros(1,num_samples);\n");
+    fprintf(fid,"rxy = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u)     = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]),   cimagf(x[i]));
+        fprintf(fid,"y(%4u)     = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]),   cimagf(y[i]));
+        fprintf(fid,"rxy(%4u)   = %12.8f + j*%12.8f;\n", i+1, crealf(rxy[i]), cimagf(rxy[i]));
+    }
+
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(y), t,imag(y));\n");
+    fprintf(fid,"  axis([0 num_symbols -2 2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('received signal');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,abs(rxy));\n");
+    fprintf(fid,"  axis([0 num_symbols 0 1.5]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('correlator output');\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/bsequence_example.c b/examples/bsequence_example.c
new file mode 100644
index 0000000..bc21ff2
--- /dev/null
+++ b/examples/bsequence_example.c
@@ -0,0 +1,52 @@
+//
+// bsequence_example.c
+//
+// This example demonstrates the interface to the bsequence (binary
+// sequence) object.  The bsequence object acts like a buffer of bits
+// which are stored and manipulated efficiently in memory.
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+//#define OUTPUT_FILENAME "bsequence_example.m"
+
+int main() {
+    // create and initialize binary sequence
+    unsigned int n=16;
+    bsequence q = bsequence_create(n);
+
+    unsigned char v[4] = {0x35, 0x35};
+    bsequence_init(q,v);
+
+    bsequence_push(q,1);
+    bsequence_push(q,1);
+    bsequence_push(q,1);
+    bsequence_push(q,1);
+
+    bsequence_push(q,0);
+    bsequence_push(q,1);
+
+    bsequence_print(q);
+
+    bsequence_circshift(q);
+    bsequence_print(q);
+    bsequence_circshift(q);
+    bsequence_print(q);
+
+    unsigned int b;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        b = bsequence_index(q,i);
+        printf("b[%3u] = %3u\n", i, b);
+    }
+    
+    // clean up memory
+    bsequence_destroy(q);
+
+    return 0;
+}
+
diff --git a/examples/cbufferf_example.c b/examples/cbufferf_example.c
new file mode 100644
index 0000000..b660f19
--- /dev/null
+++ b/examples/cbufferf_example.c
@@ -0,0 +1,48 @@
+//
+// cbufferf_example.c
+//
+// This example demonstrates the circular buffer object on
+// floating-point data.
+//
+// SEE ALSO: wdelayf_example.c
+//           windowf_example.c
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+int main() {
+    float v[] = {1, 2, 3, 4, 5, 6, 7, 8};
+    float *r; // reader
+    unsigned int num_requested = 3;
+    unsigned int num_read;
+
+    cbufferf cb = cbufferf_create(10);
+
+    cbufferf_write(cb, v, 4);
+    cbufferf_read(cb, num_requested, &r, &num_read);
+    printf("cbufferf: requested %u elements, read %u elements\n",
+            num_requested, num_read);
+
+    unsigned int i;
+    for (i=0; i<num_read; i++)
+        printf("  %u : %f\n", i, r[i]);
+
+    // release 2 elements from the buffer
+    cbufferf_release(cb, 2);
+
+    // write 8 elements
+    cbufferf_write(cb, v, 8);
+
+    // print
+    cbufferf_debug_print(cb);
+    cbufferf_print(cb);
+
+    // destroy object
+    cbufferf_destroy(cb);
+
+    printf("done.\n");
+    return 0;
+}
+
+
diff --git a/examples/cgsolve_example.c b/examples/cgsolve_example.c
new file mode 100644
index 0000000..5d8afcd
--- /dev/null
+++ b/examples/cgsolve_example.c
@@ -0,0 +1,77 @@
+// 
+// cgsolve_example.c
+//
+// Solve linear system of equations A*x = b using the conjugate-
+// gradient method where A is a symmetric positive-definite matrix.
+// Compare speed to matrixf_linsolve() for same system.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.h"
+
+int main() {
+    // options
+    unsigned int n = 8;
+
+    unsigned int i;
+
+    // allocate memory for arrays
+    float A[n*n];
+    float b[n];
+    float x[n];
+    float x_hat[n];
+
+    // generate symmetric positive-definite matrix by first generating
+    // lower triangular matrix L and computing A = L*L'
+    float L[n*n];
+    unsigned int j;
+    for (i=0; i<n; i++) {
+        for (j=0; j<n; j++) {
+#if 0
+            // sparse matrix
+            if (j > i)              matrix_access(L,n,n,i,j) = 0.0;
+            else if (j == i)        matrix_access(L,n,n,i,j) = randnf();
+            else if ((rand()%4)==0) matrix_access(L,n,n,i,j) = randnf();
+            else                    matrix_access(L,n,n,i,j) = 0.0;
+#else
+            // full matrix
+            matrix_access(L,n,n,i,j) = (j < i) ? 0.0 : randnf();
+#endif
+        }
+    }
+    matrixf_mul_transpose(L, n, n, A);
+
+    // generate random solution
+    for (i=0; i<n; i++)
+        x[i] = randnf();
+
+    // compute b
+    matrixf_mul(A, n, n,
+                x, n, 1,
+                b, n, 1);
+
+    // solve symmetric positive-definite system of equations
+    matrixf_cgsolve(A, n, b, x_hat, NULL);
+    //matrixf_linsolve(A, n, b, x_hat, NULL);
+
+    // print results
+
+    printf("A:\n");             matrixf_print(A,     n, n);
+    printf("b:\n");             matrixf_print(b,     n, 1);
+    printf("x (original):\n");  matrixf_print(x,     n, 1);
+    printf("x (estimate):\n");  matrixf_print(x_hat, n, 1);
+
+    // compute error norm
+    float e = 0.0;
+    for (i=0; i<n; i++)
+        e += (x[i] - x_hat[i])*(x[i] - x_hat[i]);
+    e = sqrt(e);
+    printf("error norm: %12.4e\n", e);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/channel_cccf_example.c b/examples/channel_cccf_example.c
new file mode 100644
index 0000000..b4c5920
--- /dev/null
+++ b/examples/channel_cccf_example.c
@@ -0,0 +1,205 @@
+//
+// channel_cccf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "channel_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("channel_cccf_example [options]\n");
+    printf("  h     : print this help file\n");
+    printf("  k     : filter samples/symbol,     default: 2\n");
+    printf("  m     : filter delay (symbols),    default: 3\n");
+    printf("  b     : filter excess bandwidth,   default: 0.5\n");
+    printf("  H     : multi-path channel length, default: 4000\n");
+    printf("  n     : number of symbols,         default: 4000\n");
+    printf("  s     : signal-to-noise ratio,     default: 30 dB\n");
+    printf("  w     : timing pll bandwidth,      default: 0.02\n");
+    printf("  t     : timing phase offset [%% symbol], t in [-0.5,0.5], default: -0.2\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int k           = 2;       // samples per symbol
+    unsigned int m           = 7;       // filter delay (symbols)
+    float        beta        = 0.25f;   // filter excess bandwidth factor
+    unsigned int num_symbols = 4000;    // number of data symbols
+    unsigned int hc_len      = 5;       // channel filter length
+    float        noise_floor = -60.0f;  // noise floor [dB]
+    float        SNRdB       = 30.0f;   // signal-to-noise ratio [dB]
+    float        bandwidth   =  0.02f;  // loop filter bandwidth
+    float        tau         = -0.2f;   // fractional symbol offset
+    float        rate        = 1.001f;  // sample rate offset
+    float        dphi        =  0.00f;  // carrier frequency offset [radians/sample]
+    float        phi         =  2.1f;   // carrier phase offset [radians]
+    modulation_scheme ms     = LIQUID_MODEM_QPSK;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hk:m:b:H:n:s:w:t:r:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'k':   k           = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'H':   hc_len      = atoi(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'w':   bandwidth   = atof(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        case 'r':   rate        = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: k (samples/symbol) must be greater than 1\n");
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (bandwidth <= 0.0f) {
+        fprintf(stderr,"error: timing PLL bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: timing phase offset must be in [-1,1]\n");
+        exit(1);
+    } else if (rate > 1.02f || rate < 0.98f) {
+        fprintf(stderr,"error: timing rate offset must be in [1.02,0.98]\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // derived/fixed values
+    unsigned int nx = num_symbols*k;
+    unsigned int ny = (unsigned int) ceilf(rate * nx) + 64;
+
+    float complex x[nx];    // input (interpolated) samples
+    float complex y[ny];    // channel output samples
+    float complex sym_out[num_symbols + 64];// synchronized symbols
+
+    // 
+    // generate input sequence using symbol stream generator
+    //
+    symstreamcf gen = symstreamcf_create_linear(LIQUID_FIRFILT_ARKAISER,k,m,beta,ms);
+    symstreamcf_write_samples(gen, x, nx);
+    symstreamcf_destroy(gen);
+
+    // create channel
+    channel_cccf channel = channel_cccf_create();
+
+    // add channel impairments
+    channel_cccf_add_awgn          (channel, noise_floor, SNRdB);
+    channel_cccf_add_carrier_offset(channel, dphi, phi);
+    channel_cccf_add_multipath     (channel, NULL, hc_len);
+    channel_cccf_add_shadowing     (channel, 1.0f, 0.1f);
+    channel_cccf_add_resamp        (channel, 0.0f, rate);
+
+    // print channel internals
+    channel_cccf_print(channel);
+
+    // apply channel to input signal
+    channel_cccf_execute(channel, x, nx, y, &ny);
+
+    // destroy channel
+    channel_cccf_destroy(channel);
+
+    // 
+    // create and run symbol synchronizer
+    //
+
+    symtrack_cccf symtrack = symtrack_cccf_create(LIQUID_FIRFILT_RRC,k,m,beta,ms);
+    
+    // set tracking bandwidth
+    symtrack_cccf_set_bandwidth(symtrack,0.05f);
+
+    unsigned int num_symbols_sync = 0;
+    symtrack_cccf_execute_block(symtrack, y, ny, sym_out, &num_symbols_sync);
+    symtrack_cccf_destroy(symtrack);
+
+    // print results
+    printf("symbols in  : %u\n", num_symbols);
+    printf("symbols out : %u\n", num_symbols_sync);
+
+    // estimate spectrum
+    unsigned int nfft = 1200;
+    float        psd[nfft];
+    spgramcf_estimate_psd(nfft, y, ny, psd);
+
+    //
+    // export output file
+    //
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"num_symbols=%u;\n",num_symbols_sync);
+
+    for (i=0; i<num_symbols_sync; i++)
+        fprintf(fid,"z(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+    // power spectral density estimate
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%3u) = %12.8f;\n", i+1, psd[i]);
+
+    fprintf(fid,"iz0 = 1:round(length(z)*0.5);\n");
+    fprintf(fid,"iz1 = round(length(z)*0.5):length(z);\n");
+    fprintf(fid,"figure('Color','white','position',[500 500 800 800]);\n");
+    fprintf(fid,"subplot(2,2,1);\n");
+    fprintf(fid,"plot(real(z(iz0)),imag(z(iz0)),'x','MarkerSize',4);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"  xlabel('In-phase');\n");
+    fprintf(fid,"  ylabel('Quadrature');\n");
+    fprintf(fid,"  title('First 50%% of symbols');\n");
+    fprintf(fid,"subplot(2,2,2);\n");
+    fprintf(fid,"  plot(real(z(iz1)),imag(z(iz1)),'x','MarkerSize',4);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  xlabel('In-phase');\n");
+    fprintf(fid,"  ylabel('Quadrature');\n");
+    fprintf(fid,"  title('Last 50%% of symbols');\n");
+    fprintf(fid,"subplot(2,2,3:4);\n");
+    fprintf(fid,"  plot(f, psd, 'LineWidth',1.5,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  pmin = 10*floor(0.1*min(psd - 5));\n");
+    fprintf(fid,"  pmax = 10*ceil (0.1*max(psd + 5));\n");
+    fprintf(fid,"  axis([-0.5 0.5 pmin pmax]);\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Power Spectral Density [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/chromosome_example.c b/examples/chromosome_example.c
new file mode 100644
index 0000000..09e37f3
--- /dev/null
+++ b/examples/chromosome_example.c
@@ -0,0 +1,87 @@
+//
+// chromosome_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+int main() {
+    unsigned int bits_per_trait[] = {4, 8, 8, 4};
+    chromosome p1 = chromosome_create(bits_per_trait, 4);
+    chromosome p2 = chromosome_create(bits_per_trait, 4);
+    chromosome c  = chromosome_create(bits_per_trait, 4);
+
+    // 0000 11111111 00000000 1111
+    p1->traits[0] = 0x0;
+    p1->traits[1] = 0xFF;
+    p1->traits[2] = 0x00;
+    p1->traits[3] = 0xF;
+
+    // 0101 01010101 01010101 0101
+    p2->traits[0] = 0x5;
+    p2->traits[1] = 0x55;
+    p2->traits[2] = 0x55;
+    p2->traits[3] = 0x5;
+
+    printf("parent [1]:\n");
+    chromosome_print(p1);
+
+    printf("parent [2]:\n");
+    chromosome_print(p2);
+
+    printf("\n\n");
+
+    // 
+    // test crossover
+    //
+
+    printf("testing crossover...\n");
+
+    chromosome_crossover(p1, p2, c, 0);
+    // .... ........ ........ ....
+    // 0101 01010101 01010101 0101
+    chromosome_print(c);
+
+    chromosome_crossover(p1, p2, c, 4);
+    // 0000 ........ ........ ....
+    // .... 01010101 01010101 0101
+    chromosome_print(c);
+
+    chromosome_crossover(p1, p2, c, 6);
+    // 0000 11...... ........ ....
+    // .... ..010101 01010101 0101
+    chromosome_print(c);
+
+    chromosome_crossover(p1, p2, c, 14);
+    // 0000 11111111 00...... ....
+    // .... ........ ..010101 0101
+    chromosome_print(c);
+
+    chromosome_crossover(p1, p2, c, 24);
+    // 0000 11111111 00000000 1111
+    // .... ........ ........ ....
+    chromosome_print(c);
+
+    // 
+    // test mutation
+    //
+
+    printf("testing mutation...\n");
+
+    unsigned int i;
+    for (i=0; i<24; i++) {
+        chromosome_clear(c);
+        chromosome_mutate(c,i);
+        // 0000 01000000 00000000 0000
+        chromosome_print(c);
+    }
+
+    chromosome_destroy(p1);
+    chromosome_destroy(p2);
+    chromosome_destroy(c);
+
+    return 0;
+}
+
diff --git a/examples/compand_cf_example.c b/examples/compand_cf_example.c
new file mode 100644
index 0000000..d627617
--- /dev/null
+++ b/examples/compand_cf_example.c
@@ -0,0 +1,64 @@
+//
+// compand_cf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "compand_cf_example.m"
+
+int main() {
+    // options
+    float mu=255.0f;
+    int n=31;
+
+    // open debug file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    float complex x, y, z;
+    int i, j;
+
+    for (i=0; i<n+1; i++) {
+        for (j=0; j<n+1; j++) {
+            x = (float)(2*i - n)/(float)(n) + _Complex_I*(float)(2*j-n)/(float)(n);
+            compress_cf_mulaw(x,mu,&y);
+            expand_cf_mulaw(y,mu,&z);
+
+            if (i==j) {
+                printf("%8.4f + j*%8.4f > ", crealf(x), cimagf(x));
+                printf("%8.4f + j*%8.4f > ", crealf(y), cimagf(y));
+                printf("%8.4f + j*%8.4f\n",  crealf(z), cimagf(z));
+            }
+
+            fprintf(fid,"x(%3d,%3d) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(x), cimagf(x));
+            fprintf(fid,"y(%3d,%3d) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(y), cimagf(y));
+        }
+    }
+
+    for (i=0; i<n+1; i++)
+        fprintf(fid,"t(%3d) = %12.4e;\n", i+1, (float)(2*i-n)/(float)(n));
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"mesh(t,t,real(y));\n");
+    fprintf(fid,"xlabel('x: real');\n");
+    fprintf(fid,"ylabel('x: imag');\n");
+    fprintf(fid,"box off;\n");
+    fprintf(fid,"view(3);\n");
+    fprintf(fid,"title('real[y]');\n");
+    //fprintf(fid,"axis([-1 1 -1 1 -1 1]);\n");
+
+    // close debug file
+    fclose(fid);
+    printf("results wrtten to %s\n", OUTPUT_FILENAME);
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/compand_example.c b/examples/compand_example.c
new file mode 100644
index 0000000..d2cebaf
--- /dev/null
+++ b/examples/compand_example.c
@@ -0,0 +1,102 @@
+//
+// compand_example.c
+//
+// This example demonstrates the interface to the compand function
+// (compression, expansion).  The compander is typically used with the
+// quantizer to increase the dynamic range of the converter, particularly for
+// low-level signals.  The transfer function is computed (emperically) and
+// printed to the screen.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "compand_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("compand_example\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  v/q   : verbose/quiet, default: verbose\n");
+    printf("  n     : number of samples, default: 31\n");
+    printf("  m     : companding parameter: mu > 0, default: 255\n");
+    printf("  r     : range > 0, default: 1.25\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int n=31;
+    float mu=255.0f;
+    float range = 1.25f;
+    int verbose = 1;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhvqn:m:r:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'v': verbose = 1;          break;
+        case 'q': verbose = 0;          break;
+        case 'n': n = atoi(optarg);     break;
+        case 'm': mu = atof(optarg);    break;
+        case 'r': range = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (mu < 0) {
+        fprintf(stderr,"error: %s, mu must be positive\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (range <= 0) {
+        fprintf(stderr,"error: %s, range must be greater than zero\n", argv[0]);
+        usage();
+        exit(1);
+    }
+
+    // open debug file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    float x = -range;
+    float y, z;
+    float dx = 2.0*range/(float)(n-1);
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        y = compress_mulaw(x,mu);
+        z = expand_mulaw(y,mu);
+        if (verbose)
+            printf("%8.4f > %8.4f > %8.4f\n", x, y, z);
+
+        fprintf(fid,"x(%3u) = %12.4e;\n", i+1, x);
+        fprintf(fid,"y(%3u) = %12.4e;\n", i+1, y);
+        fprintf(fid,"z(%3u) = %12.4e;\n", i+1, z);
+
+        x += dx;
+    }
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(x,y,'-b','LineWidth',2,x,z,'-r');\n");
+    fprintf(fid,"axis square\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('f(x)');\n");
+
+    // close debug file
+    fclose(fid);
+    printf("results wrtten to %s\n", OUTPUT_FILENAME);
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/complementary_codes_example.c b/examples/complementary_codes_example.c
new file mode 100644
index 0000000..78c06c6
--- /dev/null
+++ b/examples/complementary_codes_example.c
@@ -0,0 +1,135 @@
+//
+// complementary_codes_example.c
+// 
+// This example demonstrates how to generate complementary binary
+// codes in liquid.  A pair of codes is generated using the bsequence
+// interface, their auto-correlations are computed, and the result is
+// summed and printed to the screen.  The results are also printed to an
+// output file, which plots the sequences and their auto-correlations.
+//
+// SEE ALSO: bsequence_example.c
+//           msequence_example.c
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "complementary_codes_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("complementary_codes_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : sequence length, 8,16,32,64,... (default: 32)\n");
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int n = 32;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();                return 0;
+        case 'n':   n = atoi(optarg);       break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( (1<<liquid_nextpow2(n)) != n ) {
+        fprintf(stderr,"error: %s, sequence length must be a power of 2\n", argv[0]);
+        exit(1);
+    }
+
+    // create and initialize codes
+    bsequence a = bsequence_create(n);
+    bsequence b = bsequence_create(n);
+    bsequence_create_ccodes(a, b);
+
+    // print
+    bsequence_print(a);
+    bsequence_print(b);
+
+    // generate test sequences
+    bsequence ax = bsequence_create(n);
+    bsequence bx = bsequence_create(n);
+    bsequence_create_ccodes(ax, bx);
+
+    // compute auto-correlations
+    unsigned int i;
+    int raa[n];
+    int rbb[n];
+    for (i=0; i<n; i++) {
+        raa[i] = 2*bsequence_correlate(a,ax) - n;
+        rbb[i] = 2*bsequence_correlate(b,bx) - n;
+
+        bsequence_circshift(ax);
+        bsequence_circshift(bx);
+
+        // print to screen
+        printf("  raa[%4u] = %4d, rbb[%4u] = %4d, sum = %4d\n",
+                i, raa[i], i, rbb[i], raa[i] + rbb[i]);
+    }
+
+    // print results to file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    if (!fid) {
+        fprintf(stderr,"error: %s, cannot open output file '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"a = zeros(1,n);\n");
+    fprintf(fid,"b = zeros(1,n);\n");
+    fprintf(fid,"raa = zeros(1,n);\n");
+    fprintf(fid,"rbb = zeros(1,n);\n");
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"a(%6u) = %1u;\n", i+1, bsequence_index(a,n-i-1));
+        fprintf(fid,"b(%6u) = %1u;\n", i+1, bsequence_index(b,n-i-1));
+
+        fprintf(fid,"raa(%6u) = %12.8f;\n", i+1, raa[i] / (float)n);
+        fprintf(fid,"rbb(%6u) = %12.8f;\n", i+1, rbb[i] / (float)n);
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"   stairs(t,a);\n");
+    fprintf(fid,"   axis([-1 n -0.2 1.2]);\n");
+    //fprintf(fid,"   xlabel('delay (samples)');\n");
+    fprintf(fid,"   ylabel('a');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"   stairs(t,b);\n");
+    fprintf(fid,"   axis([-1 n -0.2 1.2]);\n");
+    //fprintf(fid,"   xlabel('delay (samples)');\n");
+    fprintf(fid,"   ylabel('b');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"   plot(t,raa,t,rbb,t,[raa+rbb]/2);\n");
+    fprintf(fid,"   axis([-1 n -0.5 1.2]);\n");
+    fprintf(fid,"   xlabel('delay (samples)');\n");
+    fprintf(fid,"   ylabel('auto-correlation');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean up memory
+    bsequence_destroy(a);
+    bsequence_destroy(b);
+    bsequence_destroy(ax);
+    bsequence_destroy(bx);
+
+    return 0;
+}
+
diff --git a/examples/conversion_example.c b/examples/conversion_example.c
new file mode 100644
index 0000000..0fefc65
--- /dev/null
+++ b/examples/conversion_example.c
@@ -0,0 +1,139 @@
+//
+// conversion_example.c
+//
+// This example demonstrates conversion from complex baseband to a real-valued
+// signal, and then down-conversion back to complex baseband while removing the
+// negative image.
+//
+//  STEP 1: A signal is generated at complex baseband consisting of narrow-band
+//          filtered noise and an offset tone (to show asymmetry in the transmit
+//          spectrum).
+//
+//  STEP 2: The signal is mixed up to a carrier 'fc' (relative to the sampling
+//          frequency) and the real-component of the result is retained. This is
+//          the DAC output. The spectrum of this signal has two images: one at
+//          +fc, the other at -fc.
+//
+//  STEP 3: The DAC output is mixed back down to complex baseband and the lower
+//          image is (mostly) filtered off. Reminants of the lower frequency
+//          component are still visible due to the wide-band and low-order
+//          filter on the receiver. The received complex baseband signal also
+//          has a reduction in power by 2 because half the signal's energy (the
+//          negative image) is filtered off.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "conversion_example.m"
+
+int main()
+{
+    // spectral periodogram options
+    unsigned int nfft        =   1200;  // spectral periodogram FFT size
+    unsigned int num_samples =  64000;  // number of samples
+    float        fc          =   0.20f; // carrier (relative to sampling rate)
+
+    // create objects
+    iirfilt_crcf   filter_tx    = iirfilt_crcf_create_lowpass(15, 0.05);
+    nco_crcf       mixer_tx     = nco_crcf_create(LIQUID_VCO);
+    nco_crcf       mixer_rx     = nco_crcf_create(LIQUID_VCO);
+    iirfilt_crcf   filter_rx    = iirfilt_crcf_create_lowpass(7, 0.2);
+
+    // set carrier frequencies
+    nco_crcf_set_frequency(mixer_tx, fc * 2*M_PI);
+    nco_crcf_set_frequency(mixer_rx, fc * 2*M_PI);
+
+    // create objects for measuring power spectral density
+    spgramcf spgram_tx  = spgramcf_create_default(nfft);
+    spgramf  spgram_dac = spgramf_create_default(nfft);
+    spgramcf spgram_rx  = spgramcf_create_default(nfft);
+
+    // run through loop one step at a time
+    unsigned int i;
+    for (i=0; i<num_samples; i++) {
+        // STEP 1: generate input signal (filtered noise with offset tone)
+        float complex v1 = (randnf() + randnf()*_Complex_I) + 3.0f*cexpf(-_Complex_I*0.2f*i);
+        iirfilt_crcf_execute(filter_tx, v1, &v1);
+
+        // save spectrum
+        spgramcf_push(spgram_tx, v1);
+
+        // STEP 2: mix signal up and save real part (DAC output)
+        nco_crcf_mix_up(mixer_tx, v1, &v1);
+        float v2 = crealf(v1);
+        nco_crcf_step(mixer_tx);
+
+        // save spectrum
+        spgramf_push(spgram_dac, v2);
+
+        // STEP 3: mix signal down and filter off image
+        float complex v3;
+        nco_crcf_mix_down(mixer_rx, v2, &v3);
+        iirfilt_crcf_execute(filter_rx, v3, &v3);
+        nco_crcf_step(mixer_rx);
+
+        // save spectrum
+        spgramcf_push(spgram_rx, v3);
+    }
+
+    // compute power spectral density output
+    float   psd_tx  [nfft];
+    float   psd_dac [nfft];
+    float   psd_rx  [nfft];
+    spgramcf_get_psd(spgram_tx,  psd_tx);
+    spgramf_get_psd( spgram_dac, psd_dac);
+    spgramcf_get_psd(spgram_rx,  psd_rx);
+
+    // destroy objects
+    spgramcf_destroy(spgram_tx);
+    spgramf_destroy(spgram_dac);
+    spgramcf_destroy(spgram_rx);
+
+    iirfilt_crcf_destroy(filter_tx);
+    nco_crcf_destroy(mixer_tx);
+    nco_crcf_destroy(mixer_rx);
+    iirfilt_crcf_destroy(filter_rx);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+
+    fprintf(fid,"nfft   = %u;\n", nfft);
+    fprintf(fid,"f      = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"psd_tx = zeros(1,nfft);\n");
+    fprintf(fid,"psd_dac= zeros(1,nfft);\n");
+    fprintf(fid,"psd_rx = zeros(1,nfft);\n");
+    
+    for (i=0; i<nfft; i++) {
+        fprintf(fid,"psd_tx (%6u) = %12.4e;\n", i+1, psd_tx [i]);
+        fprintf(fid,"psd_dac(%6u) = %12.4e;\n", i+1, psd_dac[i]);
+        fprintf(fid,"psd_rx (%6u) = %12.4e;\n", i+1, psd_rx [i]);
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"  plot(f, psd_tx,  '-', 'LineWidth',1.5,'Color',[0.7 0.7 0.7]);\n");
+    fprintf(fid,"  plot(f, psd_dac, '-', 'LineWidth',1.5,'Color',[0.0 0.5 0.3]);\n");
+    fprintf(fid,"  plot(f, psd_rx,  '-', 'LineWidth',1.5,'Color',[0.0 0.3 0.5]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 60]);\n");
+    fprintf(fid,"legend('transmit (complex)','DAC output (real)','receive (complex)','location','northeast');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/cpfskmodem_example.c b/examples/cpfskmodem_example.c
new file mode 100644
index 0000000..81a1b35
--- /dev/null
+++ b/examples/cpfskmodem_example.c
@@ -0,0 +1,213 @@
+// 
+// cpfskmodem_example.c
+//
+// This example demostrates the continuous phase frequency-shift keying
+// (CP-FSK) modem in liquid. A message signal is modulated and the
+// resulting signal is recovered using a demodulator object.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "cpfskmodem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("cpfskmodem_example -- continuous-phase frequency-shift keying example\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  t     : filter type: [square], rcos-full, rcos-half, gmsk\n");
+    printf("  p     : bits/symbol,              default:  1\n");
+    printf("  H     : modulation index,         default:  0.5\n");
+    printf("  k     : samples/symbol,           default:  8\n");
+    printf("  m     : filter delay (symbols),   default:  3\n");
+    printf("  b     : filter roll-off,          default:  0.35\n");
+    printf("  n     : number of data symbols,   default: 80\n");
+    printf("  s     : SNR [dB],                 default: 40\n");
+    printf("  F     : carrier frequency offset, default:  0\n");
+    printf("  P     : carrier phase offset,     default:  0\n");
+    printf("  T     : fractional symbol offset, default:  0\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int    bps         = 1;        // number of bits/symbol
+    float           h           = 0.5f;     // modulation index (h=1/2 for MSK)
+    unsigned int    k           = 4;        // filter samples/symbol
+    unsigned int    m           = 3;        // filter delay (symbols)
+    float           beta        = 0.35f;    // GMSK bandwidth-time factor
+    unsigned int    num_symbols = 20;       // number of data symbols
+    float           SNRdB       = 40.0f;    // signal-to-noise ratio [dB]
+    float           cfo         = 0.0f;     // carrier frequency offset
+    float           cpo         = 0.0f;     // carrier phase offset
+    float           tau         = 0.0f;     // fractional symbol timing offset
+    int             filter_type = LIQUID_CPFSK_SQUARE;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"ht:p:H:k:m:b:n:s:F:P:T:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 't':
+            if (strcmp(optarg,"square")==0) {
+                filter_type = LIQUID_CPFSK_SQUARE;
+            } else if (strcmp(optarg,"rcos-full")==0) {
+                filter_type = LIQUID_CPFSK_RCOS_FULL;
+            } else if (strcmp(optarg,"rcos-half")==0) {
+                filter_type = LIQUID_CPFSK_RCOS_PARTIAL;
+            } else if (strcmp(optarg,"gmsk")==0) {
+                filter_type = LIQUID_CPFSK_GMSK;
+            } else {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'p': bps   = atoi(optarg);         break;
+        case 'H': h     = atof(optarg);         break;
+        case 'k': k     = atoi(optarg);         break;
+        case 'm': m     = atoi(optarg);         break;
+        case 'b': beta  = atof(optarg);         break;
+        case 'n': num_symbols = atoi(optarg);   break;
+        case 's': SNRdB = atof(optarg);         break;
+        case 'F': cfo    = atof(optarg);        break;
+        case 'P': cpo    = atof(optarg);        break;
+        case 'T': tau    = atof(optarg);        break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int  num_samples = k*num_symbols;
+    unsigned int  M           = 1 << bps;
+    float         nstd        = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    unsigned int  sym_in [num_symbols]; // input symbols
+    float complex x      [num_samples]; // transmitted signal
+    float complex y      [num_samples]; // received signal
+    unsigned int  sym_out[num_symbols]; // output symbols
+
+    // create modem objects
+    cpfskmod mod = cpfskmod_create(bps, h, k, m, beta, filter_type);
+    cpfskdem dem = cpfskdem_create(bps, h, k, m, beta, filter_type);
+
+    // print modulator
+    cpfskmod_print(mod);
+    
+    // get full symbol delay
+    unsigned int delay = cpfskmod_get_delay(mod) + cpfskdem_get_delay(dem);
+    printf("delay: %u samples\n", delay);
+
+    // generate message signal
+    for (i=0; i<num_symbols; i++)
+        sym_in[i] = rand() % M;
+
+    // modulate signal
+    for (i=0; i<num_symbols; i++)
+        cpfskmod_modulate(mod, sym_in[i], &x[k*i]);
+
+    // push through channel (add noise)
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+    // demodulate signal
+    for (i=0; i<num_symbols; i++)
+        sym_out[i] = cpfskdem_demodulate(dem, &y[i*k]);
+
+    // print/count errors
+    unsigned int num_errors = 0;
+    for (i=delay; i<num_symbols; i++) {
+        int is_err = (sym_in[i-delay] == sym_out[i]) ? 0 : 1;
+        printf("  %3u : %2u %2u %s\n", i, sym_in[i-delay], sym_out[i], is_err ? "*" : "");
+        num_errors += is_err;
+    }
+    printf("symbol errors: %u / %u\n", num_errors, num_symbols-delay);
+
+    // destroy modem objects
+    cpfskmod_destroy(mod);
+    cpfskdem_destroy(dem);
+
+    // compute power spectral density of transmitted signal
+    unsigned int nfft = 1024;
+    float psd[nfft];
+    spgramcf_estimate_psd(nfft, x, num_samples, psd);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"h = %f;\n", h);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+    fprintf(fid,"delay       = %u; %% receive filter delay\n", m);
+
+    fprintf(fid,"x   = zeros(1,num_samples);\n");
+    fprintf(fid,"y   = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+    // save power spectral density
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%4u) = %12.8f;\n", i+1, psd[i]);
+
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"i = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,4,1:3);\n");
+    fprintf(fid,"  plot(t,real(x),'-', t(i),real(x(i)),'ob',...\n");
+    fprintf(fid,"       t,imag(x),'-', t(i),imag(x(i)),'og');\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('x(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,5:7);\n");
+    fprintf(fid,"  plot(t,real(y),'-', t(i),real(y(i)),'ob',...\n");
+    fprintf(fid,"       t,imag(y),'-', t(i),imag(y(i)),'og');\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('y(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    // plot I/Q constellations
+    fprintf(fid,"subplot(3,4,4);\n");
+    fprintf(fid,"  plot(real(x),imag(x),'-',real(x(i)),imag(x(i)),'rs','MarkerSize',4);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,8);\n");
+    fprintf(fid,"  plot(real(y),imag(y),'-',real(y(i)),imag(y(i)),'rs','MarkerSize',4);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    // plot PSD
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"subplot(3,4,9:12);\n");
+    fprintf(fid,"  plot(f,psd,'LineWidth',1.5);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -60 20]);\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/cpfskmodem_psd_example.c b/examples/cpfskmodem_psd_example.c
new file mode 100644
index 0000000..08dd658
--- /dev/null
+++ b/examples/cpfskmodem_psd_example.c
@@ -0,0 +1,165 @@
+// 
+// cpfsk_psd_example.c
+//
+// This example demonstrates the differences in power spectral
+// density (PSD) for different continuous-phase frequency-shift
+// keying (CP-FSK) modems in liquid. Idential bit streams are fed
+// into modulators with different pulse-shaping filters and the
+// resulting PSDs are computed and logged to a file.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "cpfsk_psd_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("cpfsk_psd_example -- continuous-phase frequency-shift keying example\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  p     : bits/symbol,              default:  1\n");
+    printf("  H     : modulation index,         default:  0.5\n");
+    printf("  k     : samples/symbol,           default:  8\n");
+    printf("  m     : filter delay (symbols),   default:  3\n");
+    printf("  b     : filter roll-off,          default:  0.35\n");
+    printf("  n     : number of data symbols,   default: 80\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int    bps         = 1;        // number of bits/symbol
+    float           h           = 0.5f;     // modulation index (h=1/2 for MSK)
+    unsigned int    k           = 8;        // filter samples/symbol
+    unsigned int    m           = 7;        // filter delay (symbols)
+    float           beta        = 0.35f;    // GMSK bandwidth-time factor
+    unsigned int    num_symbols = 48000;    // number of data symbols
+    unsigned int    nfft        = 2400;     // spectral periodogram FFT size
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hp:H:k:m:b:n:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                    return 0;
+        case 'p': bps         = atoi(optarg); break;
+        case 'H': h           = atof(optarg); break;
+        case 'k': k           = atoi(optarg); break;
+        case 'm': m           = atoi(optarg); break;
+        case 'b': beta        = atof(optarg); break;
+        case 'n': num_symbols = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int M = 1 << bps;              // constellation size
+
+    // create modulators
+    cpfskmod mod_0 = cpfskmod_create(bps, h, k, m, beta, LIQUID_CPFSK_SQUARE);
+    cpfskmod mod_1 = cpfskmod_create(bps, h, k, m, beta, LIQUID_CPFSK_RCOS_FULL);
+    cpfskmod mod_2 = cpfskmod_create(bps, h, k, m, beta, LIQUID_CPFSK_RCOS_PARTIAL);
+    cpfskmod mod_3 = cpfskmod_create(bps, h, k, m, beta, LIQUID_CPFSK_GMSK);
+
+    // buffers
+    float complex buf_0[k];
+    float complex buf_1[k];
+    float complex buf_2[k];
+    float complex buf_3[k];
+
+    // create PSD estimators
+    // spectral periodogram options
+
+    // create spectral periodogram
+    spgramcf spgram_0 = spgramcf_create_default(nfft);
+    spgramcf spgram_1 = spgramcf_create_default(nfft);
+    spgramcf spgram_2 = spgramcf_create_default(nfft);
+    spgramcf spgram_3 = spgramcf_create_default(nfft);
+
+    // estimate PSD for each symbol
+    for (i=0; i<num_symbols; i++) {
+
+        // generate random symbol
+        unsigned int sym = rand() % M;
+
+        // modulate
+        cpfskmod_modulate(mod_0, sym, buf_0);
+        cpfskmod_modulate(mod_1, sym, buf_1);
+        cpfskmod_modulate(mod_2, sym, buf_2);
+        cpfskmod_modulate(mod_3, sym, buf_3);
+        
+        // estimate PSD
+        spgramcf_write(spgram_0, buf_0, k);
+        spgramcf_write(spgram_1, buf_1, k);
+        spgramcf_write(spgram_2, buf_2, k);
+        spgramcf_write(spgram_3, buf_3, k);
+    }
+    
+    // compute power spectral density estimate output
+    float psd_0[nfft]; spgramcf_get_psd(spgram_0, psd_0);
+    float psd_1[nfft]; spgramcf_get_psd(spgram_1, psd_1);
+    float psd_2[nfft]; spgramcf_get_psd(spgram_2, psd_2);
+    float psd_3[nfft]; spgramcf_get_psd(spgram_3, psd_3);
+
+    // destroy modulators
+    cpfskmod_destroy(mod_0);
+    cpfskmod_destroy(mod_1);
+    cpfskmod_destroy(mod_2);
+    cpfskmod_destroy(mod_3);
+
+    // destroy spgram objects
+    spgramcf_destroy(spgram_0);
+    spgramcf_destroy(spgram_1);
+    spgramcf_destroy(spgram_2);
+    spgramcf_destroy(spgram_3);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+
+    // save power spectral density
+    fprintf(fid,"psd_0 = zeros(1,nfft);\n");
+    fprintf(fid,"psd_1 = zeros(1,nfft);\n");
+    fprintf(fid,"psd_2 = zeros(1,nfft);\n");
+    fprintf(fid,"psd_3 = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++) {
+        fprintf(fid,"psd_0(%4u) = %12.8f;\n", i+1, psd_0[i] - 10*log10(k));
+        fprintf(fid,"psd_1(%4u) = %12.8f;\n", i+1, psd_1[i] - 10*log10(k));
+        fprintf(fid,"psd_2(%4u) = %12.8f;\n", i+1, psd_2[i] - 10*log10(k));
+        fprintf(fid,"psd_3(%4u) = %12.8f;\n", i+1, psd_3[i] - 10*log10(k));
+    }
+
+    // plot PSD
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"  plot(f,psd_0,'LineWidth',1.5,'Color',[0.0 0.5 0.2]);\n");
+    fprintf(fid,"  plot(f,psd_1,'LineWidth',1.5,'Color',[0.0 0.2 0.5]);\n");
+    fprintf(fid,"  plot(f,psd_2,'LineWidth',1.5,'Color',[0.5 0.0 0.0]);\n");
+    fprintf(fid,"  plot(f,psd_3,'LineWidth',1.5,'Color',[0.5 0.5 0.0]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"legend('square','rcos (full)','rcos (partial)','GMSK, BT=%.2f');\n", beta);
+    fprintf(fid,"axis([-0.5 0.5 -80 10]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/crc_example.c b/examples/crc_example.c
new file mode 100644
index 0000000..d38f49a
--- /dev/null
+++ b/examples/crc_example.c
@@ -0,0 +1,75 @@
+//
+// crc_example.c
+//
+// Cyclic redundancy check (CRC) example.  This example demonstrates
+// how a CRC can be used to validate data received through un-reliable
+// means (e.g. a noisy channel).  A CRC is, in essence, a strong
+// algebraic error detection code that computes a key on a block of data
+// using base-2 polynomials.
+// SEE ALSO: checksum_example.c
+//           fec_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("crc_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : input data size (number of uncoded bytes)\n");
+    printf("  v     : checking scheme, (crc32 default):\n");
+    liquid_print_crc_schemes();
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int n     = 32;            // data length (bytes)
+    crc_scheme   check = LIQUID_CRC_32; // error-detection scheme
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:v:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n': n = atoi(optarg); break;
+        case 'v':
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported error-detection scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+
+    unsigned int i;
+
+    // initialize data array, leaving space for key at the end
+    unsigned char data[n+4];
+    for (i=0; i<n; i++)
+        data[i] = rand() & 0xff;
+
+    // append key to data message
+    crc_append_key(check, data, n);
+
+    // check uncorrupted data
+    printf("testing uncorrupted data... %s\n", crc_check_key(check, data, n) ? "pass" : "FAIL");
+
+    // corrupt message and check data again
+    data[0]++;
+    printf("testing corrupted data...   %s\n", crc_check_key(check, data, n) ? "pass" : "FAIL (ok)");
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/cvsd_example.c b/examples/cvsd_example.c
new file mode 100644
index 0000000..ccd3cc4
--- /dev/null
+++ b/examples/cvsd_example.c
@@ -0,0 +1,130 @@
+//
+// cvsd_example.c
+//
+// Continuously-variable slope delta example, sinusoidal input.
+// This example demonstrates the CVSD audio encoder interface, and
+// its response to a sinusoidal input.  The output distortion
+// ratio is computed, and the time-domain results are written to
+// a file.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "cvsd_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("cvsd_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : number of samples, default: 512\n");
+    printf("  f     : input signal frequency, default: 0.02\n");
+    printf("  b     : cvsd param: num-bits, default: 3\n");
+    printf("  z     : cvsd param: zeta, default: 1.5\n");
+    printf("  a     : cvsd param: alpha, default: 0.95\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int n=512;     // number of samples
+    float fc = 0.02;        // input signal frequency
+    unsigned int nbits=3;   // number of adjacent bits to observe
+    float zeta=1.5f;        // slope adjustment multiplier
+    float alpha = 0.95;     // pre-/post-filter coefficient
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:f:b:z:a:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'n': n = atoi(optarg);     break;
+        case 'f': fc = atof(optarg);    break;
+        case 'b': nbits = atoi(optarg); break;
+        case 'z': zeta = atof(optarg);  break;
+        case 'a': alpha = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    unsigned int i;
+
+    // data arrays
+    float x[n];             // input time series
+    unsigned char b[n];     // encoded bit pattern
+    float y[n];             // reconstructed time series
+
+    // create cvsd codecs
+    cvsd cvsd_encoder = cvsd_create(nbits, zeta, alpha);
+    cvsd cvsd_decoder = cvsd_create(nbits, zeta, alpha);
+    cvsd_print(cvsd_encoder);
+
+    // generate input time series
+    for (i=0; i<n; i++)
+        x[i] = sinf(2.0f*M_PI*fc*i) * hamming(i,n);
+
+    // encode time series
+    for (i=0; i<n; i++)
+        b[i] = cvsd_encode(cvsd_encoder, x[i]);
+
+    // compute reconstructed time series, RMS error
+    float rmse=0.0f;
+    for (i=0; i<n; i++) {
+        y[i] = cvsd_decode(cvsd_decoder, b[i]);
+
+        printf("%1u ", b[i]);
+        if ( ((i+1)%32) == 0 )
+            printf("\n");
+
+        float e = x[i]-y[i];
+        rmse += e*e;
+    }
+
+    rmse = sqrtf(rmse/n);
+
+    printf("\n");
+    printf("signal/distortion: %8.2f dB\n", -20*log10f(rmse));
+
+    // destroy cvsd objects
+    cvsd_destroy(cvsd_encoder);
+    cvsd_destroy(cvsd_decoder);
+
+    // 
+    // export results to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    fprintf(fid,"n=%u;\n", n);
+    fprintf(fid,"x=zeros(1,n);\n");
+    fprintf(fid,"y=zeros(1,n);\n");
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%3u) = %12.4e;\n", i+1, x[i]);
+        fprintf(fid,"y(%3u) = %12.4e;\n", i+1, y[i]);
+    }
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:n,x,1:n,y);\n");
+    fprintf(fid,"xlabel('time [sample index]');\n");
+    fprintf(fid,"ylabel('signal');\n");
+    fprintf(fid,"legend('audio input','cvsd output',1);\n");
+
+    // close debug file
+    fclose(fid);
+    printf("results wrtten to %s\n", OUTPUT_FILENAME);
+    printf("done.\n");
+
+    return 0;
+}
+
diff --git a/examples/detector_cccf_example.c b/examples/detector_cccf_example.c
new file mode 100644
index 0000000..82f1987
--- /dev/null
+++ b/examples/detector_cccf_example.c
@@ -0,0 +1,194 @@
+// 
+// detector_example.c
+//
+// This example demonstrates the binary pre-demodulator synchronizer. A random
+// binary sequence is generated, modulated with BPSK, and then interpolated.
+// The resulting sequence is used to generate a detector object which in turn
+// is used to detect a signal in the presence of carrier frequency and timing
+// offsets and additive white Gauss noise.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "detector_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("detector_example -- test binary pre-demodulation synchronization\n");
+    printf("options:\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : number of sync samples, default: 128\n");
+    printf("  F     : carrier frequency offset, default: 0.02\n");
+    printf("  T     : fractional sample offset dt in [-0.5, 0.5], default: 0\n");
+    printf("  S     : SNR [dB], default: 20\n");
+    printf("  t     : detection threshold, default: 0.3\n");
+}
+
+int main(int argc, char*argv[])
+{
+    //srand(time(NULL));
+
+    // options
+    unsigned int n    =  128;       // number of sync samples
+    float dt          =  0.0f;      // fractional sample timing offset
+    float noise_floor = -30.0f;     // noise floor [dB]
+    float SNRdB       =  20.0f;     // signal-to-noise ratio [dB]
+    float dphi        =  0.0f;      // carrier frequency offset
+    float phi         =  0.0f;      // carrier phase offset
+    float threshold   =  0.3f;      // detection threshold
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:T:F:S:t:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'n': n         = atoi(optarg); break;
+        case 'F': dphi      = atof(optarg); break;
+        case 'T': dt        = atof(optarg); break;
+        case 'S': SNRdB     = atof(optarg); break;
+        case 't': threshold = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (dt < -0.5f || dt > 0.5f) {
+        fprintf(stderr,"error: %s, fractional sample offset must be in [-0.5,0.5]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = 3*n;
+    float nstd = powf(10.0f, noise_floor/20.0f);
+    float gamma = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+
+    // arrays
+    float complex s[n];             // synchronization pattern (samples)
+    float complex x[num_samples];   // transmitted signal
+    float complex y[num_samples];   // received signal
+
+    // generate synchronization pattern (OFDM symbol, slightly over-sampled)
+    float complex S[n];
+    for (i=0; i<n; i++)
+        S[i] = (i < 0.4*n || i > 0.6*n) ? randnf() + _Complex_I*randnf() : 0.0f;
+    fft_run(n, S, s, LIQUID_FFT_BACKWARD, 0);
+    float s2 = 0.0f;
+    for (i=0; i<n; i++)
+        s2 += crealf(s[i]*conjf(s[i]));
+    for (i=0; i<n; i++)
+        s[i] /= sqrtf(s2 / (float)n);
+
+    // generate transmitted signal: 0 0 0 ... 0 s[0] s[1] ... s[n-1] 0 0 0 ... 0
+    for (i=0; i<n; i++) {
+        x[0*n+i] = 0.0f;
+        x[1*n+i] = s[i];
+        x[2*n+i] = 0.0f;
+    }
+
+    // generate received signal (add channel impairments)
+    unsigned int d = 11;    // fractional sample filter delay
+    firfilt_crcf finterp = firfilt_crcf_create_kaiser(2*d+1, 0.45f, 40.0f, dt);
+    for (i=0; i<num_samples+d; i++) {
+        // fractional sample timing offset
+        if (i < num_samples) firfilt_crcf_push(finterp, x[i]);
+        else                 firfilt_crcf_push(finterp, 0.0f);
+
+        if (i < d) firfilt_crcf_execute(finterp, &y[0]);
+        else       firfilt_crcf_execute(finterp, &y[i-d]);
+    }
+    firfilt_crcf_destroy(finterp);
+
+    for (i=0; i<num_samples; i++) {
+        // channel gain
+        y[i] *= gamma;
+
+        // carrier offset
+        y[i] *= cexpf(_Complex_I*(dphi*i + phi));
+        
+        // noise
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // create cross-correlator
+    detector_cccf sync = detector_cccf_create(s, n, threshold, 0.03f);
+    detector_cccf_print(sync);
+
+    // push signal through detector
+    float tau_hat   = 0.0f;
+    float dphi_hat  = 0.0f;
+    float gamma_hat = 1.0f;
+    int signal_detected = 0;
+    unsigned int index = 0;
+    for (i=0; i<num_samples; i++) {
+        
+        // correlate
+        int detected = detector_cccf_correlate(sync, y[i], &tau_hat, &dphi_hat, &gamma_hat);
+
+        if (detected) {
+            signal_detected = 1;
+            printf("****** preamble found, tau_hat=%8.6f, dphi_hat=%8.6f, gamma_hat=%8.6f\n",
+                    tau_hat, dphi_hat, gamma_hat);
+            index = i;
+        }
+    }
+
+    // destroy objects
+    detector_cccf_destroy(sync);
+    
+    // print results
+    printf("\n");
+    printf("signal detected :   %s\n", signal_detected ? "yes" : "no");
+    float delay_est = (float) index + tau_hat;
+    float delay     = (float)(2*n) + dt; // actual delay (samples)
+    printf("delay estimate  : %8.3f, actual=%8.3f (error=%8.3f) sample(s)\n", delay_est, delay, delay-delay_est);
+    printf("dphi estimate   : %8.5f, actual=%8.5f (error=%8.5f) rad/sample\n",dphi_hat,  dphi,  dphi-dphi_hat);
+    printf("gamma estimate  : %8.3f, actual=%8.3f (error=%8.3f) dB\n",        20*log10f(gamma_hat), 20*log10f(gamma), 20*log10(gamma/gamma_hat));
+    printf("\n");
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n           = %u;\n", n);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    fprintf(fid,"s = zeros(1,n);\n");
+    for (i=0; i<n; i++)
+        fprintf(fid,"s(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(s[i]), cimagf(s[i]));
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    fprintf(fid,"t=[0:(num_samples-1)];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(x), t,imag(x));\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('transmitted signal');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,real(y), t,imag(y));\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('received signal');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/dotprod_cccf_example.c b/examples/dotprod_cccf_example.c
new file mode 100644
index 0000000..7e83cee
--- /dev/null
+++ b/examples/dotprod_cccf_example.c
@@ -0,0 +1,42 @@
+//
+// dotprod_cccf_example.c
+//
+// This example demonstrates the interface to the complex floating-point
+// dot product object (dotprod_cccf).
+//
+
+#include <stdio.h>
+#include "liquid.h"
+
+int main() {
+    // input array
+    float complex x[] = { 1 + 1 * _Complex_I,
+                          2 + 1 * _Complex_I,
+                          3 + 1 * _Complex_I,
+                          4 + 1 * _Complex_I,
+                          5 + 1 * _Complex_I};
+
+    // coefficients array
+    float complex h[] = { 1 + 1 * _Complex_I,
+                         -1 + 1 * _Complex_I,
+                          1 + 1 * _Complex_I,
+                         -1 + 1 * _Complex_I,
+                          1 + 1 * _Complex_I};
+
+    // dot product result
+    float complex y;
+
+    // run regular dot product
+    dotprod_cccf_run(x,h,5,&y);
+    printf("dotprod_cccf              : %8.2f + j%8.2f\n", crealf(y), cimagf(y));
+
+    // run structured dot product
+    dotprod_cccf q = dotprod_cccf_create(x,5);
+    dotprod_cccf_execute(q,h,&y);
+    printf("dotprod_cccf (structured) : %8.2f + j%8.2f\n", crealf(y), cimagf(y));
+    dotprod_cccf_destroy(q);
+
+    return 0;
+}
+
+
diff --git a/examples/dotprod_rrrf_example.c b/examples/dotprod_rrrf_example.c
new file mode 100644
index 0000000..eb3ee34
--- /dev/null
+++ b/examples/dotprod_rrrf_example.c
@@ -0,0 +1,34 @@
+//
+// dotprod_rrrf_example.c
+//
+// This example demonstrates the interface to the floating-point dot
+// product object (dotprod_rrrf).
+//
+
+#include <stdio.h>
+#include "liquid.h"
+
+int main() {
+    // input array
+    float x[] = { 1,  2,  3,  4,  5};
+
+    // coefficients array
+    float h[] = { 1, -1,  1, -1,  1};
+
+    // dot product result
+    float y;
+
+    // run regular dot product
+    dotprod_rrrf_run(x,h,5,&y);
+    printf("dotprod_rrrf              : %8.2f\n", y);
+
+    // run structured dot product
+    dotprod_rrrf q = dotprod_rrrf_create(x,5);
+    dotprod_rrrf_execute(q,h,&y);
+    printf("dotprod_rrrf (structured) : %8.2f\n", y);
+    dotprod_rrrf_destroy(q);
+
+    return 0;
+}
+
+
diff --git a/examples/eqlms_cccf_blind_example.c b/examples/eqlms_cccf_blind_example.c
new file mode 100644
index 0000000..a80b7d1
--- /dev/null
+++ b/examples/eqlms_cccf_blind_example.c
@@ -0,0 +1,317 @@
+// 
+// eqlms_cccf_blind_example.c
+//
+// This example tests the least mean-squares (LMS) equalizer (EQ) on a
+// signal with an unknown modulation and carrier frequency offset. That
+// is, the equalization is done completely blind of the modulation
+// scheme or its underlying data set. The error estimate assumes a
+// constant modulus linear modulation scheme. This works surprisingly
+// well even more amplitude-modulated signals, e.g. 'qam16'.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqlms_cccf_blind_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: eqlms_cccf_blind_example [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  n     : number of symbols, default: 500\n");
+    printf("  s     : SNR [dB], default: 30\n");
+    printf("  c     : number of channel filter taps (minimum: 1), default: 5\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter semi-length (symbols), default: 4\n");
+    printf("  b     : filter excess bandwidth factor, default: 0.3\n");
+    printf("  p     : equalizer semi-length (symbols), default: 3\n");
+    printf("  u     : equalizer learning rate, default; 0.05\n");
+    printf("  M     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+}
+
+int main(int argc, char*argv[])
+{
+    //srand(time(NULL));
+
+    // options
+    unsigned int num_symbols=800; // number of symbols to observe
+    float SNRdB = 30.0f;          // signal-to-noise ratio [dB]
+    float fc    = 0.002f;         // carrier offset
+    unsigned int hc_len=5;        // channel filter length
+    unsigned int k=2;             // matched filter samples/symbol
+    unsigned int m=3;             // matched filter delay (symbols)
+    float beta=0.3f;              // matched filter excess bandwidth factor
+    unsigned int p=3;             // equalizer length (symbols, hp_len = 2*k*p+1)
+    float mu = 0.08f;             // equalizer learning rate
+
+    // modulation type/depth
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:s:c:k:m:b:p:u:M:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': num_symbols   = atoi(optarg); break;
+        case 's': SNRdB         = atof(optarg); break;
+        case 'c': hc_len        = atoi(optarg); break;
+        case 'k': k             = atoi(optarg); break;
+        case 'm': m             = atoi(optarg); break;
+        case 'b': beta          = atof(optarg); break;
+        case 'p': p             = atoi(optarg); break;
+        case 'u': mu            = atof(optarg); break;
+        case 'M':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_symbols == 0) {
+        fprintf(stderr,"error: %s, number of symbols must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (hc_len == 0) {
+        fprintf(stderr,"error: %s, channel must have at least 1 tap\n", argv[0]);
+        exit(1);
+    } else if (k < 2) {
+        fprintf(stderr,"error: %s, samples/symbol must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (beta < 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: %s, filter excess bandwidth must be in [0,1]\n", argv[0]);
+        exit(1);
+    } else if (p == 0) {
+        fprintf(stderr,"error: %s, equalizer semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f || mu > 1.0f) {
+        fprintf(stderr,"error: %s, equalizer learning rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int hm_len = 2*k*m+1;   // matched filter length
+    unsigned int hp_len = 2*k*p+1;   // equalizer filter length
+    unsigned int num_samples = k*num_symbols;
+
+    // bookkeeping variables
+    float complex syms_tx[num_symbols]; // transmitted data symbols
+    float complex x[num_samples];       // interpolated time series
+    float complex y[num_samples];       // channel output
+    float complex z[num_samples];       // equalized output
+    float complex syms_rx[num_symbols]; // received data symbols
+
+    float hm[hm_len];                   // matched filter response
+    float complex hc[hc_len];           // channel filter coefficients
+    float complex hp[hp_len];           // equalizer filter coefficients
+
+    unsigned int i;
+
+    // generate matched filter response
+    liquid_firdes_prototype(LIQUID_FIRFILT_RRC, k, m, beta, 0.0f, hm);
+    firinterp_crcf interp = firinterp_crcf_create(k, hm, hm_len);
+
+    // create the modem objects
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+    unsigned int M = 1 << modem_get_bps(mod);
+
+    // generate channel impulse response, filter
+    hc[0] = 1.0f;
+    for (i=1; i<hc_len; i++)
+        hc[i] = 0.09f*(randnf() + randnf()*_Complex_I);
+    firfilt_cccf fchannel = firfilt_cccf_create(hc, hc_len);
+
+    // generate random symbols
+    for (i=0; i<num_symbols; i++)
+        modem_modulate(mod, rand()%M, &syms_tx[i]);
+
+    // interpolate
+    for (i=0; i<num_symbols; i++)
+        firinterp_crcf_execute(interp, syms_tx[i], &x[i*k]);
+    
+    // push through channel
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++) {
+        firfilt_cccf_push(fchannel, x[i]);
+        firfilt_cccf_execute(fchannel, &y[i]);
+
+        // add carrier offset and noise
+        y[i] *= cexpf(_Complex_I*2*M_PI*fc*i);
+        y[i] += nstd*(randnf() + randnf()*_Complex_I)*M_SQRT1_2;
+    }
+
+    // push through equalizer
+    // create equalizer, intialized with square-root Nyquist filter
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_RRC, k, p, beta, 0.0f);
+    eqlms_cccf_set_bw(eq, mu);
+
+    // get initialized weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // filtered error vector magnitude (emperical RMS error)
+    float evm_hat = 0.03f;
+
+    // nco/pll for phase recovery
+    nco_crcf nco = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_pll_set_bandwidth(nco, 0.02f);
+
+    float complex d_hat = 0.0f;
+    unsigned int num_symbols_rx = 0;
+    for (i=0; i<num_samples; i++) {
+        // print filtered evm (emperical rms error)
+        if ( ((i+1)%50)==0 )
+            printf("%4u : rms error = %12.8f dB\n", i+1, 10*log10(evm_hat));
+
+        eqlms_cccf_push(eq, y[i]);
+        eqlms_cccf_execute(eq, &d_hat);
+
+        // store output
+        z[i] = d_hat;
+
+        // decimate by k
+        if ( (i%k) != 0 ) continue;
+
+        // update equalizer independent of the signal: estimate error
+        // assuming constant modulus signal
+        eqlms_cccf_step_blind(eq, d_hat);
+
+        // apply carrier recovery
+        float complex v;
+        nco_crcf_mix_down(nco, d_hat, &v);
+
+        // save resulting data symbol
+        if (num_symbols_rx < num_symbols)
+            syms_rx[num_symbols_rx++] = v;
+
+        // demodulate
+        unsigned int sym_out;   // output symbol
+        float complex d_prime;  // estimated input sample
+        modem_demodulate(demod, v, &sym_out);
+        modem_get_demodulator_sample(demod, &d_prime);
+        float phase_error = modem_get_demodulator_phase_error(demod);
+
+        // update pll
+        nco_crcf_pll_step(nco, phase_error);
+
+        // update rx nco object
+        nco_crcf_step(nco);
+
+        // update filtered evm estimate
+        float evm = crealf( (d_prime-v)*conjf(d_prime-v) );
+        evm_hat = 0.98f*evm_hat + 0.02f*evm;
+    }
+
+    // get equalizer weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // destroy objects
+    eqlms_cccf_destroy(eq);
+    nco_crcf_destroy(nco);
+    firinterp_crcf_destroy(interp);
+    firfilt_cccf_destroy(fchannel);
+    modem_destroy(mod);
+    modem_destroy(demod);
+
+    // 
+    // export output
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_symbols*k;\n");
+
+    // save transmit matched-filter response
+    fprintf(fid,"hm_len = 2*k*m+1;\n");
+    fprintf(fid,"hm = zeros(1,hm_len);\n");
+    for (i=0; i<hm_len; i++)
+        fprintf(fid,"hm(%4u) = %12.4e;\n", i+1, hm[i]);
+
+    // save channel impulse response
+    fprintf(fid,"hc_len = %u;\n", hc_len);
+    fprintf(fid,"hc = zeros(1,hc_len);\n");
+    for (i=0; i<hc_len; i++)
+        fprintf(fid,"hc(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(hc[i]), cimagf(hc[i]));
+
+    // save equalizer response
+    fprintf(fid,"hp_len = %u;\n", hp_len);
+    fprintf(fid,"hp = zeros(1,hp_len);\n");
+    for (i=0; i<hp_len; i++)
+        fprintf(fid,"hp(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(hp[i]), cimagf(hp[i]));
+
+    // save sample sets
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    fprintf(fid,"z = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+    }
+    fprintf(fid,"syms_rx = zeros(1,num_symbols);\n");
+    for (i=0; i<num_symbols; i++) {
+        fprintf(fid,"syms_rx(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(syms_rx[i]), cimagf(syms_rx[i]));
+    }
+
+    // plot time response
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"tsym = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(z),...\n");
+    fprintf(fid,"     t(tsym),real(z(tsym)),'x');\n");
+
+    // plot constellation
+    fprintf(fid,"syms_rx_0 = syms_rx(1:(length(syms_rx)/2));\n");
+    fprintf(fid,"syms_rx_1 = syms_rx((length(syms_rx)/2):end);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(syms_rx_0),imag(syms_rx_0),'x','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"     real(syms_rx_1),imag(syms_rx_1),'x','Color',[1 1 1]*0.0);\n");
+    fprintf(fid,"xlabel('In-Phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    // compute composite response
+    fprintf(fid,"g  = real(conv(conv(hm,hc),hp));\n");
+
+    // plot responses
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Hm = 20*log10(abs(fftshift(fft(hm/k,nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc,  nfft))));\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp,  nfft))));\n");
+    fprintf(fid,"G  = 20*log10(abs(fftshift(fft(g/k, nfft))));\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Hm, f,Hc, f,Hp, f,G,'-k','LineWidth',2, [-0.5/k 0.5/k],[-6.026 -6.026],'or');\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density');\n");
+    fprintf(fid,"legend('transmit','channel','equalizer','composite','half-power points','location','northeast');\n");
+    fprintf(fid,"axis([-0.5 0.5 -12 8]);\n");
+    fprintf(fid,"grid on;\n");
+    
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/eqlms_cccf_block_example.c b/examples/eqlms_cccf_block_example.c
new file mode 100644
index 0000000..d196309
--- /dev/null
+++ b/examples/eqlms_cccf_block_example.c
@@ -0,0 +1,188 @@
+// 
+// eqlms_cccf_block_example.c
+//
+// This example tests the least mean-squares (LMS) equalizer (EQ) on a
+// signal with an unknown modulation and carrier frequency offset.
+// Equalization is performed blind on a block of samples and the reulting
+// constellation is output to a file for plotting.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqlms_cccf_block_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: eqlms_cccf_block_example [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  n     : number of symbols, default: 500\n");
+    printf("  c     : number of channel filter taps (minimum: 1), default: 5\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter semi-length (symbols), default: 4\n");
+    printf("  b     : filter excess bandwidth factor, default: 0.3\n");
+    printf("  p     : equalizer semi-length (symbols), default: 3\n");
+    printf("  u     : equalizer learning rate, default; 0.05\n");
+}
+
+int main(int argc, char*argv[])
+{
+    //srand(time(NULL));
+
+    // options
+    unsigned int    num_samples = 2400;     // number of symbols to observe
+    unsigned int    hc_len      = 5;        // channel filter length
+    unsigned int    k           = 2;        // matched filter samples/symbol
+    unsigned int    m           = 3;        // matched filter delay (symbols)
+    float           beta        = 0.3f;     // matched filter excess bandwidth factor
+    unsigned int    p           = 3;        // equalizer length (symbols, hp_len = 2*k*p+1)
+    float           mu          = 0.15f;    // equalizer learning rate
+    modulation_scheme ms        = LIQUID_MODEM_QPSK;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:c:k:m:b:p:u:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': num_samples   = atoi(optarg); break;
+        case 'c': hc_len        = atoi(optarg); break;
+        case 'k': k             = atoi(optarg); break;
+        case 'm': m             = atoi(optarg); break;
+        case 'b': beta          = atof(optarg); break;
+        case 'p': p             = atoi(optarg); break;
+        case 'u': mu            = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+    unsigned int i;
+
+    // validate input
+    if (num_samples == 0) {
+        fprintf(stderr,"error: %s, number of symbols must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (hc_len == 0) {
+        fprintf(stderr,"error: %s, channel must have at least 1 tap\n", argv[0]);
+        exit(1);
+    } else if (k < 2) {
+        fprintf(stderr,"error: %s, samples/symbol must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (beta < 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: %s, filter excess bandwidth must be in [0,1]\n", argv[0]);
+        exit(1);
+    } else if (p == 0) {
+        fprintf(stderr,"error: %s, equalizer semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f || mu > 1.0f) {
+        fprintf(stderr,"error: %s, equalizer learning rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived/fixed values
+    unsigned int    buf_len = 37;
+    float complex   buf_input  [buf_len];
+    float complex   buf_channel[buf_len];
+    float complex   buf_output [buf_len];
+
+    // 
+    // generate input sequence using symbol stream generator
+    //
+    symstreamcf gen = symstreamcf_create_linear(LIQUID_FIRFILT_ARKAISER,k,m,beta,ms);
+
+    //
+    // create multi-path channel filter
+    //
+    float complex hc[hc_len];
+    for (i=0; i<hc_len; i++)
+        hc[i] = (i==0) ? 0.5f : (randnf() + _Complex_I*randnf())*0.2f;
+    firfilt_cccf channel = firfilt_cccf_create(hc, hc_len);
+
+    //
+    // create equalizer
+    //
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_RRC, k, p, beta, 0.0f);
+    eqlms_cccf_set_bw(eq, mu);
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"x = [];\n");
+    fprintf(fid,"y = [];\n");
+    fprintf(fid,"z = [];\n");
+
+    unsigned int num_samples_total = 0;
+    while (num_samples_total < num_samples)
+    {
+        // generate block of input samples
+        symstreamcf_write_samples(gen, buf_input, buf_len);
+
+        // apply channel to input signal
+        firfilt_cccf_execute_block(channel, buf_input, buf_len, buf_channel);
+
+        // run equalizer
+        eqlms_cccf_execute_block(eq, k, buf_channel, buf_len, buf_output);
+        
+        // save results to output file
+        for (i=0; i<buf_len; i++) {
+            fprintf(fid,"x(end+1) = %12.4e + %12.4ei;\n", crealf(buf_input  [i]), cimagf(buf_input  [i]));
+            fprintf(fid,"y(end+1) = %12.4e + %12.4ei;\n", crealf(buf_channel[i]), cimagf(buf_channel[i]));
+            fprintf(fid,"z(end+1) = %12.4e + %12.4ei;\n", crealf(buf_output [i]), cimagf(buf_output [i]));
+        }
+
+        // increment number of samples
+        num_samples_total += buf_len;
+    }
+
+    // destroy objects
+    symstreamcf_destroy(gen);
+    firfilt_cccf_destroy(channel);
+    eqlms_cccf_destroy(eq);
+
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"n = length(x);\n");
+
+    fprintf(fid,"figure('Color','white','position',[500 500 1200 500]);\n");
+    // plot constellation
+    fprintf(fid,"subplot(2,8,[5 6 7 8 13 14 15 16]),\n");
+    fprintf(fid,"  s = 1:k:n;\n");
+    fprintf(fid,"  s0 = round(length(s)/2);\n");
+    fprintf(fid,"  syms_rx_0 = z(s(1:s0));\n");
+    fprintf(fid,"  syms_rx_1 = z(s(s0:end));\n");
+    fprintf(fid,"  plot(real(syms_rx_0),imag(syms_rx_0),'x','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"       real(syms_rx_1),imag(syms_rx_1),'x','Color',[1 1 1]*0.0);\n");
+    fprintf(fid,"  xlabel('In-Phase');\n");
+    fprintf(fid,"  ylabel('Quadrature');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    // plot time response
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"subplot(2,8,1:4),\n");
+    fprintf(fid,"  plot(t,   real(z),   '-','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"       t(s),real(z(s)),'s','Color',[0 0.2 0.5],'MarkerSize',3);\n");
+    fprintf(fid,"  axis([0 n -1.5 1.5]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,8,9:12),\n");
+    fprintf(fid,"  plot(t,   imag(z),   '-','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"       t(s),imag(z(s)),'s','Color',[0 0.5 0.2],'MarkerSize',3);\n");
+    fprintf(fid,"  axis([0 n -1.5 1.5]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/eqlms_cccf_decisiondirected_example.c b/examples/eqlms_cccf_decisiondirected_example.c
new file mode 100644
index 0000000..27150b3
--- /dev/null
+++ b/examples/eqlms_cccf_decisiondirected_example.c
@@ -0,0 +1,285 @@
+// 
+// eqlms_cccf_decisiondirected_example.c
+//
+// Tests least mean-squares (LMS) equalizer (EQ) on a signal with a known
+// linear modulation scheme, but unknown data. The equalizer is updated
+// using decision-directed demodulator output samples.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqlms_cccf_decisiondirected_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: eqlms_cccf_decisiondirected_example [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  n     : number of symbols, default: 500\n");
+    printf("  s     : SNR [dB], default: 30\n");
+    printf("  c     : number of channel filter taps (minimum: 1), default: 5\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter semi-length (symbols), default: 4\n");
+    printf("  b     : filter excess bandwidth factor, default: 0.3\n");
+    printf("  p     : equalizer semi-length (symbols), default: 3\n");
+    printf("  u     : equalizer learning rate, default; 0.05\n");
+    printf("  M     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int num_symbols=500;   // number of symbols to observe
+    float SNRdB = 30.0f;            // signal-to-noise ratio [dB]
+    unsigned int hc_len=5;          // channel filter length
+    unsigned int k=2;               // matched filter samples/symbol
+    unsigned int m=3;               // matched filter delay (symbols)
+    float beta=0.3f;                // matched filter excess bandwidth factor
+    unsigned int p=3;               // equalizer length (symbols, hp_len = 2*k*p+1)
+    float mu = 0.08f;               // learning rate
+
+    // modulation type/depth
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:s:c:k:m:b:p:u:M:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': num_symbols   = atoi(optarg); break;
+        case 's': SNRdB         = atof(optarg); break;
+        case 'c': hc_len        = atoi(optarg); break;
+        case 'k': k             = atoi(optarg); break;
+        case 'm': m             = atoi(optarg); break;
+        case 'b': beta          = atof(optarg); break;
+        case 'p': p             = atoi(optarg); break;
+        case 'u': mu            = atof(optarg); break;
+        case 'M':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_symbols == 0) {
+        fprintf(stderr,"error: %s, number of symbols must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (hc_len == 0) {
+        fprintf(stderr,"error: %s, channel must have at least 1 tap\n", argv[0]);
+        exit(1);
+    } else if (k < 2) {
+        fprintf(stderr,"error: %s, samples/symbol must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (beta < 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: %s, filter excess bandwidth must be in [0,1]\n", argv[0]);
+        exit(1);
+    } else if (p == 0) {
+        fprintf(stderr,"error: %s, equalizer semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f || mu > 1.0f) {
+        fprintf(stderr,"error: %s, equalizer learning rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int hm_len = 2*k*m+1;   // matched filter length
+    unsigned int hp_len = 2*k*p+1;   // equalizer filter length
+    unsigned int num_samples = k*num_symbols;
+
+    // bookkeeping variables
+    float complex sym_tx[num_symbols];  // transmitted data sequence
+    float complex x[num_samples];       // interpolated time series
+    float complex y[num_samples];       // channel output
+    float complex z[num_samples];       // equalized output
+
+    float hm[hm_len];                   // matched filter response
+    float complex hc[hc_len];           // channel filter coefficients
+    float complex hp[hp_len];           // equalizer filter coefficients
+
+    unsigned int i;
+
+    // generate matched filter response
+    liquid_firdes_prototype(LIQUID_FIRFILT_RRC, k, m, beta, 0.0f, hm);
+    firinterp_crcf interp = firinterp_crcf_create(k, hm, hm_len);
+
+    // create the modem objects
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+    unsigned int M = 1 << modem_get_bps(mod);
+
+    // generate channel impulse response, filter
+    hc[0] = 1.0f;
+    for (i=1; i<hc_len; i++)
+        hc[i] = 0.09f*(randnf() + randnf()*_Complex_I);
+    firfilt_cccf fchannel = firfilt_cccf_create(hc, hc_len);
+
+    // generate random symbols
+    for (i=0; i<num_symbols; i++)
+        modem_modulate(mod, rand()%M, &sym_tx[i]);
+
+    // interpolate
+    for (i=0; i<num_symbols; i++)
+        firinterp_crcf_execute(interp, sym_tx[i], &x[i*k]);
+    
+    // push through channel
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++) {
+        firfilt_cccf_push(fchannel, x[i]);
+        firfilt_cccf_execute(fchannel, &y[i]);
+
+        // add noise
+        y[i] += nstd*(randnf() + randnf()*_Complex_I)*M_SQRT1_2;
+    }
+
+    // push through equalizer
+    // create equalizer, intialized with square-root Nyquist filter
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_RRC, k, p, beta, 0.0f);
+    eqlms_cccf_set_bw(eq, mu);
+
+    // get initialized weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // filtered error vector magnitude (emperical RMS error)
+    float evm_hat = 0.03f;
+
+    float complex d_hat = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        // print filtered evm (emperical rms error)
+        if ( ((i+1)%50)==0 )
+            printf("%4u : rms error = %12.8f dB\n", i+1, 10*log10(evm_hat));
+
+        eqlms_cccf_push(eq, y[i]);
+        eqlms_cccf_execute(eq, &d_hat);
+
+        // store output
+        z[i] = d_hat;
+
+        // decimate by k
+        if ( (i%k) != 0 ) continue;
+
+        // estimate transmitted signal
+        unsigned int sym_out;   // output symbol
+        float complex d_prime;  // estimated input sample
+        modem_demodulate(demod, d_hat, &sym_out);
+        modem_get_demodulator_sample(demod, &d_prime);
+
+        // update equalizer
+        eqlms_cccf_step(eq, d_prime, d_hat);
+
+        // update filtered evm estimate
+        float evm = crealf( (d_prime-d_hat)*conjf(d_prime-d_hat) );
+        evm_hat = 0.98f*evm_hat + 0.02f*evm;
+    }
+
+    // get equalizer weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // destroy objects
+    eqlms_cccf_destroy(eq);
+    firinterp_crcf_destroy(interp);
+    firfilt_cccf_destroy(fchannel);
+    modem_destroy(mod);
+    modem_destroy(demod);
+
+    // 
+    // export output
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_symbols*k;\n");
+
+    // save transmit matched-filter response
+    fprintf(fid,"hm_len = 2*k*m+1;\n");
+    fprintf(fid,"hm = zeros(1,hm_len);\n");
+    for (i=0; i<hm_len; i++)
+        fprintf(fid,"hm(%4u) = %12.4e;\n", i+1, hm[i]);
+
+    // save channel impulse response
+    fprintf(fid,"hc_len = %u;\n", hc_len);
+    fprintf(fid,"hc = zeros(1,hc_len);\n");
+    for (i=0; i<hc_len; i++)
+        fprintf(fid,"hc(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(hc[i]), cimagf(hc[i]));
+
+    // save equalizer response
+    fprintf(fid,"hp_len = %u;\n", hp_len);
+    fprintf(fid,"hp = zeros(1,hp_len);\n");
+    for (i=0; i<hp_len; i++)
+        fprintf(fid,"hp(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(hp[i]), cimagf(hp[i]));
+
+    // save sample sets
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    fprintf(fid,"z = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+    }
+
+    // plot time response
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"tsym = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(z),...\n");
+    fprintf(fid,"     t(tsym),real(z(tsym)),'x');\n");
+
+    // plot constellation
+    fprintf(fid,"tsym0 = tsym(1:(length(tsym)/2));\n");
+    fprintf(fid,"tsym1 = tsym((length(tsym)/2):end);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(z(tsym0)),imag(z(tsym0)),'x','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"     real(z(tsym1)),imag(z(tsym1)),'x','Color',[1 1 1]*0.0);\n");
+    fprintf(fid,"xlabel('In-Phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    // compute composite response
+    fprintf(fid,"g  = real(conv(conv(hm,hc),hp));\n");
+
+    // plot responses
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Hm = 20*log10(abs(fftshift(fft(hm/k,nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc,  nfft))));\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp,  nfft))));\n");
+    fprintf(fid,"G  = 20*log10(abs(fftshift(fft(g/k, nfft))));\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Hm, f,Hc, f,Hp, f,G,'-k','LineWidth',2, [-0.5/k 0.5/k],[-6.026 -6.026],'or');\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density');\n");
+    fprintf(fid,"legend('transmit','channel','equalizer','composite','half-power points',1);\n");
+    fprintf(fid,"axis([-0.5 0.5 -12 8]);\n");
+    fprintf(fid,"grid on;\n");
+    
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/eqlms_cccf_example.c b/examples/eqlms_cccf_example.c
new file mode 100644
index 0000000..bfed157
--- /dev/null
+++ b/examples/eqlms_cccf_example.c
@@ -0,0 +1,162 @@
+// 
+// eqlms_cccf_example.c
+//
+// Tests least mean-squares (LMS) equalizer (EQ) on a QPSK
+// signal at the symbol level.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqlms_cccf_example.m"
+
+int main() {
+    // options
+    unsigned int n=512;     // number of symbols to observe
+    unsigned int ntrain=256;// number of training symbols
+    unsigned int h_len=6;   // channel filter length
+    unsigned int p=12;      // equalizer order
+
+    // bookkeeping variables
+    float complex d[n];     // data sequence
+    float complex y[n];     // received data sequence (filtered by channel)
+    float complex d_hat[n]; // recovered data sequence
+    float complex h[h_len]; // channel filter coefficients
+    float complex w[p];     // equalizer filter coefficients
+    unsigned int i;
+
+    // create equalizer (default initial coefficients)
+    eqlms_cccf eq = eqlms_cccf_create(NULL,p);
+
+    // create channel filter (random delay taps)
+    h[0] = 1.0f;
+    for (i=1; i<h_len; i++)
+        h[i] = (randnf() + randnf()*_Complex_I) * 0.1f;
+    firfilt_cccf f = firfilt_cccf_create(h,h_len);
+
+    // generate random data signal
+    for (i=0; i<n; i++)
+        d[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+
+    // filter data signal through channel
+    for (i=0; i<n; i++) {
+        firfilt_cccf_push(f,d[i]);
+        firfilt_cccf_execute(f,&y[i]);
+    }
+
+    // run equalizer
+    for (i=0; i<p; i++)
+        w[i] = 0;
+    eqlms_cccf_train(eq, w, y, d, ntrain);
+
+    // create filter from equalizer output
+    firfilt_cccf feq = firfilt_cccf_create(w,p);
+
+    // run equalizer filter
+    for (i=0; i<n; i++) {
+        firfilt_cccf_push(feq,y[i]);
+        firfilt_cccf_execute(feq,&d_hat[i]);
+    }
+
+    //
+    // print results
+    //
+    printf("channel:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h(%3u) = %12.8f + j*%12.8f\n", i, crealf(h[i]), cimagf(h[i]));
+
+    printf("equalizer:\n");
+    for (i=0; i<p; i++)
+        printf("  w(%3u) = %12.8f + j*%12.8f\n", i, crealf(w[i]), cimagf(w[i]));
+
+    // compute MSE
+    float complex e;
+    float mse=0.0f;
+    for (i=0; i<n; i++) {
+        // compute mse
+        e = d[i] - d_hat[i];
+        mse += crealf(e*conj(e));
+    }
+    mse /= n;
+    printf("mse: %12.8f\n", mse);
+
+    // clean up objects
+    firfilt_cccf_destroy(f);
+    eqlms_cccf_destroy(eq);
+    firfilt_cccf_destroy(feq);
+
+
+    // 
+    // export data to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"n=%u;\n",n);
+    fprintf(fid,"ntrain=%u;\n",ntrain);
+    fprintf(fid,"p=%u;\n",p);
+    fprintf(fid,"h_len=%u;\n",h_len);
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"  h(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(h[i]), cimagf(h[i]));
+
+    for (i=0; i<p; i++)
+        fprintf(fid,"  w(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(w[i]), cimagf(w[i]));
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"  d(%3u)     = %12.4e + j*%12.4e;\n", i+1, crealf(d[i]),     cimagf(d[i]));
+        fprintf(fid,"  y(%3u)     = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]),     cimagf(y[i]));
+        fprintf(fid,"  d_hat(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(d_hat[i]), cimagf(d_hat[i]));
+    }
+
+    // plot results
+    fprintf(fid,"\n\n");
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H=20*log10(abs(fftshift(fft(h,nfft))));\n");
+    fprintf(fid,"W=20*log10(abs(fftshift(fft(w,nfft))));\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-r',f,W,'-b', f,H+W,'-k','LineWidth',2);\n");
+    fprintf(fid,"xlabel('Normalied Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"axis([-0.5 0.5 -10 10]);\n");
+    fprintf(fid,"legend('channel','equalizer','composite',0);\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"stem(0:(h_len-1),real(h),'-r');\n");
+    fprintf(fid,"stem(0:(p-1),    real(w),'-b');\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"ylabel('Real Coefficients');\n");
+    fprintf(fid,"legend('channel','equalizer',0);\n");
+    fprintf(fid,"axis([-0.25 max(h_len,p)-0.75 -0.5 1.5]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"stem(0:(h_len-1),imag(h),'-r');\n");
+    fprintf(fid,"stem(0:(p-1),    imag(w),'-b');\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"ylabel('Imag Coefficients');\n");
+    fprintf(fid,"legend('channel','equalizer',0);\n");
+    fprintf(fid,"axis([-0.25 max(h_len,p)-0.75 -0.5 1.5]);\n");
+
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(y,'xr',d_hat,'xb');\n");
+    fprintf(fid,"axis('square');\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature');\n");
+    fprintf(fid,"legend('received','equalized',1');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/eqrls_cccf_example.c b/examples/eqrls_cccf_example.c
new file mode 100644
index 0000000..721eb9c
--- /dev/null
+++ b/examples/eqrls_cccf_example.c
@@ -0,0 +1,162 @@
+// 
+// eqrls_cccf_example.c
+//
+// Tests recursive least-squares (RLS) equalizer (EQ) on a QPSK
+// signal at the symbol level.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqrls_cccf_example.m"
+
+int main() {
+    // options
+    unsigned int n=512;     // number of symbols to observe
+    unsigned int ntrain=256;// number of training symbols
+    unsigned int h_len=6;   // channel filter length
+    unsigned int p=12;      // equalizer order
+
+    // bookkeeping variables
+    float complex d[n];     // data sequence
+    float complex y[n];     // received data sequence (filtered by channel)
+    float complex d_hat[n]; // recovered data sequence
+    float complex h[h_len]; // channel filter coefficients
+    float complex w[p];     // equalizer filter coefficients
+    unsigned int i;
+
+    // create equalizer (default initial coefficients)
+    eqrls_cccf eq = eqrls_cccf_create(NULL,p);
+
+    // create channel filter (random delay taps)
+    h[0] = 1.0f;
+    for (i=1; i<h_len; i++)
+        h[i] = (randnf() + randnf()*_Complex_I) * 0.1f;
+    firfilt_cccf f = firfilt_cccf_create(h,h_len);
+
+    // generate random data signal
+    for (i=0; i<n; i++)
+        d[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+
+    // filter data signal through channel
+    for (i=0; i<n; i++) {
+        firfilt_cccf_push(f,d[i]);
+        firfilt_cccf_execute(f,&y[i]);
+    }
+
+    // run equalizer
+    for (i=0; i<p; i++)
+        w[i] = 0;
+    eqrls_cccf_train(eq, w, y, d, ntrain);
+
+    // create filter from equalizer output
+    firfilt_cccf feq = firfilt_cccf_create(w,p);
+
+    // run equalizer filter
+    for (i=0; i<n; i++) {
+        firfilt_cccf_push(feq,y[i]);
+        firfilt_cccf_execute(feq,&d_hat[i]);
+    }
+
+    //
+    // print results
+    //
+    printf("channel:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h(%3u) = %12.8f + j*%12.8f\n", i, crealf(h[i]), cimagf(h[i]));
+
+    printf("equalizer:\n");
+    for (i=0; i<p; i++)
+        printf("  w(%3u) = %12.8f + j*%12.8f\n", i, crealf(w[i]), cimagf(w[i]));
+
+    // compute MSE
+    float complex e;
+    float mse=0.0f;
+    for (i=0; i<n; i++) {
+        // compute mse
+        e = d[i] - d_hat[i];
+        mse += crealf(e*conj(e));
+    }
+    mse /= n;
+    printf("mse: %12.8f\n", mse);
+
+    // clean up objects
+    firfilt_cccf_destroy(f);
+    eqrls_cccf_destroy(eq);
+    firfilt_cccf_destroy(feq);
+
+
+    // 
+    // export data to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"n=%u;\n",n);
+    fprintf(fid,"ntrain=%u;\n",ntrain);
+    fprintf(fid,"p=%u;\n",p);
+    fprintf(fid,"h_len=%u;\n",h_len);
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"  h(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(h[i]), cimagf(h[i]));
+
+    for (i=0; i<p; i++)
+        fprintf(fid,"  w(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(w[i]), cimagf(w[i]));
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"  d(%3u)     = %12.4e + j*%12.4e;\n", i+1, crealf(d[i]),     cimagf(d[i]));
+        fprintf(fid,"  y(%3u)     = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]),     cimagf(y[i]));
+        fprintf(fid,"  d_hat(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(d_hat[i]), cimagf(d_hat[i]));
+    }
+
+    // plot results
+    fprintf(fid,"\n\n");
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H=20*log10(abs(fftshift(fft(h,nfft))));\n");
+    fprintf(fid,"W=20*log10(abs(fftshift(fft(w,nfft))));\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-r',f,W,'-b', f,H+W,'-k','LineWidth',2);\n");
+    fprintf(fid,"xlabel('Normalied Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"axis([-0.5 0.5 -10 10]);\n");
+    fprintf(fid,"legend('channel','equalizer','composite',0);\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"stem(0:(h_len-1),real(h),'-r');\n");
+    fprintf(fid,"stem(0:(p-1),    real(w),'-b');\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"ylabel('Real Coefficients');\n");
+    fprintf(fid,"legend('channel','equalizer',0);\n");
+    fprintf(fid,"axis([-0.25 max(h_len,p)-0.75 -0.5 1.5]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"stem(0:(h_len-1),imag(h),'-r');\n");
+    fprintf(fid,"stem(0:(p-1),    imag(w),'-b');\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"ylabel('Imag Coefficients');\n");
+    fprintf(fid,"legend('channel','equalizer',0);\n");
+    fprintf(fid,"axis([-0.25 max(h_len,p)-0.75 -0.5 1.5]);\n");
+
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(y,'xr',d_hat,'xb');\n");
+    fprintf(fid,"axis('square');\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature');\n");
+    fprintf(fid,"legend('received','equalized',1');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/fading_generator_example.c b/examples/fading_generator_example.c
new file mode 100644
index 0000000..03e77a5
--- /dev/null
+++ b/examples/fading_generator_example.c
@@ -0,0 +1,103 @@
+//
+// Fading generator example
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "debug_fading_generator_example.m"
+
+int main() {
+    // options
+    unsigned int h_len=51;  // doppler filter length
+    float fd=0.05f;         // maximum doppler frequency
+    float K=2.0f;           // Rice fading factor
+    float omega=1.0f;       // mean power
+    float theta=0.0f;       // angle of arrival
+    unsigned int n=256;     // number of samples
+
+    // generate filter
+    unsigned int i;
+    float h[h_len];
+    fir_design_doppler(h_len,fd,K,theta,h);
+    firfilt_rrrf fi = firfilt_rrrf_create(h,h_len);
+    firfilt_rrrf fq = firfilt_rrrf_create(h,h_len);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%3u) = %12.8e;\n", i+1, h[i]);
+
+    // generate complex fading envelope
+    float complex x, y;
+    float yi, yq;
+    float s = sqrtf((omega*K)/(K+1));
+    float sig = sqrtf(0.5f*omega/(K+1));
+    float t=0.0f;
+    for (i=0; i<h_len; i++) {
+        crandnf(&x);
+
+        firfilt_rrrf_push(fi, crealf(x));
+        firfilt_rrrf_push(fq, cimagf(x));
+
+        t += h[i]*h[i];
+    }
+    t = sqrtf(t);
+
+    for (i=0; i<n; i++) {
+        crandnf(&x);
+
+        firfilt_rrrf_push(fi, crealf(x));
+        firfilt_rrrf_push(fq, cimagf(x));
+
+        // compute outputs
+        firfilt_rrrf_execute(fi, &yi);
+        firfilt_rrrf_execute(fq, &yq);
+
+        yi /= t;
+        yq /= t;
+
+        y = (yi*sig + s) + _Complex_I*(yq*sig);
+
+        printf("%4u: r:%8.5f, angle:%5.2f\n", i, cabsf(y), cargf(y));
+
+        fprintf(fid, "y(%4u) = %12.8e + j*%12.8e;\n",i+1,crealf(y),cimagf(y));
+    }
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H=20*log10(abs(fftshift(fft(h,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-','LineWidth',2);\n");
+    fprintf(fid,"xlabel('Normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"\n\n");
+
+    fprintf(fid,"t=0:length(y)-1;\n");
+    fprintf(fid,"R=20*log10(abs(y));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,R,'-','LineWidth',2);\n");
+    fprintf(fid,"xlabel('time [samples]');\n");
+    fprintf(fid,"ylabel('fading amplitude [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    // clean up objects
+    firfilt_rrrf_destroy(fi);
+    firfilt_rrrf_destroy(fq);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/fec_example.c b/examples/fec_example.c
new file mode 100644
index 0000000..0a51522
--- /dev/null
+++ b/examples/fec_example.c
@@ -0,0 +1,141 @@
+//
+// fec_example.c
+//
+// This example demonstrates the interface for forward error-
+// correction (FEC) codes.  A buffer of data bytes is encoded and
+// corrupted with several errors.  The decoder then attempts to
+// recover the original data set.  The user may select the FEC
+// scheme from the command-line interface.
+// SEE ALSO: crc_example.c
+//           checksum_example.c
+//           packetizer_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("fec_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : input data size (number of uncoded bytes)\n");
+    printf("  c     : coding scheme, (h74 default):\n");
+    liquid_print_fec_schemes();
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int n = 4;                     // data length (bytes)
+    unsigned int nmax = 2048;               // maximum data length
+    fec_scheme fs = LIQUID_FEC_HAMMING74;   // error-correcting scheme
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:c:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n': n = atoi(optarg); break;
+        case 'c':
+            fs = liquid_getopt_str2fec(optarg);
+            if (fs == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported fec scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // ensure proper data length
+    n = (n > nmax) ? nmax : n;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    printf("dec msg len : %u\n", n);
+    printf("enc msg len : %u\n", n_enc);
+    unsigned char data[n];          // original data message
+    unsigned char msg_enc[n_enc];   // encoded data message
+    unsigned char msg_cor[n_enc];   // corrupted data message
+    unsigned char msg_dec[n];       // decoded data message
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    fec_print(q);
+
+    unsigned int i;
+
+    // create message
+    for (i=0; i<n; i++)
+        data[i] = rand() & 0xff;
+
+    // encode message
+    fec_encode(q, n, data, msg_enc);
+
+    // corrupt encoded message
+    memmove(msg_cor, msg_enc, n_enc);
+    msg_cor[0] ^= 0x04; // position 5
+#if 0
+    msg_cor[1] ^= 0x04; //
+    msg_cor[2] ^= 0x02; //
+    msg_cor[3] ^= 0x01; //
+    msg_cor[4] ^= 0x80; //
+    msg_cor[5] ^= 0x40; //
+    msg_cor[6] ^= 0x20; //
+    msg_cor[7] ^= 0x10; //
+#endif
+
+    // decode message
+    fec_decode(q, n, msg_cor, msg_dec);
+
+    printf("original message:  [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf(" %.2X", (unsigned int) (data[i]));
+    printf("\n");
+
+    printf("encoded message:   [%3u] ",n_enc);
+    for (i=0; i<n_enc; i++)
+        printf(" %.2X", (unsigned int) (msg_enc[i]));
+    printf("\n");
+
+    printf("corrupted message: [%3u] ",n_enc);
+    for (i=0; i<n_enc; i++)
+        printf("%c%.2X", msg_cor[i]==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_cor[i]));
+    printf("\n");
+
+    printf("decoded message:   [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf("%c%.2X", msg_dec[i] == data[i] ? ' ' : '*', (unsigned int) (msg_dec[i]));
+    printf("\n");
+    printf("\n");
+
+    // count bit errors
+    unsigned int j, num_sym_errors=0, num_bit_errors=0;
+    unsigned char e;
+    for (i=0; i<n; i++) {
+        num_sym_errors += (data[i] == msg_dec[i]) ? 0 : 1;
+
+        e = data[i] ^ msg_dec[i];
+        for (j=0; j<8; j++) {
+            num_bit_errors += e & 0x01;
+            e >>= 1;
+        }
+    }
+
+    //printf("number of symbol errors detected: %d\n", num_errors_detected);
+    printf("number of symbol errors received: %3u / %3u\n", num_sym_errors, n);
+    printf("number of bit errors received:    %3u / %3u\n", num_bit_errors, n*8);
+
+    // clean up objects
+    fec_destroy(q);
+
+    return 0;
+}
+
diff --git a/examples/fec_soft_example.c b/examples/fec_soft_example.c
new file mode 100644
index 0000000..90b0fd5
--- /dev/null
+++ b/examples/fec_soft_example.c
@@ -0,0 +1,186 @@
+//
+// fec_soft_example.c
+//
+// This example demonstrates the interface for forward error-correction
+// (FEC) codes with soft-decision decoding.  A buffer of data bytes is
+// encoded before the data are corrupted with at least one error and
+// noise. The decoder then attempts to recover the original data set
+// from the soft input bits.  The user may select the FEC scheme from
+// the command-line interface.
+//
+// SEE ALSO: fec_example.c
+//           packetizer_soft_example.c
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("fecsoft_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  v/q   : verbose/queit (print soft bits?)\n");
+    printf("  n     : input data size (number of uncoded bytes)\n");
+    printf("  c     : coding scheme, (h74 default):\n");
+    liquid_print_fec_schemes();
+}
+
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int n = 4;                     // data length (bytes)
+    unsigned int nmax = 2048;               // maximum data length
+    fec_scheme fs = LIQUID_FEC_HAMMING74;   // error-correcting scheme
+    int verbose = 1;                        // verbose?
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhvqn:c:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage();          return 0;
+        case 'v': verbose = 1;      break;
+        case 'q': verbose = 0;      break;
+        case 'n': n = atoi(optarg); break;
+        case 'c':
+            fs = liquid_getopt_str2fec(optarg);
+            if (fs == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported fec scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // ensure proper data length
+    n = (n > nmax) ? nmax : n;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    printf("dec msg len : %u\n", n);
+    printf("enc msg len : %u\n", n_enc);
+    unsigned char data[n];               // original data message
+    unsigned char msg_enc[n_enc];        // encoded data message
+    unsigned char msg_cor_soft[8*n_enc]; // corrupted data message (soft bits)
+    unsigned char msg_cor_hard[n_enc];   // corrupted data message (hard bits)
+    unsigned char msg_dec[n];            // decoded data message
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    fec_print(q);
+
+    unsigned int i;
+
+    // create message
+    for (i=0; i<n; i++)
+        data[i] = rand() & 0xff;
+
+    // encode message
+    fec_encode(q, n, data, msg_enc);
+
+    // convert to soft bits and add 'noise'
+    for (i=0; i<n_enc; i++) {
+        msg_cor_soft[8*i+0] = (msg_enc[i] & 0x80) ? 255 : 0;
+        msg_cor_soft[8*i+1] = (msg_enc[i] & 0x40) ? 255 : 0;
+        msg_cor_soft[8*i+2] = (msg_enc[i] & 0x20) ? 255 : 0;
+        msg_cor_soft[8*i+3] = (msg_enc[i] & 0x10) ? 255 : 0;
+        msg_cor_soft[8*i+4] = (msg_enc[i] & 0x08) ? 255 : 0;
+        msg_cor_soft[8*i+5] = (msg_enc[i] & 0x04) ? 255 : 0;
+        msg_cor_soft[8*i+6] = (msg_enc[i] & 0x02) ? 255 : 0;
+        msg_cor_soft[8*i+7] = (msg_enc[i] & 0x01) ? 255 : 0;
+    }
+
+    // flip first bit (ensure error)
+    msg_cor_soft[0] = 255 - msg_cor_soft[0];
+
+    // add noise (but not so much that it would cause a bit error)
+    for (i=0; i<8*n_enc; i++) {
+        int soft_bit = 0.8*msg_cor_soft[i] + (int)(20*randnf());
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit = 0;
+        msg_cor_soft[i] = soft_bit;
+    }
+
+    // convert to hard bits (printing purposes)
+    for (i=0; i<n_enc; i++) {
+        msg_cor_hard[i] = 0x00;
+
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+0] >> 0) & 0x80;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+1] >> 1) & 0x40;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+2] >> 2) & 0x20;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+3] >> 3) & 0x10;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+4] >> 4) & 0x08;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+5] >> 5) & 0x04;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+6] >> 6) & 0x02;
+        msg_cor_hard[i] |=(msg_cor_soft[8*i+7] >> 7) & 0x01;
+    }
+
+    // decode message
+    fec_decode_soft(q, n, msg_cor_soft, msg_dec);
+
+    printf("original message:  [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf(" %.2X", (unsigned int) (data[i]));
+    printf("\n");
+
+    printf("encoded message:   [%3u] ",n_enc);
+    for (i=0; i<n_enc; i++)
+        printf(" %.2X", (unsigned int) (msg_enc[i]));
+    printf("\n");
+
+    // print compact result
+    printf("corrupted message: [%3u] ",n_enc);
+    for (i=0; i<n_enc; i++)
+        printf("%c%.2X", msg_cor_hard[i]==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_cor_hard[i]));
+    printf("\n");
+
+    if (verbose) {
+        // print expanded result (print each soft bit value)
+        for (i=0; i<n_enc; i++) {
+            printf("%5u: ", i);
+            unsigned int j;
+            for (j=0; j<8; j++) {
+                unsigned int bit_enc = (msg_enc[i] >> (8-j-1)) & 0x01;
+                unsigned int bit_rec = (msg_cor_soft[8*i+j] > 127) ? 1 : 0;
+                //printf("%1u %3u (%1u) %c", bit_enc, msg_cor_soft[i], bit_rec, bit_enc != bit_rec ? '*' : ' ');
+                printf("%4u%c", msg_cor_soft[8*i+j], bit_enc != bit_rec ? '*' : ' ');
+            }
+            printf("  :  %c%.2X\n", msg_cor_hard[i]==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_cor_hard[i]));
+        }
+    } // verbose
+
+    printf("decoded message:   [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf("%c%.2X", msg_dec[i] == data[i] ? ' ' : '*', (unsigned int) (msg_dec[i]));
+    printf("\n");
+    printf("\n");
+
+    // count bit errors
+    unsigned int j, num_sym_errors=0, num_bit_errors=0;
+    unsigned char e;
+    for (i=0; i<n; i++) {
+        num_sym_errors += (data[i] == msg_dec[i]) ? 0 : 1;
+
+        e = data[i] ^ msg_dec[i];
+        for (j=0; j<8; j++) {
+            num_bit_errors += e & 0x01;
+            e >>= 1;
+        }
+    }
+
+    //printf("number of symbol errors detected: %d\n", num_errors_detected);
+    printf("number of symbol errors received: %3u / %3u\n", num_sym_errors, n);
+    printf("number of bit errors received:    %3u / %3u\n", num_bit_errors, n*8);
+
+    // clean up objects
+    fec_destroy(q);
+
+    return 0;
+}
+
diff --git a/examples/fft_example.c b/examples/fft_example.c
new file mode 100644
index 0000000..70d0dd1
--- /dev/null
+++ b/examples/fft_example.c
@@ -0,0 +1,104 @@
+//
+// fft_example.c
+//
+// This example demonstrates the interface to the fast discrete Fourier
+// transform (FFT).
+// SEE ALSO: mdct_example.c
+//           fct_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("fft_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  v/q   : verbose/quiet\n");
+    printf("  n     : fft size, default: 16\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int nfft = 16; // transform size
+    int method = 0;         // fft method (ignored)
+    int verbose = 0;        // verbose output?
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hvqn:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'v': verbose = 1;          break;
+        case 'q': verbose = 0;          break;
+        case 'n': nfft = atoi(optarg);  break;
+        default:
+            exit(1);
+        }
+    }
+
+    // allocate memory arrays
+    float complex * x = (float complex*) malloc(nfft*sizeof(float complex));
+    float complex * y = (float complex*) malloc(nfft*sizeof(float complex));
+    float complex * z = (float complex*) malloc(nfft*sizeof(float complex));
+
+    // initialize input
+    unsigned int i;
+    for (i=0; i<nfft; i++)
+        x[i] = (float)i - _Complex_I*(float)i;
+
+    // create fft plans
+    fftplan pf = fft_create_plan(nfft, x, y, LIQUID_FFT_FORWARD,  method);
+    fftplan pr = fft_create_plan(nfft, y, z, LIQUID_FFT_BACKWARD, method);
+
+    // print fft plans
+    fft_print_plan(pf);
+    //fft_print_plan(pr);
+
+    // execute fft plans
+    fft_execute(pf);
+    fft_execute(pr);
+
+    // destroy fft plans
+    fft_destroy_plan(pf);
+    fft_destroy_plan(pr);
+
+    // normalize inverse
+    for (i=0; i<nfft; i++)
+        z[i] /= (float) nfft;
+
+    if (verbose) {
+        // print results
+        printf("original signal, x[n]:\n");
+        for (i=0; i<nfft; i++)
+            printf("  x[%3u] = %8.4f + j%8.4f\n", i, crealf(x[i]), cimagf(x[i]));
+        printf("y[n] = fft( x[n] ):\n");
+        for (i=0; i<nfft; i++)
+            printf("  y[%3u] = %8.4f + j%8.4f\n", i, crealf(y[i]), cimagf(y[i]));
+        printf("z[n] = ifft( y[n] ):\n");
+        for (i=0; i<nfft; i++)
+            printf("  z[%3u] = %8.4f + j%8.4f\n", i, crealf(z[i]), cimagf(z[i]));
+    }
+
+    // compute RMSE between original and result
+    float rmse = 0.0f;
+    for (i=0; i<nfft; i++) {
+        float complex d = x[i] - z[i];
+        rmse += crealf(d * conjf(d));
+    }
+    rmse = sqrtf( rmse / (float)nfft );
+    printf("rmse = %12.4e\n", rmse);
+
+    // free allocated memory
+    free(x);
+    free(y);
+    free(z);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/fftfilt_crcf_example.c b/examples/fftfilt_crcf_example.c
new file mode 100644
index 0000000..57f5b12
--- /dev/null
+++ b/examples/fftfilt_crcf_example.c
@@ -0,0 +1,135 @@
+//
+// fftfilt_crcf_example.c
+//
+// Complex FFT-based finite impulse response filter example. This example
+// demonstrates the functionality of firfilt by designing a low-order 
+// prototype and using it to filter a noisy signal.  The filter coefficients
+// are  real, but the input and output arrays are complex. The filter order
+// and cutoff frequency are specified at the beginning, and the result is
+// compared to the regular corresponding firfilt_crcf output.
+//
+// SEE ALSO: `firfilt_crcf_example.c`
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "fftfilt_crcf_example.m"
+
+int main() {
+    // options
+    unsigned int h_len=57;      // filter length
+    float fc=0.10f;             // cutoff frequency
+    float As=60.0f;             // stop-band attenuation
+    unsigned int n=64;          // number of samples per block
+    unsigned int num_blocks=6;  // total number of blocks
+
+    // derived values
+    unsigned int num_samples = n * num_blocks;
+
+    // design filter
+    float h[h_len];
+    liquid_firdes_kaiser(h_len, fc, As, 0, h);
+
+    // design FFT-based filter and scale to bandwidth
+    fftfilt_crcf q0 = fftfilt_crcf_create(h, h_len, n);
+    fftfilt_crcf_set_scale(q0, 2.0f*fc);
+
+    // design regular FIR filter
+    firfilt_crcf q1 = firfilt_crcf_create(h, h_len);
+    firfilt_crcf_set_scale(q1, 2.0f*fc);
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x[num_samples];   // input
+    float complex y0[num_samples];  // output (fftfilt)
+    float complex y1[num_samples];  // output (firfilt)
+
+    // generate input signal (noise)
+    for (i=0; i<num_samples; i++)
+        x[i] = randnf() + _Complex_I*randnf();
+
+    // run signal through fft-based filter in blocks
+    for (i=0; i<num_blocks; i++)
+        fftfilt_crcf_execute(q0, &x[i*n], &y1[i*n]);
+
+    // run signal through regular filter one sample at a time
+    for (i=0; i<num_samples; i++) {
+        // run filter
+        firfilt_crcf_push(q1, x[i]);
+        firfilt_crcf_execute(q1, &y0[i]);
+    }
+
+    // destroy filter objects
+    fftfilt_crcf_destroy(q0);
+    firfilt_crcf_destroy(q1);
+
+    // compute error norm
+    float rmse = 0.0f;
+    printf("  %6s : %8s : %8s (%8s), %8s : %8s (%8s)\n",
+            "index",
+            "re{fir}", "re{fft}", "re{err}",
+            "im{fir}", "im{fft}", "im{err}");
+    for (i=0; i<num_samples; i++) {
+        float complex e = y0[i] - y1[i];
+        printf("  %6u : %8.5f : %8.5f (%8.5f), %8.5f : %8.5f (%8.5f)\n",
+                i,
+                crealf(y0[i]), crealf(y1[i]), crealf(e),
+                cimagf(y0[i]), cimagf(y1[i]), cimagf(e));
+
+        // accumulate error
+        rmse += crealf( e*conjf(e) );
+    }
+    // normalize RMS error
+    rmse = sqrtf( rmse/(float)num_samples );
+    printf("  rmse : %12.4e\n", rmse);
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"h_len=%u;\n", h_len);
+    fprintf(fid,"n=%u;\n",num_samples);
+    fprintf(fid,"x =zeros(1,n);\n");
+    fprintf(fid,"y0=zeros(1,n);\n");
+    fprintf(fid,"y1=zeros(1,n);\n");
+
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x( %4u) = %12.4e + j*%12.4e;\n", i+1, crealf( x[i]), cimagf( x[i]));
+        fprintf(fid,"y0(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y0[i]), cimagf(y0[i]));
+        fprintf(fid,"y1(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y1[i]), cimagf(y1[i]));
+    }
+
+    // plot output
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(y0),'-','Color',[0 0.5 0.2],'LineWidth',1,...\n");
+    fprintf(fid,"       t,real(y1),'-','Color',[0 0.2 0.5],'LineWidth',1);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('fftfilt','firfilt','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,imag(y0),'-','Color',[0 0.5 0.2],'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(y1),'-','Color',[0 0.2 0.5],'LineWidth',1);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('fftfilt','firfilt','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firdecim_crcf_example.c b/examples/firdecim_crcf_example.c
new file mode 100644
index 0000000..84d8a73
--- /dev/null
+++ b/examples/firdecim_crcf_example.c
@@ -0,0 +1,196 @@
+//
+// firdecim_crcf_example.c
+//
+// This example demonstrates the interface to the firdecim (finite
+// impulse response decimator) family of objects.
+// Data symbols are generated and then interpolated according to a
+// finite impulse response square-root Nyquist filter.  The resulting
+// sequence is then decimated with the same filter, matched to the
+// interpolator.
+//
+// SEE ALSO: firinterp_crcf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firdecim_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firdecim_crcf_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : samples/symbol (interp factor), k > 1, default: 2\n");
+    printf("  m     : filter delay (symbols), m > 0, default: 2\n");
+    printf("  b     : beta, excess bandwidth factor, 0 < beta < 1, default: 0.5\n");
+    printf("  n     : number of data symbols, default: 8\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=2;                   // samples/symbol
+    unsigned int m=2;                   // filter delay
+    float beta = 0.5f;                  // filter excess bandwidth
+    unsigned int num_data_symbols=8;    // number of data symbols
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:n:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                          return 0;
+        case 'k': k = atoi(optarg);                 break;
+        case 'm': m = atoi(optarg);                 break;
+        case 'b': beta = atof(optarg);              break;
+        case 'n': num_data_symbols = atoi(optarg);  break;
+        default:
+            usage();
+            return 1;
+        }
+    }
+
+    // validate options
+    if (k < 2) {
+        fprintf(stderr,"error: %s, interp factor must be greater than 1\n", argv[0]);
+        return 1;
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, filter delay must be greater than 0\n", argv[0]);
+        return 1;
+    } else if (beta <= 0.0 || beta > 1.0f) {
+        fprintf(stderr,"error: %s, beta (excess bandwidth factor) must be in (0,1]\n", argv[0]);
+        return 1;
+    } else if (num_data_symbols < 1) {
+        fprintf(stderr,"error: %s, must have at least one data symbol\n", argv[0]);
+        return 1;
+    }
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;
+    unsigned int num_symbols = num_data_symbols + 2*m;
+    unsigned int num_samples = k*num_symbols;
+
+    // design filter and create interpolator and decimator objects
+    float h[h_len];     // transmit filter
+    float g[h_len];     // receive filter (reverse of h)
+    liquid_firdes_rrcos(k,m,beta,0.3f,h);
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        g[i] = h[h_len-i-1];
+    firinterp_crcf interp = firinterp_crcf_create(k,h,h_len);
+    firdecim_crcf  decim  = firdecim_crcf_create(k,g,h_len);
+
+    // allocate memory for buffers
+    float complex x[num_symbols];   // input symbols
+    float complex y[num_samples];   // interpolated sequence
+    float complex z[num_symbols];   // decimated (received) symbols
+
+    // generate input symbols, padded with zeros at the end
+    for (i=0; i<num_data_symbols; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f) * _Complex_I;
+    }
+    for ( ; i<num_symbols; i++)
+        x[i] = 0.0f;
+
+    // run interpolator
+    for (i=0; i<num_symbols; i++) {
+        firinterp_crcf_execute(interp, x[i], &y[k*i]);
+    }
+
+    // run decimator
+    for (i=0; i<num_symbols; i++) {
+        firdecim_crcf_execute(decim, &y[k*i], &z[i]);
+
+        // normalize output by samples/symbol
+        z[i] /= k;
+    }
+
+    // destroy objects
+    firinterp_crcf_destroy(interp);
+    firdecim_crcf_destroy(decim);
+
+    // print results to screen
+    printf("filter impulse response :\n");
+    for (i=0; i<h_len; i++)
+        printf("  [%4u] : %8.4f\n", i, h[i]);
+
+    printf("input symbols\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("  [%4u] : %8.4f + j*%8.4f", i, crealf(x[i]), cimagf(x[i]));
+
+        // highlight actual data symbols
+        if (i < num_data_symbols) printf(" *\n");
+        else                      printf("\n");
+    }
+
+    printf("interpolator output samples:\n");
+    for (i=0; i<num_samples; i++) {
+        printf("  [%4u] : %8.4f + j*%8.4f", i, crealf(y[i]), cimagf(y[i]));
+
+        if ( (i >= k*m) && ((i%k)==0))  printf(" **\n");
+        else                            printf("\n");
+    }
+
+    printf("output symbols:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("  [%4u] : %8.4f + j*%8.4f", i, crealf(z[i]), cimagf(z[i]));
+
+        // highlight symbols (compensate for filter delay)
+        if ( i < 2*m ) printf("\n");
+        else           printf(" *\n");
+    }
+
+    // open output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"h_len=%u;\n",h_len);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = k*num_symbols;\n");
+    fprintf(fid,"h = zeros(1,h_len);\n");
+    fprintf(fid,"x = zeros(1,num_symbols);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%4u) = %12.4e;\n", i+1, h[i]);
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"z(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tx = [0:(num_symbols-1)];\n");
+    fprintf(fid,"ty = [0:(num_samples-1)]/k - m;\n");
+    fprintf(fid,"tz = [0:(num_symbols-1)] - 2*m;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(ty,real(y),'-',tx,real(x),'s',tz,real(z),'x');\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('real');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"    legend('interp','data in','data out',0);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(ty,imag(y),'-',tx,imag(x),'s',tz,imag(z),'x');\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('imag');\n");
+    fprintf(fid,"    grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firdes_kaiser_example.c b/examples/firdes_kaiser_example.c
new file mode 100644
index 0000000..beb0197
--- /dev/null
+++ b/examples/firdes_kaiser_example.c
@@ -0,0 +1,101 @@
+//
+// firdes_kaiser_example.c
+//
+// This example demonstrates finite impulse response filter design
+// using a Kaiser window.
+// SEE ALSO: firdespm_example.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firdes_kaiser_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firdes_kaiser_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  f     : filter cutoff frequency,           0 < f < 0.5, default: 0.2\n");
+    printf("  s     : filter stop-band attenuation [dB], 0 < s,       default: 60\n");
+    printf("  m     : fractional sample delay,        -0.5 < m < 0.5, default: 0\n");
+    printf("  n     : filter length [taps],              n > 1,       default: 55\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    float fc=0.2f;          // filter cutoff frequency
+    float As=60.0f;         // stop-band attenuation [dB]
+    float mu=0.0f;          // fractional timing offset
+    unsigned int h_len=55;  // filter length
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhf:n:s:m:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'f': fc    = atof(optarg); break;
+        case 'n': h_len = atoi(optarg); break;
+        case 's': As    = atof(optarg); break;
+        case 'm': mu    = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( fc <= 0.0f ) {
+        fprintf(stderr,"error: %s, filter cutoff frequency must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if ( h_len == 0 ) {
+        fprintf(stderr,"error: %s, filter length must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+
+    printf("filter design parameters\n");
+    printf("    cutoff frequency            :   %8.4f\n", fc);
+    printf("    stop-band attenuation [dB]  :   %8.4f\n", As);
+    printf("    fractional sample offset    :   %8.4f\n", mu);
+    printf("    filter length               :   %u\n", h_len);
+
+    // generate the filter
+    unsigned int i;
+    float h[h_len];
+    liquid_firdes_kaiser(h_len,fc,As,mu,h);
+
+    // print coefficients
+    for (i=0; i<h_len; i++)
+        printf("h(%4u) = %16.12f;\n", i+1, h[i]);
+
+    // output to file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"h_len=%u;\n",h_len);
+    fprintf(fid,"fc=%12.4e;\n",fc);
+    fprintf(fid,"As=%12.4e;\n",As);
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%4u) = %12.4e;\n", i+1, h[i]);
+
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"H=20*log10(abs(fftshift(fft(h*2*fc,nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure; plot(f,H,'Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"title(['Filter design/Kaiser window f_c: %f, S_L: %f, h: %u']);\n",
+            fc, -As, h_len);
+    fprintf(fid,"axis([-0.5 0.5 -As-40 10]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firdespm_example.c b/examples/firdespm_example.c
new file mode 100644
index 0000000..1def3e8
--- /dev/null
+++ b/examples/firdespm_example.c
@@ -0,0 +1,110 @@
+//
+// firdespm_example.c
+//
+// This example demonstrates finite impulse response filter design
+// using the Parks-McClellan algorithm.
+// SEE ALSO: firdes_kaiser_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firdespm_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firdespm_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  f     : filter cutoff frequency,       0 < f < 0.5, default: 0.2\n");
+    printf("  t     : filter transition bandwidth,   0 < t < 0.5, default: 0.1\n");
+    printf("  s     : stop-band attenuation [dB],    0 < s,       default: 60\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    float fc=0.2f;          // filter cutoff frequency
+    float ft=0.1f;          // filter transition
+    float As=60.0f;         // stop-band attenuation [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhf:t:s:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'f': fc = atof(optarg);            break;
+        case 't': ft = atof(optarg);            break;
+        case 's': As = atof(optarg);            break;
+        default:
+            exit(1);
+        }
+    }
+    printf("filter design parameters\n");
+    printf("    cutoff frequency            :   %12.8f\n", fc);
+    printf("    transition bandwidth        :   %12.8f\n", ft);
+    printf("    stop-band attenuation [dB]  :   %12.8f\n", As);
+
+    // derived values
+    unsigned int h_len = estimate_req_filter_len(ft,As);
+    printf("h_len : %u\n", h_len);
+    float fp = fc - 0.5*ft;     // pass-band cutoff frequency
+    float fs = fc + 0.5*ft;     // stop-band cutoff frequency
+    liquid_firdespm_btype btype = LIQUID_FIRDESPM_BANDPASS;
+
+    // derived values
+    unsigned int num_bands = 2;
+    float bands[4]   = {0.0f, fp, fs, 0.5f};
+    float des[2]     = {1.0f, 0.0f};
+    float weights[2] = {1.0f, 1.0f};
+    liquid_firdespm_wtype wtype[2] = {LIQUID_FIRDESPM_FLATWEIGHT,
+                                      LIQUID_FIRDESPM_EXPWEIGHT};
+
+    unsigned int i;
+    float h[h_len];
+#if 0
+    firdespm q = firdespm_create(n,num_bands,bands,des,weights,wtype,btype);
+    firdespm_print(q);
+    firdespm_execute(q,h);
+    firdespm_destroy(q);
+#else
+    firdespm_run(h_len,num_bands,bands,des,weights,wtype,btype,h);
+#endif
+
+    // print coefficients
+    for (i=0; i<h_len; i++)
+        printf("h(%4u) = %16.12f;\n", i+1, h[i]);
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"h_len=%u;\n", h_len);
+    fprintf(fid,"fc=%12.4e;\n",fc);
+    fprintf(fid,"As=%12.4e;\n",As);
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%4u) = %20.8e;\n", i+1, h[i]);
+
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"H=20*log10(abs(fftshift(fft(h,nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure; plot(f,H,'Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"title(['Filter design/Kaiser window f_c: %f, S_L: %f, h: %u']);\n",
+            fc, -As, h_len);
+    fprintf(fid,"axis([-0.5 0.5 -As-40 10]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firfarrow_rrrf_example.c b/examples/firfarrow_rrrf_example.c
new file mode 100644
index 0000000..969ff7d
--- /dev/null
+++ b/examples/firfarrow_rrrf_example.c
@@ -0,0 +1,96 @@
+//
+// firfarrow_rrrf_example.c
+//
+// Demonstrates the functionality of the finite impulse response Farrow
+// filter for arbitrary fractional sample group delay.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firfarrow_rrrf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firfarrow_rrrf_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  t     : fractional sample offset, t in [-0.5,0.5], default: 0.2\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int h_len       = 19;      // filter length
+    unsigned int p           = 5;       // polynomial order
+    float        fc          = 0.45f;   // filter cutoff
+    float        As          = 60.0f;   // stop-band attenuation [dB]
+    float        mu          = 0.1f;    // fractional sample delay
+    unsigned int num_samples = 60;      // number of samples to evaluate
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"ht:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 't': mu = atof(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // data arrays
+    float x[num_samples];   // input data array
+    float y[num_samples];   // output data array
+
+    // create and initialize Farrow filter object
+    firfarrow_rrrf f = firfarrow_rrrf_create(h_len, p, fc, As);
+    firfarrow_rrrf_set_delay(f, mu);
+
+    // create low-pass filter for input signal
+    iirfilt_rrrf lowpass = iirfilt_rrrf_create_lowpass(3, 0.1f);
+
+    // push input through filter
+    unsigned int i;
+    for (i=0; i<num_samples; i++) {
+        // generate input (filtered noise)
+        iirfilt_rrrf_execute(lowpass, randnf(), &x[i]);
+
+        // push result through Farrow filter to add slight delay
+        firfarrow_rrrf_push(f, x[i]);
+        firfarrow_rrrf_execute(f, &y[i]);
+    }
+
+    // destroy Farrow and low-pass filter objects
+    firfarrow_rrrf_destroy(f);
+    iirfilt_rrrf_destroy(lowpass);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"h_len = %u;\n", h_len);
+    fprintf(fid,"mu = %f;\n", mu);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%4u) = %12.4e; y(%4u) = %12.4e;\n", i+1, x[i], i+1, y[i]);
+
+    // plot the results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"tx = 0:(num_samples-1);     %% input time scale\n");
+    fprintf(fid,"ty = tx - (h_len-1)/2 + mu; %% output time scale\n");
+    fprintf(fid,"plot(tx, x,'-s','MarkerSize',3, ...\n");
+    fprintf(fid,"     ty, y,'-x','MarkerSize',3);\n");
+    fprintf(fid,"legend('input','output',0);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firfilt_cccf_example.c b/examples/firfilt_cccf_example.c
new file mode 100644
index 0000000..a1d3999
--- /dev/null
+++ b/examples/firfilt_cccf_example.c
@@ -0,0 +1,139 @@
+//
+// firfilt_cccf_example.c
+//
+// This example demonstrates the finite impulse response (FIR) filter
+// with complex coefficients as a cross-correlator between transmitted
+// and received sequences.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firfilt_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firfilt_cccf_example:\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : sequence length, default: 32\n");
+    printf("  s     : SNR, signal-to-noise ratio [dB], default: 20\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int sequence_len = 256;    // sequence length
+    float        SNRdB        = 10.0f;  // signal-to-noise ratio (dB)
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:s:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': sequence_len = atof(optarg);  break;
+        case 's': SNRdB = atoi(optarg);         break;
+        default:
+            usage();
+            exit(-1);
+        }
+    }
+    
+    unsigned int i;
+
+    // derived values
+    unsigned int num_samples = 3*sequence_len;
+
+    // data arrays
+    float complex sequence[sequence_len];   // sequence
+    float complex x[num_samples];           // input sequence
+    float complex rxy[num_samples];         // correlator output
+
+    // generate random sequence
+    for (i=0; i<sequence_len; i++) {
+        sequence[i] = (rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2) +
+                      (rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2) * _Complex_I;
+    }
+
+    // generate correlator object
+    firfilt_cccf q = firfilt_cccf_create(sequence, sequence_len);
+
+    // normalize by number of points
+    firfilt_cccf_set_scale(q, 1.0f / (float)sequence_len);
+
+    // generate the input: fill buffer with zeros then insert
+    // the sequence in the middle flipped and conjugated
+    for (i=0; i<num_samples; i++)
+        x[i] = 0.0f;
+    for (i=0; i<sequence_len; i++)
+        x[sequence_len + (sequence_len-i-1)] = conjf(sequence[i]);
+
+    // add noise
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++)
+        cawgn(&x[i],nstd);
+        
+    // compute cross-correlation
+    for (i=0; i<num_samples; i++) {
+        firfilt_cccf_push(q,x[i]);
+        firfilt_cccf_execute(q,&rxy[i]);
+    }
+
+    // find peak
+    float complex rxy_peak = 0;
+    for (i=0; i<num_samples; i++) {
+        if (i==0 || cabsf(rxy[i]) > cabsf(rxy_peak))
+            rxy_peak = rxy[i];
+    }
+    printf("peak cross-correlation : %12.8f, angle %12.8f\n", cabsf(rxy_peak),
+                                                              cargf(rxy_peak));
+
+    // destroy allocated objects
+    firfilt_cccf_destroy(q);
+
+    // 
+    // write results to file
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    // write signal to output file
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n",i+1,crealf(x[i]),cimagf(x[i]));
+
+        fprintf(fid,"rxy(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rxy[i]), cimagf(rxy[i]));
+    }
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t=0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1),\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,real(x),'Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  plot(t,imag(x),'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('received signal');\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  axis([0 num_samples -1.5 1.5]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(2,1,2),\n");
+    fprintf(fid,"  plot(t,abs(rxy),'Color',[0.5 0 0],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('cross-correlation magnitude');\n");
+    fprintf(fid,"  axis([0 num_samples -0.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    fprintf(fid,"\n\n");
+    fclose(fid);
+    printf("data written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firfilt_crcf_example.c b/examples/firfilt_crcf_example.c
new file mode 100644
index 0000000..5e615f7
--- /dev/null
+++ b/examples/firfilt_crcf_example.c
@@ -0,0 +1,126 @@
+//
+// firfilt_crcf_example.c
+//
+// Complex finite impulse response filter example. Demonstrates the 
+// functionality of firfilt by designing a low-order prototype and using it 
+// to filter a noisy signal.  The filter coefficients are real, but the 
+// input and output arrays are complex. The filter order and cutoff 
+// frequency are specified at the beginning.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firfilt_crcf_example.m"
+
+int main() {
+    // options
+    unsigned int h_len=65;  // filter length
+    float fc=0.1f;          // cutoff frequency
+    float As=60.0f;         // stop-band attenuation
+    unsigned int n=240;     // number of samples
+
+    // design filter from prototype and scale to bandwidth
+    firfilt_crcf q = firfilt_crcf_create_kaiser(h_len, fc, As, 0.0f);
+    firfilt_crcf_set_scale(q, 2.0f*fc);
+    firfilt_crcf_print(q);
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x[n];
+    float complex y[n];
+
+    // generate input signal (sine wave with decaying amplitude)
+    unsigned int wlen = (unsigned int)roundf(0.75*n);
+    for (i=0; i<n; i++) {
+        // generate input signal
+        x[i] = 0.7f*cexpf(2*M_PI*0.057f*_Complex_I*i) +
+               0.3f*cexpf(2*M_PI*0.357f*_Complex_I*i);
+
+        // apply window
+        x[i] *= i < wlen ? hamming(i,wlen) : 0;
+
+        // run filter
+        firfilt_crcf_push(q, x[i]);
+        firfilt_crcf_execute(q, &y[i]);
+    }
+
+    // compute response
+    unsigned int nfft = 1024;
+    float complex H[nfft];
+    for (i=0; i<nfft; i++) {
+        float freq = ((float)i - 0.5f*(float)nfft) / (float)nfft;
+        firfilt_crcf_freqresponse(q, freq, &H[i]);
+    }
+
+    // destroy filter object
+    firfilt_crcf_destroy(q);
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"h_len=%u;\n", h_len);
+    fprintf(fid,"n=%u;\n",n);
+    fprintf(fid,"nfft=%u;\n",nfft);
+    fprintf(fid,"x=zeros(1,n);\n");
+    fprintf(fid,"y=zeros(1,n);\n");
+    fprintf(fid,"H=zeros(1,nfft);\n");
+
+    for (i=0; i<n; i++) {
+        //printf("%4u : %12.8f + j*%12.8f\n", i, crealf(y), cimagf(y));
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(H[i]), cimagf(H[i]));
+
+    // plot output
+    fprintf(fid,"tx=0:(n-1);\n");
+    fprintf(fid,"ty=tx - (h_len-1)/2;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       ty,real(y),'-','Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('input','filtered output','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-','Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('input','filtered output','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+
+    // plot frequency response
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"g = 1/sum(abs(x));\n");
+    fprintf(fid,"X = 20*log10(abs(fftshift(fft(x*g,nfft))));\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y*g,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,X,'Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"     f,Y,'Color',[0 0.5 0.25],'LineWidth',2,...\n");
+    fprintf(fid,"     f,20*log10(abs(H)),'Color',[0 0.25 0.5],'LineWidth',1);\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"legend('input','output','filter','location','northeast');\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firfilt_rrrf_example.c b/examples/firfilt_rrrf_example.c
new file mode 100644
index 0000000..48eac74
--- /dev/null
+++ b/examples/firfilt_rrrf_example.c
@@ -0,0 +1,92 @@
+//
+// firfilt_rrrf_example.c
+//
+
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firfilt_rrrf_example.m"
+
+int main() {
+    // options
+    unsigned int h_len=27;  // filter length
+    float fc=0.1f;          // filter cutoff
+    float As=60.0f;         // stop-band attenuation [dB]
+    float mu=0.0f;          // timing offset
+    unsigned int n=200;     // number of random input samples
+
+    // derived values
+    unsigned int num_samples = n + h_len;
+
+    // arrays
+    float x[num_samples];   // filter input
+    float y[num_samples];   // filter output
+
+    unsigned int i;
+#if 0
+    float h[h_len];
+    liquid_firdes_kaiser(h_len,fc,As,mu,h);
+    firfilt_rrrf f = firfilt_rrrf_create(h,h_len);
+#else
+    firfilt_rrrf f = firfilt_rrrf_create_kaiser(h_len,fc,As,mu);
+#endif
+    firfilt_rrrf_print(f);
+
+    for (i=0; i<num_samples; i++) {
+        // generate noise
+        x[i] = (i<n) ? randnf() : 0.0f;
+
+        firfilt_rrrf_push(f, x[i]);
+        firfilt_rrrf_execute(f, &y[i]); 
+
+        printf("x[%4u] = %12.8f, y[%4u] = %12.8f;\n", i, x[i], i, y[i]);
+    }   
+
+    // destroy filter object
+    firfilt_rrrf_destroy(f);
+
+    // 
+    // export results
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% firfilt_rrrf_example.m: auto-generated file\n\n");
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%4u) = %12.4e; y(%4u) = %12.4e;\n", i+1, x[i], i+1, y[i]);
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x/sqrt(num_samples),nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y/sqrt(num_samples),nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"t=[0:(num_samples-1)];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,x,'Color',[0.3 0.3 0.3],...\n");
+    fprintf(fid,"       t,y,'LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('signals');\n");
+    fprintf(fid,"  axis([0 num_samples -4 4]);\n");
+    fprintf(fid,"  legend('noise','filtered noise',1);");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(f,X,'Color',[0.3 0.3 0.3],...\n");
+    fprintf(fid,"       f,Y,'LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('normalized frequency');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  axis([-0.5 0.5 -80 40]);\n");
+    fprintf(fid,"  legend('noise','filtered noise',1);");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firhilb_decim_example.c b/examples/firhilb_decim_example.c
new file mode 100644
index 0000000..389067f
--- /dev/null
+++ b/examples/firhilb_decim_example.c
@@ -0,0 +1,87 @@
+//
+// firhilb_decim_example.c
+//
+// Hilbert transform: 2:1 real-to-complex decimator.  This example
+// demonstrates the functionality of firhilb (finite impulse response
+// Hilbert transform) decimator which converts a real time series
+// into a complex one with half the number of samples.  The input
+// is a real-valued sinusoid of N samples. The output is a complex-
+// valued sinusoid of N/2 samples.
+//
+// SEE ALSO: firhilb_interp_example.c
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firhilb_decim_example.m"
+
+int main() {
+    unsigned int m=5;               // Hilbert filter semi-length
+    float As=60.0f;                 // stop-band attenuation [dB]
+    float fc=0.37f;                 // signal center frequency
+    unsigned int num_samples=128;   // number of samples
+
+    // data arrays
+    float x[2*num_samples];         // real input
+    float complex y[num_samples];   // complex output
+
+    // initialize input array
+    unsigned int i;
+    for (i=0; i<2*num_samples; i++)
+        x[i] = cosf(2*M_PI*fc*i);
+
+    // create Hilbert transform object
+    firhilbf q = firhilbf_create(m,As);
+
+    // execute transform (decimator) to compute complex signal
+    firhilbf_decim_execute_block(q, x, num_samples, y);
+
+    // destroy Hilbert transform object
+    firhilbf_destroy(q);
+
+    printf("firhilb decimated %u real samples to %u complex samples\n",
+            num_samples, 2*num_samples);
+
+    // 
+    // export results to file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"h_len=%u;\n", 4*m+1);
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++) {
+        // print results
+        fprintf(fid,"x(%3u) = %12.4e;\n", 2*i+1, x[2*i+0]);
+        fprintf(fid,"x(%3u) = %12.4e;\n", 2*i+2, x[2*i+1]);
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // plot results
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"%% compute normalized windowing functions\n");
+    fprintf(fid,"wx = 1.8534/num_samples*hamming(length(x)).'; %% x window\n");
+    fprintf(fid,"wy = 1.8534/num_samples*hamming(length(y)).'; %% y window\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x.*wx,nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(        (fft(y.*wy,nfft))));\n");
+    fprintf(fid,"f =[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"fd=[0:(nfft-1)]/(2*nfft);\n");
+    fprintf(fid,"figure; plot(f,X,'Color',[0.5 0.5 0.5],fd,Y,'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original/real','transformed/decimated','location','northeast');");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firhilb_example.c b/examples/firhilb_example.c
new file mode 100644
index 0000000..a95edf4
--- /dev/null
+++ b/examples/firhilb_example.c
@@ -0,0 +1,121 @@
+//
+// firhilb_example.c
+//
+// Hilbert transform example.  This example demonstrates the
+// functionality of firhilbf (finite impulse response Hilbert transform)
+// which converts a complex time series into a real one and then back.
+//
+// SEE ALSO: firhilb_interp_example.c
+//           firhilb_example.c
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firhilb_example.m"
+
+int main() {
+    unsigned int m = 7;                     // Hilbert filter semi-length
+    float As       = 60.0f;                 // stop-band attenuation [dB]
+    float fc       = 0.123456;              // signal center frequency
+    unsigned int num_input_samples=128;     // number of samples
+
+    // derived values
+    unsigned int h_len = 4*m+1;             // filter length
+    unsigned int num_total_samples = num_input_samples + h_len;
+
+    // create Hilbert transform object
+    firhilbf qi = firhilbf_create(m,As);    // interpolator
+    firhilbf qd = firhilbf_create(m,As);    // decimator
+    firhilbf_print(qi);
+
+    // data arrays
+    float complex x[  num_total_samples];   // complex input
+    float         y[2*num_total_samples];   // real output
+    float complex z[  num_total_samples];   // complex output
+
+    // initialize input array
+    unsigned int i;
+    for (i=0; i<num_total_samples; i++) {
+        x[i]  = cexpf(_Complex_I*2*M_PI*fc*i);
+        x[i] *= (i < num_input_samples) ? 1.855f*hamming(i,num_input_samples) : 0.0f;
+    }
+
+    // execute interpolator (complex to real conversion)
+    firhilbf_interp_execute_block(qi, x, num_total_samples, y);
+    
+    // execute decimator (real to complex conversion)
+    firhilbf_decim_execute_block(qd, y, num_total_samples, z);
+
+    // destroy Hilbert transform object
+    firhilbf_destroy(qi);
+    firhilbf_destroy(qd);
+
+    // 
+    // export results to file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"h_len=%u;\n", 4*m+1);
+    fprintf(fid,"num_input_samples=%u;\n", num_input_samples);
+    fprintf(fid,"num_total_samples=%u;\n", num_total_samples);
+    fprintf(fid,"tx = 0:(num_total_samples-1);\n");
+    fprintf(fid,"ty = [0:(2*num_total_samples-1)]/2;\n");
+    fprintf(fid,"tz = tx;\n");
+
+    for (i=0; i<num_total_samples; i++) {
+        // print results
+        fprintf(fid,"x(%3u) = %12.4e + %12.4ej;\n",   i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%3u) = %12.4e;\n",           2*i+1, y[2*i+0]);
+        fprintf(fid,"y(%3u) = %12.4e;\n",           2*i+2, y[2*i+1]);
+        fprintf(fid,"z(%3u) = %12.4e + %12.4ej;\n",   i+1, crealf(z[i]), cimagf(z[i]));
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'Color',[0.00 0.25 0.50],'LineWidth',1.3,...\n");
+    fprintf(fid,"       tx,imag(x),'Color',[0.00 0.50 0.25],'LineWidth',1.3);\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  ylabel('transformed/complex');\n");
+    fprintf(fid,"  axis([0 num_total_samples -2 2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(ty,y,'Color',[0.00 0.25 0.50],'LineWidth',1.3);\n");
+    fprintf(fid,"  ylabel('original/real');\n");
+    fprintf(fid,"  axis([0 num_total_samples -2 2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(tz,real(z),'Color',[0.00 0.25 0.50],'LineWidth',1.3,...\n");
+    fprintf(fid,"       tz,imag(z),'Color',[0.00 0.50 0.25],'LineWidth',1.3);\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  ylabel('transformed/complex');\n");
+    fprintf(fid,"  axis([0 num_total_samples -2 2]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    // plot results
+    fprintf(fid,"nfft=4096;\n");
+    fprintf(fid,"%% compute normalized windowing functions\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x/num_input_samples,nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y/num_input_samples,nfft))));\n");
+    fprintf(fid,"Z=20*log10(abs(fftshift(fft(z/num_input_samples,nfft))));\n");
+    fprintf(fid,"f =[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure; plot(f,  X,'LineWidth',1,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"             f*2,Y,'LineWidth',2,'Color',[0.00 0.50 0.25],...\n");
+    fprintf(fid,"             f,  Z,'LineWidth',1,'Color',[0.00 0.25 0.50]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1.0 1.0 -80 20]);\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original/cplx','transformed/real','regenerated/cplx','location','northeast');");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firhilb_interp_example.c b/examples/firhilb_interp_example.c
new file mode 100644
index 0000000..87946c8
--- /dev/null
+++ b/examples/firhilb_interp_example.c
@@ -0,0 +1,86 @@
+//
+// firhilb_interp_example.c
+//
+// Hilbert transform: 1:2 complex-to-real interpolator.  This
+// example demonstrates the functionality of firhilb (finite
+// impulse response Hilbert transform) interpolator which converts
+// a complex time series into a real one with twice the number of
+// samples.  The input is a complex-valued sinusoid of N samples.
+// The output is a real-valued sinusoid of 2*N samples.
+//
+// SEE ALSO: firhilb_decim_example.c
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firhilb_interp_example.m"
+
+int main() {
+    unsigned int m=5;               // Hilbert filter semi-length
+    float As=60.0f;                 // stop-band attenuation [dB]
+    float fc=0.37f;                 // signal center frequency
+    unsigned int num_samples=128;   // number of samples
+
+    // data arrays
+    float complex x[num_samples];   // complex input
+    float y[2*num_samples];         // real output
+
+    // initialize input array
+    unsigned int i;
+    for (i=0; i<num_samples; i++)
+        x[i] = cexpf(_Complex_I*2*M_PI*fc*i);
+
+    // create Hilbert transform object
+    firhilbf q = firhilbf_create(m,As);
+
+    // execute transform (interpolator) to compute real signal
+    firhilbf_interp_execute_block(q, x, num_samples, y);
+
+    // destroy Hilbert transform object
+    firhilbf_destroy(q);
+
+    printf("firhilb interpolated %u complex samples to %u real samples\n",
+            2*num_samples, num_samples);
+
+    // 
+    // export results to file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"h_len=%u;\n", 4*m+1);
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++) {
+        // print results
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%3u) = %12.4e;\n", 2*i+1, y[2*i+0]);
+        fprintf(fid,"y(%3u) = %12.4e;\n", 2*i+2, y[2*i+1]);
+    }
+
+    // plot results
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"%% compute normalized windowing functions\n");
+    fprintf(fid,"wx = 1.8534/num_samples*hamming(length(x)).'; %% x window\n");
+    fprintf(fid,"wy = 1.8534/num_samples*hamming(length(y)).'; %% y window\n");
+    fprintf(fid,"X=20*log10(abs(        (fft(x.*wx,nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y.*wy,nfft))));\n");
+    fprintf(fid,"f =[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"fi=[0:(nfft-1)]/(2*nfft);\n");
+    fprintf(fid,"figure; plot(fi,X,'Color',[0.5 0.5 0.5],f,Y,'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original/complex','transformed/interpolated','location','northeast');");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firinterp_crcf_example.c b/examples/firinterp_crcf_example.c
new file mode 100644
index 0000000..78ca119
--- /dev/null
+++ b/examples/firinterp_crcf_example.c
@@ -0,0 +1,145 @@
+//
+// firinterp_crcf_example.c
+//
+// This example demonstrates the firinterp object (interpolator) interface.
+// Data symbols are generated and then interpolated according to a
+// finite impulse response Nyquist filter.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firinterp_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firinterp_crcf_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : samples/symbol (interp factor), k > 1, default: 4\n");
+    printf("  m     : filter delay (symbols), m > 0, default: 3\n");
+    printf("  s     : filter stop-band attenuation [dB], default: 60\n");
+    printf("  n     : number of data symbols, default: 16\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;                   // samples/symbol
+    unsigned int m=3;                   // filter delay
+    float As = 60.0f;                   // filter stop-band attenuation
+    unsigned int num_data_symbols=16;   // number of data symbols
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:s:n:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                          return 0;
+        case 'k': k = atoi(optarg);                 break;
+        case 'm': m = atoi(optarg);                 break;
+        case 's': As = atof(optarg);                break;
+        case 'n': num_data_symbols = atoi(optarg);  break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate options
+    if (k < 2) {
+        fprintf(stderr,"error: %s, interp factor must be greater than 1\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, filter delay must be greater than 0\n", argv[0]);
+        exit(1);
+    } else if (num_data_symbols < 1) {
+        fprintf(stderr,"error: %s, must have at least one data symbol\n", argv[0]);
+        usage();
+        return 1;
+    }
+
+    // derived values
+    unsigned int num_symbols = num_data_symbols + 2*m;  // compensate for filter delay
+    unsigned int num_samples = k*num_symbols;
+
+    // create interpolator from prototype
+    firinterp_crcf q = firinterp_crcf_create_kaiser(k,m,As);
+
+    // generate input signal and interpolate
+    float complex x[num_symbols];   // input symbols
+    float complex y[num_samples];   // output samples
+    unsigned int i;
+    for (i=0; i<num_data_symbols; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f) * _Complex_I;
+    }
+
+    // pad end of sequence with zeros
+    for (i=num_data_symbols; i<num_symbols; i++)
+        x[i] = 0.0f;
+
+    // interpolate symbols
+    for (i=0; i<num_symbols; i++)
+        firinterp_crcf_execute(q, x[i], &y[k*i]);
+
+    // destroy interpolator object
+    firinterp_crcf_destroy(q);
+
+    // print results to screen
+    printf("x(t) :\n");
+    for (i=0; i<num_symbols; i++)
+        printf("  x(%4u) = %8.4f + j*%8.4f;\n", i, crealf(x[i]), cimagf(x[i]));
+
+    printf("y(t) :\n");
+    for (i=0; i<num_samples; i++) {
+        printf("  y(%4u) = %8.4f + j*%8.4f;", i, crealf(y[i]), cimagf(y[i]));
+        if ( (i >= k*m) && ((i%k)==0))
+            printf(" **\n");
+        else
+            printf("\n");
+    }
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = k*num_symbols;\n");
+    fprintf(fid,"x = zeros(1,num_symbols);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tx = [0:(num_symbols-1)];\n");
+    fprintf(fid,"ty = [0:(num_samples-1)]/k - m;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(ty,real(y),'-',tx,real(x),'s');\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('real');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(ty,imag(y),'-',tx,imag(x),'s');\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('imag');\n");
+    fprintf(fid,"    grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firpfbch2_crcf_example.c b/examples/firpfbch2_crcf_example.c
new file mode 100644
index 0000000..1dcb2db
--- /dev/null
+++ b/examples/firpfbch2_crcf_example.c
@@ -0,0 +1,169 @@
+//
+// firpfbch2_crcf_example.c
+//
+// Example of the finite impulse response (FIR) polyphase filterbank
+// (PFB) channelizer with an output rate of 2 Fs / M as an (almost)
+// perfect reconstructive system.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firpfbch2_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  M     : number of channels, default: 6\n");
+    printf("  m     : prototype filter semi-length, default: 4\n");
+    printf("  s     : prototype filter stop-band attenuation, default: 80\n");
+    printf("  n     : number of 'symbols' to analyze, default: 20\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int num_channels=6;    // number of channels
+    unsigned int m = 4;             // filter semi-length (symbols)
+    unsigned int num_symbols=20;    // number of symbols
+    float As = 80.0f;               // filter stop-band attenuation
+    
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hM:m:s:n:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                     return 0;
+        case 'M':   num_channels = atoi(optarg); break;
+        case 'm':   m            = atoi(optarg); break;
+        case 's':   As           = atof(optarg); break;
+        case 'n':   num_symbols  = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (num_channels < 2 || num_channels % 2) {
+        fprintf(stderr,"error: %s, number of channels must be greater than 2 and even\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: %s, number of symbols must be greater than zero", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    // allocate arrays
+    float complex x[num_samples];
+    float complex y[num_samples];
+
+    // generate input signal
+    for (i=0; i<num_samples; i++) {
+        //x[i] = (i==0) ? 1.0f : 0.0f;
+        x[i] = cexpf( (-0.05f + 0.07f*_Complex_I)*i );  // decaying complex exponential
+    }
+
+    // create filterbank objects from prototype
+    firpfbch2_crcf qa = firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER,    num_channels, m, As);
+    firpfbch2_crcf qs = firpfbch2_crcf_create_kaiser(LIQUID_SYNTHESIZER, num_channels, m, As);
+    firpfbch2_crcf_print(qa);
+    firpfbch2_crcf_print(qs);
+
+    // run channelizer
+    float complex Y[num_channels];
+    for (i=0; i<num_samples; i+=num_channels/2) {
+        // run analysis filterbank
+        firpfbch2_crcf_execute(qa, &x[i], Y);
+
+        // run synthesis filterbank
+        firpfbch2_crcf_execute(qs, Y, &y[i]);
+    }
+
+    // destroy filterbank objects
+    firpfbch2_crcf_destroy(qa); // analysis fitlerbank
+    firpfbch2_crcf_destroy(qs); // synthesis filterbank
+
+    // print output
+    for (i=0; i<num_samples; i++)
+        printf("%4u : %12.8f + %12.8fj\n", i, crealf(y[i]), cimagf(y[i]));
+
+    // compute RMSE
+    float rmse = 0.0f;
+    unsigned int delay = 2*num_channels*m - num_channels/2 + 1;
+    for (i=0; i<num_samples; i++) {
+        float complex err = y[i] - (i < delay ? 0.0f : x[i-delay]);
+        rmse += crealf( err*conjf(err) );
+    }
+    rmse = sqrtf( rmse/(float)num_samples );
+    printf("rmse : %12.4e\n", rmse);
+
+    //
+    // EXPORT DATA TO FILE
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_channels=%u;\n", num_channels);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols=%u;\n",  num_symbols);
+    fprintf(fid,"num_samples = num_channels*num_symbols;\n");
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+
+    // save input and output arrays
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimag(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimag(y[i]));
+    }
+
+    // save error vector
+    for (i=delay; i<num_samples; i++) {
+        float complex e = y[i] - x[i-delay];
+        fprintf(fid,"e(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(e), cimag(e));
+    }
+
+    // plot results
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"delay = %u;\n", delay);
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"title('composite');\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"    plot(t,real(x), t-delay,real(y),'s','MarkerSize',1);\n");
+    fprintf(fid,"    axis([-2 num_samples -0.3 1.1]);\n");
+    fprintf(fid,"    ylabel('real');\n");
+    fprintf(fid,"    legend('original','reconstructed','location','northeast');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"    plot(t,imag(x), t-delay,imag(y),'s','MarkerSize',1);\n");
+    fprintf(fid,"    axis([-2 num_samples -0.3 1.1]);\n");
+    fprintf(fid,"    ylabel('imag');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"    plot(t-delay,real(e), t-delay,imag(e));\n");
+    fprintf(fid,"    emax = 1.2*max(abs(e));\n");
+    fprintf(fid,"    axis([-2 num_samples -emax emax]);\n");
+    fprintf(fid,"    legend('real','imag','location','northeast');\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('error');\n");
+    fprintf(fid,"    grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firpfbch_crcf_analysis_example.c b/examples/firpfbch_crcf_analysis_example.c
new file mode 100644
index 0000000..2a622c8
--- /dev/null
+++ b/examples/firpfbch_crcf_analysis_example.c
@@ -0,0 +1,148 @@
+//
+// firpfbch_crcf_analysis_example.c
+//
+// Example of the analysis channelizer filterbank. The input signal is
+// comprised of several signals spanning different frequency bands. The
+// channelizer downconverts each to baseband (maximally decimated), and
+// the resulting spectrum of each is plotted.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firpfbch_crcf_analysis_example.m"
+
+int main() {
+    // options
+    unsigned int num_channels =  8;     // number of channels
+    unsigned int m            =  4;     // filter delay
+    float        As           = 60;     // stop-band attenuation
+    unsigned int num_frames   = 25;     // number of frames
+
+    //
+    unsigned int i;
+    unsigned int k;
+
+    // derived values
+    unsigned int num_samples = num_frames * num_channels;
+
+    // data arrays
+    float complex x[num_samples];  // time-domain input  [size: num_samples  x 1         ]
+    float complex y[num_samples];  // channelized output [size: num_channels x num_frames]
+
+    // initialize input with zeros
+    for (i=0; i<num_samples; i++)
+        x[i] = 0.0f;
+
+    // generate input signal(s)
+    unsigned int num_signals = 4;
+    float fc[4] = {0.0f,   0.25f,  0.375f, -0.375f}; // center frequencies
+    float bw[4] = {0.035f, 0.035f, 0.035f,  0.035f}; // bandwidths
+    unsigned int pulse_len = 137;
+    float pulse[pulse_len];
+    for (i=0; i<num_signals; i++) {
+        // create pulse
+        liquid_firdes_kaiser(pulse_len, bw[i], 50.0f, 0.0f, pulse);
+
+        // add pulse to input signal with carrier offset
+        for (k=0; k<pulse_len; k++)
+            x[k] += pulse[k] * cexpf(_Complex_I*2*M_PI*fc[i]*k) * bw[i];
+    }
+
+    // create prototype filter
+    unsigned int h_len = 2*num_channels*m + 1;
+    float h[h_len];
+    liquid_firdes_kaiser(h_len, 0.5f/(float)num_channels, As, 0.0f, h);
+
+#if 0
+    // create filterbank channelizer object using internal method for filter
+    firpfbch_crcf q = firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, num_channels, m, As);
+#else
+    // create filterbank channelizer object using external filter coefficients
+    firpfbch_crcf q = firpfbch_crcf_create(LIQUID_ANALYZER, num_channels, 2*m, h);
+#endif
+
+    // channelize input data
+    for (i=0; i<num_frames; i++) {
+        // execute analysis filter bank
+        firpfbch_crcf_analyzer_execute(q, &x[i*num_channels], &y[i*num_channels]);
+    }
+
+    // destroy channelizer object
+    firpfbch_crcf_destroy(q);
+    
+    // 
+    // export results to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_channels = %u;\n", num_channels);
+    fprintf(fid,"m            = %u;\n", m);
+    fprintf(fid,"num_frames   = %u;\n", num_frames);
+    fprintf(fid,"h_len        = 2*num_channels*m+1;\n");
+    fprintf(fid,"num_samples  = num_frames*num_channels;\n");
+
+    fprintf(fid,"h = zeros(1,h_len);\n");
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(num_channels, num_frames);\n");
+
+    // save prototype filter
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"  h(%6u) = %12.4e;\n", i+1, h[i]);
+
+    // save input signal
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"  x(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    // save channelized output signals
+    for (i=0; i<num_frames; i++) {
+        for (k=0; k<num_channels; k++) {
+            float complex v = y[i*num_channels + k];
+            fprintf(fid,"  y(%3u,%6u) = %12.4e + 1i*%12.4e;\n", k+1, i+1, crealf(v), cimagf(v));
+        }
+    }
+
+    // plot results
+    fprintf(fid,"\n");
+    fprintf(fid,"nfft = 1024;\n"); // TODO: use nextpow2
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H = 20*log10(abs(fftshift(fft(h/num_channels,nfft))));\n");
+    fprintf(fid,"X = 20*log10(abs(fftshift(fft(x,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(f, H, 'Color', [0 0.5 0.25], 'LineWidth', 2);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -100 10]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Prototype Filter PSD');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(f, X, 'Color', [0 0.25 0.5], 'LineWidth', 2);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -100 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Input PSD');\n");
+
+    // compute the PSD of each output and plot results on a square grid
+    fprintf(fid,"n = ceil(sqrt(num_channels));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"for i=1:num_channels,\n");
+    fprintf(fid,"  Y = 20*log10(abs(fftshift(fft(y(i,:),nfft))));\n");
+    fprintf(fid,"  subplot(n,n,i);\n");
+    fprintf(fid,"  plot(f, Y, 'Color', [0.25 0 0.25], 'LineWidth', 1.5);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -120 20]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  title(num2str(i-1));\n");
+    fprintf(fid,"end;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/firpfbch_crcf_example.c b/examples/firpfbch_crcf_example.c
new file mode 100644
index 0000000..3f4f065
--- /dev/null
+++ b/examples/firpfbch_crcf_example.c
@@ -0,0 +1,298 @@
+//
+// firpfbch_crcf_example.c
+//
+// Finite impulse response (FIR) polyphase filter bank (PFB)
+// channelizer example.  This example demonstrates the functionality
+// of the polyphase filter bank channelizer and how its output
+// is mathematically equivalent to a series of parallel down-
+// converters (mixers/decimators). Both the synthesis and analysis
+// filter banks are presented.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firpfbch_crcf_example.m"
+
+int main() {
+    // options
+    unsigned int num_channels=4;    // number of channels
+    unsigned int p=3;               // filter length (symbols)
+    unsigned int num_symbols=6;     // number of symbols
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate synthesis filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence
+    unsigned int h_len = p*num_channels;
+    float h[h_len];
+    for (i=0; i<h_len; i++) h[i] = randnf();
+
+    // generate analysis filter
+    unsigned int g_len = p*num_channels;
+    float g[g_len];
+    for (i=0; i<g_len; i++)
+        g[i] = h[g_len-i-1];
+
+    // create synthesis/analysis filter objects
+    firfilt_crcf fs = firfilt_crcf_create(h, h_len);
+    firfilt_crcf fa = firfilt_crcf_create(g, g_len);
+
+    // create synthesis/analysis filterbank channelizer objects
+    firpfbch_crcf qs = firpfbch_crcf_create(LIQUID_SYNTHESIZER, num_channels, p, h);
+    firpfbch_crcf qa = firpfbch_crcf_create(LIQUID_ANALYZER,    num_channels, p, g);
+
+    float complex x[num_samples];                   // random input (noise)
+    float complex Y0[num_symbols][num_channels];    // channelized output (filterbank)
+    float complex Y1[num_symbols][num_channels];    // channelized output
+    float complex z0[num_samples];                  // time-domain output (filterbank)
+    float complex z1[num_samples];                  // time-domain output
+
+    // generate input sequence (complex noise)
+    for (i=0; i<num_samples; i++)
+        x[i] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+
+    // 
+    // ANALYZERS
+    //
+    
+    // 
+    // run analysis filter bank
+    //
+    for (i=0; i<num_symbols; i++)
+        firpfbch_crcf_analyzer_execute(qa, &x[i*num_channels], &Y0[i][0]);
+
+
+    // 
+    // run traditional down-converter (inefficient)
+    //
+    float dphi; // carrier frequency
+    unsigned int n=0;
+    for (i=0; i<num_channels; i++) {
+
+        // reset filter
+        firfilt_crcf_reset(fa);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+            // push down-converted sample into filter
+            firfilt_crcf_push(fa, x[j]*cexpf(-_Complex_I*j*dphi));
+
+            // compute output at the appropriate sample time
+            assert(n<num_symbols);
+            if ( ((j+1)%num_channels)==0 ) {
+                firfilt_crcf_execute(fa, &Y1[n][i]);
+                n++;
+            }
+        }
+        assert(n==num_symbols);
+
+    }
+
+
+    // 
+    // SYNTHESIZERS
+    //
+
+    // 
+    // run synthesis filter bank
+    //
+    for (i=0; i<num_symbols; i++)
+        firpfbch_crcf_synthesizer_execute(qs, &Y0[i][0], &z0[i*num_channels]);
+
+    // 
+    // run traditional up-converter (inefficient)
+    //
+
+    // clear output array
+    for (i=0; i<num_samples; i++)
+        z1[i] = 0.0f;
+
+    float complex y_hat;
+    for (i=0; i<num_channels; i++) {
+        // reset filter
+        firfilt_crcf_reset(fs);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset input symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+
+            // interpolate sequence
+            if ( (j%num_channels)==0 ) {
+                assert(n<num_symbols);
+                firfilt_crcf_push(fs, Y1[n][i]);
+                n++;
+            } else {
+                firfilt_crcf_push(fs, 0);
+            }
+            firfilt_crcf_execute(fs, &y_hat);
+
+            // accumulate up-converted sample
+            z1[j] += y_hat * cexpf(_Complex_I*j*dphi);
+        }
+        assert(n==num_symbols);
+    }
+
+    // destroy objects
+    firfilt_crcf_destroy(fs);
+    firfilt_crcf_destroy(fa);
+    firpfbch_crcf_destroy(qs);
+    firpfbch_crcf_destroy(qa);
+
+
+    //
+    // RESULTS
+    //
+
+    // 
+    // analyzers
+    //
+
+    // print filterbank channelizer
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+
+    // print traditional channelizer
+    printf("\n");
+    printf("traditional channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y1[i][j]), cimagf(Y1[i][j]));
+        }
+        printf("\n");
+    }
+
+
+    float mse_analyzer[num_channels];
+    float complex d;
+    for (i=0; i<num_channels; i++) {
+        mse_analyzer[i] = 0.0f;
+        for (j=0; j<num_symbols; j++) {
+            d = Y0[j][i] - Y1[j][i];
+            mse_analyzer[i] += crealf(d*conjf(d));
+        }
+
+        mse_analyzer[i] /= num_symbols;
+    }
+    printf("\n");
+    printf("rmse: ");
+    for (i=0; i<num_channels; i++)
+        printf("%12.4e          ", sqrt(mse_analyzer[i]));
+    printf("\n");
+
+
+    // 
+    // synthesizers
+    //
+
+    printf("\n");
+    printf("output: filterbank:             traditional:\n");
+    for (i=0; i<num_samples; i++) {
+        printf("%3u: %10.5f+%10.5fj  %10.5f+%10.5fj\n",
+            i,
+            crealf(z0[i]), cimagf(z0[i]),
+            crealf(z1[i]), cimagf(z1[i]));
+    }
+
+    float mse_synthesizer = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        d = z0[i] - z1[i];
+        mse_synthesizer += crealf(d*conjf(d));
+    }
+    mse_synthesizer /= num_samples;
+    printf("\n");
+    printf("rmse: %12.4e\n", sqrtf(mse_synthesizer));
+
+    //
+    // EXPORT DATA TO FILE
+    //
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_channels=%u;\n", num_channels);
+    fprintf(fid,"num_symbols=%u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_channels*num_symbols;\n");
+
+    fprintf(fid,"x  = zeros(1,num_samples);\n");
+    fprintf(fid,"y0 = zeros(num_symbols,num_channels);\n");
+    fprintf(fid,"y1 = zeros(num_symbols,num_channels);\n");
+    fprintf(fid,"z0 = zeros(1,num_samples);\n");
+    fprintf(fid,"z1 = zeros(1,num_samples);\n");
+
+    // input
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimag(x[i]));
+
+    // analysis
+    for (i=0; i<num_symbols; i++) {
+        for (j=0; j<num_channels; j++) {
+            fprintf(fid,"y0(%4u,%4u) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(Y0[i][j]), cimag(Y0[i][j]));
+            fprintf(fid,"y1(%4u,%4u) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(Y1[i][j]), cimag(Y1[i][j]));
+        }
+    }
+
+    // synthesis
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"z0(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z0[i]), cimag(z0[i]));
+        fprintf(fid,"z1(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z1[i]), cimag(z1[i]));
+    }
+    fprintf(fid,"z0 = z0 / num_channels;\n");
+    fprintf(fid,"z1 = z1 / num_channels;\n");
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"ts = 0:(num_symbols-1);\n");
+    fprintf(fid,"for i=1:num_channels,\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"title(['channel ' num2str(i)]);\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(ts,real(y0(:,i)),'-x', ts,real(y1(:,i)),'s');\n");
+    //fprintf(fid,"    axis([0 (num_symbols-1) -2 2]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(ts,imag(y0(:,i)),'-x', ts,imag(y1(:,i)),'s');\n");
+    //fprintf(fid,"    axis([0 (num_symbols-1) -2 2]);\n");
+    fprintf(fid,"end;\n");
+
+    fprintf(fid,"t  = 0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"title('composite');\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(t,real(z0),'-x', t,real(z1),'s');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(t,imag(z0),'-x', t,imag(z1),'s');\n");
+
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/firpfbch_crcf_synthesis_example.c b/examples/firpfbch_crcf_synthesis_example.c
new file mode 100644
index 0000000..529916c
--- /dev/null
+++ b/examples/firpfbch_crcf_synthesis_example.c
@@ -0,0 +1,157 @@
+//
+// firpfbch_crcf_synthesis_example.c
+//
+// Example of the synthesis channelizer filterbank. The input signal is
+// comprised of several signals spanning different frequency bands. The
+// channelizer downconverts each to baseband (maximally decimated), and
+// the resulting spectrum of each is plotted.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firpfbch_crcf_synthesis_example.m"
+
+int main() {
+    // options
+    unsigned int num_channels = 16;     // number of channels
+    unsigned int m            =  5;     // filter delay
+    float        As           = 60;     // stop-band attenuation
+    unsigned int num_frames   = 25;     // number of frames
+
+    //
+    unsigned int i;
+    unsigned int k;
+
+    // derived values
+    unsigned int num_samples = num_frames * num_channels;
+
+    // data arrays
+    float complex x[num_channels][num_frames];  // channelized input
+    float complex y[num_samples];               // time-domain output [size: num_samples  x 1]
+
+    // create narrow-band pulse
+    unsigned int pulse_len = 17;        // pulse length [samples]
+    float        bw        = 0.30f;     // pulse width (bandwidth)
+    float        pulse[pulse_len];      // buffer
+    liquid_firdes_kaiser(pulse_len, bw, 50.0f, 0.0f, pulse);
+
+    // generate input signal(s)
+    int enabled[num_channels];  // signal enabled?
+    for (i=0; i<num_channels; i++) {
+        // pseudo-random channel enabled flag
+        enabled[i] = ((i*37)%101)%2;
+
+        if (enabled[i]) {
+            // move pulse to input buffer
+            for (k=0; k<num_frames; k++)
+                x[i][k] = k < pulse_len ? bw*pulse[k] : 0.0f;
+        } else {
+            // channel disabled; set as nearly zero (-100 dB impulse)
+            for (k=0; k<num_frames; k++)
+                x[i][k] = (k == 0) ? 1e-5f : 0.0f;
+        }
+    }
+
+    // create prototype filter
+    unsigned int h_len = 2*num_channels*m + 1;
+    float h[h_len];
+    liquid_firdes_kaiser(h_len, 0.5f/(float)num_channels, As, 0.0f, h);
+
+#if 0
+    // create filterbank channelizer object using internal method for filter
+    firpfbch_crcf q = firpfbch_crcf_create_kaiser(LIQUID_SYNTHESIZER, num_channels, m, As);
+#else
+    // create filterbank channelizer object using external filter coefficients
+    firpfbch_crcf q = firpfbch_crcf_create(LIQUID_SYNTHESIZER, num_channels, 2*m, h);
+#endif
+
+    // channelize input data
+    float complex v[num_channels];
+    for (i=0; i<num_frames; i++) {
+        // assemble input vector
+        for (k=0; k<num_channels; k++)
+            v[k] = x[k][i];
+
+        // execute synthesis filter bank
+        firpfbch_crcf_synthesizer_execute(q, v, &y[i*num_channels]);
+    }
+
+    // destroy channelizer object
+    firpfbch_crcf_destroy(q);
+    
+    // 
+    // export results to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_channels = %u;\n", num_channels);
+    fprintf(fid,"m            = %u;\n", m);
+    fprintf(fid,"num_frames   = %u;\n", num_frames);
+    fprintf(fid,"h_len        = 2*num_channels*m+1;\n");
+    fprintf(fid,"num_samples  = num_frames*num_channels;\n");
+
+    fprintf(fid,"h = zeros(1,h_len);\n");
+    fprintf(fid,"x = zeros(num_channels, num_frames);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+
+    // save prototype filter
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"  h(%6u) = %12.4e;\n", i+1, h[i]);
+
+    // save channelized input signals
+    for (i=0; i<num_frames; i++) {
+        for (k=0; k<num_channels; k++) {
+            float complex v = x[k][i];
+            fprintf(fid,"  x(%3u,%6u) = %12.4e + 1i*%12.4e;\n", k+1, i+1, crealf(v), cimagf(v));
+        }
+    }
+
+    // save output time signal
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"  y(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // compute the PSD of each input and plot results on a square grid
+    fprintf(fid,"nfft = 1024;\n"); // TODO: use nextpow2
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"n = ceil(sqrt(num_channels));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"for i=1:num_channels,\n");
+    fprintf(fid,"  X = 20*log10(abs(fftshift(fft(x(i,:),nfft))));\n");
+    fprintf(fid,"  subplot(n,n,i);\n");
+    fprintf(fid,"  plot(f, X, 'Color', [0.25 0 0.25], 'LineWidth', 1.5);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -120 20]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  title(num2str(i-1));\n");
+    fprintf(fid,"end;\n");
+
+    // plot results
+    fprintf(fid,"\n");
+    fprintf(fid,"H = 20*log10(abs(fftshift(fft(h/num_channels,nfft))));\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y/num_channels,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(f, H, 'Color', [0 0.5 0.25], 'LineWidth', 2);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -100 10]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Prototype Filter PSD');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(f, Y, 'Color', [0 0.25 0.5], 'LineWidth', 2);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -100 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Output PSD');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/flexframesync_example.c b/examples/flexframesync_example.c
new file mode 100644
index 0000000..98d6f71
--- /dev/null
+++ b/examples/flexframesync_example.c
@@ -0,0 +1,176 @@
+//
+// flexframesync_example.c
+//
+// This example demonstrates the basic interface to the flexframegen and
+// flexframesync objects used to completely encapsulate raw data bytes
+// into frame samples (nearly) ready for over-the-air transmission. A
+// 14-byte header and variable length payload are encoded into baseband
+// symbols using the flexframegen object.  The resulting symbols are
+// interpolated using a root-Nyquist filter and the resulting samples are
+// then fed into the flexframesync object which attempts to decode the
+// frame. Whenever frame is found and properly decoded, its callback
+// function is invoked.
+//
+// SEE ALSO: flexframesync_reconfig_example.c
+//           framesync64_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+void usage()
+{
+    printf("flexframesync_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  s     : signal-to-noise ratio [dB], default: 20\n");
+    printf("  F     : carrier frequency offset, default: 0.01\n");
+    printf("  n     : payload length [bytes], default: 120\n");
+    printf("  m     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+    printf("  d     : enable debugging\n");
+}
+
+// flexframesync callback function
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata);
+
+int main(int argc, char *argv[])
+{
+    //srand( time(NULL) );
+
+    // options
+    modulation_scheme ms     =  LIQUID_MODEM_QPSK; // mod. scheme
+    crc_scheme check         =  LIQUID_CRC_32;     // data validity check
+    fec_scheme fec0          =  LIQUID_FEC_NONE;   // fec (inner)
+    fec_scheme fec1          =  LIQUID_FEC_NONE;   // fec (outer)
+    unsigned int payload_len =  120;               // payload length
+    int debug_enabled        =  0;                 // enable debugging?
+    float noise_floor        = -60.0f;             // noise floor
+    float SNRdB              =  20.0f;             // signal-to-noise ratio
+    float dphi               =  0.01f;             // carrier frequency offset
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhs:F:n:m:v:c:k:d")) != EOF){
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                                       return 0;
+        case 's': SNRdB         = atof(optarg);                  break;
+        case 'F': dphi          = atof(optarg);                  break;
+        case 'n': payload_len   = atol(optarg);                  break;
+        case 'm': ms            = liquid_getopt_str2mod(optarg); break;
+        case 'v': check         = liquid_getopt_str2crc(optarg); break;
+        case 'c': fec0          = liquid_getopt_str2fec(optarg); break;
+        case 'k': fec1          = liquid_getopt_str2fec(optarg); break;
+        case 'd': debug_enabled = 1;                             break;
+        default:
+            exit(-1);
+        }
+    }
+
+    // derived values
+    unsigned int i;
+    float nstd  = powf(10.0f, noise_floor/20.0f);         // noise std. dev.
+    float gamma = powf(10.0f, (SNRdB+noise_floor)/20.0f); // channel gain
+
+    // create flexframegen object
+    flexframegenprops_s fgprops;
+    flexframegenprops_init_default(&fgprops);
+    fgprops.mod_scheme  = ms;
+    fgprops.check       = check;
+    fgprops.fec0        = fec0;
+    fgprops.fec1        = fec1;
+    flexframegen fg = flexframegen_create(&fgprops);
+
+    // frame data (header and payload)
+    unsigned char header[14];
+    unsigned char payload[payload_len];
+
+    // create flexframesync object
+    flexframesync fs = flexframesync_create(callback,NULL);
+    if (debug_enabled)
+        flexframesync_debug_enable(fs);
+
+    // initialize header, payload
+    for (i=0; i<14; i++)
+        header[i] = i;
+    for (i=0; i<payload_len; i++)
+        payload[i] = rand() & 0xff;
+
+    // assemble the frame
+    flexframegen_assemble(fg, header, payload, payload_len);
+    flexframegen_print(fg);
+
+    // generate the frame in blocks
+    unsigned int  buf_len = 256;
+    float complex x[buf_len];
+    float complex y[buf_len];
+
+    int frame_complete = 0;
+    float phi = 0.0f;
+    while (!frame_complete) {
+        // write samples to buffer
+        frame_complete = flexframegen_write_samples(fg, x, buf_len);
+
+        // add noise and push through synchronizer
+        for (i=0; i<buf_len; i++) {
+            // apply channel gain and carrier offset to input
+            y[i] = gamma * x[i] * cexpf(_Complex_I*phi);
+            phi += dphi;
+
+            // add noise
+            y[i] += nstd*( randnf() + _Complex_I*randnf())*M_SQRT1_2;
+        }
+
+        // run through frame synchronizer
+        flexframesync_execute(fs, y, buf_len);
+    }
+
+    // export debugging file
+    if (debug_enabled)
+        flexframesync_debug_print(fs, "flexframesync_debug.m");
+
+    flexframesync_print(fs);
+    // destroy allocated objects
+    flexframegen_destroy(fg);
+    flexframesync_destroy(fs);
+
+    printf("done.\n");
+    return 0;
+}
+
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    printf("******** callback invoked\n");
+
+    framesyncstats_print(&_stats);
+    printf("    header crc          :   %s\n", _header_valid ?  "pass" : "FAIL");
+    printf("    payload length      :   %u\n", _payload_len);
+    printf("    payload crc         :   %s\n", _payload_valid ?  "pass" : "FAIL");
+
+    return 0;
+}
+
diff --git a/examples/flexframesync_reconfig_example.c b/examples/flexframesync_reconfig_example.c
new file mode 100644
index 0000000..ab397da
--- /dev/null
+++ b/examples/flexframesync_reconfig_example.c
@@ -0,0 +1,122 @@
+//
+// flexframesync_reconfig_example.c
+//
+// Demonstrates the reconfigurability of the flexframegen and
+// flexframesync objects.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME  "flexframesync_reconfig_example.m"
+
+void usage()
+{
+    printf("flexframesync_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  s     : signal-to-noise ratio [dB], default: 30\n");
+    printf("  n     : number of frames, default: 3\n");
+}
+
+int main(int argc, char *argv[]) {
+    srand( time(NULL) );
+
+    // define parameters
+    float SNRdB = 30.0f;
+    float noise_floor = -30.0f;
+    unsigned int num_frames = 3;
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhvqs:f:m:p:n:")) != EOF){
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 's': SNRdB = atof(optarg);         break;
+        case 'n': num_frames = atoi(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // create flexframegen object
+    flexframegenprops_s fgprops;
+    flexframegenprops_init_default(&fgprops);
+    flexframegen fg = flexframegen_create(NULL);
+
+    // frame data
+    unsigned char   header[14];
+    unsigned char * payload = NULL;
+
+    // create flexframesync object with default properties
+    flexframesync fs = flexframesync_create(NULL,NULL);
+
+    // channel
+    float nstd  = powf(10.0f, noise_floor/20.0f);         // noise std. dev.
+    float gamma = powf(10.0f, (SNRdB+noise_floor)/20.0f); // channel gain
+
+    unsigned int i;
+    // initialize header, payload
+    for (i=0; i<14; i++)
+        header[i] = i;
+
+    // frame buffers, properties
+    unsigned int  buf_len = 256;
+    float complex buf[buf_len];
+
+    unsigned int j;
+    for (j=0; j<num_frames; j++) {
+        // configure frame generator properties
+        unsigned int payload_len = (rand() % 256) + 1;   // random payload length
+        fgprops.check            = LIQUID_CRC_NONE;      // data validity check
+        fgprops.fec0             = LIQUID_FEC_NONE;      // inner FEC scheme
+        fgprops.fec1             = LIQUID_FEC_NONE;      // outer FEC scheme
+        fgprops.mod_scheme       = (rand() % 2) ? LIQUID_MODEM_QPSK : LIQUID_MODEM_QAM16;
+
+        // reallocate memory for payload
+        payload = realloc(payload, payload_len*sizeof(unsigned char));
+
+        // initialize payload
+        for (i=0; i<payload_len; i++)
+            payload[i] = rand() & 0xff;
+
+        // set properties and assemble the frame
+        flexframegen_setprops(fg, &fgprops);
+        flexframegen_assemble(fg, header, payload, payload_len);
+        printf("frame %u, ", j);
+        flexframegen_print(fg);
+
+        // write the frame in blocks
+        int frame_complete = 0;
+        while (!frame_complete) {
+            // write samples to buffer
+            frame_complete = flexframegen_write_samples(fg, buf, buf_len);
+
+            // add channel impairments (gain and noise)
+            for (i=0; i<buf_len; i++)
+                buf[i] = buf[i]*gamma + nstd * (randnf() + _Complex_I*randnf()) * M_SQRT1_2;
+
+            // push through sync
+            flexframesync_execute(fs, buf, buf_len);
+        }
+
+    } // num frames
+
+    // print frame data statistics
+    flexframesync_print(fs);
+
+    // clean up allocated memory
+    flexframegen_destroy(fg);
+    flexframesync_destroy(fs);
+    free(payload);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/framesync64_example.c b/examples/framesync64_example.c
new file mode 100644
index 0000000..943cf4d
--- /dev/null
+++ b/examples/framesync64_example.c
@@ -0,0 +1,194 @@
+//
+// framesync64_example.c
+//
+// This example demonstrates the interfaces to the framegen64 and
+// framesync64 objects used to completely encapsulate data for
+// over-the-air transmission.  A 64-byte payload is generated, and then
+// encoded, modulated, and interpolated using the framegen64 object.
+// The resulting complex baseband samples are corrupted with noise and
+// moderate carrier frequency and phase offsets before the framesync64
+// object attempts to decode the frame.  The resulting data are compared
+// to the original to validate correctness.
+//
+// SEE ALSO: flexframesync_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME  "framesync64_example.m"
+
+void usage()
+{
+    printf("framesync64_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  d     : enable debugging\n");
+    printf("  S     : signal-to-noise ratio [dB], default: 20\n");
+    printf("  F     : carrier frequency offset, default: 0\n");
+    printf("  P     : carrier phase offset, default: 0\n");
+    printf("  T     : fractional sample timing offset, default: 0.01\n");
+}
+
+// static callback function
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata);
+
+// global arrays
+unsigned char header[8];
+unsigned char payload[64];
+
+int main(int argc, char*argv[])
+{
+    srand( time(NULL) );
+
+    // options
+    float SNRdB       =  20.0f; // signal-to-noise ratio
+    float noise_floor = -40.0f; // noise floor
+    float dphi        =  0.01f; // carrier frequency offset
+    float theta       =  0.0f;  // carrier phase offset
+    float dt          =  -0.2f;  // fractional sample timing offset
+    int debug_enabled = 0;
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hdS:F:P:T:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'd': debug_enabled = 1;    break;
+        case 'S': SNRdB = atof(optarg); break;
+        case 'F': dphi  = atof(optarg); break;
+        case 'P': theta = atof(optarg); break;
+        case 'T': dt    = atof(optarg); break;
+        default:
+            exit(-1);
+        }
+    }
+
+    // derived values
+    unsigned int frame_len = LIQUID_FRAME64_LEN;          // fixed frame length
+    unsigned int num_samples = frame_len + 200;           // total number of samples
+    float nstd  = powf(10.0f, noise_floor/20.0f);         // noise std. dev.
+    float gamma = powf(10.0f, (SNRdB+noise_floor)/20.0f); // channel gain
+
+    // create frame generator
+    framegen64 fg = framegen64_create();
+    framegen64_print(fg);
+
+    // create frame synchronizer using default properties
+    framesync64 fs = framesync64_create(callback,NULL);
+    framesync64_print(fs);
+    if (debug_enabled)
+        framesync64_debug_enable(fs);
+
+    // data payload
+    unsigned int i;
+    // initialize header and payload data
+    for (i=0; i<8; i++)
+        header[i] = i;
+    for (i=0; i<64; i++)
+        payload[i] = rand() & 0xff;
+
+    // allocate memory for the frame samples
+    float complex frame[frame_len]; // generated frame
+    float complex y[num_samples];   // received sequence
+    
+    // generate the frame
+    framegen64_execute(fg, header, payload, frame);
+
+    // fractional sample timing offset
+    unsigned int d = 11;    // fractional sample filter delay
+    firfilt_crcf finterp = firfilt_crcf_create_kaiser(2*d+1, 0.45f, 40.0f, -dt);
+    for (i=0; i<num_samples; i++) {
+        // fractional sample timing offset
+        if      (i < 100)             firfilt_crcf_push(finterp, 0.0f);
+        else if (i < frame_len + 100) firfilt_crcf_push(finterp, frame[i-100]);
+        else                          firfilt_crcf_push(finterp, 0.0f);
+
+        // compute output
+        firfilt_crcf_execute(finterp, &y[i]);
+    }
+    firfilt_crcf_destroy(finterp);
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i] *= cexpf(_Complex_I*(dphi*i +theta));
+        y[i] *= gamma;
+        y[i] += nstd*( randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // synchronize/receive the frame
+    framesync64_execute(fs, y, num_samples);
+
+    // export debugging file
+    if (debug_enabled)
+        framesync64_debug_print(fs, "framesync64_debug.m");
+
+    // clean up allocated objects
+    framegen64_destroy(fg);
+    framesync64_destroy(fs);
+    
+    // 
+    // export results
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s: auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"frame_len   = %u;\n", frame_len);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    for (i=0; i<num_samples; i++)
+        fprintf(fid, "y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"t=0:(length(y)-1);\n");
+    fprintf(fid,"plot(t,real(y),t,imag(y));\n");
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// static callback function
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    printf("*** callback invoked ***\n");
+    printf("    error vector mag.   : %12.8f dB\n", _stats.evm);
+    printf("    rssi                : %12.8f dB\n", _stats.rssi);
+    printf("    carrier offset      : %12.8f\n", _stats.cfo);
+    printf("    mod. scheme         : %s\n", modulation_types[_stats.mod_scheme].fullname);
+    printf("    mod. depth          : %u\n", _stats.mod_bps);
+    printf("    payload CRC         : %s\n", crc_scheme_str[_stats.check][1]);
+    printf("    payload fec (inner) : %s\n", fec_scheme_str[_stats.fec0][1]);
+    printf("    payload fec (outer) : %s\n", fec_scheme_str[_stats.fec1][1]);
+    printf("    header crc          : %s\n", _header_valid ? "pass" : "FAIL");
+    printf("    header data         : %.2x %.2x %.2x %.2x %.2x %.2x %.2x %.2x\n",
+            _header[0], _header[1], _header[2], _header[3],
+            _header[4], _header[5], _header[6], _header[7]);
+    printf("    num header errors   : %u / %u\n",
+            count_bit_errors_array(_header, header, 8),
+            8*8);
+    printf("    payload crc         : %s\n", _payload_valid ? "pass" : "FAIL");
+    printf("    num payload errors  : %u / %u\n",
+            count_bit_errors_array(_payload, payload, 64),
+            64*8);
+
+    return 0;
+}
+
diff --git a/examples/freqmodem_example.c b/examples/freqmodem_example.c
new file mode 100644
index 0000000..e7d8935
--- /dev/null
+++ b/examples/freqmodem_example.c
@@ -0,0 +1,136 @@
+// file: freqmodem_test.c
+//
+// Tests simple modulation/demodulation without noise or phase
+// offset
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "freqmodem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("freqmodem_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  n     : number of samples, default: 1024\n");
+    printf("  S     : SNR [dB], default: 30\n");
+    printf("  k     : FM modulation factor, default: 0.1\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float        kf          = 0.1f;    // modulation factor
+    unsigned int num_samples = 1024;    // number of samples
+    float        SNRdB       = 30.0f;   // signal-to-noise ratio [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:S:k:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                    return 0;
+        case 'n':   num_samples = atoi(optarg); break;
+        case 'S':   SNRdB       = atof(optarg); break;
+        case 'k':   kf          = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // create mod/demod objects
+    freqmod mod = freqmod_create(kf);   // modulator
+    freqdem dem = freqdem_create(kf);   // demodulator
+    freqmod_print(mod);
+
+    unsigned int i;
+    float         m[num_samples];       // message signal
+    float complex r[num_samples];       // received signal (complex baseband)
+    float         y[num_samples];       // demodulator output
+
+    // generate message signal (sum of sines)
+    for (i=0; i<num_samples; i++) {
+        m[i] = 0.3f*cosf(2*M_PI*0.013f*i + 0.0f) +
+               0.2f*cosf(2*M_PI*0.021f*i + 0.4f) +
+               0.4f*cosf(2*M_PI*0.037f*i + 1.7f);
+    }
+
+    // modulate signal
+    freqmod_modulate_block(mod, m, num_samples, r);
+
+    // add channel impairments
+    float nstd = powf(10.0f,-SNRdB/20.0f);
+    for (i=0; i<num_samples; i++)
+        r[i] += nstd*( randnf() + _Complex_I*randnf() ) * M_SQRT1_2;
+
+    // demodulate signal
+    freqdem_demodulate_block(dem, r, num_samples, y);
+
+    // destroy modem objects
+    freqmod_destroy(mod);
+    freqdem_destroy(dem);
+
+    // compute RMS error (ignore first sample)
+    float rmse = 0.0;
+    for (i=1; i<num_samples; i++) {
+        float err = y[i] - m[i];
+        rmse += err*err;
+    }
+    rmse = sqrtf( rmse / (float)(num_samples-1) );
+    printf("rmse = %12.4e\n", rmse);
+
+    // 
+    // write results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n=%u;\n",num_samples);
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"m(%3u) = %12.4e;\n", i+1, m[i]);
+        fprintf(fid,"r(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(r[i]), cimagf(r[i]));
+        fprintf(fid,"y(%3u) = %12.4e;\n", i+1, y[i]);
+    }
+    // plot time-domain result
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t,m,'LineWidth',1,'Color',[0 0.2 0.5],...);\n");
+    fprintf(fid,"       t,y,'LineWidth',1,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  axis([0 n -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('Normalized Time [t/T_s]');\n");
+    fprintf(fid,"  ylabel('m(t), y(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    // compute spectral responses
+    fprintf(fid,"nfft=2^(1+nextpow2(n));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"w = hamming(n)';\n");
+    fprintf(fid,"g = 1 / (mean(w) * n);\n");
+    fprintf(fid,"M = 20*log10(abs(fftshift(fft(m.*w*g,nfft))));\n");
+    fprintf(fid,"R = 20*log10(abs(fftshift(fft(r.*w*g,nfft))));\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y.*w*g,nfft))));\n");
+    // plot spectral response (audio)
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(f,M,'LineWidth',1.2,'Color',[0 0.2 0.5],...\n");
+    fprintf(fid,"       f,Y,'LineWidth',1.2,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -80 20]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Audio PSD [dB]');\n");
+    // plot spectral response (RF)
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(f,R,'LineWidth',1.2,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -80 20]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('RF PSD [dB]');\n");
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/fskmodem_example.c b/examples/fskmodem_example.c
new file mode 100644
index 0000000..3f92423
--- /dev/null
+++ b/examples/fskmodem_example.c
@@ -0,0 +1,153 @@
+// 
+// fskmodem_example.c
+//
+// This example demostrates the M-ary frequency-shift keying
+// (MFSK) modem in liquid. A message signal is modulated and the
+// resulting signal is recovered using a demodulator object.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "fskmodem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("fskmodem_example -- frequency-shift keying example\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  m     : bits/symbol,              default:  1\n");
+    printf("  k     : samples/symbol,           default:  2*2^m\n");
+    printf("  b     : signal bandwidth          default:  0.2\n");
+    printf("  n     : number of data symbols,   default: 80\n");
+    printf("  s     : SNR [dB],                 default: 40\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int m           =   3;     // number of bits/symbol
+    unsigned int k           =   0;     // filter samples/symbol
+    unsigned int num_symbols = 8000;    // number of data symbols
+    float        SNRdB       = 40.0f;   // signal-to-noise ratio [dB]
+    float        bandwidth   = 0.20;    // frequency spacing
+    unsigned int nfft        = 1200;    // FFT size for compute spectrum
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hm:k:b:n:s:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'm': m           = atoi(optarg);   break;
+        case 'k': k           = atoi(optarg);   break;
+        case 'b': bandwidth   = atof(optarg);   break;
+        case 'n': num_symbols = atoi(optarg);   break;
+        case 's': SNRdB       = atof(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+    unsigned int j;
+
+    // derived values
+    if (k == 0)
+        k = 2 << m; // set samples per symbol if not otherwise specified
+    unsigned int M    = 1 << m;
+    float        nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // validate input
+    if (k < M) {
+        fprintf(stderr,"errors: %s, samples/symbol must be at least modulation size (M=%u)\n", __FILE__,M);
+        exit(1);
+    } else if (k > 2048) {
+        fprintf(stderr,"errors: %s, samples/symbol exceeds maximum (2048)\n", __FILE__);
+        exit(1);
+    } else if (M > 1024) {
+        fprintf(stderr,"errors: %s, modulation size (M=%u) exceeds maximum (1024)\n", __FILE__, M);
+        exit(1);
+    } else if (bandwidth <= 0.0f || bandwidth >= 0.5f) {
+        fprintf(stderr,"errors: %s, bandwidht must be in (0,0.5)\n", __FILE__);
+        exit(1);
+    }
+
+    // create modulator/demodulator pair
+    fskmod mod = fskmod_create(m,k,bandwidth);
+    fskdem dem = fskdem_create(m,k,bandwidth);
+    fskdem_print(dem);
+
+    // 
+    float complex buf_tx[k];    // transmit buffer
+    float complex buf_rx[k];    // transmit buffer
+    
+    // spectral periodogram
+    spgramcf periodogram = spgramcf_create_default(nfft);
+
+    // modulate, demodulate, count errors
+    unsigned int num_symbol_errors = 0;
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        unsigned int sym_in = rand() % M;
+
+        // modulate
+        fskmod_modulate(mod, sym_in, buf_tx);
+
+        // add noise
+        for (j=0; j<k; j++)
+            buf_rx[j] = buf_tx[j] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+        // demodulate
+        unsigned int sym_out = fskdem_demodulate(dem, buf_rx);
+
+        // count errors
+        num_symbol_errors += (sym_in == sym_out) ? 0 : 1;
+
+        // estimate power spectral density
+        spgramcf_write(periodogram, buf_rx, k);
+    }
+
+    printf("symbol errors: %u / %u\n", num_symbol_errors, num_symbols);
+
+    // compute power spectral density of received signal
+    float psd[nfft];
+    spgramcf_get_psd(periodogram, psd);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export results
+    //
+    
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"M = %u;\n", M);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+
+    // save power spectral density
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%4u) = %12.8f;\n", i+1, psd[i]);
+
+    // plot PSD
+    fprintf(fid,"figure('Color','white');\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"plot(f,psd,'LineWidth',1.5,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"axis([-0.5 0.5 -40 20]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/gasearch_example.c b/examples/gasearch_example.c
new file mode 100644
index 0000000..e10a2f5
--- /dev/null
+++ b/examples/gasearch_example.c
@@ -0,0 +1,131 @@
+//
+// gasearch_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gasearch_example.m"
+
+// utility callback function
+float utility_callback(void * _userdata, chromosome _c);
+
+// peak callback function; value nearest {0.5, 0.5, 0.5, ...}
+float peak_callback(void * _userdata, chromosome _c);
+
+// rosenbrock callback function
+float rosenbrock_callback(void * _userdata, chromosome _c);
+
+int main() {
+    unsigned int num_parameters = 8;        // dimensionality of search (minimum 1)
+    unsigned int bits_per_parameter = 16;   // parameter resolution
+    unsigned int num_iterations = 1000;     // number of iterations to run
+    unsigned int population_size = 20;      // GA population size
+    float mutation_rate = 0.10f;            // GA mutation rate
+
+    unsigned int i;
+    float optimum_utility;
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    // create prototype chromosome
+    chromosome prototype = chromosome_create_basic(num_parameters, bits_per_parameter);
+
+    // create gasearch object
+    gasearch ga = gasearch_create_advanced(
+                                    //&utility_callback,
+                                    //&rosenbrock_callback,
+                                    &peak_callback,
+                                    NULL,
+                                    prototype,
+                                    LIQUID_OPTIM_MAXIMIZE,
+                                    population_size,
+                                    mutation_rate);
+    gasearch_print(ga);
+
+    // execute search
+    //optimum_utility = gasearch_run(ga, num_iterations, -1e-6f);
+
+    // execute search one iteration at a time
+    fprintf(fid,"u = zeros(1,%u);\n", num_iterations);
+    for (i=0; i<num_iterations; i++) {
+        gasearch_evolve(ga);
+
+        gasearch_getopt(ga, prototype, &optimum_utility);
+        fprintf(fid,"u(%3u) = %12.4e;\n", i+1, optimum_utility);
+
+        printf("%4u : %16.8f\n", i, optimum_utility);
+    }
+
+    // print results
+    printf("\n");
+    gasearch_print(ga);
+
+    printf("optimum utility : %12.8f\n", optimum_utility);
+    chromosome_printf(prototype);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(u);\n");
+    fprintf(fid,"xlabel('iteration');\n");
+    fprintf(fid,"ylabel('utility');\n");
+    fprintf(fid,"title('gradient search results');\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // test results, optimum at [1, 1, 1, ... 1];
+
+    chromosome_destroy(prototype);
+    gasearch_destroy(ga);
+
+    return 0;
+}
+
+// utility callback function
+float utility_callback(void * _userdata, chromosome _c)
+{
+    unsigned int n = chromosome_get_num_traits(_c);
+    float v;    // chromosome value
+    float u=0;  // total utility
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // extract chromosome value
+        v = chromosome_valuef(_c,i);
+
+        // accumulate utility
+        u += v;
+    }
+
+    return sqrtf(fabsf(u-1.0));
+}
+
+// peak callback function; value nearest {0.5, 0.5, 0.5, ...}
+float peak_callback(void * _userdata, chromosome _c)
+{
+    unsigned int n = chromosome_get_num_traits(_c);
+    float v[n]; // chromosome values
+
+    unsigned int i;
+    float u_global = 1.0f;
+    float sig = 0.2f;
+    for (i=0; i<n; i++) {
+        // extract chromosome values
+        v[i] = chromosome_valuef(_c,i);
+
+        float e = v[i] - 0.5f;
+        float u = exp(-e*e/(2*sig*sig));
+        u_global *= u;
+    }
+
+    return u_global;
+}
+
+
diff --git a/examples/gasearch_knapsack_example.c b/examples/gasearch_knapsack_example.c
new file mode 100644
index 0000000..d028c80
--- /dev/null
+++ b/examples/gasearch_knapsack_example.c
@@ -0,0 +1,203 @@
+//
+// gasearch_knapsack_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gasearch_knapsack_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: gasearch_knapsack_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : number of items available, default: 1000\n");
+    printf("  i     : number of iterations (generations) to run, default: 2000\n");
+    printf("  c     : knapsack capacity (maximum weight), default: 20\n");
+    printf("  p     : ga population size, default: 100\n");
+    printf("  m     : ga mutation rate, default: 0.4\n");
+}
+
+// knapsack object structure definition
+struct knapsack_s {
+    unsigned int num_items; // total number of items available
+    float * weight;         // weight of each item
+    float * value;          // value of each item
+    float capacity;         // maximum weight allowable
+};
+
+// print knapsack object
+//  _bag        :   knapsack object pointer
+//  _c          :   test chromosome
+void  knapsack_print(struct knapsack_s * _bag,
+                     chromosome _c);
+
+// utility callback function
+//  _userdata   :   knapsack object pointer
+//  _c          :   test chromosome
+float knapsack_utility(void * _userdata,
+                       chromosome _c);
+
+int main(int argc, char*argv[])
+{
+    unsigned int num_items = 1000;      // number of items available
+    unsigned int num_iterations = 2000; // number of iterations to run
+    float capacity = 20.0f;             // total capacity of the knapsack
+    unsigned int population_size = 100; // number of chromosomes in the population
+    float mutation_rate = 0.40f;        // mutation rate of the GA
+
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:i:c:p:m:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n': num_items = atoi(optarg);         break;
+        case 'i': num_iterations = atoi(optarg);    break;
+        case 'c': capacity = atof(optarg);          break;
+        case 'p': population_size = atoi(optarg);   break;
+        case 'm': mutation_rate = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_items == 0) {
+        fprintf(stderr,"error: %s, knapsack must have at least 1 item\n", argv[0]);
+        exit(1);
+    } else if (capacity <= 0.0f) {
+        fprintf(stderr,"error: %s, knapsack capacity must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (population_size <= 0) {
+        fprintf(stderr,"error: %s, ga population size must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (mutation_rate < 0.0f || mutation_rate > 1.0f) {
+        fprintf(stderr,"error: %s, ga mutation rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // create knapsack/items (random weight, value)
+    struct knapsack_s bag;
+    bag.num_items = num_items;
+    bag.capacity = capacity;
+    bag.weight = (float*) malloc( bag.num_items*sizeof(float) );
+    bag.value  = (float*) malloc( bag.num_items*sizeof(float) );
+    for (i=0; i<num_items; i++) {
+        bag.weight[i] = randf();    // random number in [0,1]
+        bag.value[i]  = randf();    // random number in [0,1]
+    }
+
+    // create prototype chromosome (1 bit/item)
+    chromosome prototype = chromosome_create_basic(num_items, 1);
+    //chromosome_init_random(prototype); // initialize to random
+    chromosome_print(prototype);
+
+    // print knapsack
+    knapsack_print(&bag, prototype);
+
+    float optimum_utility;
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    // create gasearch object
+    gasearch ga = gasearch_create_advanced(&knapsack_utility,
+                                             (void*)&bag,
+                                             prototype,
+                                             LIQUID_OPTIM_MAXIMIZE,
+                                             population_size,
+                                             mutation_rate);
+
+    // execute search one iteration at a time
+    fprintf(fid,"u = zeros(1,%u);\n", num_iterations);
+    for (i=0; i<num_iterations; i++) {
+        gasearch_evolve(ga);
+
+        gasearch_getopt(ga, prototype, &optimum_utility);
+        if (((i+1)%100)==0)
+            printf("  %4u : %12.8f;\n", i+1, optimum_utility);
+
+        fprintf(fid,"u(%3u) = %12.4e;\n", i+1, optimum_utility);
+    }
+
+    // print results
+    gasearch_getopt(ga, prototype, &optimum_utility);
+    knapsack_print(&bag, prototype);
+
+    fprintf(fid,"figure;\n");
+    //fprintf(fid,"semilogy(u);\n");
+    fprintf(fid,"plot(u);\n");
+    fprintf(fid,"xlabel('iteration');\n");
+    fprintf(fid,"ylabel('utility');\n");
+    fprintf(fid,"title('GA search results');\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // free allocated objects and memory
+    chromosome_destroy(prototype);
+    gasearch_destroy(ga);
+    free(bag.value);
+    free(bag.weight);
+
+    return 0;
+}
+
+
+// 
+// knapsack methods
+//
+
+// print knapsack object
+//  _bag        :   knapsack object pointer
+//  _c          :   test chromosome
+void knapsack_print(struct knapsack_s * _bag,
+                    chromosome _s)
+{
+    unsigned int i;
+    printf("knapsack: %u items, capacity : %12.8f\n", _bag->num_items, _bag->capacity);
+    for (i=0; i<_bag->num_items; i++) {
+        printf("  %3u : %6.4f @ $%6.4f", i, _bag->weight[i], _bag->value[i]);
+        unsigned int n = chromosome_value(_s, i);
+        if (n != 0) printf(" *\n");
+        else        printf("\n");
+    }
+}
+
+// utility callback function
+//  _userdata   :   knapsack object pointer
+//  _c          :   test chromosome
+float knapsack_utility(void * _userdata, chromosome _c)
+{
+    struct knapsack_s * _bag = (struct knapsack_s *) _userdata;
+
+    // chromosome represents number of each item in knapsack
+    float total_value = 0;
+    float total_weight = 0;
+    unsigned int i;
+    for (i=0; i<_bag->num_items; i++) {
+        if ( chromosome_value(_c,i) == 1 ) {
+            // include this item into knapsack
+            total_value  += _bag->value[i];
+            total_weight += _bag->weight[i];
+        }
+    }
+
+    // check for invalid solution, returning distance metric
+    if (total_weight > _bag->capacity)
+        return _bag->capacity - total_weight;
+
+    // return total value of knapsack
+    return total_value;
+}
+
diff --git a/examples/gmskframesync_example.c b/examples/gmskframesync_example.c
new file mode 100644
index 0000000..3459de6
--- /dev/null
+++ b/examples/gmskframesync_example.c
@@ -0,0 +1,232 @@
+//
+// gmskframesync_example.c
+//
+// Example demonstrating the GMSK flexible frame synchronizer.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gmskframesync_example.m"
+
+void usage()
+{
+    printf("gmskframesync_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  d     : enable internal synchronizer debugging\n");
+    printf("  n     : frame length [bytes], default: 40\n");
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+    printf("  s     : signal-to-noise ratio [dB], default: 30\n");
+    printf("  F     : carrier frequency offset, default: 0.05\n");
+}
+
+struct framedata_s {
+    unsigned char * payload;
+    unsigned int payload_len;
+};
+
+// callback function
+int callback(unsigned char *  _header,
+             int              _header_valid,
+             unsigned char *  _payload,
+             unsigned int     _payload_len,
+             int              _payload_valid,
+             framesyncstats_s _stats,
+             void *           _userdata);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    unsigned int payload_len = 40;  // length of payload (bytes)
+    crc_scheme check = LIQUID_CRC_32;
+    fec_scheme fec0  = LIQUID_FEC_HAMMING128;
+    fec_scheme fec1  = LIQUID_FEC_NONE;
+    float noise_floor = -60.0f;     // noise floor
+    float SNRdB = 30.0f;            // signal-to-noise ratio [dB]
+    float dphi  = 0.05f;            // carrier offset
+
+    int debug_enabled = 0;          // debug option
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hdn:v:c:k:s:F:")) != EOF){
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'd': debug_enabled = 1;            break;
+        case 'n': payload_len   = atoi(optarg); break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            // inner FEC scheme
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported inner FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            // outer FEC scheme
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported outer FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 's': SNRdB         = atof(optarg); break;
+        case 'F': dphi          = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // fixed values
+    unsigned int k = 2;
+
+    // derived values
+    float nstd  = powf(10.0f, noise_floor/20.0f);
+    float gamma = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+
+    // allocate memory for payload and initialize
+    unsigned char header[8];
+    unsigned char payload[payload_len];
+    for (i=0; i<8; i++)           header[i]  = i;
+    for (i=0; i<payload_len; i++) payload[i] = rand() & 0xff;
+    struct framedata_s fd = {payload, payload_len};
+
+    // create frame generator
+    gmskframegen fg = gmskframegen_create();
+
+    // create frame synchronizer
+    gmskframesync fs = gmskframesync_create(callback, (void*)&fd);
+    if (debug_enabled)
+        gmskframesync_debug_enable(fs);
+
+    // assemble frame and print
+    gmskframegen_assemble(fg, header, payload, payload_len, check, fec0, fec1);
+    gmskframegen_print(fg);
+
+    // allocate memory for full frame (with noise)
+    unsigned int frame_len = gmskframegen_getframelen(fg);
+    unsigned int num_samples = (frame_len * k) + 800;
+    float complex x[num_samples];
+    float complex y[num_samples];
+
+    // 
+    // generate frame
+    //
+    unsigned int n=0;
+    for (n=0; n<600; n++)
+        x[n] = 0.0f;
+    int frame_complete = 0;
+    while (!frame_complete) {
+        frame_complete = gmskframegen_write_samples(fg, &x[n]);
+        n += k;
+    }
+    for ( ; n<num_samples; n++)
+        x[n] = 0.0f;
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i]  = x[i];
+        y[i] *= gamma;
+        y[i] *= cexpf(_Complex_I*M_2_PI*dphi*i);
+        y[i] += nstd*(randnf() + randnf()*_Complex_I)*M_SQRT1_2;
+    }
+
+    // push samples through synchronizer
+    gmskframesync_execute(fs, y, num_samples);
+
+    // write debug output if enabled
+    if (debug_enabled)
+        gmskframesync_debug_print(fs, "gmskframesync_debug.m");
+
+    // destroy objects
+    gmskframegen_destroy(fg);
+    gmskframesync_destroy(fs);
+
+
+    // 
+    // export output
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    if (fid == NULL) {
+        fprintf(stderr,"error: %s, could not open '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    fprintf(fid,"\n");
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%6u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"\n");
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t, real(y), t,imag(y));\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('received signal');\n");
+    fprintf(fid,"legend('real','imag',0);\n");
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// callback function
+int callback(unsigned char *  _header,
+             int              _header_valid,
+             unsigned char *  _payload,
+             unsigned int     _payload_len,
+             int              _payload_valid,
+             framesyncstats_s _stats,
+             void *           _userdata)
+{
+    printf("***** callback invoked *****\n");
+    printf("  header crc    :   %s\n", _header_valid ? "pass" : "FAIL");
+    printf("  header data   :  ");
+    unsigned int i;
+    for (i=0; i<8; i++)
+        printf(" %.2X", _header[i]);
+    printf("\n");
+    printf("  rssi          :   %-8.3f dB\n", _stats.rssi);
+    printf("  evm           :   %-8.3f dB\n", _stats.evm);
+    printf("  payload       :   %u bytes (crc %s)\n", _payload_len, _payload_valid ? "pass" : "FAIL");
+    printf("  check         :   %s\n", crc_scheme_str[_stats.check][1]);
+    printf("  fec (inner)   :   %s\n", fec_scheme_str[_stats.fec0][1]);
+    printf("  fec (outer)   :   %s\n", fec_scheme_str[_stats.fec1][1]);
+
+    // count errors
+    struct framedata_s * fd = (struct framedata_s *) _userdata;
+    unsigned int bit_errors = count_bit_errors_array(fd->payload, _payload, _payload_len);
+    printf("  bit errors    :   %-4u / %-4u\n", bit_errors, 8*_payload_len);
+
+    return 0;
+}
+
diff --git a/examples/gmskmodem_example.c b/examples/gmskmodem_example.c
new file mode 100644
index 0000000..362b37d
--- /dev/null
+++ b/examples/gmskmodem_example.c
@@ -0,0 +1,143 @@
+// 
+// gmskmodem_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gmskmodem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("gmskmodem_example -- Gaussian minimum-shift keying modem example\n");
+    printf("options (default values in <>):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : samples/symbol, default: 4\n");
+    printf("  m     : filter delay [symbols], default: 3\n");
+    printf("  n     : number of data symbols, default: 200\n");
+    printf("  b     : bandwidth-time product, 0 <= b <= 1, default: 0.3\n");
+    printf("  s     : SNR [dB], default: 30\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;                   // filter samples/symbol
+    unsigned int m=3;                   // filter delay (symbols)
+    float BT=0.3f;                      // bandwidth-time product
+    unsigned int num_data_symbols=200;  // number of data symbols
+    float SNRdB = 30.0f;                // signal-to-noise ratio [dB]
+    float phi = 0.0f;                   // carrier phase offset
+    float dphi = 0.0f;                  // carrier frequency offset
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:n:b:s:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'k': k = atoi(optarg); break;
+        case 'm': m = atoi(optarg); break;
+        case 'n': num_data_symbols = atoi(optarg); break;
+        case 'b': BT = atof(optarg); break;
+        case 's': SNRdB = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (BT <= 0.0f || BT >= 1.0f) {
+        fprintf(stderr,"error: %s, bandwidth-time product must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_symbols = num_data_symbols + 2*m;
+    unsigned int num_samples = k*num_symbols;
+    float nstd = powf(10.0f,-SNRdB/20.0f);  // noise standard deviation
+
+    // create modulator
+    gmskmod mod   = gmskmod_create(k, m, BT);
+    gmskmod_print(mod);
+
+    // create demodulator
+    gmskdem demod = gmskdem_create(k, m, BT);
+    gmskdem_set_eq_bw(demod, 0.01f);
+    gmskdem_print(demod);
+
+    unsigned int i;
+    unsigned int s[num_symbols];
+    float complex x[num_samples];
+    float complex y[num_samples];
+    unsigned int sym_out[num_symbols];
+
+    // generate random data sequence
+    for (i=0; i<num_symbols; i++)
+        s[i] = rand() % 2;
+
+    // modulate signal
+    for (i=0; i<num_symbols; i++)
+        gmskmod_modulate(mod, s[i], &x[k*i]);
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i]  = x[i]*cexpf(_Complex_I*(phi + i*dphi));
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // demodulate signal
+    for (i=0; i<num_symbols; i++)
+        gmskdem_demodulate(demod, &y[k*i], &sym_out[i]);
+
+    // destroy modem objects
+    gmskmod_destroy(mod);
+    gmskdem_destroy(demod);
+
+    // print results to screen
+    unsigned int delay = 2*m;
+    unsigned int num_errors=0;
+    for (i=delay; i<num_symbols; i++) {
+        //printf("  %4u : %2u (%2u)\n", i, s[i-delay], sym_out[i]);
+        num_errors += (s[i-delay] == sym_out[i]) ? 0 : 1;
+    }
+    printf("symbol errors : %4u / %4u\n", num_errors, num_data_symbols);
+
+    // write results to output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"BT = %f;\n", BT);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(y),t,imag(y));\n");
+
+    // artificially demodulate (generate receive filter, etc.)
+    float hr[2*k*m+1];
+    liquid_firdes_gmskrx(k,m,BT,0,hr);
+    for (i=0; i<2*k*m+1; i++)
+        fprintf(fid,"hr(%3u) = %12.8f;\n", i+1, hr[i]);
+    fprintf(fid,"z = filter(hr,1,arg( ([y(2:end) 0]).*conj(y) ))/k;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,z,t(k:k:end),z(k:k:end),'or');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/gradsearch_datafit_example.c b/examples/gradsearch_datafit_example.c
new file mode 100644
index 0000000..ff487bc
--- /dev/null
+++ b/examples/gradsearch_datafit_example.c
@@ -0,0 +1,149 @@
+//
+// gradsearch_datafit_example.c
+//
+// Fit 3-parameter curve to sampled data set in the minimum
+// mean-squared error sense.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gradsearch_datafit_example.m"
+
+// gradient search data set
+struct gsdataset {
+    float * x;
+    float * y;
+    unsigned int n;
+};
+
+// gradient search curve-fit error
+float gserror(void * _dataset,
+              float * _v,
+              unsigned int _n);
+
+// parameterized function
+float gsfunc(float _x, float * _v)
+{
+    float c0 = _v[0];
+    float c1 = _v[1];
+    float c2 = _v[2];
+
+    return c0 + sincf(c1*(_x-c2));
+}
+
+
+int main() {
+    // options
+    unsigned int num_samples = 400;     // number of samples
+    float sig = 0.1f;                   // noise variance
+    unsigned int num_iterations = 1000; // number of iterations to run
+
+    float v[3] = {1, 1, 1};
+    unsigned int i;
+
+    // range
+    float xmin = 0.0f;
+    float xmax = 6.0f;
+    float dx = (xmax - xmin) / (num_samples-1);
+
+    // generate data set
+    float x[num_samples];
+    float y[num_samples];
+    for (i=0; i<num_samples; i++) {
+        x[i] = xmin + i*dx;
+        y[i] = sincf(x[i]) + randnf()*sig;
+    }
+    struct gsdataset q = {x, y, num_samples};
+
+    // create gradsearch object
+    gradsearch gs = gradsearch_create((void*)&q, v, 3, gserror, LIQUID_OPTIM_MINIMIZE);
+
+    float rmse;
+
+    // execute search
+    //rmse = gradsearch_run(gs, num_iterations, -1e-6f);
+
+     // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+   // execute search one iteration at a time
+    fprintf(fid,"u = zeros(1,%u);\n", num_iterations);
+    for (i=0; i<num_iterations; i++) {
+        rmse = gserror((void*)&q,v,3);
+        fprintf(fid,"u(%3u) = %12.4e;\n", i+1, rmse);
+
+        gradsearch_step(gs);
+
+        if (((i+1)%100)==0)
+            gradsearch_print(gs);
+    }
+
+    // print results
+    printf("\n");
+    gradsearch_print(gs);
+    printf("  c0 = %12.8f, opt = 1\n", v[0]);
+    printf("  c1 = %12.8f, opt = 0\n", v[1]);
+    printf("  c2 = %12.8f, opt = 1\n", v[2]);
+    printf("  rmse = %12.4e\n", rmse);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(u);\n");
+    fprintf(fid,"xlabel('iteration');\n");
+    fprintf(fid,"ylabel('error');\n");
+    fprintf(fid,"title('gradient search results');\n");
+    fprintf(fid,"grid on;\n");
+
+    // save sampled data set
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"  x(%4u) = %12.8f;\n", i+1, x[i]);
+        fprintf(fid,"  y(%4u) = %12.8f;\n", i+1, y[i]);
+        fprintf(fid,"  y_hat(%4u) = %12.8f;\n", i+1, gsfunc(x[i],v));
+    }
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(x,y,'x', x,y_hat,'-');\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('f(x)');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"legend('data','fit',1);\n");
+
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    gradsearch_destroy(gs);
+
+    return 0;
+}
+
+// gradient search fit
+float gserror(void * _dataset,
+              float * _v,
+              unsigned int _n)
+{
+    struct gsdataset * p = (struct gsdataset *) _dataset;
+
+    float rmse = 0.0f; // mean-squared error
+    unsigned int i;
+    for (i=0; i<p->n; i++) {
+        // compute function estimate
+        float y_hat = gsfunc(p->x[i], _v);
+
+        // compute error
+        float e = p->y[i] - y_hat;
+
+        // accumulate RMS error
+        rmse += e*e;
+    }
+
+    // normalize error and return
+    rmse = sqrtf(rmse / (float)(p->n));
+    return rmse;
+}
+
diff --git a/examples/gradsearch_example.c b/examples/gradsearch_example.c
new file mode 100644
index 0000000..fea15ae
--- /dev/null
+++ b/examples/gradsearch_example.c
@@ -0,0 +1,111 @@
+//
+// gradsearch_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gradsearch_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  n     : number of parameters, default: 6\n");
+    printf("  t     : number of iterations, default: 2000\n");
+    printf("  u     : utility function: {rosenbrock, invgauss, multimodal, spiral}\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int num_parameters = 6;    // dimensionality of search (minimum 2)
+    unsigned int num_iterations = 2000; // number of iterations to run
+    utility_function func = liquid_rosenbrock;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:t:u:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'n':   num_parameters = atoi(optarg);  break;
+        case 't':   num_iterations = atoi(optarg);  break;
+        case 'u':
+            if      (strcmp(optarg,"rosenbrock")==0) func = liquid_rosenbrock;
+            else if (strcmp(optarg,"invgauss")==0)   func = liquid_invgauss;
+            else if (strcmp(optarg,"multimodal")==0) func = liquid_multimodal;
+            else if (strcmp(optarg,"spiral")==0)     func = liquid_spiral;
+            else {
+                fprintf(stderr,"error: %s, unknown/unsupported utility '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    float optimum_vect[num_parameters];
+    unsigned int i;
+    for (i=0; i<num_parameters; i++)
+        optimum_vect[i] = 0.1f*(float)i;
+
+    float optimum_utility;
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    // create gradsearch object
+    gradsearch gs = gradsearch_create(NULL,
+                                      optimum_vect,
+                                      num_parameters,
+                                      func,
+                                      LIQUID_OPTIM_MINIMIZE);
+
+    // execute search
+    //optimum_utility = gradsearch_run(gs, num_iterations, -1e-6f);
+
+    // execute search one iteration at a time
+    fprintf(fid,"u = zeros(1,%u);\n", num_iterations);
+    unsigned int d=1;
+    for (i=0; i<num_iterations; i++) {
+        optimum_utility = func(NULL,optimum_vect,num_parameters);
+        fprintf(fid,"u(%3u) = %12.4e;\n", i+1, optimum_utility);
+
+        gradsearch_step(gs);
+
+        if (((i+1)%d)==0 || i==0) {
+            printf("%5u: ", i+1);
+            gradsearch_print(gs);
+
+            if ((i+1)==10*d) d*=10;
+        }
+    }
+
+    // print results
+    printf("\n");
+    printf("%5u: ", num_iterations);
+    gradsearch_print(gs);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(u);\n");
+    fprintf(fid,"xlabel('iteration');\n");
+    fprintf(fid,"ylabel('utility');\n");
+    fprintf(fid,"title('gradient search results');\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // test results, optimum at [1, 1, 1, ... 1];
+
+    gradsearch_destroy(gs);
+
+    return 0;
+}
diff --git a/examples/iirdecim_crcf_example.c b/examples/iirdecim_crcf_example.c
new file mode 100644
index 0000000..35a4a4f
--- /dev/null
+++ b/examples/iirdecim_crcf_example.c
@@ -0,0 +1,155 @@
+//
+// iirdecim_crcf_example.c
+//
+// This example demonstrates the interface to the iirdecim (infinite
+// impulse response decimator) family of objects.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirdecim_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirdecim_crcf_example:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  M     : decimation factor, M > 1, default: 4\n");
+    printf("  n     : number of input samples, default: 400\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int M=4;               // decimation rate
+    unsigned int num_samples=400;   // number of input samples
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhM:n:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'M': M = atoi(optarg);             break;
+        case 'n': num_samples = atoi(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate options
+    if (M < 2) {
+        fprintf(stderr,"error: %s, decim factor must be greater than 1\n", argv[0]);
+        exit(1);
+    } else if (num_samples < 1) {
+        fprintf(stderr,"error: %s, must have at least one sample\n", argv[0]);
+        usage();
+        return 1;
+    }
+
+    // ensure number of samples is divisible by M
+    num_samples += (num_samples % M);
+
+    // create decimator from prototype
+    unsigned int order = 8;
+    iirdecim_crcf q = iirdecim_crcf_create_default(M,order);
+
+    // compute group delay
+    float delay = iirdecim_crcf_groupdelay(q,0.0f);
+
+    // generate input signal and decimate
+    float complex x[num_samples];   // input samples
+    float complex y[num_samples/M]; // output samples
+    unsigned int i;
+    unsigned int w_len = num_samples > 4*delay ? num_samples - 4*delay : num_samples;
+    for (i=0; i<num_samples; i++) {
+        x[i] =  cexpf(_Complex_I*(2*M_PI*0.03*i - 0.3*i*i/(float)num_samples));
+        x[i] *= i < w_len ? hamming(i,w_len) : 0.0f;
+    }
+
+    // decimate input
+    for (i=0; i<num_samples/M; i++)
+        iirdecim_crcf_execute(q, &x[M*i], &y[i]);
+
+    // destroy decimator object
+    iirdecim_crcf_destroy(q);
+
+#if 0
+    // print results to screen
+    printf("x(t) :\n");
+    for (i=0; i<num_samples; i++)
+        printf("  x(%4u) = %8.4f + j*%8.4f;\n", i, crealf(x[i]), cimagf(x[i]));
+
+    printf("y(t) :\n");
+    for (i=0; i<num_samples; i++) {
+        printf("  y(%4u) = %8.4f + j*%8.4f;", i, crealf(y[i]), cimagf(y[i]));
+        if ( (i >= M*m) && ((i%M)==0))
+            printf(" **\n");
+        else
+            printf("\n");
+    }
+#endif
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"M = %u;\n", M);
+    fprintf(fid,"delay = %f;\n", delay);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples/M);\n");
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<num_samples/M; i++)
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tx = [0:(num_samples  -1)];\n");
+    fprintf(fid,"ty = [0:(num_samples/M-1)]*M - delay;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(tx,real(x),'-s','MarkerSize',1,ty,real(y),'-s','MarkerSize',3);\n");
+    fprintf(fid,"    legend('input','decim','location','northeast');\n");
+    fprintf(fid,"    axis([0 num_samples -1.2 1.2]);\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('real');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(tx,imag(x),'-s','MarkerSize',1,ty,imag(y),'-s','MarkerSize',3);\n");
+    fprintf(fid,"    legend('input','decim','location','northeast');\n");
+    fprintf(fid,"    axis([0 num_samples -1.2 1.2]);\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('imag');\n");
+    fprintf(fid,"    grid on;\n");
+    
+    // plot spectral response
+    fprintf(fid,"nfft = 2048;\n");
+    fprintf(fid,"X  = 20*log10(abs(fftshift(fft(x/length(x),nfft))));\n");
+    fprintf(fid,"Y  = 20*log10(abs(fftshift(fft(y/length(y),nfft))));\n");
+    fprintf(fid,"fx = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"fy = fx / M;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(fx,X,'-','Color',[0.5 0.5 0.5],...\n");
+    fprintf(fid,"     fy,Y,'-','Color',[0.0 0.5 0.3],'LineWidth',2);\n");
+    fprintf(fid,"legend('input','interp','location','northeast');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/Fs]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 0]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/iirdes_analog_example.c b/examples/iirdes_analog_example.c
new file mode 100644
index 0000000..efab30e
--- /dev/null
+++ b/examples/iirdes_analog_example.c
@@ -0,0 +1,264 @@
+//
+// iirdes_analog_example.c
+//
+// Tests infinite impulse reponse (IIR) analog filter design.
+// While this example seems purely academic as IIR filters
+// used in liquid are all digital, it is important to realize
+// that they are all derived from their analog counterparts.
+// This example serves to check the response of the analog
+// filters to ensure they are correct.  The results of design
+// are written to a file.
+// SEE ALSO: iirdes_example.c
+//           iir_filter_crcf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirdes_analog_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirdes_analog_example -- infinite impulse response filter design\n");
+    printf("options (default values in []):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [butter], cheby1, cheby2, ellip, bessel\n");
+//    printf("  b     : filter transformation: [LP], HP, BP, BS\n");
+    printf("  n     : filter order, n > 0 [5]\n");
+    printf("  r     : passband ripple in dB (cheby1, ellip), r > 0 [3.0]\n");
+    printf("  s     : stopband attenuation in dB (cheby2, ellip), s > 0 [60.0]\n");
+    printf("  f     : angular passband cut-off frequency, f > 0 [1.0]\n");
+//    printf("  c     : center frequency (BP, BS cases), 0 < c < 0.5 [0.25]\n");
+//    printf("  o     : format [sos], tf\n");
+//    printf("          sos   : second-order sections form\n");
+//    printf("          tf    : regular transfer function form (potentially\n");
+//    printf("                  unstable for large orders\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int order=3;   // filter order
+    float wc=1.0f;          // angular cutoff frequency
+    float Ap=3.0f;          // pass-band Ap
+    float As=60.0f;         // stop-band attenuation
+
+    // filter type
+    liquid_iirdes_filtertype ftype = LIQUID_IIRDES_BUTTER;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:n:r:s:f:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 't':
+            if (strcmp(optarg,"butter")==0) {
+                ftype = LIQUID_IIRDES_BUTTER;
+            } else if (strcmp(optarg,"cheby1")==0) {
+                ftype = LIQUID_IIRDES_CHEBY1;
+            } else if (strcmp(optarg,"cheby2")==0) {
+                ftype = LIQUID_IIRDES_CHEBY2;
+            } else if (strcmp(optarg,"ellip")==0) {
+                ftype = LIQUID_IIRDES_ELLIP;
+            } else if (strcmp(optarg,"bessel")==0) {
+                ftype = LIQUID_IIRDES_BESSEL;
+            } else {
+                fprintf(stderr,"error: %s, unknown filter type \"%s\"\n", argv[0], optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        case 'n': order = atoi(optarg); break;
+        case 'r': Ap = atof(optarg);    break;
+        case 's': As = atof(optarg);    break;
+        case 'f': wc = atof(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (wc <= 0) {
+        fprintf(stderr,"error: %s, cutoff frequency out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (Ap <= 0) {
+        fprintf(stderr,"error: %s, pass-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (As <= 0) {
+        fprintf(stderr,"error: %s, stop-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    }
+
+    // number of analaog poles/zeros
+    unsigned int npa = order;
+    unsigned int nza = 0;
+
+    // complex analog zeros, poles, gain
+    float complex za[order];
+    float complex pa[order];
+    float complex ka;
+
+    unsigned int i;
+    
+    unsigned int r = order % 2;
+    unsigned int L = (order-r)/2;
+
+    float Gp, Gs;
+    float ep = sqrtf( powf(10.0f, Ap / 10.0f) - 1.0f );
+    float es = powf(10.0f, -As / 20.0f);
+
+    switch (ftype) {
+    case LIQUID_IIRDES_BUTTER:
+        printf("Butterworth filter design:\n");
+        nza = 0;
+        butter_azpkf(order,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY1:
+        printf("Cheby-I filter design:\n");
+        nza = 0;
+        cheby1_azpkf(order,ep,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY2:
+        printf("Cheby-II filter design:\n");
+        nza = 2*L;
+        float epsilon = powf(10.0f, -As/20.0f);
+        cheby2_azpkf(order,epsilon,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_ELLIP:
+        printf("elliptic filter design:\n");
+        nza = 2*L;
+        Gp = powf(10.0f, -Ap / 20.0f);
+        Gs = powf(10.0f, -As / 20.0f);
+        printf("  Gp = %12.8f\n", Gp);
+        printf("  Gs = %12.8f\n", Gs);
+
+        // epsilon values
+        ep = sqrtf(1.0f/(Gp*Gp) - 1.0f);
+        es = sqrtf(1.0f/(Gs*Gs) - 1.0f);
+
+        ellip_azpkf(order,ep,es,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_BESSEL:
+        printf("Bessel filter design:\n");
+        bessel_azpkf(order,za,pa,&ka);
+        nza = 0;
+        break;
+    default:
+        fprintf(stderr,"error: %s: unknown filter type\n", argv[0]);
+        exit(1);
+    }
+
+    // transform zeros, poles, gain
+    for (i=0; i<npa; i++) {
+        pa[i] *= wc;
+        ka *= wc;
+    }
+    for (i=0; i<nza; i++) {
+        za[i] *= wc;
+        ka /= wc;
+    }
+
+    // print zeros, poles, gain to screen
+    for (i=0; i<nza; i++)
+        printf("z(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(za[i]), cimagf(za[i]));
+    printf("\n");
+    for (i=0; i<npa; i++)
+        printf("p(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(pa[i]), cimagf(pa[i]));
+    printf("\n");
+    printf("ka = %12.4e + j*%12.4e;\n", crealf(ka), cimagf(ka));
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"order=%u;\n", order);
+    fprintf(fid,"wc = %12.8f;\n", wc);
+    fprintf(fid,"npa = %u;\n", npa);
+    fprintf(fid,"nza = %u;\n", nza);
+    fprintf(fid,"pa = zeros(1,npa);\n");
+    fprintf(fid,"za = zeros(1,nza);\n");
+    for (i=0; i<npa; i++)
+        fprintf(fid,"pa(%3u) = %16.8e + j*%16.8e;\n", i+1, crealf(pa[i]), cimagf(pa[i]));
+    for (i=0; i<nza; i++)
+        fprintf(fid,"za(%3u) = %16.8e + j*%16.8e;\n", i+1, crealf(za[i]), cimagf(za[i]));
+
+    fprintf(fid,"k = %16.8e;\n", crealf(ka));
+
+    fprintf(fid,"b = 1;\n");
+    fprintf(fid,"for i=1:nza,\n");
+    fprintf(fid,"  b = conv(b,[1 za(i)]);\n");
+    fprintf(fid,"end;\n");
+
+    fprintf(fid,"a = 1;\n");
+    fprintf(fid,"for i=1:npa,\n");
+    fprintf(fid,"  a = conv(a,[1 pa(i)]);\n");
+    fprintf(fid,"end;\n");
+
+    fprintf(fid,"b = real(b)*k;\n");
+    fprintf(fid,"a = real(a);\n");
+
+    fprintf(fid,"w = 10.^[-2:0.002:2]*wc;\n");
+    fprintf(fid,"s = j*w;\n");
+    fprintf(fid,"h = polyval(b,s) ./ polyval(a,s);\n");
+    fprintf(fid,"H = 20*log10(abs(h));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = [0:0.02:1]*2*pi;\n");
+    fprintf(fid,"plot(wc*cos(t),wc*sin(t),'-','Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"     real(pa),imag(pa),'x');\n");
+    fprintf(fid,"if nza > 0,\n");
+    fprintf(fid,"  hold on; plot(real(za),imag(za),'ob'); hold off;\n");
+    fprintf(fid,"  legend('(\\omega_c)','poles','zeros',0);\n");
+    fprintf(fid,"else,\n");
+    fprintf(fid,"  legend('(\\omega_c)','poles',0);\n");
+    fprintf(fid,"end;\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2*max([wc abs(pa) abs(za)]));\n");
+    //fprintf(fid,"     real(za),imag(za),'x');\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('real');\n");
+    fprintf(fid,"ylabel('imag');\n");
+    fprintf(fid,"\n");
+
+    // plot group delay
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"gd = gradient(unwrap(arg(h)))./gradient(w);\n");
+    fprintf(fid,"semilogx(w,gd);\n");
+    fprintf(fid,"xlabel('Angular frequency, \\omega [rad/s]');\n");
+    fprintf(fid,"ylabel('group delay [s]');\n");
+    fprintf(fid,"gd_min = min(gd); if gd_min < 0, gd_min=0; end;\n");
+    fprintf(fid,"axis([wc/100 wc*100 gd_min 1.1*max(gd)]);\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot magnitude response
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  semilogx(w,H,'-'); grid on;\n");
+    fprintf(fid,"  axis([wc/100 wc*100 -5 1]);\n");
+    fprintf(fid,"  xlabel('Angular frequency, \\omega [rad/s]');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  semilogx(w,H,'-'); grid on;\n");
+    fprintf(fid,"  axis([wc/100 wc*100 -100 10]);\n");
+    fprintf(fid,"  xlabel('Angular frequency, \\omega [rad/s]');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/iirdes_example.c b/examples/iirdes_example.c
new file mode 100644
index 0000000..9e8aa89
--- /dev/null
+++ b/examples/iirdes_example.c
@@ -0,0 +1,262 @@
+// 
+// iirdes_example.c
+//
+// Tests infinite impulse reponse (IIR) digital filter design.
+// SEE ALSO: iirdes_analog_example.c
+//           iir_filter_crcf_example.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirdes_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirdes_example -- infinite impulse response filter design\n");
+    printf("options (default values in []):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [butter], cheby1, cheby2, ellip, bessel\n");
+    printf("  b     : filter transformation: [LP], HP, BP, BS\n");
+    printf("  n     : filter order, n > 0 [5]\n");
+    printf("  r     : passband ripple in dB (cheby1, ellip), r > 0 [1.0]\n");
+    printf("  s     : stopband attenuation in dB (cheby2, ellip), s > 0 [60.0]\n");
+    printf("  f     : passband cut-off, 0 < f < 0.5 [0.2]\n");
+    printf("  c     : center frequency (BP, BS cases), 0 < c < 0.5 [0.25]\n");
+    printf("  o     : format [sos], tf\n");
+    printf("          sos   : second-order sections form\n");
+    printf("          tf    : regular transfer function form (potentially\n");
+    printf("                  unstable for large orders\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int order=5;   // filter order
+    float fc = 0.20f;       // cutoff frequency (low-pass prototype)
+    float f0 = 0.25f;       // center frequency (band-pass, band-stop)
+    float As = 60.0f;       // stopband attenuation [dB]
+    float Ap = 1.0f;        // passband ripple [dB]
+
+    // filter type
+    liquid_iirdes_filtertype ftype = LIQUID_IIRDES_BUTTER;
+
+    // band type
+    liquid_iirdes_bandtype btype = LIQUID_IIRDES_LOWPASS;
+
+    // output format: second-order sections or transfer function
+    liquid_iirdes_format format = LIQUID_IIRDES_SOS;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:b:n:r:s:f:c:o:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 't':
+            if (strcmp(optarg,"butter")==0) {
+                ftype = LIQUID_IIRDES_BUTTER;
+            } else if (strcmp(optarg,"cheby1")==0) {
+                ftype = LIQUID_IIRDES_CHEBY1;
+            } else if (strcmp(optarg,"cheby2")==0) {
+                ftype = LIQUID_IIRDES_CHEBY2;
+            } else if (strcmp(optarg,"ellip")==0) {
+                ftype = LIQUID_IIRDES_ELLIP;
+            } else if (strcmp(optarg,"bessel")==0) {
+                ftype = LIQUID_IIRDES_BESSEL;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown filter type \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        case 'b':
+            if (strcmp(optarg,"LP")==0) {
+                btype = LIQUID_IIRDES_LOWPASS;
+            } else if (strcmp(optarg,"HP")==0) {
+                btype = LIQUID_IIRDES_HIGHPASS;
+            } else if (strcmp(optarg,"BP")==0) {
+                btype = LIQUID_IIRDES_BANDPASS;
+            } else if (strcmp(optarg,"BS")==0) {
+                btype = LIQUID_IIRDES_BANDSTOP;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown band type \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        case 'n': order = atoi(optarg); break;
+        case 'r': Ap = atof(optarg);    break;
+        case 's': As = atof(optarg);    break;
+        case 'f': fc = atof(optarg);    break;
+        case 'c': f0 = atof(optarg);    break;
+        case 'o':
+            if (strcmp(optarg,"sos")==0) {
+                format = LIQUID_IIRDES_SOS;
+            } else if (strcmp(optarg,"tf")==0) {
+                format = LIQUID_IIRDES_TF;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown output format \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (fc <= 0 || fc >= 0.5) {
+        fprintf(stderr,"error: %s, cutoff frequency out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (f0 < 0 || f0 > 0.5) {
+        fprintf(stderr,"error: %s, center frequency out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (Ap <= 0) {
+        fprintf(stderr,"error: %s, pass-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (As <= 0) {
+        fprintf(stderr,"error: %s, stop-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    }
+
+    // derived values : compute filter length
+    unsigned int N = order; // effective order
+    // filter order effectively doubles for band-pass, band-stop
+    // filters due to doubling the number of poles and zeros as
+    // a result of filter transformation
+    if (btype == LIQUID_IIRDES_BANDPASS ||
+        btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        N *= 2;
+    }
+    unsigned int r = N % 2;     // odd/even order
+    unsigned int L = (N-r)/2;   // filter semi-length
+
+    // allocate memory for filter coefficients
+    unsigned int h_len = (format == LIQUID_IIRDES_SOS) ? 3*(L+r) : N+1;
+    float b[h_len];
+    float a[h_len];
+
+    // design filter
+    liquid_iirdes(ftype, btype, format, order, fc, f0, Ap, As, b, a);
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    fprintf(fid,"n=%u;\n", order);
+    fprintf(fid,"r=%u;\n", r);
+    fprintf(fid,"L=%u;\n", L);
+    fprintf(fid,"nfft=1024;\n");
+
+    unsigned int i;
+    if (format == LIQUID_IIRDES_TF) {
+        // print coefficients
+        for (i=0; i<=N; i++) printf("a[%3u] = %12.8f;\n", i, a[i]);
+        for (i=0; i<=N; i++) printf("b[%3u] = %12.8f;\n", i, b[i]);
+
+        fprintf(fid,"a = zeros(1,n+1);\n");
+        fprintf(fid,"b = zeros(1,n+1);\n");
+        for (i=0; i<=N; i++) {
+            fprintf(fid,"a(%3u) = %12.4e;\n", i+1, a[i]);
+            fprintf(fid,"b(%3u) = %12.4e;\n", i+1, b[i]);
+        }
+        fprintf(fid,"\n");
+        fprintf(fid,"H = fft(b,nfft)./fft(a,nfft);\n");
+        fprintf(fid,"H = fftshift(H);\n");
+        fprintf(fid,"%% group delay\n");
+        fprintf(fid,"c = conv(b,fliplr(conj(a)));\n");
+        fprintf(fid,"cr = c.*[0:(length(c)-1)];\n");
+        fprintf(fid,"t0 = fftshift(fft(cr,nfft));\n");
+        fprintf(fid,"t1 = fftshift(fft(c, nfft));\n");
+        fprintf(fid,"polebins = find(abs(t1)<1e-6);\n");
+        fprintf(fid,"t0(polebins)=0;\n");
+        fprintf(fid,"t1(polebins)=1;\n");
+        fprintf(fid,"gd = real(t0./t1) - length(a) + 1;\n");
+
+    } else {
+        float * B = b;
+        float * A = a;
+        // print coefficients
+        printf("B [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", B[3*i+0], B[3*i+1], B[3*i+2]);
+        printf("A [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", A[3*i+0], A[3*i+1], A[3*i+2]);
+
+        unsigned int j;
+        for (i=0; i<L+r; i++) {
+            for (j=0; j<3; j++) {
+                fprintf(fid,"B(%3u,%3u) = %16.8e;\n", i+1, j+1, B[3*i+j]);
+                fprintf(fid,"A(%3u,%3u) = %16.8e;\n", i+1, j+1, A[3*i+j]);
+            }
+        }
+        fprintf(fid,"\n");
+        fprintf(fid,"H = ones(1,nfft);\n");
+        fprintf(fid,"gd = zeros(1,nfft);\n");
+        fprintf(fid,"t0 = zeros(1,nfft);\n");
+        fprintf(fid,"t1 = zeros(1,nfft);\n");
+        fprintf(fid,"for i=1:(L+r),\n");
+        fprintf(fid,"    H = H .* fft(B(i,:),nfft)./fft(A(i,:),nfft);\n");
+        fprintf(fid,"    %% group delay\n");
+        fprintf(fid,"    c = conv(B(i,:),fliplr(conj(A(i,:))));\n");
+        fprintf(fid,"    cr = c.*[0:4];\n");
+        fprintf(fid,"    t0 = fftshift(fft(cr,nfft));\n");
+        fprintf(fid,"    t1 = fftshift(fft(c, nfft));\n");
+        fprintf(fid,"    polebins = find(abs(t1)<1e-6);\n");
+        fprintf(fid,"    t0(polebins)=0;\n");
+        fprintf(fid,"    t1(polebins)=1;\n");
+        fprintf(fid,"    gd = gd + real(t0./t1) - 2;\n");
+        fprintf(fid,"end;\n");
+        fprintf(fid,"H = fftshift(H);\n");
+    }
+
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"figure;\n");
+
+    // plot magnitude response (detail)
+    fprintf(fid,"subplot(3,1,1),\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'-','Color',[0.5 0 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 -4 1]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Filter PSD [dB]');\n");
+
+    // plot magnitude response (full range)
+    fprintf(fid,"subplot(3,1,2),\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'-','Color',[0.5 0 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 -100 10]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Filter PSD [dB]');\n");
+
+    // plot group delay
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(f,gd,'-','Color',[0 0.5 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 0 ceil(1.1*max(gd))]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Group delay [samples]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/iirdes_pll_example.c b/examples/iirdes_pll_example.c
new file mode 100644
index 0000000..2c8f2f2
--- /dev/null
+++ b/examples/iirdes_pll_example.c
@@ -0,0 +1,150 @@
+//
+// iirdes_pll_example.c
+//
+// This example demonstrates 2nd-order IIR phase-locked loop filter
+// design with a practical simulation.
+//
+// SEE ALSO: nco_pll_example.c
+//           nco_pll_modem_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirdes_pll_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirdes_pll_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  b     : pll bandwidth, default: 0.01\n");
+    printf("  z     : zeta (damping factor), default 0.70711\n");
+    printf("  K     : loop filter gain, default: 1000\n");
+    printf("  n     : number of samples, default: 512\n");
+    printf("  p     : phase offset (radians), default: pi/4\n");
+    printf("  f     : frequency offset (radians), default: 0.3\n");
+}
+
+int main(int argc, char*argv[]) {
+    srand( time(NULL) );
+
+    // options
+    float phase_offset = M_PI / 4.0f;   // phase offset
+    float frequency_offset = 0.3f;      // frequency offset
+    float pll_bandwidth = 0.01f;        // PLL bandwidth
+    float zeta = 1/sqrtf(2.0f);         // PLL damping factor
+    float K = 1000.0f;                  // PLL loop gain
+    unsigned int n=512;                 // number of iterations
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhb:z:K:n:p:f:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();    return 0;
+        case 'b':   pll_bandwidth = atof(optarg);   break;
+        case 'z':   zeta = atof(optarg);            break;
+        case 'K':   K = atof(optarg);               break;
+        case 'n':   n = atoi(optarg);               break;
+        case 'p':   phase_offset = atof(optarg);    break;
+        case 'f':   frequency_offset= atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+    unsigned int d=n/32;      // print every "d" lines
+
+    // validate input
+    if (pll_bandwidth <= 0.0f) {
+        fprintf(stderr,"error: bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (zeta <= 0.0f) {
+        fprintf(stderr,"error: damping factor must be greater than 0\n");
+        exit(1);
+    } else if (K <= 0.0f) {
+        fprintf(stderr,"error: loop gain must be greater than 0\n");
+        exit(1);
+    }
+
+    // data arrays
+    float complex x[n];         // input complex sinusoid
+    float complex y[n];         // output complex sinusoid
+    float phase_error[n];       // output phase error
+
+    // generate PLL filter
+    float b[3];
+    float a[3];
+    iirdes_pll_active_lag(pll_bandwidth, zeta, K, b, a);
+    iirfilt_rrrf pll = iirfilt_rrrf_create(b,3,a,3);
+    iirfilt_rrrf_print(pll);
+
+    unsigned int i;
+    float phi;
+    for (i=0; i<n; i++) {
+        phi = phase_offset + i*frequency_offset;
+        x[i] = cexpf(_Complex_I*phi);
+    }
+
+    // run loop
+    float theta = 0.0f;
+    y[0] = 1.0f;
+    for (i=0; i<n; i++) {
+
+        // generate complex sinusoid
+        y[i] = cexpf(_Complex_I*theta);
+
+        // compute phase error
+        phase_error[i] = cargf(x[i]*conjf(y[i]));
+
+        // update pll
+        iirfilt_rrrf_execute(pll, phase_error[i], &theta);
+
+        // print phase error
+        if ((i)%d == 0 || i==n-1 || i==0)
+            printf("%4u : phase error = %12.8f\n", i, phase_error[i]);
+    }
+
+    // destroy filter object
+    iirfilt_rrrf_destroy(pll);
+
+    // write output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"x = zeros(1,n);\n");
+    fprintf(fid,"y = zeros(1,n);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"e(%4u) = %12.4e;\n", i+1, phase_error[i]);
+    }
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),t,real(y));\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,imag(x),t,imag(y));\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,e);\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('phase error');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/iirfilt_cccf_example.c b/examples/iirfilt_cccf_example.c
new file mode 100644
index 0000000..34aebab
--- /dev/null
+++ b/examples/iirfilt_cccf_example.c
@@ -0,0 +1,205 @@
+//
+// iirfilt_cccf_example.c
+//
+// Complex infinite impulse response filter example. Demonstrates the
+// functionality of iirfilt with complex coefficients by designing a
+// filter with specified parameters and then filters noise.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirfilt_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirfilt_cccf_example -- infinite impulse response filter example\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print help\n");
+    printf("  t     : filter type: [butter], cheby1, cheby2, ellip, bessel\n");
+    printf("  b     : filter transformation: [LP], HP, BP, BS\n");
+    printf("  n     : filter order, n > 0 [5]\n");
+    printf("  r     : passband ripple in dB (cheby1, ellip), r > 0 [1.0]\n");
+    printf("  s     : stopband attenuation in dB (cheby2, ellip), s > 0 [40.0]\n");
+    printf("  f     : passband cut-off, 0 < f < 0.5 [0.2]\n");
+    printf("  c     : center frequency (BP, BS cases), 0 < c < 0.5 [0.25]\n");
+    printf("  o     : format [sos], tf\n");
+    printf("          sos   : second-order sections form\n");
+    printf("          tf    : regular transfer function form (potentially\n");
+    printf("                  unstable for large orders\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int order=4;   // filter order
+    float fc=0.1f;          // cutoff frequency
+    float f0=0.0f;          // center frequency
+    float Ap=1.0f;          // pass-band ripple
+    float As=40.0f;         // stop-band attenuation
+    unsigned int n=128;     // number of samples
+    liquid_iirdes_filtertype ftype  = LIQUID_IIRDES_ELLIP;
+    liquid_iirdes_bandtype   btype  = LIQUID_IIRDES_LOWPASS;
+    liquid_iirdes_format     format = LIQUID_IIRDES_SOS;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"ht:b:n:r:s:f:c:o:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();    return 0;
+        case 't':
+            if      (strcmp(optarg,"butter")==0) ftype = LIQUID_IIRDES_BUTTER;
+            else if (strcmp(optarg,"cheby1")==0) ftype = LIQUID_IIRDES_CHEBY1;
+            else if (strcmp(optarg,"cheby2")==0) ftype = LIQUID_IIRDES_CHEBY2;
+            else if (strcmp(optarg,"ellip") ==0) ftype = LIQUID_IIRDES_ELLIP;
+            else if (strcmp(optarg,"bessel")==0) ftype = LIQUID_IIRDES_BESSEL;
+            else {
+                fprintf(stderr,"error: iirdes_example, unknown filter type '%s'\n", optarg);
+                exit(1);
+            }
+            break;
+        case 'b':
+            if      (strcmp(optarg,"LP")==0) btype = LIQUID_IIRDES_LOWPASS;
+            else if (strcmp(optarg,"HP")==0) btype = LIQUID_IIRDES_HIGHPASS;
+            else if (strcmp(optarg,"BP")==0) btype = LIQUID_IIRDES_BANDPASS;
+            else if (strcmp(optarg,"BS")==0) btype = LIQUID_IIRDES_BANDSTOP;
+            else {
+                fprintf(stderr,"error: iirdes_example, unknown band type '%s'\n", optarg);
+                exit(1);
+            }
+            break;
+        case 'n': order = atoi(optarg); break;
+        case 'r': Ap = atof(optarg);    break;
+        case 's': As = atof(optarg);    break;
+        case 'f': fc = atof(optarg);    break;
+        case 'c': f0 = atof(optarg);    break;
+        case 'o':
+            if      (strcmp(optarg,"sos")==0) format = LIQUID_IIRDES_SOS;
+            else if (strcmp(optarg,"tf") ==0) format = LIQUID_IIRDES_TF;
+            else {
+                fprintf(stderr,"error: iirdes_example, unknown output format '%s'\n", optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // design filter from prototype
+    iirfilt_cccf q = iirfilt_cccf_create_prototype(ftype, btype, format, order, fc, f0, Ap, As);
+    iirfilt_cccf_print(q);
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x[n];
+    float complex y[n];
+
+    // generate input signal (noisy sine wave with decaying amplitude)
+    unsigned int wlen = (3*n)/4;
+    for (i=0; i<n; i++) {
+        // input signal (windowed noise)
+        x[i]  = randnf() + _Complex_I*randnf();
+        x[i] *= i < wlen ? hamming(i,wlen) : 0.0f;
+
+        // run filter
+        iirfilt_cccf_execute(q, x[i], &y[i]);
+    }
+
+    // compute two-sided frequency response
+    unsigned int nfft=512;
+    float complex H[nfft];
+    for (i=0; i<nfft; i++) {
+        float freq = (float)i / (float)nfft - 0.5f;
+        iirfilt_cccf_freqresponse(q, freq, &H[i]);
+    }
+
+    // destroy filter object
+    iirfilt_cccf_destroy(q);
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"order=%u;\n", order);
+
+    // save input, output arrays
+    fprintf(fid,"n=%u;\n",n);
+    fprintf(fid,"x=zeros(1,n);\n");
+    fprintf(fid,"y=zeros(1,n);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // save frequency response
+    fprintf(fid,"nfft=%u;\n",nfft);
+    fprintf(fid,"H=zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(H[i]), cimagf(H[i]));
+
+    // plot time-domain output
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,real(y),'-','Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('input','filtered output',1);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,imag(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(y),'-','Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('input','filtered output',1);\n");
+    fprintf(fid,"  grid on;\n");
+
+    // plot spectral output
+    fprintf(fid,"X = 20*log10(abs(fftshift(fft(x))));\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot([0:(n-1)]/n-0.5, X, 'Color', [1 1 1]*0.5,\n");
+    fprintf(fid,"     [0:(n-1)]/n-0.5, Y, 'Color', [0 0.25 0.50]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('input','filtered output',1);\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot ideal frequency response
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)));\n");
+    fprintf(fid,"  axis([-0.5 0.5 -3 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('Pass band (dB)',0);\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)));\n");
+    fprintf(fid,"  axis([-0.5 0.5 -100 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('Stop band (dB)',0);\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(f,180/pi*arg(H));\n");
+    fprintf(fid,"  axis([-0.5 0.5 -180 180]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('Phase (degrees)',0);\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/iirfilt_crcf_dcblocker_example.c b/examples/iirfilt_crcf_dcblocker_example.c
new file mode 100644
index 0000000..d81321f
--- /dev/null
+++ b/examples/iirfilt_crcf_dcblocker_example.c
@@ -0,0 +1,100 @@
+//
+// iirfilt_crcf_dcblocker_example.c
+//
+// This example demonstrates how to create a DC-blocking recursive
+// (infinite impulse response) filter.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirfilt_crcf_dcblocker_example.m"
+
+int main() {
+    // options
+    unsigned int num_samples = 1200;    // number of samples
+    float        alpha       = 0.10f;   // filter cut-off
+
+    // design filter from prototype
+    iirfilt_crcf q = iirfilt_crcf_create_dc_blocker(alpha);
+    iirfilt_crcf_print(q);
+
+    // allocate memory for data arrays
+    float complex x[num_samples];   // original input
+    float complex y[num_samples];   // input with DC offset
+    float complex z[num_samples];   // DC-blocked result
+
+    // generate signals
+    unsigned int i;
+    for (i=0; i<num_samples; i++) {
+        // original input signal
+        x[i] = cexpf( (0.070f*i + 1e-4f*i*i)*_Complex_I );
+
+        // add DC offset
+        y[i] = x[i] + 2.0f*cexpf( 0.007f*_Complex_I*i );
+
+        // run filter to try to remove DC offset
+        iirfilt_crcf_execute(q, y[i], &z[i]);
+    }
+
+    // destroy filter object
+    iirfilt_crcf_destroy(q);
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"num_samples=%u;\n",num_samples);
+    fprintf(fid,"x=zeros(1,num_samples);\n");
+    fprintf(fid,"y=zeros(1,num_samples);\n");
+
+    // save input, output arrays
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+    }
+
+    // plot output
+    fprintf(fid,"t=0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(x),'-','Color',[0 0.2 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('original input');\n");
+    fprintf(fid,"  legend('real','imag','location','southwest');\n");
+    fprintf(fid,"  axis([0 num_samples -3 3]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(t,real(y),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(y),'-','Color',[0 0.5 0.2],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('input with DC offset');\n");
+    fprintf(fid,"  legend('real','imag','location','southwest');\n");
+    fprintf(fid,"  axis([0 num_samples -3 3]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t,real(z),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(z),'-','Color',[0 0.2 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('DC-blocked output');\n");
+    fprintf(fid,"  legend('real','imag','location','southwest');\n");
+    fprintf(fid,"  axis([0 num_samples -3 3]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    // close output file
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/iirfilt_crcf_example.c b/examples/iirfilt_crcf_example.c
new file mode 100644
index 0000000..71b22ba
--- /dev/null
+++ b/examples/iirfilt_crcf_example.c
@@ -0,0 +1,130 @@
+//
+// iirfilt_crcf_example.c
+//
+// Complex infinite impulse response filter example. Demonstrates
+// the functionality of iirfilt by designing a low-order
+// prototype (e.g. Butterworth) and using it to filter a noisy
+// signal.  The filter coefficients are real, but the input and
+// output arrays are complex.  The filter order and cutoff
+// frequency are specified at the beginning.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirfilt_crcf_example.m"
+
+int main() {
+    // options
+    unsigned int order =   4;       // filter order
+    float        fc    =   0.1f;    // cutoff frequency
+    float        f0    =   0.0f;    // center frequency
+    float        Ap    =   1.0f;    // pass-band ripple
+    float        As    =  40.0f;    // stop-band attenuation
+    unsigned int n     = 128;       // number of samples
+    liquid_iirdes_filtertype ftype  = LIQUID_IIRDES_ELLIP;
+    liquid_iirdes_bandtype   btype  = LIQUID_IIRDES_LOWPASS;
+    liquid_iirdes_format     format = LIQUID_IIRDES_SOS;
+
+    // design filter from prototype
+    iirfilt_crcf q = iirfilt_crcf_create_prototype(
+                        ftype, btype, format, order, fc, f0, Ap, As);
+    iirfilt_crcf_print(q);
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x[n];
+    float complex y[n];
+
+    // generate input signal (noisy sine wave with decaying amplitude)
+    for (i=0; i<n; i++) {
+        x[i] = cexpf((2*M_PI*0.057f*_Complex_I - 0.04f)*i);
+        x[i] += 0.1f*(randnf() + _Complex_I*randnf());
+
+        // run filter
+        iirfilt_crcf_execute(q, x[i], &y[i]);
+    }
+
+    // compute response
+    unsigned int nfft=512;
+    float complex H[nfft];
+    for (i=0; i<nfft; i++) {
+        float freq = 0.5f * (float)i / (float)nfft;
+        iirfilt_crcf_freqresponse(q, freq, &H[i]);
+    }
+
+    // destroy filter object
+    iirfilt_crcf_destroy(q);
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"order=%u;\n", order);
+    fprintf(fid,"n=%u;\n",n);
+    fprintf(fid,"nfft=%u;\n",nfft);
+    fprintf(fid,"x=zeros(1,n);\n");
+    fprintf(fid,"y=zeros(1,n);\n");
+    fprintf(fid,"H=zeros(1,nfft);\n");
+
+    for (i=0; i<n; i++) {
+        //printf("%4u : %12.8f + j*%12.8f\n", i, crealf(y), cimagf(y));
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(H[i]), cimagf(H[i]));
+
+    // plot output
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,real(y),'-','Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('input','filtered output','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,imag(x),'-','Color',[1 1 1]*0.5,'LineWidth',1,...\n");
+    fprintf(fid,"       t,imag(y),'-','Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('input','filtered output','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+
+    // plot frequency response
+    fprintf(fid,"f=0.5*[0:(nfft-1)]/nfft;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0 0.5 -3 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('Pass band [dB]');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0 0.5 -100 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('Stop band [dB]');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(f,180/pi*arg(H),'Color',[0 0.25 0.5],'LineWidth',2);\n");
+    //fprintf(fid,"  axis([0 0.5 -100 0]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('Phase [degrees]');\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/iirinterp_crcf_example.c b/examples/iirinterp_crcf_example.c
new file mode 100644
index 0000000..75cc3c1
--- /dev/null
+++ b/examples/iirinterp_crcf_example.c
@@ -0,0 +1,135 @@
+//
+// iirinterp_crcf_example.c
+//
+// This example demonstrates the iirinterp object (IIR interpolator)
+// interface.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirinterp_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirinterp_crcf_example:\n");
+    printf("  h     : print help\n");
+    printf("  k     : samples/symbol (interp factor), k > 1, default: 4\n");
+    printf("  n     : number of input samples, default: 64\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k = 4;             // interpolation factor
+    unsigned int num_samples = 64;  // number of input samples
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:n:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'k': k           = atoi(optarg);   break;
+        case 'n': num_samples = atoi(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate options
+    if (k < 2) {
+        fprintf(stderr,"error: %s, interp factor must be greater than 1\n", argv[0]);
+        exit(1);
+    } else if (num_samples < 1) {
+        fprintf(stderr,"error: %s, must have at least one data symbol\n", argv[0]);
+        usage();
+        return 1;
+    }
+
+    // create interpolator from prototype
+    unsigned int order = 8;
+    iirinterp_crcf q = iirinterp_crcf_create_default(k,order);
+
+    // derived values
+    float delay = iirinterp_crcf_groupdelay(q,0.0f);
+
+    // generate input signal and interpolate
+    float complex x[  num_samples]; // input samples
+    float complex y[k*num_samples]; // output samples
+    unsigned int i;
+    for (i=0; i<num_samples; i++) {
+        // input signal (sinusoidal chirp)
+        x[i] = cexpf(_Complex_I*(-0.17f*i + 0.9*i*i/(float)num_samples));
+
+        // apply window
+        x[i] *= (i < num_samples-5) ? hamming(i,num_samples) : 0.0f;
+
+        // push through interpolator
+        iirinterp_crcf_execute(q, x[i], &y[k*i]);
+    }
+
+    // destroy interpolator object
+    iirinterp_crcf_destroy(q);
+
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"delay = %f;\n", delay);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"x = zeros(1,  num_samples);\n");
+    fprintf(fid,"y = zeros(1,k*num_samples);\n");
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<k*num_samples; i++)
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, k*crealf(y[i]), k*cimagf(y[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tx = [0:(  num_samples-1)];\n");
+    fprintf(fid,"ty = [0:(k*num_samples-1)]/k - delay;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"    plot(tx,real(x),'-s','MarkerSize',3,ty,real(y),'-s','MarkerSize',1);\n");
+    fprintf(fid,"    legend('input','interp','location','northeast');\n");
+    fprintf(fid,"    axis([0 num_samples -1.2 1.2]);\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('real');\n");
+    fprintf(fid,"    grid on;\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"    plot(tx,imag(x),'-s','MarkerSize',3,ty,imag(y),'-s','MarkerSize',1);\n");
+    fprintf(fid,"    legend('input','interp','location','northeast');\n");
+    fprintf(fid,"    axis([0 num_samples -1.2 1.2]);\n");
+    fprintf(fid,"    xlabel('time');\n");
+    fprintf(fid,"    ylabel('imag');\n");
+    fprintf(fid,"    grid on;\n");
+
+    // power spectral density
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"fx   = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"fy   = k*fx;\n");
+    fprintf(fid,"X    = 20*log10(abs(fftshift(fft(x  ,nfft))));\n");
+    fprintf(fid,"Y    = 20*log10(abs(fftshift(fft(y/k,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(fx,X,'LineWidth',2, fy,Y,'LineWidth',1);\n");
+    fprintf(fid,"legend('input','interp','location','northeast');\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/interleaver_example.c b/examples/interleaver_example.c
new file mode 100644
index 0000000..4aea5f3
--- /dev/null
+++ b/examples/interleaver_example.c
@@ -0,0 +1,60 @@
+//
+// interleaver_example.c
+//
+// This example demonstrates the functionality of the liquid
+// interleaver object.  Interleavers serve to distribute 
+// grouped bit errors evenly throughout a block of data. This
+// aids certain forward error-correction codes in correcting
+// bit errors.  In this example, data bits are interleaved and
+// de-interleaved; the resulting sequence is validated to
+// match the original.
+// SEE ALSO: packetizer_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h> // for rand()
+
+#include "liquid.h"
+
+int main() {
+    // options
+    unsigned int n=9; // message length
+
+    // create the interleaver
+    interleaver q = interleaver_create(n);
+    interleaver_set_depth(q, 4);
+    interleaver_print(q);
+
+    // create arrays
+    unsigned char x[n]; // original message data
+    unsigned char y[n]; // interleaved data
+    unsigned char z[n]; // de-interleaved data
+
+    // generate random data sequence
+    unsigned int i;
+    for (i=0; i<n; i++)
+        x[i] = rand()%256;
+
+    // interleave/de-interleave the data
+    interleaver_encode(q,x,y);
+    interleaver_decode(q,y,z);
+    //interleaver_print(q);
+
+    // print, compute errors
+    unsigned int num_errors=0;
+    printf("%6s %6s %6s\n", "x", "y", "z");
+    for (i=0; i<n; i++) {
+        printf("%6u %6u %6u\n", x[i], y[i], z[i]);
+        //printf("y[%u] = %u\n", i, (unsigned int) (y[i]));
+        //printf("y[%u] = %#0x\n", i, (unsigned int) (y[i]));
+        num_errors += (x[i]==z[i]) ? 0 : 1;
+    }
+    printf("errors: %u / %u\n", num_errors, n);
+
+    // destroy the interleaver object
+    interleaver_destroy(q);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/interleaver_scatterplot_example.c b/examples/interleaver_scatterplot_example.c
new file mode 100644
index 0000000..4861fd9
--- /dev/null
+++ b/examples/interleaver_scatterplot_example.c
@@ -0,0 +1,144 @@
+//
+// interleaver_scatterplot_example.c
+// 
+// generate interleaver scatterplot figure
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "interleaver_scatterplot_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("interleaver_scatterplot_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : number of bytes, default: 8\n");
+    printf("  d     : interleaver depth, default: 4\n");
+}
+
+// find most significant bit in array (starting from left)
+unsigned int interleaver_find_bit(unsigned char * _x,
+                                  unsigned int _n);
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int n=8; // message length
+    unsigned int depth = 4;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:d:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'n': n = atoi(optarg);     break;
+        case 'd': depth = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // create the interleaver
+    interleaver q = interleaver_create(n);
+    interleaver_set_depth(q, depth);
+
+    // create arrays
+    unsigned char x[n]; // original message data
+    unsigned char y[n]; // interleaved data
+
+    unsigned int index[8*n];
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k=0;
+
+    // initialize data
+    memset(x, 0, n*sizeof(unsigned char));
+    for (i=0; i<n; i++) {
+        for (j=0; j<8; j++) {
+            x[i] = 1 << (8-j-1);
+
+            // run interleaver and find most significant
+            // bit in resulting array
+            interleaver_encode(q,x,y);
+            index[k] = interleaver_find_bit(y,n);
+
+            // increment index counter
+            k++;
+        }
+        // reset input message sequence
+        x[i] = 0;
+    }
+
+    assert(k==8*n);
+
+    // destroy the interleaver object
+    interleaver_destroy(q);
+
+    // write output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    if (fid == NULL) {
+        fprintf(stderr,"error: %s, could not open file '%s' for writing.\n", argv[0],OUTPUT_FILENAME);
+        exit(1);
+    }
+    // print header
+    fprintf(fid,"%% %s : auto-generated file (do not edit)\n", OUTPUT_FILENAME);
+    fprintf(fid,"%% invoked as :");
+    for (i=0; i<argc; i++)
+        fprintf(fid," %s",argv[i]);
+    fprintf(fid,"\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;\n", 8*n);
+
+    // export data to file
+    for (i=0; i<8*n; i++)
+        fprintf(fid,"index(%6u) = %6u;\n", i+1, index[i]);
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"i = [0:(n-1)];\n");
+    fprintf(fid,"plot(i/n,index/n,'x');\n");
+    fprintf(fid,"axis square\n");
+    fprintf(fid,"axis([0 1 0 1]);\n");
+
+    // close it up
+    fclose(fid);
+
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// find most significant bit in array (starting from left)
+unsigned int interleaver_find_bit(unsigned char * _x,
+                                  unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        if (_x[i] != 0)
+            break;
+    }
+
+    unsigned char byte = _x[i];
+    // find most significant bit in byte
+    switch (byte) {
+    case (1<<7):    return 8*i + 0;     // 1000 0000
+    case (1<<6):    return 8*i + 1;     // 0100 0000
+    case (1<<5):    return 8*i + 2;     // 0010 0000
+    case (1<<4):    return 8*i + 3;     // 0001 0000
+    case (1<<3):    return 8*i + 4;     // 0000 1000
+    case (1<<2):    return 8*i + 5;     // 0000 0100
+    case (1<<1):    return 8*i + 6;     // 0000 0010
+    case (1<<0):    return 8*i + 7;     // 0000 0001
+    default:
+        fprintf(stderr,"error: interleaver_find_bid(), invalid array\n");
+        exit(1);
+    }
+}
+
diff --git a/examples/interleaver_soft_example.c b/examples/interleaver_soft_example.c
new file mode 100644
index 0000000..69edefc
--- /dev/null
+++ b/examples/interleaver_soft_example.c
@@ -0,0 +1,89 @@
+//
+// interleaver_soft_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h> // for rand()
+
+#include "liquid.h"
+
+int main() {
+    // options
+    unsigned int n=9; // message length (bits)
+
+    // create the interleaver
+    interleaver q = interleaver_create(n);
+    interleaver_print(q);
+
+    // create arrays
+    unsigned char msg_org[n];   // original message data (bytes)
+    unsigned char msg_int[n];   // interleaved data (bytes)
+    unsigned char msg_rec[8*n]; // interleaved data (soft bits)
+    unsigned char msg_dec[8*n]; // de-interleaved data (soft bits)
+    unsigned char msg_ppp[n];   // de-interleaved data (bytes)
+
+    // generate random data sequence
+    unsigned int i;
+    for (i=0; i<n; i++)
+        msg_org[i] = rand()%256;
+
+    // interleave the data
+    interleaver_encode(q, msg_org, msg_int);
+
+    // expand to 'soft' bits
+    for (i=0; i<n; i++) {
+        msg_rec[8*i+0] = ( (msg_int[i] >> 7) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+1] = ( (msg_int[i] >> 6) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+2] = ( (msg_int[i] >> 5) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+3] = ( (msg_int[i] >> 4) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+4] = ( (msg_int[i] >> 3) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+5] = ( (msg_int[i] >> 2) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+6] = ( (msg_int[i] >> 1) & 0x01 ) ? 255 : 0;
+        msg_rec[8*i+7] = ( (msg_int[i] >> 0) & 0x01 ) ? 255 : 0;
+    }
+
+    // de-interleave the soft bits
+    interleaver_decode_soft(q, msg_rec, msg_dec);
+
+    // pack the bits
+    for (i=0; i<n; i++) {
+        msg_ppp[i] = 0x00;
+
+        msg_ppp[i] |= (msg_dec[8*i+0] > 127) ? 0x80 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+1] > 127) ? 0x40 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+2] > 127) ? 0x20 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+3] > 127) ? 0x10 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+4] > 127) ? 0x08 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+5] > 127) ? 0x04 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+6] > 127) ? 0x02 : 0;
+        msg_ppp[i] |= (msg_dec[8*i+7] > 127) ? 0x01 : 0;
+    }
+
+    // compute errors and print results
+    unsigned int num_errors=0;
+    printf("%6s %6s %6s\n", "org", "int", "dec");
+    for (i=0; i<n; i++) {
+        unsigned char byte_int = 0x00;
+        byte_int |= (msg_rec[8*i+0] > 127) ? 0x80 : 0;
+        byte_int |= (msg_rec[8*i+1] > 127) ? 0x40 : 0;
+        byte_int |= (msg_rec[8*i+2] > 127) ? 0x20 : 0;
+        byte_int |= (msg_rec[8*i+3] > 127) ? 0x10 : 0;
+        byte_int |= (msg_rec[8*i+4] > 127) ? 0x08 : 0;
+        byte_int |= (msg_rec[8*i+5] > 127) ? 0x04 : 0;
+        byte_int |= (msg_rec[8*i+6] > 127) ? 0x02 : 0;
+        byte_int |= (msg_rec[8*i+7] > 127) ? 0x01 : 0;
+
+        printf("%6u %6u %6u\n", msg_org[i], byte_int, msg_ppp[i]);
+        //printf("y[%u] = %u\n", i, (unsigned int) (y[i]));
+        //printf("y[%u] = %#0x\n", i, (unsigned int) (y[i]));
+        num_errors += (msg_org[i] == msg_ppp[i]) ? 0 : 1;
+    }
+    printf("errors: %u / %u\n", num_errors, n);
+
+    // destroy the interleaver object
+    interleaver_destroy(q);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/kbd_window_example.c b/examples/kbd_window_example.c
new file mode 100644
index 0000000..0162dc5
--- /dev/null
+++ b/examples/kbd_window_example.c
@@ -0,0 +1,58 @@
+//
+// kbd_window_example.c
+//
+// Kaiser-Bessel derived window example
+//
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "kbd_window_example.m"
+
+int main() {
+    // options
+    unsigned int n=64;      // window length
+    float beta = 20.0f;     // Kaiser beta factor
+
+    unsigned int i;
+    float w[n];
+    liquid_kbd_window(n,beta,w);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"n=%u;\n",n);
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"w(%4u) = %12.4e;\n", i+1, w[i]);
+        printf("w[%4u] = %12.4e;\n", i, w[i]);
+    }   
+
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"W=20*log10(abs(fftshift(fft(w/sum(w),nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,w,'Color',[0 0.25 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('window');\n");
+    fprintf(fid,"  axis([0 n-1 -0.1 1.1]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(f,W,'Color',[0 0.5 0.25],'LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('normalized frequency');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  axis([-0.5 0.5 -140 20]);\n");
+    fprintf(fid,"title(['Kaiser-Bessel derived window']);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/libliquid_example.c b/examples/libliquid_example.c
new file mode 100644
index 0000000..ebfb9b3
--- /dev/null
+++ b/examples/libliquid_example.c
@@ -0,0 +1,20 @@
+//
+// libliquid_example.c
+//
+// Tests library versioning.
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "liquid.h"
+
+int main() {
+    // 
+    LIQUID_VALIDATE_LIBVERSION
+
+    printf("liquid version              :   %s\n", liquid_version);
+    printf("liquid libversion           :   %s\n", liquid_libversion());
+    printf("liquid libversion number    :   %d\n", liquid_libversion_number());
+
+    return 0;
+}
diff --git a/examples/lpc_example.c b/examples/lpc_example.c
new file mode 100644
index 0000000..91dcbab
--- /dev/null
+++ b/examples/lpc_example.c
@@ -0,0 +1,143 @@
+//
+// lpc_example.c
+//
+// This example demonstrates linear prediction in liquid. An input signal
+// is generated which exhibits a strong temporal correlation. The linear
+// predictor generates an approximating all-pole filter which minimizes
+// the squared error between the prediction and the actual output.
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "lpc_example.m"
+
+int main() {
+    // options
+    unsigned int n = 200;   // input sequence length
+    unsigned int p = 4;     // prediction filter order
+
+    // create low-pass filter object
+    iirfilt_rrrf f = iirfilt_rrrf_create_lowpass(2, 0.05f);
+    iirfilt_rrrf_print(f);
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float y[n];         // input signal (filtered noise)
+    float a_hat[p+1];   // lpc output
+    float g_hat[p+1];   // lpc output
+
+    // generate input signal (filtered noise)
+    for (i=0; i<n; i++)
+        iirfilt_rrrf_execute(f, randnf(), &y[i]);
+
+    // destroy filter object
+    iirfilt_rrrf_destroy(f);
+
+    // run linear prediction algorithm
+    liquid_lpc(y,n,p,a_hat,g_hat);
+
+    // run prediction filter
+    float a_lpc[p+1];
+    float b_lpc[p+1];
+    for (i=0; i<p+1; i++) {
+        a_lpc[i] = (i==0) ? 1.0f : 0.0f;
+        b_lpc[i] = (i==0) ? 0.0f : -a_hat[i];
+    }
+    f = iirfilt_rrrf_create(b_lpc,p+1, a_lpc,p+1);
+    iirfilt_rrrf_print(f);
+    float y_hat[n];
+    for (i=0; i<n; i++)
+        iirfilt_rrrf_execute(f, y[i], &y_hat[i]);
+    iirfilt_rrrf_destroy(f);
+
+    // compute prediction error
+    float err[n];
+    for (i=0; i<n; i++)
+        err[i] = y[i] - y_hat[i];
+
+    // compute autocorrelation of prediction error
+    float lag[n];
+    float rxx[n];
+    for (i=0; i<n; i++) {
+        lag[i] = (float)i;
+        rxx[i] = 0.0f;
+        unsigned int j;
+        for (j=i; j<n; j++)
+            rxx[i] += err[j] * err[j-i];
+    }
+    float rxx0 = rxx[0];
+    for (i=0; i<n; i++)
+        rxx[i] /= rxx0;
+
+    // print results
+    for (i=0; i<p+1; i++)
+        printf("  a[%3u] = %12.8f, g[%3u] = %12.8f\n", i, a_hat[i], i, g_hat[i]);
+
+    printf("  prediction rmse = %12.8f\n", sqrtf(rxx0 / n));
+
+    // 
+    // plot results to output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"p=%u;\n", p);
+    fprintf(fid,"n=%u;\n",n);
+
+#if 0
+    fprintf(fid,"b  = zeros(1,p+1);\n");
+    fprintf(fid,"a  = zeros(1,p+1);\n");
+    for (i=0; i<p+1; i++) {
+        fprintf(fid,"b(%4u) = %12.4e;\n", i+1, b_lpc[i]);
+        fprintf(fid,"a(%4u) = %12.4e;\n", i+1, a_lpc[i]);
+    }
+#endif
+
+    fprintf(fid,"y      = zeros(1,n);\n");
+    fprintf(fid,"y_hat  = zeros(1,n);\n");
+    fprintf(fid,"lag    = zeros(1,n);\n");
+    fprintf(fid,"rxx    = zeros(1,n);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"y_hat(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y_hat[i]), cimagf(y_hat[i]));
+        fprintf(fid,"lag(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(lag[i]), cimagf(lag[i]));
+        fprintf(fid,"rxx(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rxx[i]), cimagf(rxx[i]));
+    }
+
+    // plot output
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(5,1,1:3);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,y,    'LineWidth',1.5,'Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  plot(t,y_hat,'LineWidth',1.5,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  ylabel('signal');\n");
+    fprintf(fid,"  legend('input','LPC estimate','location','northeast');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(5,1,4);\n");
+    fprintf(fid,"  plot(t,y-y_hat,'LineWidth',2,'Color',[1 1 1]*0.6);\n");
+    fprintf(fid,"  ylabel('error');\n");
+    fprintf(fid,"  axis([0 n -0.1 0.1]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(5,1,5);\n");
+    fprintf(fid,"  plot(t,rxx,'LineWidth',2,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('r_{xx}(e)');\n");
+    fprintf(fid,"  axis([0 n -1.0 1]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/matched_filter_example.c b/examples/matched_filter_example.c
new file mode 100644
index 0000000..6b1a485
--- /dev/null
+++ b/examples/matched_filter_example.c
@@ -0,0 +1,206 @@
+//
+// matched_filter_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "matched_filter_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("matched_filter_example options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [rrcos], rkaiser, arkaiser, hM3, gmsk, fexp, fsech, farcsech\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 2\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 3\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.5\n");
+    printf("  n     : number of symbols, default: 16\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=2;   // samples/symbol
+    unsigned int m=3;   // symbol delay
+    float beta=0.7f;    // excess bandwidth factor
+    unsigned int num_symbols=16;
+    int ftype_tx = LIQUID_FIRFILT_RRC;
+    int ftype_rx = LIQUID_FIRFILT_RRC;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:k:m:b:n:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();            return 0;
+        case 't':
+            if (strcmp(optarg,"gmsk")==0) {
+                ftype_tx = LIQUID_FIRFILT_GMSKTX;
+                ftype_rx = LIQUID_FIRFILT_GMSKRX;
+            } else {
+                ftype_tx = liquid_getopt_str2firfilt(optarg);
+                ftype_rx = liquid_getopt_str2firfilt(optarg);
+            }
+            if (ftype_tx == LIQUID_FIRFILT_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'k':   k = atoi(optarg);           break;
+        case 'm':   m = atoi(optarg);           break;
+        case 'b':   beta = atof(optarg);        break;
+        case 'n':   num_symbols = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, beta must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_samples = num_symbols*k;
+    unsigned int h_len  = 2*k*m+1;               // transmit/receive filter length
+    unsigned int hc_len = 4*k*m+1;               // composite filter length
+
+    // arrays
+    float ht[h_len];    // transmit filter
+    float hr[h_len];    // receive filter
+    float hc[hc_len];   // composite filter
+
+    // design the filter(s)
+    liquid_firdes_prototype(ftype_tx, k, m, beta, 0, ht);
+    liquid_firdes_prototype(ftype_rx, k, m, beta, 0, hr);
+
+    for (i=0; i<h_len; i++) printf("ht(%3u) = %12.8f;\n", i+1, ht[i]);
+    for (i=0; i<h_len; i++) printf("hr(%3u) = %12.8f;\n", i+1, hr[i]);
+
+#if 0
+    // generate receive filter coefficients (reverse of transmit)
+    float hr[h_len];
+    for (i=0; i<h_len; i++)
+        hr[i] = ht[h_len-i-1];
+#endif
+
+    // compute composite filter response
+    for (i=0; i<4*k*m+1; i++) {
+        int lag = (int)i - (int)(2*k*m);
+        hc[i] = liquid_filter_crosscorr(ht,h_len, hr,h_len, lag);
+    }
+
+    // compute filter inter-symbol interference
+    float rxy0 = liquid_filter_crosscorr(ht,h_len, hr,h_len, 0);
+    float isi_rms=0;
+    for (i=1; i<2*m; i++) {
+        float e = liquid_filter_crosscorr(ht,h_len, hr,h_len, i*k) / rxy0;
+        isi_rms += e*e;
+    }
+    isi_rms = sqrtf( isi_rms / (float)(2*m-1) );
+    printf("  isi (rms) : %12.8f dB\n", 20*log10f(isi_rms));
+
+    // compute relative stop-band energy
+    unsigned int nfft = 2048;
+    float As = liquid_filter_energy(ht, h_len, 0.5f*(1.0f + beta)/(float)k, nfft);
+    printf("  As        : %12.8f dB\n", 20*log10f(As));
+
+    // generate signal
+    float sym_in[num_symbols];
+    float y[num_samples];
+    float sym_out[num_symbols];
+
+    // create interpolator and decimator
+    firinterp_rrrf interp = firinterp_rrrf_create(k, ht, h_len);
+    firdecim_rrrf  decim  = firdecim_rrrf_create( k, hr, h_len);
+
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        sym_in[i] = (rand() % 2) ? 1.0f : -1.0f;
+
+        // interpolate
+        firinterp_rrrf_execute(interp, sym_in[i], &y[i*k]);
+
+        // decimate
+        firdecim_rrrf_execute(decim, &y[i*k], &sym_out[i]);
+
+        // normalize output
+        sym_out[i] /= k;
+
+        printf("  %3u : %8.5f", i, sym_out[i]);
+        if (i>=2*m) printf(" *\n");
+        else        printf("\n");
+    }
+
+    // clean up objects
+    firinterp_rrrf_destroy(interp);
+    firdecim_rrrf_destroy(decim);
+
+    //
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %12.8f;\n", beta);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = k*num_symbols;\n");
+
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++)
+        fprintf(fid," y(%3u) = %12.8f;\n", i+1, y[i]);
+
+    for (i=0; i<h_len;  i++) fprintf(fid,"ht(%3u) = %20.8e;\n", i+1, ht[i]);
+    for (i=0; i<h_len;  i++) fprintf(fid,"hr(%3u) = %20.8e;\n", i+1, hr[i]);
+    for (i=0; i<hc_len; i++) fprintf(fid,"hc(%3u) = %20.8e;\n", i+1, hc[i]);
+
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Ht = 20*log10(abs(fftshift(fft(ht/k,   nfft))));\n");
+    fprintf(fid,"Hr = 20*log10(abs(fftshift(fft(hr/k,   nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc/k^2, nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Hc,'-','LineWidth',2,...\n");
+    fprintf(fid,"     [0.5/k],[-6],'or',...\n");
+    fprintf(fid,"     [0.5/k*(1-beta) 0.5/k*(1-beta)],[-100 10],'-r',...\n");
+    fprintf(fid,"     [0.5/k*(1+beta) 0.5/k*(1+beta)],[-100 10],'-r');\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD');\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 10]);\n");
+    fprintf(fid,"grid on;\n");
+
+    // compute composite filter
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hc = conv(ht,hr)/k;\n");
+    fprintf(fid,"t = [(-2*k*m):(2*k*m)]/k;\n");
+    fprintf(fid,"i0 = [0:k:4*k*m]+1;\n");
+    fprintf(fid,"plot(t,    hc,    '-s',...\n");
+    fprintf(fid,"     t(i0),hc(i0),'or');\n");
+    fprintf(fid,"xlabel('symbol index');\n");
+    fprintf(fid,"ylabel('matched filter response');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+    
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/math_lngamma_example.c b/examples/math_lngamma_example.c
new file mode 100644
index 0000000..5c1a325
--- /dev/null
+++ b/examples/math_lngamma_example.c
@@ -0,0 +1,55 @@
+//
+// math_lngamma_example.c
+//
+// Demonstrates accuracy of lngamma function
+//
+
+#include "liquid.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#define OUTPUT_FILENAME "math_lngamma_example.m"
+
+int main() {
+    unsigned int n = 256;   // number of steps
+    float zmin = 1e-3f;     // minimum value
+    float zmax = 6.00f;     // maximum value
+
+    unsigned int d = n/32;  // print every d values to screen
+
+    // log scale values
+    float xmin = logf(zmin);
+    float xmax = logf(zmax);
+    float dx = (xmax-xmin)/(n-1);
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    unsigned int i;
+    float z;
+    float g;
+    float x = xmin; // log(z)
+    for (i=0; i<n; i++) {
+        z = expf(x);
+        g = liquid_lngammaf(z);
+
+        fprintf(fid,"z(%4u) = %16.8e; g(%4u) = %16.8e;\n", i+1, z, i+1, g);
+        if ( (i%d)==0 )
+            printf("lngamma(%12.8f) = %12.8f\n",z,g);
+        x += dx;
+    }
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  semilogx(z,g,z,log(gamma(z)));\n");
+    fprintf(fid,"  xlabel('z');\n");
+    fprintf(fid,"  ylabel('lngamma(z)');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  loglog(z,abs(log(gamma(z))-g));\n");
+    fprintf(fid,"  xlabel('z');\n");
+    fprintf(fid,"  ylabel('error');\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/math_primitive_root_example.c b/examples/math_primitive_root_example.c
new file mode 100644
index 0000000..8e0e74b
--- /dev/null
+++ b/examples/math_primitive_root_example.c
@@ -0,0 +1,29 @@
+//
+// math_primitive_root_example.c
+//
+// Demonstrates computing primitive root of a number using modular
+// arithmetic.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.h"
+
+int main(int argc, char*argv[])
+{
+    // maximum number
+    unsigned int n = 140;
+    unsigned int i;
+
+    printf("primitive roots of prime numbers up to %u:\n", n);
+    for (i=3; i<=n; i++) {
+        if (!liquid_is_prime(i))
+            continue;
+        
+        unsigned int root = liquid_primitive_root_prime(i);
+        printf("  %4u : %4u\n", i, root);
+    }
+
+    return 0;
+}
diff --git a/examples/modem_arb_example.c b/examples/modem_arb_example.c
new file mode 100644
index 0000000..ae17488
--- /dev/null
+++ b/examples/modem_arb_example.c
@@ -0,0 +1,131 @@
+//
+// modem_arb_example.c
+//
+// This example demonstrates the functionality of the arbitrary
+// modem, a digital modulator/demodulator object with signal
+// constellation points chosen arbitrarily.  A simple bit-error
+// rate simulation is then run to test the performance of the
+// modem.  The results are written to a file.
+// SEE ALSO: modem_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_arb_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("modem_arb_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  p     : modulation depth (default 4 bits/symbol)\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int bps=6;         // bits per symbol
+    unsigned int n=1024;        // number of data points to evaluate
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhp:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage(); return 0;
+        case 'p': bps = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (bps == 0) {
+        fprintf(stderr,"error: %s, bits/symbol must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int i;
+    unsigned int M = 1<<bps;    // constellation size
+
+    // initialize constellation table
+    float complex constellation[M];
+    // initialize constellation (spiral)
+    for (i=0; i<M; i++) {
+        float r   = (float)i / logf((float)M) + 4.0f;
+        float phi = (float)i / logf((float)M);
+        constellation[i] = r * cexpf(_Complex_I*phi);
+    }
+    
+    // create mod/demod objects
+    modem mod   = modem_create_arbitrary(constellation, M);
+    modem demod = modem_create_arbitrary(constellation, M);
+
+    modem_print(mod);
+
+    // run simulation
+    float complex x[n];
+    unsigned int num_errors = 0;
+
+    // run simple BER simulation
+    num_errors = 0;
+    unsigned int sym_in;
+    unsigned int sym_out;
+    for (i=0; i<n; i++) {
+        // generate and modulate random symbol
+        sym_in = modem_gen_rand_sym(mod);
+        modem_modulate(mod, sym_in, &x[i]);
+
+        // add noise
+        x[i] += 0.05 * randnf() * cexpf(_Complex_I*M_PI*randf());
+
+        // demodulate
+        modem_demodulate(demod, x[i], &sym_out);
+
+        // accumulate errors
+        num_errors += count_bit_errors(sym_in,sym_out);
+    }
+    printf("num bit errors: %4u / %4u\n", num_errors, bps*n);
+
+    // destroy modem objects
+    modem_destroy(mod);
+    modem_destroy(demod);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"bps = %u;\n", bps);
+    fprintf(fid,"M = %u;\n", M);
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1,
+                                                     crealf(x[i]),
+                                                     cimagf(x[i]));
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(x,'x','MarkerSize',1);\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+    fprintf(fid,"title(['Arbitrary ' num2str(M) '-QAM']);\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.9);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+    printf("done.\n");
+
+    return 0;
+}
diff --git a/examples/modem_example.c b/examples/modem_example.c
new file mode 100644
index 0000000..0c17878
--- /dev/null
+++ b/examples/modem_example.c
@@ -0,0 +1,117 @@
+// 
+// modem_example.c
+//
+// This example demonstates the digital modulator/demodulator
+// (modem) object.  Data symbols are modulated into complex
+// samples which are then demodulated without noise or phase
+// offsets.  The user may select the modulation scheme via
+// the command-line interface.
+// SEE ALSO: modem_arb_example.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("modem_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  v/q   : verbose/quiet\n");
+    printf("  m     : modulation scheme (qam16 default)\n");
+    liquid_print_modulation_schemes();
+}
+
+
+int main(int argc, char*argv[])
+{
+    // create mod/demod objects
+    modulation_scheme ms = LIQUID_MODEM_QAM16;
+    int verbose = 1;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hvqm:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();        return 0;
+        case 'v':   verbose = 1;    break;
+        case 'q':   verbose = 0;    break;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // create the modem objects
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+
+    // ensure bits/symbol matches modem description (only
+    // applicable to certain specific modems)
+    unsigned int bps = modem_get_bps(mod);
+
+    modem_print(mod);
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"m = %u;\n", bps);
+    fprintf(fid,"M = %u;\n", 1<<bps);
+    fprintf(fid,"c = zeros(1,M);\n");
+    fprintf(fid,"i_str = cell(1,M);\n");
+
+    unsigned int i; // modulated symbol
+    unsigned int s; // demodulated symbol
+    unsigned int num_symbols = 1<<bps;
+    float complex x;
+    unsigned int num_sym_errors = 0;
+    unsigned int num_bit_errors = 0;
+
+    for (i=0; i<num_symbols; i++) {
+        modem_modulate(mod, i, &x);
+        modem_demodulate(demod, x, &s);
+
+        if (verbose)
+            printf("%4u : %12.8f + j*%12.8f\n", i, crealf(x), cimagf(x));
+
+        num_sym_errors += i == s ? 0 : 1;
+        num_bit_errors += count_bit_errors(i,s);
+
+        // write symbol to output file
+        fprintf(fid,"c(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x), cimagf(x));
+        fprintf(fid,"i_str{%3u} = [num2str(%3u)];\n", i+1, i);
+    }
+    printf("num sym errors: %4u / %4u\n", num_sym_errors, num_symbols);
+    printf("num bit errors: %4u / %4u\n", num_bit_errors, num_symbols*bps);
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(c,'o','MarkerSize',2);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"text(real(c)+0.02, imag(c)+0.02, i_str);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('in phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    modem_destroy(mod);
+    modem_destroy(demod);
+    return 0;
+}
diff --git a/examples/modem_soft_example.c b/examples/modem_soft_example.c
new file mode 100644
index 0000000..b38e0e7
--- /dev/null
+++ b/examples/modem_soft_example.c
@@ -0,0 +1,118 @@
+// 
+// modem_soft_example.c
+//
+// This example demonstates soft demodulation of linear
+// modulation schemes.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_soft_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("modem_soft_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  m     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+}
+
+// print a string of bits to the standard output
+//  _x      :   input symbol
+//  _bps    :   bits/symbol
+//  _n      :   number of characters to print (zero-padding)
+void print_bitstring(unsigned int _x,
+                     unsigned int _bps,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_bps; i++)
+        printf("%1u", (_x >> (_bps-i-1)) & 1);
+    for (i=_bps; i<_n; i++)
+        printf(" ");
+}
+
+
+int main(int argc, char*argv[])
+{
+    // create mod/demod objects
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhm:")) != EOF) {
+        switch (dopt) {
+        case 'h':
+            usage();
+            return 0;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // create the modem objects
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+
+    // ensure bits/symbol matches modem description (only
+    // applicable to certain specific modems)
+    unsigned int bps = modem_get_bps(mod);
+
+    modem_print(mod);
+
+    unsigned int i;         // modulated symbol
+    unsigned int s_hard;    // demodulated symbol (hard)
+    unsigned char soft_bits[bps];
+    unsigned int s_soft;    // demodulated symbol (soft, compacted)
+    unsigned int num_symbols = 1<<bps;
+    float complex x;
+    unsigned int num_sym_errors = 0;
+    unsigned int num_bit_errors = 0;
+
+    printf("\n");
+    printf("  %-11s %-11s %-11s  : ", "input sym.", "hard demod", "soft demod");
+    for (i=0; i<bps; i++)
+        printf("   b[%1u]", i);
+    printf("\n");
+
+    for (i=0; i<num_symbols; i++) {
+        // modulate symbol
+        modem_modulate(mod, i, &x);
+
+        // demodulate, including soft decision
+        modem_demodulate_soft(demod, x, &s_hard, soft_bits);
+
+        // re-pack soft bits to hard decision
+        liquid_pack_soft_bits(soft_bits, bps, &s_soft);
+
+        // print results
+        printf("  ");
+        print_bitstring(i,     bps,12);
+        print_bitstring(s_hard,bps,12);
+        print_bitstring(s_soft,bps,12);
+        printf(" : ");
+        unsigned int j;
+        for (j=0; j<bps; j++)
+            printf("%7u", soft_bits[j]);
+        printf("\n");
+
+        num_sym_errors += i == s_soft ? 0 : 1;
+        num_bit_errors += count_bit_errors(i,s_soft);
+    }
+    printf("num sym errors: %4u / %4u\n", num_sym_errors, num_symbols);
+    printf("num bit errors: %4u / %4u\n", num_bit_errors, num_symbols*bps);
+
+    modem_destroy(mod);
+    modem_destroy(demod);
+    return 0;
+}
diff --git a/examples/modular_arithmetic_example.c b/examples/modular_arithmetic_example.c
new file mode 100644
index 0000000..12bfd1d
--- /dev/null
+++ b/examples/modular_arithmetic_example.c
@@ -0,0 +1,35 @@
+//
+// modular_arithmetic_example.c
+//
+// This example demonstates some modular arithmetic functions.
+//
+
+#include <stdio.h>
+#include "liquid.h"
+
+int main() {
+    unsigned int n=280;
+    unsigned int factors[LIQUID_MAX_FACTORS];
+    unsigned int num_factors=0;
+
+    // compute factors of n
+    liquid_factor(n,factors,&num_factors);
+    printf("factors of %u:\n", n);
+    unsigned int i;
+    for (i=0; i<num_factors; i++)
+        printf("%3u\n", factors[i]);
+    
+    // compute unique factors
+    liquid_unique_factor(n,factors,&num_factors);
+    printf("unique factors of %u:\n", n);
+    for (i=0; i<num_factors; i++)
+        printf("%3u\n", factors[i]);
+
+    // compute Euler's totient function
+    unsigned int t = liquid_totient(n);
+    printf("totient(%u) = %u\n", n, t);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/msequence_example.c b/examples/msequence_example.c
new file mode 100644
index 0000000..df35a08
--- /dev/null
+++ b/examples/msequence_example.c
@@ -0,0 +1,106 @@
+//
+// msequence_example.c
+//
+// This example demonstrates the auto-correlation properties of a
+// maximal-length sequence (m-sequence).  An m-sequence of a
+// certain length is used to generate two binary sequences
+// (buffers) which are then cross-correlated.  The resulting
+// correlation produces -1 for all values except at index zero,
+// where the sequences align.
+// SEE ALSO: bsequence_example.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "msequence_example.m"
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int m=5;   // shift register length, n=2^m - 1
+
+    // create and initialize m-sequence
+    msequence ms = msequence_create_default(m);
+    msequence_print(ms);
+    unsigned int n = msequence_get_length(ms);
+    signed int rxx[n];  // auto-correlation
+
+    // create and initialize first binary sequence on m-sequence
+    bsequence bs1 = bsequence_create(n);
+    bsequence_init_msequence(bs1, ms);
+
+    // create and initialize second binary sequence on same m-sequence
+    bsequence bs2 = bsequence_create(n);
+    bsequence_init_msequence(bs2, ms);
+
+    // when sequences are aligned, autocorrelation is equal to length
+    rxx[0] = 2*bsequence_correlate(bs1, bs2) - n;
+
+    // when sequences are misaligned, autocorrelation is equal to -1
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // compute auto-correlation
+        rxx[i] = 2*bsequence_correlate(bs1, bs2)-n;
+
+        // circular shift the second sequence
+        bsequence_circshift(bs2);
+    }
+    
+    // p/n sequence
+    signed int x[n];
+    for (i=0; i<n; i++)
+        x[i] = bsequence_index(bs1, i);
+
+    // clean up memory
+    bsequence_destroy(bs1);
+    bsequence_destroy(bs2);
+    msequence_destroy(ms);
+
+    // print results
+    for (i=0; i<n; i++)
+        printf("rxx[%3u] = %3d\n", i, rxx[i]);
+
+    //
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    if (!fid) {
+        fprintf(stderr,"error: %s, cannot open output file '%s' for writing\n", argv[0], OUTPUT_FILENAME);
+        exit(1);
+    }
+
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"p = zeros(1,n);\n");
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%6u)   = %3d;\n",    i+1, x[i]);
+        fprintf(fid,"rxx(%6u) = %12.8f;\n", i+1, rxx[i] / (float)n);
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(n-1);\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"   stairs(t,x);\n");
+    fprintf(fid,"   axis([-1 n -0.2 1.2]);\n");
+    fprintf(fid,"   ylabel('x');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"   plot(t,rxx,t,rxx);\n");
+    fprintf(fid,"   axis([-1 n -0.5 1.2]);\n");
+    fprintf(fid,"   xlabel('delay (samples)');\n");
+    fprintf(fid,"   ylabel('auto-correlation');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    return 0;
+}
+
diff --git a/examples/msourcecf_example.c b/examples/msourcecf_example.c
new file mode 100644
index 0000000..ad4be9f
--- /dev/null
+++ b/examples/msourcecf_example.c
@@ -0,0 +1,115 @@
+//
+// msourcecf_example.c
+//
+// This example demonstrates generating multiple signal sources simultaneously
+// for testing using the msource (multi-source) family of objects.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "msourcecf_example.m"
+
+int main()
+{
+    // msource parameters
+    int          ms          = LIQUID_MODEM_QPSK;
+    unsigned int k           =     4;
+    unsigned int m           =     9;
+    float        beta        = 0.30f;
+
+    // spectral periodogram options
+    unsigned int nfft        =   2400;  // spectral periodogram FFT size
+    unsigned int num_samples =  48000;  // number of samples
+
+    unsigned int i;
+
+    // create spectral periodogram
+    spgramcf periodogram = spgramcf_create_default(nfft);
+
+    unsigned int buf_len = 1024;
+    float complex buf[buf_len];
+
+    // create stream generator
+    msourcecf gen = msourcecf_create();
+    
+    int id;
+
+    // add noise source (wide-band)
+    id =
+    msourcecf_add_noise(gen, 1.00f);
+    msourcecf_set_gain(gen, id, -60.0f);
+
+    // add noise source (narrow-band)
+    id =
+    msourcecf_add_noise(gen, 0.10f);
+    msourcecf_set_frequency(gen, id, 0.4*2*M_PI);
+    msourcecf_set_gain(gen, id, -20.0f);
+
+    // add tone
+    id =
+    msourcecf_add_tone(gen);
+    msourcecf_set_frequency(gen, id, -0.4*2*M_PI);
+    msourcecf_set_gain(gen, id, -40.0f);
+
+    // add modulated data
+    id =
+    msourcecf_add_modem(gen,ms,k,m,beta);
+    //msourcecf_set_frequency(gen, id, -0.2*2*M_PI);
+    //msourcecf_set_gain(gen, id, 0.1f);
+    
+    // print source generator object
+    msourcecf_print(gen);
+
+    unsigned int total_samples = 0;
+    while (total_samples < num_samples) {
+        // write samples to buffer
+        msourcecf_write_samples(gen, buf, buf_len);
+
+        // push resulting sample through periodogram
+        spgramcf_write(periodogram, buf, buf_len);
+        
+        // accumulated samples
+        total_samples += buf_len;
+    }
+    printf("total samples: %u\n", total_samples);
+
+    // compute power spectral density output
+    float psd[nfft];
+    spgramcf_get_psd(periodogram, psd);
+
+    // destroy objects
+    msourcecf_destroy(gen);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f    = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H    = zeros(1,nfft);\n");
+    
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%6u) = %12.4e;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f, H, '-', 'LineWidth',1.5);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/msresamp2_crcf_example.c b/examples/msresamp2_crcf_example.c
new file mode 100644
index 0000000..012d62f
--- /dev/null
+++ b/examples/msresamp2_crcf_example.c
@@ -0,0 +1,238 @@
+//
+// msresamp2_crcf_example.c
+//
+// Demonstration of the multi-stage half-band resampler
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "msresamp2_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: %s [OPTION]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  r     : resampling rate (output/input), default: 0.25\n");
+    printf("  s     : stop-band attenuation [dB],     default: 60\n");
+    printf("  n     : number of low-rate samples,     default: 120\n");
+    printf("  f     : pass-band cut-off frequency,    default: 0.1\n");
+    printf("  c     : center frequency of filter,     default: 0\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float r=0.25f;          // resampling rate (output/input)
+    float As=60.0f;         // resampling filter stop-band attenuation [dB]
+    unsigned int n=120;     // number of low-rate samples
+    float fc=0.1f;          // complex sinusoid frequency
+    float f0=0.f;           // center frequency
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hr:s:n:f:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                return 0;
+        case 'r': r       = atof(optarg); break;
+        case 's': As      = atof(optarg); break;
+        case 'n': n       = atoi(optarg); break;
+        case 'f': fc      = atof(optarg); break;
+        case 'c': f0      = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (n == 0) {
+        fprintf(stderr,"error: %s, number of input samples must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (r <= 0.0f) {
+        fprintf(stderr,"error: %s, resampling rate must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if ( roundf(fabsf(log2f(r))) > 10 ) {
+        fprintf(stderr,"error: %s, resampling rate unreasonable\n", argv[0]);
+        exit(1);
+    }
+
+    // determine type and compute number of stages
+    unsigned int num_stages = (unsigned int) roundf(fabsf(log2f(r)));
+    int type = r < 1. ? LIQUID_RESAMP_DECIM : LIQUID_RESAMP_INTERP;
+
+    printf("msresamp2:\n");
+    printf("    rate    :   %12.8f\n", r);
+    printf("    log2(r) :   %12.8f\n", log2f(r));
+    printf("    type    :   %s\n", type == LIQUID_RESAMP_DECIM ? "decim" : "interp");
+    printf("    stages  :   %u\n", num_stages);
+
+    unsigned int i;
+
+    // create multi-stage arbitrary resampler object
+    msresamp2_crcf q = msresamp2_crcf_create(type, num_stages, fc, f0, As);
+    msresamp2_crcf_print(q);
+    float delay = msresamp2_crcf_get_delay(q);
+
+    // number of input samples (zero-padded)
+    unsigned int M  = (1 << num_stages);    // integer resampling rate
+    unsigned int nx = (type == LIQUID_RESAMP_DECIM) ? n * M : n;
+    unsigned int ny = (type == LIQUID_RESAMP_DECIM) ? n     : n * M;
+    unsigned int wlen = round(0.75 * nx);
+
+    // allocate memory for arrays
+    float complex x[nx];
+    float complex y[ny];
+
+    // generate input signal
+    float wsum = 0.0f;
+    for (i=0; i<nx; i++) {
+        // compute window
+        float w = i < wlen ? kaiser(i, wlen, 10.0f, 0.0f) : 0.0f;
+
+        // apply window to complex sinusoid
+        x[i] = cexpf(_Complex_I*2*M_PI*0.37021f*fc*i) * w;
+
+        // accumulate window
+        wsum += w;
+    }
+
+    // run resampler
+    unsigned int dx = (type == LIQUID_RESAMP_INTERP) ? 1 : M; // input stride
+    unsigned int dy = (type == LIQUID_RESAMP_INTERP) ? M : 1; // output stride
+    for (i=0; i<n; i++)
+        msresamp2_crcf_execute(q, &x[i*dx], &y[i*dy]);
+
+    // clean up allocated objects
+    msresamp2_crcf_destroy(q);
+    
+    // 
+    // analyze resulting signal
+    //
+
+    // check that the actual resampling rate is close to the target
+    float r_actual = (float)ny / (float)nx;
+    float fy = fc / r;      // expected output frequency
+
+    // run FFT and ensure that carrier has moved and that image
+    // frequencies and distortion have been adequately suppressed
+    unsigned int nfft = 1 << liquid_nextpow2(n*M);
+    float complex yfft[nfft];   // fft input
+    float complex Yfft[nfft];   // fft output
+    for (i=0; i<nfft; i++)
+        yfft[i] = i < ny ? y[i] : 0.0f;
+    fft_run(nfft, yfft, Yfft, LIQUID_FFT_FORWARD, 0);
+    fft_shift(Yfft, nfft);  // run FFT shift
+
+    // find peak frequency
+    float Ypeak = 0.0f;
+    float fpeak = 0.0f;
+    float max_sidelobe = -1e9f;     // maximum side-lobe [dB]
+    float main_lobe_width = 0.07f;  // TODO: figure this out from Kaiser's equations
+    for (i=0; i<nfft; i++) {
+        // normalized output frequency
+        float f = (float)i/(float)nfft - 0.5f;
+
+        // scale FFT output appropriately
+        float Ymag = 20*log10f( cabsf(Yfft[i] / (r * wsum)) );
+
+        // find frequency location of maximum magnitude
+        if (Ymag > Ypeak || i==0) {
+            Ypeak = Ymag;
+            fpeak = f;
+        }
+
+        // find peak side-lobe value, ignoring frequencies
+        // within a certain range of signal frequency
+        if ( fabsf(f-fy) > main_lobe_width )
+            max_sidelobe = Ymag > max_sidelobe ? Ymag : max_sidelobe;
+    }
+
+    // print results and check frequency location
+    printf("output results:\n");
+    printf("  output delay              :   %12.8f samples\n", delay);
+    printf("  desired resampling rate   :   %12.8f\n", r);
+    printf("  measured resampling rate  :   %12.8f    (%u/%u)\n", r_actual, ny, nx);
+    printf("  peak spectrum             :   %12.8f dB (expected 0.0 dB)\n", Ypeak);
+    printf("  peak frequency            :   %12.8f    (expected %-12.8f)\n", fpeak, fy);
+    printf("  max sidelobe              :   %12.8f dB (expected at least %.2f dB)\n", max_sidelobe, -As);
+
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"delay=%f;\n", delay);
+    fprintf(fid,"r=%12.8f;\n", r);
+
+    fprintf(fid,"nx = %u;\n", nx);
+    fprintf(fid,"x = zeros(1,nx);\n");
+    for (i=0; i<nx; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    fprintf(fid,"ny = %u;\n", ny);
+    fprintf(fid,"y = zeros(1,ny);\n");
+    for (i=0; i<ny; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // time-domain results
+    fprintf(fid,"\n");
+    fprintf(fid,"%% plot time-domain result\n");
+    fprintf(fid,"tx=[0:(length(x)-1)];\n");
+    fprintf(fid,"ty=[0:(length(y)-1)]/r-delay;\n");
+    fprintf(fid,"tmin = min(tx(1),  ty(1)  );\n");
+    fprintf(fid,"tmax = max(tx(end),ty(end));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  axis([tmin tmax -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0 0.5 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  axis([tmin tmax -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    // frequency-domain results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"nfft=4*2^nextpow2(max(nx,ny));\n");
+    fprintf(fid,"%% estimate PSD, normalize by array length\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x,nfft)/length(x))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y,nfft)/length(y))));\n");
+    fprintf(fid,"G=max(X);\n");
+    fprintf(fid,"X=X-G;\n");
+    fprintf(fid,"Y=Y-G;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"if r>1, fx = f/r; fy = f;   %% interpolated\n");
+    fprintf(fid,"else,   fx = f;   fy = f*r; %% decimated\n");
+    fprintf(fid,"end;\n");
+    fprintf(fid,"plot(fx,X,'LineWidth',1,  'Color',[0.5 0.5 0.5],...\n");
+    fprintf(fid,"     fy,Y,'LineWidth',1.5,'Color',[0.1 0.3 0.5]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','resampled','location','northeast');");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/msresamp_crcf_example.c b/examples/msresamp_crcf_example.c
new file mode 100644
index 0000000..1e47bb9
--- /dev/null
+++ b/examples/msresamp_crcf_example.c
@@ -0,0 +1,224 @@
+//
+// msresamp_crcf_example.c
+//
+// Demonstration of the multi-stage arbitrary resampler
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "msresamp_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: %s [OPTION]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  r     : resampling rate (output/input), default: 0.23175\n");
+    printf("  s     : stop-band attenuation [dB],     default: 60\n");
+    printf("  n     : number of input samples,        default: 400\n");
+    printf("  f     : input signal frequency,         default: 0.017\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float r=0.23175f;       // resampling rate (output/input)
+    float As=60.0f;         // resampling filter stop-band attenuation [dB]
+    unsigned int n=400;     // number of input samples
+    float fc=0.017f;        // complex sinusoid frequency
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hr:s:n:f:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                return 0;
+        case 'r': r       = atof(optarg); break;
+        case 's': As      = atof(optarg); break;
+        case 'n': n       = atoi(optarg); break;
+        case 'f': fc      = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (n == 0) {
+        fprintf(stderr,"error: %s, number of input samples must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (r <= 0.0f) {
+        fprintf(stderr,"error: %s, resampling rate must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if ( fabsf(log2f(r)) > 10 ) {
+        fprintf(stderr,"error: %s, resampling rate unreasonable\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // create multi-stage arbitrary resampler object
+    msresamp_crcf q = msresamp_crcf_create(r,As);
+    msresamp_crcf_print(q);
+    float delay = msresamp_crcf_get_delay(q);
+
+    // number of input samples (zero-padded)
+    unsigned int nx = n + (int)ceilf(delay) + 10;
+
+    // output buffer with extra padding for good measure
+    unsigned int ny_alloc = (unsigned int) (2*(float)nx * r);  // allocation for output
+
+    // allocate memory for arrays
+    float complex x[nx];
+    float complex y[ny_alloc];
+
+    // generate input signal
+    float wsum = 0.0f;
+    for (i=0; i<nx; i++) {
+        // compute window
+        float w = i < n ? kaiser(i, n, 10.0f, 0.0f) : 0.0f;
+
+        // apply window to complex sinusoid
+        x[i] = cexpf(_Complex_I*2*M_PI*fc*i) * w;
+
+        // accumulate window
+        wsum += w;
+    }
+
+    // run resampler
+    unsigned int ny;
+    msresamp_crcf_execute(q, x, nx, y, &ny);
+
+    // clean up allocated objects
+    msresamp_crcf_destroy(q);
+    
+    
+    // 
+    // analyze resulting signal
+    //
+
+    // check that the actual resampling rate is close to the target
+    float r_actual = (float)ny / (float)nx;
+    float fy = fc / r;      // expected output frequency
+
+    // run FFT and ensure that carrier has moved and that image
+    // frequencies and distortion have been adequately suppressed
+    unsigned int nfft = 1 << liquid_nextpow2(ny);
+    float complex yfft[nfft];   // fft input
+    float complex Yfft[nfft];   // fft output
+    for (i=0; i<nfft; i++)
+        yfft[i] = i < ny ? y[i] : 0.0f;
+    fft_run(nfft, yfft, Yfft, LIQUID_FFT_FORWARD, 0);
+    fft_shift(Yfft, nfft);  // run FFT shift
+
+    // find peak frequency
+    float Ypeak = 0.0f;
+    float fpeak = 0.0f;
+    float max_sidelobe = -1e9f;     // maximum side-lobe [dB]
+    float main_lobe_width = 0.07f;  // TODO: figure this out from Kaiser's equations
+    for (i=0; i<nfft; i++) {
+        // normalized output frequency
+        float f = (float)i/(float)nfft - 0.5f;
+
+        // scale FFT output appropriately
+        float Ymag = 20*log10f( cabsf(Yfft[i] / (r * wsum)) );
+
+        // find frequency location of maximum magnitude
+        if (Ymag > Ypeak || i==0) {
+            Ypeak = Ymag;
+            fpeak = f;
+        }
+
+        // find peak side-lobe value, ignoring frequencies
+        // within a certain range of signal frequency
+        if ( fabsf(f-fy) > main_lobe_width )
+            max_sidelobe = Ymag > max_sidelobe ? Ymag : max_sidelobe;
+    }
+
+    // print results and check frequency location
+    printf("output results:\n");
+    printf("  output delay              :   %12.8f samples\n", delay);
+    printf("  desired resampling rate   :   %12.8f\n", r);
+    printf("  measured resampling rate  :   %12.8f    (%u/%u)\n", r_actual, ny, nx);
+    printf("  peak spectrum             :   %12.8f dB (expected 0.0 dB)\n", Ypeak);
+    printf("  peak frequency            :   %12.8f    (expected %-12.8f)\n", fpeak, fy);
+    printf("  max sidelobe              :   %12.8f dB (expected at least %.2f dB)\n", max_sidelobe, -As);
+
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"delay=%f;\n", delay);
+    fprintf(fid,"r=%12.8f;\n", r);
+
+    fprintf(fid,"nx = %u;\n", nx);
+    fprintf(fid,"x = zeros(1,nx);\n");
+    for (i=0; i<nx; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    fprintf(fid,"ny = %u;\n", ny);
+    fprintf(fid,"y = zeros(1,ny);\n");
+    for (i=0; i<ny; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // time-domain results
+    fprintf(fid,"\n");
+    fprintf(fid,"%% plot time-domain result\n");
+    fprintf(fid,"tx=[0:(length(x)-1)];\n");
+    fprintf(fid,"ty=[0:(length(y)-1)]/r-delay;\n");
+    fprintf(fid,"tmin = min(tx(1),  ty(1)  );\n");
+    fprintf(fid,"tmax = max(tx(end),ty(end));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  axis([tmin tmax -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0 0.5 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  axis([tmin tmax -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    // frequency-domain results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"nfft=2^nextpow2(max(nx,ny));\n");
+    fprintf(fid,"%% estimate PSD, normalize by array length\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x,nfft)/length(x))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y,nfft)/length(y))));\n");
+    fprintf(fid,"G=max(X);\n");
+    fprintf(fid,"X=X-G;\n");
+    fprintf(fid,"Y=Y-G;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"if r>1, fx = f/r; fy = f;   %% interpolated\n");
+    fprintf(fid,"else,   fx = f;   fy = f*r; %% decimated\n");
+    fprintf(fid,"end;\n");
+    fprintf(fid,"plot(fx,X,'LineWidth',1,  'Color',[0.5 0.5 0.5],...\n");
+    fprintf(fid,"     fy,Y,'LineWidth',1.5,'Color',[0.1 0.3 0.5]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','resampled','location','northeast');");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/nco_example.c b/examples/nco_example.c
new file mode 100644
index 0000000..ad1f285
--- /dev/null
+++ b/examples/nco_example.c
@@ -0,0 +1,35 @@
+// 
+// nco_example.c
+//
+// This example demonstrates the most basic functionality of the
+// numerically-controlled oscillator (NCO) object.
+//
+// SEE ALSO: nco_pll_example.c
+//           nco_pll_modem_example.c
+//
+
+#include <stdio.h>
+#include <math.h>
+#include "liquid.h"
+
+int main() {
+    // create the NCO object
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, M_PI/10);
+
+    unsigned int i;
+    float s, c;
+    for (i=0; i<11; i++) {
+        nco_crcf_sincos(p, &s, &c);
+        printf("  %3u : %8.5f + j %8.5f\n",
+                i, c, s);
+        nco_crcf_step(p);
+    }
+
+    // clean up allocated memory
+    nco_crcf_destroy(p);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/nco_pll_example.c b/examples/nco_pll_example.c
new file mode 100644
index 0000000..e347a29
--- /dev/null
+++ b/examples/nco_pll_example.c
@@ -0,0 +1,153 @@
+//
+// nco_pll_example.c
+//
+// This example demonstrates how the use the nco/pll object
+// (numerically-controlled oscillator with phase-locked loop) interface for
+// tracking to a complex sinusoid.  The loop bandwidth, phase offset, and
+// other parameter can be specified via the command-line interface.
+//
+// SEE ALSO: nco_example.c
+//           nco_pll_modem_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "nco_pll_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("nco_pll_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  b     : pll bandwidth, default: 0.01\n");
+    printf("  n     : number of samples, default: 512\n");
+    printf("  p     : phase offset (radians), default: pi/4\n");
+    printf("  f     : frequency offset (radians), default: 0.3\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // set random seed
+    srand( time(NULL) );
+
+    // parameters
+    float phase_offset     = 0.0f;      // initial phase offset
+    float frequency_offset = 0.40f;     // initial frequency offset
+    float pll_bandwidth    = 0.003f;    // phase-locked loop bandwidth
+    unsigned int n         = 512;       // number of iterations
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhb:n:p:f:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();    return 0;
+        case 'b':   pll_bandwidth = atof(optarg);   break;
+        case 'n':   n = atoi(optarg);               break;
+        case 'p':   phase_offset = atof(optarg);    break;
+        case 'f':   frequency_offset= atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // objects
+    nco_crcf nco_tx = nco_crcf_create(LIQUID_VCO);
+    nco_crcf nco_rx = nco_crcf_create(LIQUID_VCO);
+
+    // initialize objects
+    nco_crcf_set_phase(nco_tx, phase_offset);
+    nco_crcf_set_frequency(nco_tx, frequency_offset);
+    nco_crcf_pll_set_bandwidth(nco_rx, pll_bandwidth);
+
+    // generate input
+    float complex x[n];
+    float complex y[n];
+    float phase_error[n];
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // generate complex sinusoid
+        nco_crcf_cexpf(nco_tx, &x[i]);
+
+        // update nco
+        nco_crcf_step(nco_tx);
+    }
+
+    // run loop
+    for (i=0; i<n; i++) {
+#if 0
+        // test resetting bandwidth in middle of acquisition
+        if (i == 100) nco_pll_set_bandwidth(nco_rx, pll_bandwidth*0.2f);
+#endif
+
+        // generate input
+        nco_crcf_cexpf(nco_rx, &y[i]);
+
+        // update rx nco object
+        nco_crcf_step(nco_rx);
+
+        // compute phase error
+        phase_error[i] = cargf(x[i]*conjf(y[i]));
+
+        // update pll
+        nco_crcf_pll_step(nco_rx, phase_error[i]);
+
+        // print phase error
+        if ( (i+1)%50 == 0 || i==n-1 || i==0)
+            printf("%4u : phase error = %12.8f\n", i+1, phase_error[i]);
+    }
+    nco_crcf_destroy(nco_tx);
+    nco_crcf_destroy(nco_rx);
+
+    // write output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"x = zeros(1,n);\n");
+    fprintf(fid,"y = zeros(1,n);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"e(%4u) = %12.4e;\n", i+1, phase_error[i]);
+    }
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,real(x),'Color',[1 1 1]*0.8);\n");
+    fprintf(fid,"  plot(t,real(y),'Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  axis([0 n -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,imag(x),'Color',[1 1 1]*0.8);\n");
+    fprintf(fid,"  plot(t,imag(y),'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  axis([0 n -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t,e,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('phase error');\n");
+    fprintf(fid,"  axis([0 n -pi pi]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/nco_pll_modem_example.c b/examples/nco_pll_modem_example.c
new file mode 100644
index 0000000..0e86960
--- /dev/null
+++ b/examples/nco_pll_modem_example.c
@@ -0,0 +1,184 @@
+//
+// nco_pll_modem_example.c
+//
+// This example demonstrates how the nco/pll object (numerically-controlled
+// oscillator with phase-locked loop) can be used for carrier frequency
+// recovery in digital modems.  The modem type, SNR, and other parameters are
+// specified via the command-line interface.
+//
+// SEE ALSO: nco_example.c
+//           nco_pll_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "nco_pll_modem_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("nco_pll_modem_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  s     : signal-to-noise ratio, default: 30dB\n");
+    printf("  b     : pll bandwidth, default: 1e-3\n");
+    printf("  n     : number of symbols, default: 256\n");
+    printf("  P     : phase offset (radians), default: pi/10 ~ 0.3146\n");
+    printf("  F     : frequency offset (radians), default: 0.001\n");
+    printf("  m     : modulation scheme, default: qpsk\n");
+    liquid_print_modulation_schemes();
+}
+
+int main(int argc, char*argv[]) {
+    srand( time(NULL) );
+    // parameters
+    float phase_offset = M_PI/10;
+    float frequency_offset = 0.001f;
+    float SNRdB = 30.0f;
+    float pll_bandwidth = 0.02f;
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+    unsigned int n=256;     // number of iterations
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhs:b:n:P:F:m:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();    return 0;
+        case 's':   SNRdB = atof(optarg);           break;
+        case 'b':   pll_bandwidth = atof(optarg);   break;
+        case 'n':   n = atoi(optarg);               break;
+        case 'P':   phase_offset = atof(optarg);    break;
+        case 'F':   frequency_offset= atof(optarg); break;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme \"%s\"\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+    unsigned int d=n/32;      // print every "d" lines
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid, "%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid, "clear all;\n");
+    fprintf(fid, "phi=zeros(1,%u);\n",n);
+    fprintf(fid, "r=zeros(1,%u);\n",n);
+
+    // objects
+    nco_crcf nco_tx = nco_crcf_create(LIQUID_VCO);
+    nco_crcf nco_rx = nco_crcf_create(LIQUID_VCO);
+
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+
+    unsigned int bps = modem_get_bps(mod);
+
+    // initialize objects
+    nco_crcf_set_phase(nco_tx, phase_offset);
+    nco_crcf_set_frequency(nco_tx, frequency_offset);
+    nco_crcf_pll_set_bandwidth(nco_rx, pll_bandwidth);
+
+    float noise_power = powf(10.0f, -SNRdB/20.0f);
+
+    // print parameters
+    printf("PLL example :\n");
+    printf("modem : %u-%s\n", 1<<bps, modulation_types[ms].name);
+    printf("frequency offset: %6.3f, phase offset: %6.3f, SNR: %6.2fdB, pll b/w: %6.3f\n",
+            frequency_offset, phase_offset, SNRdB, pll_bandwidth);
+
+    // run loop
+    unsigned int i, M=1<<bps, sym_in, sym_out, num_errors=0;
+    float phase_error;
+    float complex x, r, v, noise;
+    for (i=0; i<n; i++) {
+        // generate random symbol
+        sym_in = rand() % M;
+
+        // modulate
+        modem_modulate(mod, sym_in, &x);
+
+        // channel
+        //r = nco_crcf_cexpf(nco_tx);
+        nco_crcf_mix_up(nco_tx, x, &r);
+
+        // add complex white noise
+        crandnf(&noise);
+        r += noise * noise_power;
+
+        // 
+        //v = nco_crcf_cexpf(nco_rx);
+        nco_crcf_mix_down(nco_rx, r, &v);
+
+        // demodulate
+        modem_demodulate(demod, v, &sym_out);
+        num_errors += count_bit_errors(sym_in, sym_out);
+
+        // error estimation
+        //phase_error = cargf(r*conjf(v));
+        phase_error = modem_get_demodulator_phase_error(demod);
+
+        // perfect error estimation
+        //phase_error = nco_tx->theta - nco_rx->theta;
+
+        // print every line in a format that octave can read
+        fprintf(fid, "phi(%u) = %10.6E;\n", i+1, phase_error);
+        fprintf(fid, "r(%u) = %10.6E + j*%10.6E;\n",
+                i+1, crealf(v), cimagf(v));
+
+        if ((i+1)%d == 0 || i==n-1) {
+            printf("  %4u: e_hat : %6.3f, phase error : %6.3f, freq error : %6.3f\n",
+                    i+1,                                // iteration
+                    phase_error,                        // estimated phase error
+                    nco_crcf_get_phase(nco_tx) - nco_crcf_get_phase(nco_rx),// true phase error
+                    nco_crcf_get_frequency(nco_tx) - nco_crcf_get_frequency(nco_rx)// true frequency error
+                  );
+        }
+
+        // update tx nco object
+        nco_crcf_step(nco_tx);
+
+        // update pll
+        nco_crcf_pll_step(nco_rx, phase_error);
+
+        // update rx nco object
+        nco_crcf_step(nco_rx);
+    }
+
+    fprintf(fid, "figure;\n");
+    fprintf(fid, "plot(1:length(phi),phi,'LineWidth',2,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid, "xlabel('Symbol Index');\n");
+    fprintf(fid, "ylabel('Phase Error [radians]');\n");
+    fprintf(fid, "grid on;\n");
+
+    fprintf(fid, "t0 = round(0.25*length(r));\n");
+    fprintf(fid, "figure;\n");
+    fprintf(fid, "plot(r(1:t0),'x','Color',[0.6 0.6 0.6],r(t0:end),'x','Color',[0 0.25 0.5]);\n");
+    fprintf(fid, "grid on;\n");
+    fprintf(fid, "axis([-1.5 1.5 -1.5 1.5]);\n");
+    fprintf(fid, "axis('square');\n");
+    fprintf(fid, "xlabel('In-Phase');\n");
+    fprintf(fid, "ylabel('Quadrature');\n");
+    fprintf(fid, "legend(['first 25%%'],['last 75%%'],1);\n");
+    fclose(fid);
+
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    nco_crcf_destroy(nco_tx);
+    nco_crcf_destroy(nco_rx);
+
+    modem_destroy(mod);
+    modem_destroy(demod);
+
+    printf("bit errors: %u / %u\n", num_errors, bps*n);
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/nyquist_filter_example.c b/examples/nyquist_filter_example.c
new file mode 100644
index 0000000..c36fd06
--- /dev/null
+++ b/examples/nyquist_filter_example.c
@@ -0,0 +1,124 @@
+//
+// nyquist_filter_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "nyquist_filter_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("nyquist_filter_example options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [kaiser], pm, rcos, fexp, fsech, farcsech\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 2\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 4\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.33\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=2;   // samples/symbol
+    unsigned int m=4;   // symbol delay
+    float beta=0.33f;   // excess bandwidth factor
+    int ftype = LIQUID_FIRFILT_RCOS;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:k:m:b:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 't':
+            if (strcmp(optarg,"kaiser")==0) {
+                ftype = LIQUID_FIRFILT_KAISER;
+            } else if (strcmp(optarg,"pm")==0) {
+                ftype = LIQUID_FIRFILT_PM;
+            } else if (strcmp(optarg,"rcos")==0) {
+                ftype = LIQUID_FIRFILT_RCOS;
+            } else if (strcmp(optarg,"fexp")==0) {
+                ftype = LIQUID_FIRFILT_FEXP;
+            } else if (strcmp(optarg,"fsech")==0) {
+                ftype = LIQUID_FIRFILT_FSECH;
+            } else if (strcmp(optarg,"farcsech")==0) {
+                ftype = LIQUID_FIRFILT_FARCSECH;
+            } else {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'k':   k = atoi(optarg);           break;
+        case 'm':   m = atoi(optarg);           break;
+        case 'b':   beta = atof(optarg);        break;
+        default:
+            exit(1);
+        }
+    }
+
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, beta must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // initialize objects
+    unsigned int h_len = 2*k*m+1;
+    float h[h_len];
+
+    // design the filter
+    liquid_firdes_prototype(ftype,k,m,beta,0,h);
+
+    // print the coefficients to the screen
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        printf("h(%3u) = %12.8f\n", i+1, h[i]);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %12.8f;\n", beta);
+    fprintf(fid,"h_len = 2*k*m+1;\n");
+
+    fprintf(fid,"h = zeros(1,h_len);\n");
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%3u) = %20.8e;\n", i+1, h[i]);
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H = 20*log10(abs(fftshift(fft(h/k,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-','LineWidth',2,...\n");
+    fprintf(fid,"     [0.5/k],[-20*log10(2)],'or',...\n");
+    fprintf(fid,"     [0.5/k*(1-beta) 0.5/k*(1-beta)],[-100 10],'-r',...\n");
+    fprintf(fid,"     [0.5/k*(1+beta) 0.5/k*(1+beta)],[-100 10],'-r');\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD');\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 10]);\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+    
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/ofdmflexframesync_example.c b/examples/ofdmflexframesync_example.c
new file mode 100644
index 0000000..a030eb6
--- /dev/null
+++ b/examples/ofdmflexframesync_example.c
@@ -0,0 +1,227 @@
+//
+// ofdmflexframesync_example.c
+//
+// Example demonstrating the OFDM flexible frame synchronizer.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+void usage()
+{
+    printf("ofdmflexframesync_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  s     : signal-to-noise ratio [dB], default: 20\n");
+    printf("  F     : carrier frequency offset, default: 0.002\n");
+    printf("  M     : number of subcarriers (must be even), default: 64\n");
+    printf("  C     : cyclic prefix length, default: 16\n");
+    printf("  n     : payload length [bytes], default: 120\n");
+    printf("  m     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+    printf("  d     : enable debugging\n");
+}
+
+// callback function
+int callback(unsigned char *  _header,
+             int              _header_valid,
+             unsigned char *  _payload,
+             unsigned int     _payload_len,
+             int              _payload_valid,
+             framesyncstats_s _stats,
+             void *           _userdata);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int M = 64;                // number of subcarriers
+    unsigned int cp_len = 16;           // cyclic prefix length
+    unsigned int taper_len = 4;         // taper length
+    unsigned int payload_len = 120;     // length of payload (bytes)
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+    fec_scheme fec0  = LIQUID_FEC_NONE;
+    fec_scheme fec1  = LIQUID_FEC_HAMMING128;
+    crc_scheme check = LIQUID_CRC_32;
+    float noise_floor = -30.0f;         // noise floor [dB]
+    float SNRdB = 20.0f;                // signal-to-noise ratio [dB]
+    float dphi = 0.02f;                 // carrier frequency offset
+    int debug_enabled =  0;             // enable debugging?
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhds:F:M:C:n:m:v:c:k:")) != EOF){
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'd': debug_enabled = 1;            break;
+        case 's': SNRdB         = atof(optarg); break;
+        case 'F': dphi          = atof(optarg); break;
+        case 'M': M             = atoi(optarg); break;
+        case 'C': cp_len        = atoi(optarg); break;
+        case 'n': payload_len   = atol(optarg); break;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported mod. scheme: %s\n", argv[0], optarg);
+                exit(-1);
+            }
+            break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            // inner FEC scheme
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported inner FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            // outer FEC scheme
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported outer FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(-1);
+        }
+    }
+
+    unsigned int i;
+
+    // TODO : validate options
+
+    // derived values
+    unsigned int frame_len = M + cp_len;
+    float complex buffer[frame_len]; // time-domain buffer
+    float nstd = powf(10.0f, noise_floor/20.0f);
+    float gamma = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+
+    // allocate memory for header, payload
+    unsigned char header[8];
+    unsigned char payload[payload_len];
+
+    // initialize subcarrier allocation
+    unsigned char p[M];
+    ofdmframe_init_default_sctype(M, p);
+
+    // create frame generator
+    ofdmflexframegenprops_s fgprops;
+    ofdmflexframegenprops_init_default(&fgprops);
+    fgprops.check           = check;
+    fgprops.fec0            = fec0;
+    fgprops.fec1            = fec1;
+    fgprops.mod_scheme      = ms;
+    ofdmflexframegen fg = ofdmflexframegen_create(M, cp_len, taper_len, p, &fgprops);
+
+    // create frame synchronizer
+    ofdmflexframesync fs = ofdmflexframesync_create(M, cp_len, taper_len, p, callback, (void*)payload);
+    if (debug_enabled)
+        ofdmflexframesync_debug_enable(fs);
+
+    // initialize header/payload and assemble frame
+    for (i=0; i<8; i++)
+        header[i] = i & 0xff;
+    for (i=0; i<payload_len; i++)
+        payload[i] = rand() & 0xff;
+    ofdmflexframegen_assemble(fg, header, payload, payload_len);
+    ofdmflexframegen_print(fg);
+    ofdmflexframesync_print(fs);
+
+    // initialize frame synchronizer with noise
+    for (i=0; i<1000; i++) {
+        float complex noise = nstd*( randnf() + _Complex_I*randnf())*M_SQRT1_2;
+        ofdmflexframesync_execute(fs, &noise, 1);
+    }
+
+    // generate frame, push through channel
+    int last_symbol=0;
+    nco_crcf nco = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_set_frequency(nco, dphi);
+    while (!last_symbol) {
+        // generate symbol
+        last_symbol = ofdmflexframegen_writesymbol(fg, buffer);
+
+        // apply channel
+        for (i=0; i<frame_len; i++) {
+            float complex noise = nstd*( randnf() + _Complex_I*randnf())*M_SQRT1_2;
+            buffer[i] *= gamma;
+            buffer[i] += noise;
+            
+            nco_crcf_mix_up(nco, buffer[i], &buffer[i]);
+            nco_crcf_step(nco);
+        }
+
+        // receive symbol
+        ofdmflexframesync_execute(fs, buffer, frame_len);
+    }
+    nco_crcf_destroy(nco);
+
+    // export debugging file
+    if (debug_enabled)
+        ofdmflexframesync_debug_print(fs, "ofdmflexframesync_debug.m");
+
+    // destroy objects
+    ofdmflexframegen_destroy(fg);
+    ofdmflexframesync_destroy(fs);
+
+    printf("done.\n");
+    return 0;
+}
+
+// callback function
+int callback(unsigned char *  _header,
+             int              _header_valid,
+             unsigned char *  _payload,
+             unsigned int     _payload_len,
+             int              _payload_valid,
+             framesyncstats_s _stats,
+             void *           _userdata)
+{
+    printf("**** callback invoked : rssi = %8.3f dB, evm = %8.3f dB, cfo = %8.5f\n", _stats.rssi, _stats.evm, _stats.cfo);
+
+    unsigned int i;
+
+    // print header data to standard output
+    printf("  header rx  :");
+    for (i=0; i<8; i++)
+        printf(" %.2X", _header[i]);
+    printf("\n");
+
+    // print payload data to standard output
+    printf("  payload rx :");
+    for (i=0; i<_payload_len; i++) {
+        printf(" %.2X", _payload[i]);
+        if ( ((i+1)%26)==0 && i !=_payload_len-1 )
+            printf("\n              ");
+    }
+    printf("\n");
+
+    // count errors in received payload and print to standard output
+    unsigned char * payload_tx = (unsigned char*) _userdata;
+    unsigned int num_errors = count_bit_errors_array(_payload, payload_tx, _payload_len);
+    printf("  bit errors : %u / %u\n", num_errors, 8*_payload_len);
+
+    return 0;
+}
+
diff --git a/examples/ofdmframegen_example.c b/examples/ofdmframegen_example.c
new file mode 100644
index 0000000..7f33db4
--- /dev/null
+++ b/examples/ofdmframegen_example.c
@@ -0,0 +1,71 @@
+//
+//
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmframegen_example.m"
+
+int main() {
+    // options
+    unsigned int num_subcarriers=64;// 
+    unsigned int cp_len=16;         // cyclic prefix length
+    //unsigned int num_symbols=2;     // number of ofdm symbols
+
+    // 
+    unsigned int frame_len = num_subcarriers + cp_len;
+
+    //unsigned int num_samples = num_subcarriers * num_frames;
+
+    // create synthesizer/analyzer objects
+    ofdmframegen fg = ofdmframegen_create(num_subcarriers, cp_len);
+    ofdmframegen_print(fg);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_subcarriers=%u;\n", num_subcarriers);
+    fprintf(fid,"cp_len=%u;\n", cp_len);
+    fprintf(fid,"frame_len=%u;\n", frame_len);
+
+    fprintf(fid,"X = zeros(1,num_subcarriers);\n");
+    fprintf(fid,"x = zeros(1,frame_len);\n");
+
+    unsigned int i;
+    float complex X[num_subcarriers];   // channelized symbols
+    float complex x[frame_len];         // time-domain samples
+
+    for (i=0; i<num_subcarriers; i++) {
+        X[i] = i==4 ? 0.707f + _Complex_I*0.707f : 0.0f;
+    }
+
+    ofdmframegen_execute(fg,X,x);
+
+    //
+    for (i=0; i<num_subcarriers; i++)
+        fprintf(fid,"X(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(X[i]), cimagf(X[i]));
+
+    //
+    for (i=0; i<frame_len; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    // print results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t=0:(frame_len-1);\n");
+    fprintf(fid,"plot(t,real(x),t,imag(x));\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/ofdmframesync_example.c b/examples/ofdmframesync_example.c
new file mode 100644
index 0000000..abb118a
--- /dev/null
+++ b/examples/ofdmframesync_example.c
@@ -0,0 +1,278 @@
+//
+// ofdmframesync_example.c
+//
+// Example demonstrating the base OFDM frame synchronizer with different
+// parameters and options.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmframesync_example.m"
+
+void usage()
+{
+    printf("Usage: ofdmframesync_example [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  M     : number of subcarriers (must be even),  default: 1200\n");
+    printf("  C     : cyclic prefix length,                  default: 60\n");
+    printf("  T     : taper length,                          default: 50\n");
+    printf("  s     : signal-to-noise ratio [dB],            default: 30\n");
+}
+
+// forward declaration of callback function; this will be invoked for every
+// OFDM symbol received by the parent ofdmframesync object. The object will
+// reset when something other than a zero is returned.
+//  _X          : array of received subcarrier samples [size: _M x 1]
+//  _p          : subcarrier allocation array [size: _M x 1]
+//  _M          : number of subcarriers
+//  _userdata   : user-defined data pointer
+static int callback(float complex * _X,
+                    unsigned char * _p,
+                    unsigned int    _M,
+                    void *          _userdata);
+
+// custom data type to pass to callback function
+struct rx_symbols {
+    float complex syms_bpsk[2000];  // even subcarrier symbols
+    float complex syms_qpsk[2000];  // odd subcarrier symbols
+    unsigned int  num_bpsk;         // counter
+    unsigned int  num_qpsk;         // counter
+};
+
+// main function
+int main(int argc, char*argv[])
+{
+    // set the random seed differently for each run
+    srand(time(NULL));
+
+    // options
+    unsigned int M           = 1200;    // number of subcarriers
+    unsigned int cp_len      = 60;      // cyclic prefix length
+    unsigned int taper_len   = 50;      // taper length
+    unsigned int num_symbols = 20;      // number of data symbols
+    float noise_floor        = -120.0f; // noise floor [dB]
+    float SNRdB              = 30.0f;   // signal-to-noise ratio [dB]
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hdM:C:T:s:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'M': M         = atoi(optarg);     break;
+        case 'C': cp_len    = atoi(optarg);     break;
+        case 'T': taper_len = atoi(optarg);     break;
+        case 's': SNRdB     = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int frame_len   = M + cp_len;
+    unsigned int num_samples = (3+num_symbols)*frame_len;
+    float        nstd        = powf(10.0f, noise_floor/20.0f);
+    float        gamma       = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+
+    unsigned char p[M];
+    float complex X[M];             // channelized symbols
+    float complex y[num_samples];   // output time series
+
+    // initialize subcarrier allocation
+    ofdmframe_init_default_sctype(M, p);
+
+    // create subcarrier notch in upper half of band
+    unsigned int n0 = (unsigned int) (0.13 * M);    // lower edge of notch
+    unsigned int n1 = (unsigned int) (0.21 * M);    // upper edge of notch
+    for (i=n0; i<n1; i++)
+        p[i] = OFDMFRAME_SCTYPE_NULL;
+
+    // create struct for holding data symbols
+    struct rx_symbols data;
+    data.num_bpsk = 0;
+    data.num_qpsk = 0;
+
+    // create frame generator
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, p);
+    //ofdmframegen_print(fg);
+
+    // create frame synchronizer
+    ofdmframesync fs = ofdmframesync_create(M, cp_len, taper_len, p, callback, (void*)&data);
+    //ofdmframesync_print(fs);
+
+    unsigned int n=0;
+
+    // write first S0 symbol
+    ofdmframegen_write_S0a(fg, &y[n]);
+    n += frame_len;
+
+    // write second S0 symbol
+    ofdmframegen_write_S0b(fg, &y[n]);
+    n += frame_len;
+
+    // write S1 symbol
+    ofdmframegen_write_S1( fg, &y[n]);
+    n += frame_len;
+
+    // modulate data subcarriers
+    for (i=0; i<num_symbols; i++) {
+
+        // load different subcarriers with different data
+        unsigned int j;
+        for (j=0; j<M; j++) {
+            // ignore 'null' and 'pilot' subcarriers
+            if (p[j] != OFDMFRAME_SCTYPE_DATA)
+                continue;
+
+            // use BPSK for even frequencies, QPSK for odd
+            if ( (j % 2) == 0 ) {
+                // BPSK
+                X[j] = rand() % 2 ? -1.0f : 1.0f;
+            } else {
+                // QPSK
+                X[j] = (rand() % 2 ? -0.707f : 0.707f) +
+                       (rand() % 2 ? -0.707f : 0.707f) * _Complex_I;
+            }
+        }
+
+        // generate OFDM symbol in the time domain
+        ofdmframegen_writesymbol(fg, X, &y[n]);
+        n += frame_len;
+    }
+
+    // add channel effects
+    for (i=0; i<num_samples; i++) {
+
+        // channel gain
+        y[i] *= gamma;
+
+        // add noise
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // execute synchronizer on entire frame
+    ofdmframesync_execute(fs,y,num_samples);
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    ofdmframesync_destroy(fs);
+
+    // estimate power spectral density of received signal
+    unsigned int nfft = 1024;   // FFT size
+    float        psd[nfft];     // PSD estimate output array
+    spgramcf periodogram = spgramcf_create_default(nfft);
+    spgramcf_estimate_psd(periodogram, y, num_samples, psd);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"M           = %u;\n", M);
+    fprintf(fid,"noise_floor = %f;\n", noise_floor);
+    fprintf(fid,"SNRdB       = %f;\n", SNRdB);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+
+    // save received time-domain signal
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // save power spectral density estimate
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%3u) = %12.4e;\n", i+1, psd[i]);
+    fprintf(fid,"psd = 10*log10( fftshift(psd) );\n");
+
+    // save received symbols
+    fprintf(fid,"num_bpsk = %u;\n", data.num_bpsk);
+    fprintf(fid,"num_qpsk = %u;\n", data.num_qpsk);
+    fprintf(fid,"syms_bpsk = zeros(1,num_bpsk);\n");
+    fprintf(fid,"syms_qpsk = zeros(1,num_qpsk);\n");
+    for (i=0; i<data.num_bpsk; i++)
+        fprintf(fid,"syms_bpsk(%3u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(data.syms_bpsk[i]), cimagf(data.syms_bpsk[i]));
+    for (i=0; i<data.num_qpsk; i++)
+        fprintf(fid,"syms_qpsk(%3u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(data.syms_qpsk[i]), cimagf(data.syms_qpsk[i]));
+
+    // plot power spectral density
+    fprintf(fid,"\n\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,psd,'LineWidth',1.5,'Color',[0 0.3 0.5]);\n");
+    fprintf(fid,"psd_min = noise_floor - 10;\n");
+    fprintf(fid,"psd_max = noise_floor + 10 + max(SNRdB, 0);\n");
+    fprintf(fid,"axis([-0.5 0.5 psd_min psd_max]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+
+    // plot received constellation
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(real(syms_bpsk),imag(syms_bpsk),'x','MarkerSize',4,'Color',[0 0.3 0.5]);\n");
+    fprintf(fid,"plot(real(syms_qpsk),imag(syms_qpsk),'x','MarkerSize',4,'Color',[0 0.5 0.3]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('I');\n");
+    fprintf(fid,"ylabel('Q');\n");
+    fprintf(fid,"legend('even subcarriers (BPSK)','odd subcarriers (QPSK)','location','northeast');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// callback function
+//  _X          : array of received subcarrier samples [size: _M x 1]
+//  _p          : subcarrier allocation array [size: _M x 1]
+//  _M          : number of subcarriers
+//  _userdata   : user-defined data pointer
+static int callback(float complex * _X,
+                    unsigned char * _p,
+                    unsigned int    _M,
+                    void *          _userdata)
+{
+    // print status to the screen
+    printf("**** callback invoked\n");
+
+    // save received data subcarriers
+    struct rx_symbols * data = (struct rx_symbols*) _userdata;
+    unsigned int i;
+    for (i=0; i<_M; i++) {
+        // ignore 'null' and 'pilot' subcarriers
+        if (_p[i] != OFDMFRAME_SCTYPE_DATA)
+            continue;
+
+        // extract BPSK (even frequencies) and QPSK (odd frequencies) symbols
+        if ( (i % 2) == 0 && data->num_bpsk < 2000) {
+            // save at most 2000 BPSK symbols
+            data->syms_bpsk[data->num_bpsk] = _X[i];
+            data->num_bpsk++;
+        } else if ( (i % 2) == 1 && data->num_qpsk < 2000) {
+            // save at most 2000 QPSK symbols
+            data->syms_qpsk[data->num_qpsk] = _X[i];
+            data->num_qpsk++;
+        }
+    }
+
+    // return
+    return 0;
+}
+
diff --git a/examples/packetizer_example.c b/examples/packetizer_example.c
new file mode 100644
index 0000000..c68da53
--- /dev/null
+++ b/examples/packetizer_example.c
@@ -0,0 +1,157 @@
+//
+// packetizer_example.c
+//
+// Demonstrates the functionality of the packetizer object.  Data are encoded
+// using two forward error-correction schemes (an inner and outer code) before
+// data errors are introduced.  The decoder then tries to recover the original
+// data message.
+//
+// SEE ALSO: fec_example.c
+//           crc_example.c
+//           packetizer_soft_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("packetizer_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : input data size (number of uncoded bytes): 8 default\n");
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int n=8;                       // original data message length
+    crc_scheme check = LIQUID_CRC_32;       // data integrity check
+    fec_scheme fec0 = LIQUID_FEC_HAMMING74; // inner code
+    fec_scheme fec1 = LIQUID_FEC_NONE;      // outer code
+
+    // read command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:v:c:k:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n':
+            n = atoi(optarg);
+            if (n < 1) {
+                printf("error: packet length must be positive\n");
+                usage();
+                exit(-1);
+            }
+            break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            // inner FEC scheme
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported inner FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            // outer FEC scheme
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported outer FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+    unsigned int k = packetizer_compute_enc_msg_len(n,check,fec0,fec1);
+    packetizer p = packetizer_create(n,check,fec0,fec1);
+    packetizer_print(p);
+
+    // initialize arrays
+    unsigned char msg_org[n];   // original message
+    unsigned char msg_enc[k];   // encoded message
+    unsigned char msg_rec[k];   // recieved message
+    unsigned char msg_dec[n];   // decoded message
+    int crc_pass;
+
+    // initialize original data message
+    for (i=0; i<n; i++)
+        msg_org[i] = rand() % 256;
+
+    // encode packet
+    packetizer_encode(p,msg_org,msg_enc);
+
+    // add error(s)
+    memmove(msg_rec, msg_enc, k*sizeof(unsigned char));
+    msg_rec[0] ^= 0x01;
+
+    // decode packet
+    crc_pass =
+    packetizer_decode(p,msg_rec,msg_dec);
+
+    // clean up allocated objects
+    packetizer_destroy(p);
+
+    // print results
+    printf("original message:  [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf(" %.2X", (unsigned int) (msg_org[i]));
+    printf("\n");
+
+    printf("encoded message:   [%3u] ",k);
+    for (i=0; i<k; i++)
+        printf(" %.2X", (unsigned int) (msg_enc[i]));
+    printf("\n");
+
+    printf("received message:  [%3u] ",k);
+    for (i=0; i<k; i++)
+        printf("%c%.2X", msg_rec[i]==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_rec[i]));
+    printf("\n");
+
+    printf("decoded message:   [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf("%c%.2X", msg_dec[i] == msg_org[i] ? ' ' : '*', (unsigned int) (msg_dec[i]));
+    printf("\n");
+    printf("\n");
+
+    // count bit errors
+    unsigned int num_sym_errors=0;
+    unsigned int num_bit_errors=0;
+    for (i=0; i<n; i++) {
+        num_sym_errors += (msg_org[i] == msg_dec[i]) ? 0 : 1;
+
+        num_bit_errors += count_bit_errors(msg_org[i], msg_dec[i]);
+    }
+
+    //printf("number of symbol errors detected: %d\n", num_errors_detected);
+    printf("number of symbol errors received: %4u / %4u\n", num_sym_errors, n);
+    printf("number of bit errors received:    %4u / %4u\n", num_bit_errors, n*8);
+
+    if (crc_pass)
+        printf("(crc passed)\n");
+    else
+        printf("(crc failed)\n");
+
+    return 0;
+}
+
diff --git a/examples/packetizer_soft_example.c b/examples/packetizer_soft_example.c
new file mode 100644
index 0000000..ea9f96d
--- /dev/null
+++ b/examples/packetizer_soft_example.c
@@ -0,0 +1,196 @@
+//
+// packetizer_soft_example.c
+//
+// This example demonstrates the functionality of the packetizer object
+// for soft-decision decoding.  Data are encoded using two forward error-
+// correction schemes (an inner and outer code) before noise and data
+// errors are added. The decoder then tries to recover the original data
+// message. Only the outer code uses soft-decision decoding.
+//
+// SEE ALSO: fec_soft_example.c
+//           packetizer_example.c
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("packetizer_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  n     : input data size (number of uncoded bytes): 8 default\n");
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): h74 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int n=8;                       // original data message length
+    crc_scheme check = LIQUID_CRC_32;       // data integrity check
+    fec_scheme fec0 = LIQUID_FEC_HAMMING74; // inner code
+    fec_scheme fec1 = LIQUID_FEC_NONE;      // outer code
+
+    // read command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhn:v:c:k:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 'n':
+            n = atoi(optarg);
+            if (n < 1) {
+                printf("error: packet length must be positive\n");
+                usage();
+                exit(-1);
+            }
+            break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            // inner FEC scheme
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported inner FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            // outer FEC scheme
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported outer FEC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+    unsigned int k = packetizer_compute_enc_msg_len(n,check,fec0,fec1);
+    packetizer p = packetizer_create(n,check,fec0,fec1);
+    packetizer_print(p);
+
+    // initialize arrays
+    unsigned char msg_org[n];   // original message
+    unsigned char msg_enc[k];   // encoded message
+    unsigned char msg_rec[8*k]; // recieved message (soft bits)
+    unsigned char msg_dec[n];   // decoded message
+    int crc_pass;
+
+    // initialize original data message
+    for (i=0; i<n; i++)
+        msg_org[i] = rand() % 256;
+
+    // encode packet
+    packetizer_encode(p,msg_org,msg_enc);
+
+    // convert to soft bits and add 'noise'
+    for (i=0; i<k; i++) {
+        msg_rec[8*i+0] = (msg_enc[i] & 0x80) ? 255 : 0;
+        msg_rec[8*i+1] = (msg_enc[i] & 0x40) ? 255 : 0;
+        msg_rec[8*i+2] = (msg_enc[i] & 0x20) ? 255 : 0;
+        msg_rec[8*i+3] = (msg_enc[i] & 0x10) ? 255 : 0;
+        msg_rec[8*i+4] = (msg_enc[i] & 0x08) ? 255 : 0;
+        msg_rec[8*i+5] = (msg_enc[i] & 0x04) ? 255 : 0;
+        msg_rec[8*i+6] = (msg_enc[i] & 0x02) ? 255 : 0;
+        msg_rec[8*i+7] = (msg_enc[i] & 0x01) ? 255 : 0;
+    }
+
+    // flip first bit (ensure error)
+    msg_rec[0] = 255 - msg_rec[0];
+
+    // add noise (but not so much that it would cause a bit error)
+    for (i=0; i<8*k; i++) {
+        int soft_bit = msg_rec[i] + (int)(20*randnf());
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit = 0;
+        msg_rec[i] = soft_bit;
+    }
+
+    // decode packet
+    crc_pass =
+    packetizer_decode_soft(p,msg_rec,msg_dec);
+
+    // clean up allocated objects
+    packetizer_destroy(p);
+
+    // print results
+    printf("original message:  [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf(" %.2X", (unsigned int) (msg_org[i]));
+    printf("\n");
+
+    printf("encoded message:   [%3u] ",k);
+    for (i=0; i<k; i++)
+        printf(" %.2X", (unsigned int) (msg_enc[i]));
+    printf("\n");
+
+#if 0
+    printf("received message:  [%3u] ",k);
+    for (i=0; i<k; i++)
+        printf("%c%.2X", msg_rec[i]==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_rec[i]));
+    printf("\n");
+#endif
+
+    //if (verbose) {
+    if (1) {
+        // print expanded result (print each soft bit value)
+        for (i=0; i<k; i++) {
+            unsigned char msg_cor_hard = 0x00;
+            printf("%5u: ", i);
+            unsigned int j;
+            for (j=0; j<8; j++) {
+                msg_cor_hard |= (msg_rec[8*i+j] > 127) ? 1<<(8-j-1) : 0;
+                unsigned int bit_enc = (msg_enc[i] >> (8-j-1)) & 0x01;
+                unsigned int bit_rec = (msg_rec[8*i+j] > 127) ? 1 : 0;
+                //printf("%1u %3u (%1u) %c", bit_enc, msg_rec[i], bit_rec, bit_enc != bit_rec ? '*' : ' ');
+                printf("%4u%c", msg_rec[8*i+j], bit_enc != bit_rec ? '*' : ' ');
+            }
+            printf("  :  %c%.2X\n", msg_cor_hard==msg_enc[i] ? ' ' : '*', (unsigned int) (msg_cor_hard));
+        }
+    } // verbose
+
+
+    printf("decoded message:   [%3u] ",n);
+    for (i=0; i<n; i++)
+        printf("%c%.2X", msg_dec[i] == msg_org[i] ? ' ' : '*', (unsigned int) (msg_dec[i]));
+    printf("\n");
+    printf("\n");
+
+    // count bit errors
+    unsigned int num_sym_errors=0;
+    unsigned int num_bit_errors=0;
+    for (i=0; i<n; i++) {
+        num_sym_errors += (msg_org[i] == msg_dec[i]) ? 0 : 1;
+
+        num_bit_errors += count_bit_errors(msg_org[i], msg_dec[i]);
+    }
+
+    //printf("number of symbol errors detected: %d\n", num_errors_detected);
+    printf("number of symbol errors received: %4u / %4u\n", num_sym_errors, n);
+    printf("number of bit errors received:    %4u / %4u\n", num_bit_errors, n*8);
+
+    if (crc_pass)
+        printf("(crc passed)\n");
+    else
+        printf("(crc failed)\n");
+
+    return 0;
+}
+
diff --git a/examples/pll_example.c b/examples/pll_example.c
new file mode 100644
index 0000000..3bbdbe0
--- /dev/null
+++ b/examples/pll_example.c
@@ -0,0 +1,92 @@
+//
+// pll_example.c
+// 
+// Demonstrates a basic phase-locked loop to track the phase of a
+// complex sinusoid.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.h"
+
+// output to octave-friendly format
+#define OUTPUT_FILENAME "pll_example.m"
+
+int main() {
+    // parameters
+    float phase_offset      = 0.8f;     // carrier phase offset
+    float frequency_offset  = 0.01f;    // carrier frequency offset
+    float wn                = 0.05f;    // pll bandwidth
+    float zeta              = 0.707f;   // pll damping factor
+    float K                 = 1000;     // pll loop gain
+    unsigned int n          = 256;      // number of samples
+    unsigned int d          = n/32;     // print every "d" lines
+
+    //
+    float theta[n];         // input phase
+    float complex x[n];     // input sinusoid
+    float phi[n];           // output phase
+    float complex y[n];     // output sinusoid
+
+    // generate iir loop filter object
+    iirfilt_rrrf H = iirfilt_rrrf_create_pll(wn, zeta, K);
+    iirfilt_rrrf_print(H);
+
+    unsigned int i;
+
+    // generate input
+    float t=phase_offset;
+    float dt = frequency_offset;
+    for (i=0; i<n; i++) {
+        theta[i] = t;
+        x[i] = cexpf(_Complex_I*theta[i]);
+
+        t += dt;
+    }
+
+    // run loop
+    float phi_hat=0.0f;
+    for (i=0; i<n; i++) {
+        y[i] = cexpf(_Complex_I*phi_hat);
+
+        // compute error
+        float e = cargf(x[i]*conjf(y[i]));
+
+        if ( (i%d)==0 )
+            printf("e(%3u) = %12.8f;\n", i, e);
+
+        // filter error
+        iirfilt_rrrf_execute(H,e,&phi_hat);
+
+        phi[i] = phi_hat;
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(H);
+
+    // open output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"n=%u;\n",n);
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"theta(%3u) = %16.8e;\n", i+1, theta[i]);
+        fprintf(fid,"  phi(%3u) = %16.8e;\n", i+1, phi[i]);
+    }
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,theta,t,phi);\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('phase');\n");
+
+    fclose(fid);
+
+    printf("output written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/poly_findroots_example.c b/examples/poly_findroots_example.c
new file mode 100644
index 0000000..045b21b
--- /dev/null
+++ b/examples/poly_findroots_example.c
@@ -0,0 +1,36 @@
+// 
+// poly_findroots_example.c
+//
+// test polynomial root-finding algorithm (Bairstow's method)
+//
+
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+int main() {
+    unsigned int n=6;   // polynomial length (order+1)
+    unsigned int i;
+
+    // generate polynomial
+    float p[6] = {6,11,-33,-33,11,6};
+    float complex roots[n-1];
+
+    // print polynomial
+    printf("polynomial:\n");
+    for (i=0; i<n; i++)
+        printf("  p[%3u] = %12.4f\n", i, p[i]);
+
+    // compute roots of polynomial
+    polyf_findroots(p,n,roots);
+    printf("roots:\n");
+    for (i=0; i<n-1; i++) {
+        printf("  r[%3u] = %12.8f + j*%12.8f\n", i,
+                                                 crealf(roots[i]),
+                                                 cimagf(roots[i]));
+    }
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/polyfit_example.c b/examples/polyfit_example.c
new file mode 100644
index 0000000..8983552
--- /dev/null
+++ b/examples/polyfit_example.c
@@ -0,0 +1,72 @@
+// 
+// polyfit_example.c
+//
+// Test polynomial fit to sample data.
+//
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "polyfit_example.m"
+
+int main() {
+
+    unsigned int n=15;      // number of samples
+    unsigned int order=2;   // polynomial order
+
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+
+    // initialize data vectors
+    float x[n];
+    float y[n];
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float v = (float)(i) - (float)(n)/2 + 0.5f;
+        x[i] = v;
+        y[i] = v*v + randnf();
+
+        printf("x : %12.8f, y : %12.8f\n", x[i], y[i]);
+        fprintf(fid,"x(%3u) = %12.4e; y(%3u) = %12.4e;\n", i+1, x[i], i+1, y[i]);
+    }
+
+    // compute coefficients
+    unsigned int k=order+1;
+    float p[k];
+    polyf_fit(x,y,n,p,k);
+
+    for (i=0; i<k; i++) {
+        printf("p[%3u] = %12.4e\n", i, p[i]);
+        // print polynomial coefficients vector in reverse order
+        fprintf(fid,"p(%3u) = %12.4e;\n", i+1, p[k-i-1]);
+    }
+
+    // evaluate polynomial
+    float xmin = -7.5f;
+    float xmax =  7.5f;
+    unsigned int num_steps = 64;
+    float dx = (xmax-xmin)/(num_steps-1);
+    float xtest = xmin;
+    float ytest;
+    for (i=0; i<num_steps; i++) {
+        ytest = polyf_val(p,k,xtest);
+        fprintf(fid,"xtest(%3u) = %12.4e; ytest(%3u) = %12.4e;\n", i+1, xtest, i+1, ytest);
+        xtest += dx;
+    }
+    
+    // plot results
+    fprintf(fid,"plot(x,y,'x',xtest,ytest,'-');\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('y, p^{(%u)}(x)');\n", order);
+    fprintf(fid,"legend('data','poly-fit',0);\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/polyfit_lagrange_example.c b/examples/polyfit_lagrange_example.c
new file mode 100644
index 0000000..640e356
--- /dev/null
+++ b/examples/polyfit_lagrange_example.c
@@ -0,0 +1,77 @@
+// 
+// polyfit_lagrange_example.c
+//
+// Test exact polynomial fit to sample data using Lagrange
+// interpolating polynomials.
+// SEE ALSO: polyfit_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "polyfit_lagrange_example.m"
+
+int main() {
+    unsigned int n=15;      // number of samples
+
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+
+    // initialize data vectors
+    float x[n];
+    float y[n];
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // compute Chebyshev points of the second kind
+        x[i] = cosf(M_PI*(float)(i)/(float)(n-1));
+
+        // random samples
+        y[i] = 0.2f*randnf();
+
+        printf("x : %12.8f, y : %12.8f\n", x[i], y[i]);
+        fprintf(fid,"x(%3u) = %12.4e; y(%3u) = %12.4e;\n", i+1, x[i], i+1, y[i]);
+    }
+
+    // compute Lagrange interpolation weights
+    //float p[n];
+    //polyf_fit_lagrange(x,y,n,p);
+    float w[n];
+    polyf_fit_lagrange_barycentric(x,n,w);
+
+    // print coefficients
+    // NOTE : for Chebyshev points of the second kind, w[i] = (-1)^i * (i==0 || i==n-1 ? 1 : 2)
+    for (i=0; i<n; i++)
+        printf("  w[%3u] = %12.4e;\n", i, w[i]);
+
+    // evaluate polynomial
+    float xmin = -1.1f;
+    float xmax =  1.1f;
+    unsigned int num_steps = 16*n;
+    float dx = (xmax-xmin)/(num_steps-1);
+    float xtest = xmin;
+    float ytest;
+    for (i=0; i<num_steps; i++) {
+        ytest = polyf_val_lagrange_barycentric(x,y,w,xtest,n);
+        fprintf(fid,"xtest(%3u) = %12.4e; ytest(%3u) = %12.4e;\n", i+1, xtest, i+1, ytest);
+        xtest += dx;
+    }
+
+    // plot results
+    fprintf(fid,"plot(x,y,'s',xtest,ytest,'-');\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('y, p^{(%u)}(x)');\n", n);
+    fprintf(fid,"legend('data','poly-fit (barycentric)',0);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1.1 1.1 1.5*min(y) 1.5*max(y)]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/qdetector_cccf_example.c b/examples/qdetector_cccf_example.c
new file mode 100644
index 0000000..c10bdd6
--- /dev/null
+++ b/examples/qdetector_cccf_example.c
@@ -0,0 +1,227 @@
+// 
+// qdetector_example.c
+//
+// This example demonstrates the functionality of the qdetector object
+// to detect an arbitrary signal in time in the presence of noise,
+// carrier frequency/phase offsets, and fractional-sample timing
+// offsets.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "qdetector_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("qdetector_cccf_example\n");
+    printf("options:\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : number of sync symbols,     default:  80\n");
+    printf("  k     : samples/symbol,             default:  2\n");
+    printf("  m     : filter delay,               default:  7 sybmols\n");
+    printf("  b     : excess bandwidth factor,    default:  0.3\n");
+    printf("  F     : carrier frequency offset,   default: -0.01\n");
+    printf("  T     : fractional sample offset,   default:  0\n");
+    printf("  S     : SNR [dB],                   default:  20 dB\n");
+    printf("  t     : detection threshold,        default:  0.3\n");
+    printf("  r     : carrier offset search range,default:  0.05\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int sequence_len =   80;   // number of sync symbols
+    unsigned int k            =    2;   // samples/symbol
+    unsigned int m            =    7;   // filter delay [symbols]
+    float        beta         = 0.3f;   // excess bandwidth factor
+    int          ftype        = LIQUID_FIRFILT_ARKAISER;
+    float        gamma        = 10.0f;  // channel gain
+    float        tau          = -0.3f;  // fractional sample timing offset
+    float        dphi         = -0.01f; // carrier frequency offset
+    float        phi          =  0.5f;  // carrier phase offset
+    float        SNRdB        = 20.0f;  // signal-to-noise ratio [dB]
+    float        threshold    =  0.5f;  // detection threshold
+    float        range        =  0.05f; // carrier offset search range [radians/sample]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:k:m:b:F:T:S:t:r:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'n': sequence_len  = atoi(optarg); break;
+        case 'k': k             = atoi(optarg); break;
+        case 'm': m             = atoi(optarg); break;
+        case 'b': beta          = atof(optarg); break;
+        case 'F': dphi          = atof(optarg); break;
+        case 'T': tau           = atof(optarg); break;
+        case 'S': SNRdB         = atof(optarg); break;
+        case 't': threshold     = atof(optarg); break;
+        case 'r': range         = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (tau < -0.5f || tau > 0.5f) {
+        fprintf(stderr,"error: %s, fractional sample offset must be in [-0.5,0.5]\n", argv[0]);
+        exit(1);
+    }
+
+    // generate synchronization sequence (QPSK symbols)
+    float complex sequence[sequence_len];
+    for (i=0; i<sequence_len; i++) {
+        sequence[i] = (rand() % 2 ? 1.0f : -1.0f) * M_SQRT1_2 +
+                      (rand() % 2 ? 1.0f : -1.0f) * M_SQRT1_2 * _Complex_I;
+    }
+
+    //
+    float tau_hat   = 0.0f;
+    float gamma_hat = 0.0f;
+    float dphi_hat  = 0.0f;
+    float phi_hat   = 0.0f;
+    int   frame_detected = 0;
+
+    // create detector
+    qdetector_cccf q = qdetector_cccf_create_linear(sequence, sequence_len, ftype, k, m, beta);
+    qdetector_cccf_set_threshold(q, threshold);
+    qdetector_cccf_set_range    (q, range);
+    qdetector_cccf_print(q);
+
+    //
+    unsigned int seq_len     = qdetector_cccf_get_seq_len(q);
+    unsigned int buf_len     = qdetector_cccf_get_buf_len(q);
+    unsigned int num_samples = 2*buf_len;   // double buffer length to ensure detection
+    unsigned int num_symbols = buf_len;
+
+    // arrays
+    float complex y[num_samples];       // received signal
+    float complex syms_rx[num_symbols]; // recovered symbols
+
+    // get pointer to sequence and generate full sequence
+    float complex * v = (float complex*) qdetector_cccf_get_sequence(q);
+    unsigned int filter_delay = 15;
+    firfilt_crcf filter = firfilt_crcf_create_kaiser(2*filter_delay+1, 0.4f, 60.0f, -tau);
+    float        nstd        = 0.1f;
+    for (i=0; i<num_samples; i++) {
+        // add delay
+        firfilt_crcf_push(filter, i < seq_len ? v[i] : 0);
+        firfilt_crcf_execute(filter, &y[i]);
+
+        // channel gain
+        y[i] *= gamma;
+
+        // carrier offset
+        y[i] *= cexpf(_Complex_I*(dphi*i + phi));
+        
+        // noise
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+    firfilt_crcf_destroy(filter);
+
+    // run detection on sequence
+    for (i=0; i<num_samples; i++) {
+        v = qdetector_cccf_execute(q,y[i]);
+
+        if (v != NULL) {
+            printf("\nframe detected!\n");
+            frame_detected = 1;
+
+            // get statistics
+            tau_hat   = qdetector_cccf_get_tau(q);
+            gamma_hat = qdetector_cccf_get_gamma(q);
+            dphi_hat  = qdetector_cccf_get_dphi(q);
+            phi_hat   = qdetector_cccf_get_phi(q);
+            break;
+        }
+    }
+
+    unsigned int num_syms_rx = 0;   // output symbol counter
+    unsigned int counter     = 0;   // decimation counter
+    if (frame_detected) {
+        // recover symbols
+        firfilt_crcf mf = firfilt_crcf_create_rnyquist(ftype, k, m, beta, tau_hat);
+        firfilt_crcf_set_scale(mf, 1.0f / (float)(k*gamma_hat));
+        nco_crcf     nco = nco_crcf_create(LIQUID_VCO);
+        nco_crcf_set_frequency(nco, dphi_hat);
+        nco_crcf_set_phase    (nco,  phi_hat);
+
+        for (i=0; i<buf_len; i++) {
+            // 
+            float complex sample;
+            nco_crcf_mix_down(nco, v[i], &sample);
+            nco_crcf_step(nco);
+
+            // apply decimator
+            firfilt_crcf_push(mf, sample);
+            counter++;
+            if (counter == k-1)
+                firfilt_crcf_execute(mf, &syms_rx[num_syms_rx++]);
+            counter %= k;
+        }
+
+        nco_crcf_destroy(nco);
+        firfilt_crcf_destroy(mf);
+    }
+
+    // destroy objects
+    qdetector_cccf_destroy(q);
+
+    // print results
+    printf("\n");
+    printf("frame detected  :   %s\n", frame_detected ? "yes" : "no");
+    if (frame_detected) {
+        printf("  gamma hat     : %8.3f, actual=%8.3f (error=%8.3f)\n",            gamma_hat, gamma, gamma_hat - gamma);
+        printf("  tau hat       : %8.3f, actual=%8.3f (error=%8.3f) samples\n",    tau_hat,   tau,   tau_hat   - tau  );
+        printf("  dphi hat      : %8.5f, actual=%8.5f (error=%8.5f) rad/sample\n", dphi_hat,  dphi,  dphi_hat  - dphi );
+        printf("  phi hat       : %8.5f, actual=%8.5f (error=%8.5f) radians\n",    phi_hat,   phi,   phi_hat   - phi  );
+        printf("  symbols rx    : %u\n", num_syms_rx);
+    }
+    printf("\n");
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"sequence_len= %u;\n", sequence_len);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"num_syms_rx = %u;\n", num_syms_rx);
+    fprintf(fid,"syms_rx     = zeros(1,num_syms_rx);\n");
+    for (i=0; i<num_syms_rx; i++)
+        fprintf(fid,"syms_rx(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(syms_rx[i]), cimagf(syms_rx[i]));
+
+    fprintf(fid,"t=[0:(num_samples-1)];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(4,1,1);\n");
+    fprintf(fid,"  plot(t,real(y), t,imag(y));\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('received signal');\n");
+    fprintf(fid,"subplot(4,1,2:4);\n");
+    fprintf(fid,"  plot(real(syms_rx), imag(syms_rx), 'x');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('real');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/examples/qnsearch_example.c b/examples/qnsearch_example.c
new file mode 100644
index 0000000..798418d
--- /dev/null
+++ b/examples/qnsearch_example.c
@@ -0,0 +1,67 @@
+//
+// qnsearch_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "qnsearch_example.m"
+
+int main() {
+    unsigned int num_parameters = 8;    // dimensionality of search (minimum 2)
+    unsigned int num_iterations = 4000; // number of iterations to run
+
+    float optimum_vect[num_parameters];
+    unsigned int i;
+    for (i=0; i<num_parameters; i++)
+        optimum_vect[i] = 0.0f;
+
+    float optimum_utility;
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    // create qnsearch object
+    qnsearch gs = qnsearch_create(
+        NULL, optimum_vect, num_parameters, &liquid_rosenbrock, LIQUID_OPTIM_MINIMIZE);
+
+    // execute search
+    //optimum_utility = qnsearch_run(gs, num_iterations, -1e-6f);
+
+    // execute search one iteration at a time
+    fprintf(fid,"u = zeros(1,%u);\n", num_iterations);
+    for (i=0; i<num_iterations; i++) {
+        optimum_utility = liquid_rosenbrock(NULL,optimum_vect,num_parameters);
+        fprintf(fid,"u(%3u) = %12.4e;\n", i+1, optimum_utility);
+
+        qnsearch_step(gs);
+
+        if (((i+1)%100)==0)
+            qnsearch_print(gs);
+    }
+
+    // print results
+    printf("\n");
+    qnsearch_print(gs);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(u);\n");
+    fprintf(fid,"xlabel('iteration');\n");
+    fprintf(fid,"ylabel('utility');\n");
+    fprintf(fid,"title('quasinewton search results');\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // test results, optimum at [1, 1, 1, ... 1];
+
+    qnsearch_destroy(gs);
+
+    return 0;
+}
diff --git a/examples/qpacketmodem_example.c b/examples/qpacketmodem_example.c
new file mode 100644
index 0000000..980c755
--- /dev/null
+++ b/examples/qpacketmodem_example.c
@@ -0,0 +1,139 @@
+//
+// qpacketmodem_example.c
+//
+// This example demonstrates the basic packet modem encoder/decoder
+// operation. A packet of data is encoded and modulated into symbols,
+// channel noise is added, and the resulting packet is demodulated
+// and decoded.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+void usage()
+{
+    printf("ofdmflexframesync_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  n     : payload length [bytes], default: 400\n");
+    printf("  m     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): g2412 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+    printf("  s     : signal-to-noise ratio [dB], default: 6\n");
+}
+
+int main(int argc, char *argv[])
+{
+    //srand( time(NULL) );
+
+    // options
+    modulation_scheme ms     = LIQUID_MODEM_QPSK;        // mod. scheme
+    crc_scheme   check       = LIQUID_CRC_32;            // data validity check
+    fec_scheme   fec0        = LIQUID_FEC_GOLAY2412;     // fec (inner)
+    fec_scheme   fec1        = LIQUID_FEC_NONE;          // fec (outer)
+    unsigned int payload_len = 400;                      // payload length
+    float        SNRdB       = 6.0f;                     // SNR [dB]
+    const char   filename[]  = "qpacketmodem_example.m"; // output filename
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hn:m:v:c:k:s:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                                     return 0;
+        case 'n': payload_len = atol(optarg);                  break;
+        case 'm': ms          = liquid_getopt_str2mod(optarg); break;
+        case 'v': check       = liquid_getopt_str2crc(optarg); break;
+        case 'c': fec0        = liquid_getopt_str2fec(optarg); break;
+        case 'k': fec1        = liquid_getopt_str2fec(optarg); break;
+        case 's': SNRdB       = atof(optarg);                  break;
+        default:
+            exit(-1);
+        }
+    }
+    unsigned int i;
+
+    // derived values
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // create and configure packet encoder/decoder object
+    qpacketmodem q = qpacketmodem_create();
+    qpacketmodem_configure(q, payload_len, check, fec0, fec1, ms);
+    qpacketmodem_print(q);
+
+    // initialize payload
+    unsigned char payload_tx[payload_len];
+    unsigned char payload_rx[payload_len];
+
+    // initialize payload
+    for (i=0; i<payload_len; i++) {
+        payload_tx[i] = rand() & 0xff;
+        payload_rx[i] = 0x00;
+    }
+
+    // get frame length
+    unsigned int frame_len = qpacketmodem_get_frame_len(q);
+
+    // allocate memory for frame samples
+    float complex frame_tx[frame_len];
+    float complex frame_rx[frame_len];
+
+    // encode frame
+    qpacketmodem_encode(q, payload_tx, frame_tx);
+
+    // add noise
+    for (i=0; i<frame_len; i++)
+        frame_rx[i] = frame_tx[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+    // decode frame
+    int crc_pass = qpacketmodem_decode(q, frame_rx, payload_rx);
+
+    // count errors
+    unsigned int num_bit_errors = count_bit_errors_array(payload_tx, payload_rx, payload_len);
+
+    // print results
+    printf("payload pass ? %s, errors: %u / %u\n",
+            crc_pass ? "pass" : "FAIL",
+            num_bit_errors,
+            8*payload_len);
+
+    // destroy allocated objects
+    qpacketmodem_destroy(q);
+
+    // write symbols to output file for plotting
+    FILE * fid = fopen(filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: could not open '%s' for writing\n", filename);
+        return -1;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", filename);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"frame_len = %u;\n", frame_len);
+    fprintf(fid,"y = zeros(1,frame_len);\n");
+    for (i=0; i<frame_len; i++)
+        fprintf(fid,"y(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(frame_rx[i]), cimagf(frame_rx[i]));
+    fprintf(fid,"figure('Color','white');\n");
+    fprintf(fid,"plot(real(y),imag(y),'x','MarkerSize',3);\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('real');\n");
+    fprintf(fid,"ylabel('imag');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", filename);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/qpacketmodem_performance_example.c b/examples/qpacketmodem_performance_example.c
new file mode 100644
index 0000000..300c516
--- /dev/null
+++ b/examples/qpacketmodem_performance_example.c
@@ -0,0 +1,199 @@
+//
+// qpacketmodem_performance_example.c
+//
+// This example demonstrates the performance of the qpacket modem
+// object to combine forward error-correction and modulation in one
+// simple interface.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+    
+#define OUTPUT_FILENAME "qpacketmodem_performance_example.m"
+
+void usage()
+{
+    printf("qpacketmodem_performance_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  p     : payload length [bytes], default: 400\n");
+    printf("  m     : modulation scheme (qpsk default)\n");
+    liquid_print_modulation_schemes();
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner): g2412 default\n");
+    printf("  k     : coding scheme (outer): none default\n");
+    liquid_print_fec_schemes();
+    printf("  s     : SNR start [dB], default: -2\n");
+    printf("  x     : SNR max [dB], default: 13\n");
+    printf("  n     : number of SNR steps, default: 16\n");
+    printf("  t     : number of trials, default: 800\n");
+}
+
+int main(int argc, char *argv[])
+{
+    //srand( time(NULL) );
+
+    // options
+    modulation_scheme ms                = LIQUID_MODEM_QPSK;    // mod. scheme
+    crc_scheme        check             = LIQUID_CRC_32;        // data validity check
+    fec_scheme        fec0              = LIQUID_FEC_GOLAY2412; // fec (inner)
+    fec_scheme        fec1              = LIQUID_FEC_NONE;      // fec (outer)
+    unsigned int      payload_len       = 400;                  // payload length
+    float             SNRdB_min         = -5.0f;                // signal-to-noise ratio (minimum)
+    float             SNRdB_max         = 10.0f;                // signal-to-noise ratio (maximum)
+    unsigned int      num_snr           = 31;                   // number of SNR steps
+    unsigned int      num_packet_trials = 800;                  // number of trials
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hp:m:v:c:k:s:x:n:t:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                                           return 0;
+        case 'p': payload_len       = atol(optarg);                  break;
+        case 'm': ms                = liquid_getopt_str2mod(optarg); break;
+        case 'v': check             = liquid_getopt_str2crc(optarg); break;
+        case 'c': fec0              = liquid_getopt_str2fec(optarg); break;
+        case 'k': fec1              = liquid_getopt_str2fec(optarg); break;
+        case 's': SNRdB_min         = atof(optarg);                  break;
+        case 'x': SNRdB_max         = atof(optarg);                  break;
+        case 'n': num_snr           = atoi(optarg);                  break;
+        case 't': num_packet_trials = atoi(optarg);                  break;
+        default:
+            exit(-1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1);
+
+    // create and configure packet encoder/decoder object
+    qpacketmodem q = qpacketmodem_create();
+    qpacketmodem_configure(q, payload_len, check, fec0, fec1, ms);
+    qpacketmodem_print(q);
+
+    // get frame length
+    unsigned int frame_len = qpacketmodem_get_frame_len(q);
+    unsigned int num_bit_trials = 8*num_packet_trials*payload_len;
+
+    // initialize payload
+    unsigned char payload_tx       [payload_len]; // payload (transmitted)
+    unsigned char payload_rx       [payload_len]; // payload (received)
+    float complex frame_tx         [frame_len];   // frame samples (transmitted)
+    float complex frame_rx         [frame_len];   // frame samples (received)
+    unsigned int  num_bit_errors   [num_snr];     // bit errors for each SNR point
+    unsigned int  num_packet_errors[num_snr];     // packet errors for each SNR point
+    float         BER              [num_snr];     // bit error rate
+    float         PER              [num_snr];     // packet error rate
+
+    printf("  %8s %8s %8s %12s %8s %8s %6s\n",
+            "SNR [dB]", "errors", "bits", "BER", "errors", "packets", "PER");
+    unsigned int s;
+    for (s=0; s<num_snr; s++) {
+        // compute SNR for this level
+        float SNRdB = SNRdB_min + s*SNRdB_step; // SNR in dB for this round
+        float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
+
+        // reset counters
+        num_bit_errors[s]    = 0;
+        num_packet_errors[s] = 0;
+
+        unsigned int t;
+        for (t=0; t<num_packet_trials; t++) {
+            // initialize payload
+            for (i=0; i<payload_len; i++) {
+                payload_tx[i] = rand() & 0xff;
+                payload_rx[i] = 0x00;
+            }
+
+            // encode frame
+            qpacketmodem_encode(q, payload_tx, frame_tx);
+
+            // add noise
+            for (i=0; i<frame_len; i++)
+                frame_rx[i] = frame_tx[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+            // decode frame
+            int crc_pass = qpacketmodem_decode(q, frame_rx, payload_rx);
+
+            // accumulate errors
+            num_bit_errors[s]    += count_bit_errors_array(payload_tx, payload_rx, payload_len);
+            num_packet_errors[s] += crc_pass ? 0 : 1;
+
+        }
+        BER[s] = (float)num_bit_errors[s]    / (float)num_bit_trials;
+        PER[s] = (float)num_packet_errors[s] / (float)num_packet_trials;
+        printf("  %8.2f %8u %8u %12.4e %8u %8u %6.2f%%\n",
+                SNRdB,
+                num_bit_errors[s], num_bit_trials, BER[s],
+                num_packet_errors[s], num_packet_trials, PER[s]*100.0f);
+    }
+
+    // destroy allocated objects
+    qpacketmodem_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"payload_len       = %u;  %% payload length [bytes]\n", payload_len);
+    fprintf(fid,"frame_len         = %u;  %% frame length [symbols]\n", frame_len);
+    fprintf(fid,"num_snr           = %u;\n", num_snr);
+    fprintf(fid,"num_packet_trials = %u;\n", num_packet_trials);
+    fprintf(fid,"num_bit_trials    = %u;\n", num_bit_trials);
+    fprintf(fid,"SNRdB_min         = %8.2f;\n", SNRdB_min);
+    fprintf(fid,"SNRdB_max         = %8.2f;\n", SNRdB_max);
+    fprintf(fid,"scheme            = '%s/%s/%s';\n", 
+            fec_scheme_str[fec0][1],
+            fec_scheme_str[fec1][1],
+            modulation_types[ms].name);
+    fprintf(fid,"rate              = 8*payload_len / frame_len; %% true rate [bits/symbol]\n");
+    for (i=0; i<num_snr; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f; ",  i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"BER(%6u)   = %12.4e; ",  i+1, BER[i]);
+        fprintf(fid,"PER(%6u)   = %12.4e;\n", i+1, PER[i]);
+    }
+    
+    // plot BER vs. SNR
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"SNRdB_bpsk = -15:0.5:40;\n");
+    fprintf(fid,"semilogy(SNRdB_bpsk, 0.5*erfc(sqrt(10.^[SNRdB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         SNRdB,      BER, '-x');\n");
+    fprintf(fid,"axis([SNRdB_min SNRdB_max 1e-6 1]);\n");
+    fprintf(fid,"legend('uncoded BPSK',scheme,'location','northeast');\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title(['BER vs. SNR for ' scheme]);\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot PER vs. SNR
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(SNRdB, PER, '-x');\n");
+    fprintf(fid,"axis([SNRdB_min SNRdB_max 1e-3 1]);\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('Packet Error Rate');\n");
+    fprintf(fid,"title(['PER vs. SNR for ' scheme]);\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/qpilotsync_example.c b/examples/qpilotsync_example.c
new file mode 100644
index 0000000..626dbfb
--- /dev/null
+++ b/examples/qpilotsync_example.c
@@ -0,0 +1,163 @@
+//
+// qpilotsync_example.c
+//
+// This example demonstrates...
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     : print usage\n");
+    printf("  n     : payload length [symbols], default: 400\n");
+    printf("  p     : pilot spacing [symbols],  default: 20\n");
+    printf("  s     : SNR [dB],                 default: 20\n");
+    printf("  m     : modulation scheme,        default: qam16\n");
+    liquid_print_modulation_schemes();
+}
+
+int main(int argc, char *argv[])
+{
+    //srand( time(NULL) );
+
+    // options
+    int          ms            = LIQUID_MODEM_QAM16;        // mod. scheme
+    unsigned int payload_len   = 400;                       // payload length
+    unsigned int pilot_spacing =  20;                       // pilot spacing
+    float        SNRdB         = 20.0f;                     // signal-to-noise ratio [dB]
+    const char   filename[]    = "qpilotsync_example.m";    // output filename
+    float        dphi          = -0.0075f;                   // carrier frequency offset
+    float        phi           = 2.1800f;                   // carrier phase offset
+    float        gain          = 0.5f;                      // channel gain
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hn:p:s:m:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                                       return 0;
+        case 'n': payload_len   = atoi(optarg);                  break;
+        case 'p': pilot_spacing = atoi(optarg);                  break;
+        case 's': SNRdB         = atof(optarg);                  break;
+        case 'm': ms            = liquid_getopt_str2mod(optarg); break;
+        default:
+            exit(-1);
+        }
+    }
+    unsigned int i;
+
+    // derived values
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // create pilot generator and synchronizer objects
+    qpilotgen  pg = qpilotgen_create( payload_len, pilot_spacing);
+    qpilotsync ps = qpilotsync_create(payload_len, pilot_spacing);
+    qpilotgen_print(pg);
+
+    // get frame length
+    unsigned int frame_len = qpilotgen_get_frame_len(pg);
+
+    // allocate arrays
+    unsigned char payload_sym_tx[payload_len];  // transmitted payload symbols
+    float complex payload_tx    [payload_len];  // transmitted payload samples
+    float complex frame_tx      [frame_len];    // transmitted frame samples
+    float complex frame_rx      [frame_len];    // received frame samples
+    float complex payload_rx    [payload_len];  // received payload samples
+    unsigned char payload_sym_rx[payload_len];  // received payload symbols
+
+    // create modem objects for payload
+    modem mod = modem_create(ms);
+    modem dem = modem_create(ms);
+
+    // assemble payload symbols
+    for (i=0; i<payload_len; i++) {
+        // generate random symbol
+        payload_sym_tx[i] = modem_gen_rand_sym(mod);
+
+        // modulate
+        modem_modulate(mod, payload_sym_tx[i], &payload_tx[i]);
+    }
+
+    // assemble frame
+    qpilotgen_execute(pg, payload_tx, frame_tx);
+
+    // add channel impairments
+    for (i=0; i<frame_len; i++) {
+        frame_rx[i]  = frame_tx[i] * cexpf(_Complex_I*dphi*i + _Complex_I*phi);
+        frame_rx[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+        frame_rx[i] *= gain;
+    }
+
+    // recieve frame
+    qpilotsync_execute(ps, frame_rx, payload_rx);
+
+    // demodulate
+    for (i=0; i<payload_len; i++) {
+        unsigned int sym_demod;
+        modem_demodulate(dem, payload_rx[i], &sym_demod);
+        payload_sym_rx[i] = (unsigned char)sym_demod;
+    }
+
+    // count errors
+    unsigned int bit_errors = 0;
+    for (i=0; i<payload_len; i++)
+        bit_errors += count_bit_errors(payload_sym_rx[i], payload_sym_tx[i]);
+    printf("received bit errors : %u / %u\n", bit_errors, payload_len * modem_get_bps(mod));
+
+    // destroy allocated objects
+    qpilotgen_destroy(pg);
+    qpilotsync_destroy(ps);
+    modem_destroy(mod);
+    modem_destroy(dem);
+
+
+    // write symbols to output file for plotting
+    FILE * fid = fopen(filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: could not open '%s' for writing\n", filename);
+        return -1;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", filename);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"payload_len = %u;\n", payload_len);
+    fprintf(fid,"frame_len   = %u;\n", frame_len);
+    fprintf(fid,"frame_tx   = zeros(1,frame_len);\n");
+    fprintf(fid,"payload_rx = zeros(1,payload_len);\n");
+    for (i=0; i<frame_len; i++)
+        fprintf(fid,"frame_rx(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(frame_rx[i]), cimagf(frame_rx[i]));
+    for (i=0; i<payload_len; i++)
+        fprintf(fid,"payload_rx(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(payload_rx[i]), cimagf(payload_rx[i]));
+    fprintf(fid,"figure('Color','white','position',[100 100 950 400]);\n");
+    fprintf(fid,"subplot(1,2,1);\n");
+    fprintf(fid,"  plot(real(frame_rx),  imag(frame_rx),  'x','MarkerSize',3);\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('real');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  title('received');\n");
+    fprintf(fid,"subplot(1,2,2);\n");
+    fprintf(fid,"  plot(real(payload_rx),imag(payload_rx),'x','MarkerSize',3);\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('real');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  title('recovered');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", filename);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/quantize_example.c b/examples/quantize_example.c
new file mode 100644
index 0000000..d36b231
--- /dev/null
+++ b/examples/quantize_example.c
@@ -0,0 +1,122 @@
+//
+// quantize_example.c
+//
+// Demonstrates the quantizer/compander combo.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "quantize_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("quantize_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  b     : number of bits, 0 < b <= 16 [default: 4]\n");
+    printf("  m     : mu, compression factor, mu > 0 [default: 255.0]\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    unsigned int num_bits=4;
+    float mu = 255.0f;
+    unsigned int num_samples = 64;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhb:m:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                  return 0;
+        case 'b': num_bits = atoi(optarg);  break;
+        case 'm': mu = atof(optarg);        break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_bits > 16 || num_bits < 1) {
+        fprintf(stderr,"error: %s, quantizer bits must be in [1,16]\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f) {
+        fprintf(stderr,"error: %s, mu must be greater than 0\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+    float x[num_samples];
+    float y[num_samples];
+
+    float v;        // compressed sample
+    unsigned int q; // quantized sample
+    float u;        // uncompressed sample
+    float e;        // error
+    float rmse = 0.0f;
+    printf("         input        ADC        output\n");
+    printf("         -----        ---        ------\n");
+    float phi = 0.0f;
+    float dphi = 0.02f * 256.0f / (float) num_samples;
+    for (i=0; i<num_samples; i++) {
+        // generate windowed pulse
+        x[i] = 0.5f*cosf(2.0f*M_PI*phi) + 0.5f*cosf(2.0f*M_PI*phi*0.57f);
+        x[i] *= 0.5f * kaiser(i, num_samples, 10.0f, 0.0f);
+        phi += dphi;
+
+        // compress sample
+        v = compress_mulaw(x[i],mu);
+
+        // quantize: analog to digital converter
+        q = quantize_adc(v,num_bits);
+
+        // quantize: digital to analog converter
+        u = quantize_dac(q,num_bits);
+
+        // expand sample
+        y[i] = expand_mulaw(u,mu);
+
+        // compute error
+        e = y[i] - x[i];
+        rmse += e*e;
+
+        printf("%4u : %12.8f > 0x%4.4x > %12.8f\n", i, x[i], q, y[i]);
+    }
+    rmse = sqrtf(rmse / (float)num_samples);
+    printf("-----------------------\n");
+    printf("rmse : %12.4e\n", rmse);
+
+    // open debug file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %16.8e; y(%4u) = %16.8e;\n", i+1, x[i], i+1, y[i]);
+    }
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t  = 0:(num_samples-1);\n");
+    fprintf(fid,"%% generate stairs-like plot\n");
+    fprintf(fid,"t0 = reshape([t(1:num_samples-1)(:) t(1:num_samples-1)(:)]',1,2*(num_samples-1)) + 0.5;\n");
+    fprintf(fid,"y0 = reshape([y(1:num_samples-1)(:) y(2:num_samples)(:)]',  1,2*(num_samples-1));\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(t,x,t0,y0);\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('signal');\n");
+    fprintf(fid,"legend('original','reconstructed',1);\n");
+    fprintf(fid,"grid on;\n");
+ 
+    // close debug file
+    fclose(fid);
+    printf("results wrtten to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/random_histogram_example.c b/examples/random_histogram_example.c
new file mode 100644
index 0000000..c3a8941
--- /dev/null
+++ b/examples/random_histogram_example.c
@@ -0,0 +1,357 @@
+// 
+// random_histogram_example.c
+//
+// This example tests the random number generators for different
+// distributions.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "random_histogram_example.m"
+
+
+// print usage/help message
+void usage()
+{
+    printf("random_histogram_example [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  N     : number of trials\n");
+    printf("  n     : number of histogram bins\n");
+    printf("  d     : distribution: {uniform, normal, exp, weib, gamma, nak, rice}\n");
+    printf("  e     : eta    NORMAL: mean\n");
+    printf("  s     : sigma  NORMAL: standard deviation\n");
+    printf("  l     : lambda EXPONENTIAL: decay factor\n");
+    printf("  a     : alpha  WEIBULL: shape\n");
+    printf("  b     : beta   WEIBULL: spread\n");
+    printf("  g     : gamma  WEIBULL: threshold\n");
+    printf("  A     : alpha  GAMMA: shape\n");
+    printf("  B     : beta   GAMMA: spread\n");
+    printf("  m     : m      NAKAGAMI: shape\n");
+    printf("  o     : omega  NAKAGAMI: spread\n");
+    printf("  K     : K      RICE-K: spread\n");
+    printf("  O     : omega  RICE-K: spread\n");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+    unsigned long int num_trials = 100000; // number of trials
+    unsigned int num_bins = 30;
+    enum {
+        UNIFORM=0,
+        NORMAL,
+        EXPONENTIAL,
+        WEIBULL,
+        GAMMA,
+        NAKAGAMIM,
+        RICEK
+    } distribution=NORMAL;
+
+    // distribution parameters
+    float eta = 0.0f;       // NORMAL: mean
+    float sigma = 1.0f;     // NORMAL: standard deviation
+    float lambda = 3.0f;    // EXPONENTIAL: decay factor
+    float alphaw = 1.0f;    // WEIBULL: shape
+    float betaw = 1.0f;     // WEIBULL: spread
+    float gammaw = 1.0f;    // WEIBULL: threshold
+    float alphag = 4.5f;    // GAMMA: shape
+    float betag = 1.0f;     // GAMMA: spread
+    float m = 4.5f;         // NAKAGAMI: shape factor
+    float omeganak = 1.0f;  // NAKAGMAI: spread factor
+    float K = 4.0f;         // RICE-K: K-factor (shape)
+    float omegarice = 1.0f; // RICE-K: spread factor
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhN:n:d:e:s:l:a:b:g:A:B:m:o:K:O:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 'N': num_trials = atoi(optarg); break;
+        case 'n': num_bins = atoi(optarg); break;
+        case 'd':
+            if      (strcmp(optarg,"uniform")==0)   distribution = UNIFORM;
+            else if (strcmp(optarg,"normal")==0)    distribution = NORMAL;
+            else if (strcmp(optarg,"exp")==0)       distribution = EXPONENTIAL;
+            else if (strcmp(optarg,"weib")==0)      distribution = WEIBULL;
+            else if (strcmp(optarg,"gamma")==0)     distribution = GAMMA;
+            else if (strcmp(optarg,"nak")==0)       distribution = NAKAGAMIM;
+            else if (strcmp(optarg,"rice")==0)      distribution = RICEK;
+            else {
+                fprintf(stderr,"error: %s, unknown/unsupported distribution '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+        case 'e': eta       = atof(optarg); break;
+        case 's': sigma     = atof(optarg); break;
+        case 'l': lambda    = atof(optarg); break;
+        case 'a': alphaw    = atof(optarg); break;
+        case 'b': betaw     = atof(optarg); break;
+        case 'g': gammaw    = atof(optarg); break;
+        case 'A': alphag    = atof(optarg); break;
+        case 'B': betag     = atof(optarg); break;
+        case 'm': m         = atof(optarg); break;
+        case 'o': omeganak  = atof(optarg); break;
+        case 'K': K         = atof(optarg); break;
+        case 'O': omegarice = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_bins == 0) {
+        fprintf(stderr,"error: %s, number of bins must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (num_trials == 0) {
+        fprintf(stderr,"error: %s, number of trials must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+
+    float xmin = 0.0f;
+    float xmax = 1.0f;
+
+    unsigned long int i;
+
+    // make a guess at the histogram range so we don't need to
+    // store all the generated random variables in a giant array.
+    if (distribution == UNIFORM) {
+        xmin =  0.0f;
+        xmax =  1.0f;
+    } else if (distribution == NORMAL) {
+        xmin = eta - 4.0f*sigma;
+        xmax = eta + 4.0f*sigma;
+    } else if (distribution == EXPONENTIAL) {
+        xmin = 0.0f;
+        xmax = 7.0f / lambda;
+    } else if (distribution == WEIBULL) {
+        xmin = gammaw;
+        xmax = gammaw + betaw*powf( -logf(1e-3f), 1.0f/alphaw );
+    } else if (distribution == GAMMA) {
+        xmin = 0.0f;
+        xmax = 6.5 * betag + 2.0*alphag;
+    } else if (distribution == NAKAGAMIM) {
+        xmin = 0.0f;
+        xmax = 1.5f*( powf(omeganak, 0.8f) + 1.0f/m );
+    } else if (distribution == RICEK) {
+        xmin = 0.0f;
+        xmax = 3.0f*logf(omegarice+1.0f) + 1.5f/(K+1.0f);
+    } else {
+        fprintf(stderr, "error: %s, unknown/unsupported distribution\n", argv[0]);
+        exit(1);
+    }
+
+    //
+    //float xspan = xmax - xmin;
+    float bin_width = (xmax - xmin) / (num_bins);
+
+    // initialize histogram
+    unsigned int hist[num_bins];
+    for (i=0; i<num_bins; i++)
+        hist[i] = 0;
+
+    // generate random variables
+    float x = 0.0f;
+    float m1 = 0.0f;    // first moment
+    float m2 = 0.0f;    // second moment
+    for (i=0; i<num_trials; i++) {
+        switch (distribution) {
+        case UNIFORM:     x = randf();                          break;
+        case NORMAL:      x = sigma*randnf() + eta;             break;
+        case EXPONENTIAL: x = randexpf(lambda);                 break;
+        case WEIBULL:     x = randweibf(alphaw,betaw,gammaw);   break;
+        case GAMMA:       x = randgammaf(alphag,betag);         break;
+        case NAKAGAMIM:   x = randnakmf(m,omeganak);            break;
+        case RICEK:       x = randricekf(K,omegarice);          break;
+        default:
+            fprintf(stderr,"error: %s, unknown/unsupported distribution\n", argv[0]);
+            exit(1);
+        }
+
+        // compute bin index
+        unsigned int index;
+        float ihat = num_bins * (x - xmin) / (xmax - xmin);
+        if (ihat < 0.0f)
+            index = 0;
+        else
+            index = (unsigned int)ihat;
+        
+        if (index >= num_bins)
+            index = num_bins-1;
+
+        hist[index]++;
+
+        // update statistics
+        m1 += x;    // first moment
+        m2 += x*x;  // second moment
+    }
+
+    //
+    m1 /= (float)num_trials;
+    m2 /= (float)num_trials;
+
+    // compute expected distribution
+    unsigned int num_steps = 100;
+    float xstep = (xmax - xmin) / (num_steps - 1);
+    float f[num_steps];
+    float F[num_steps];
+    for (i=0; i<num_steps; i++) {
+        x = xmin + i*xstep;
+        switch (distribution) {
+        case UNIFORM:
+            f[i] = randf_pdf(x);
+            F[i] = randf_cdf(x);
+            break;
+        case NORMAL:
+            f[i] = randnf_pdf(x,eta,sigma);
+            F[i] = randnf_cdf(x,eta,sigma);
+            break;
+        case EXPONENTIAL:
+            f[i] = randexpf_pdf(x,lambda);
+            F[i] = randexpf_cdf(x,lambda);
+            break;
+        case WEIBULL:
+            f[i] = randweibf_pdf(x,alphaw,betaw,gammaw);
+            F[i] = randweibf_cdf(x,alphaw,betaw,gammaw);
+            break;
+        case GAMMA:
+            f[i] = randgammaf_pdf(x,alphag,betag);
+            F[i] = randgammaf_cdf(x,alphag,betag);
+            break;
+        case NAKAGAMIM:
+            f[i] = randnakmf_pdf(x,m,omeganak);
+            F[i] = randnakmf_cdf(x,m,omeganak);
+            break;
+        case RICEK:
+            f[i] = randricekf_pdf(x,K,omegarice);
+            F[i] = randricekf_cdf(x,K,omegarice);
+            break;
+        default:
+            fprintf(stderr,"error: %s, unknown/unsupported distribution\n", argv[0]);
+            exit(1);
+        }
+    }
+
+    // print results to screen
+    // find max(hist)
+    unsigned int hist_max = 0;
+    for (i=0; i<num_bins; i++)
+        hist_max = hist[i] > hist_max ? hist[i] : hist_max;
+
+    printf("%8s : %6s [%6s]\n", "x", "count", "prob.");
+    for (i=0; i<num_bins; i++) {
+        printf("%8.2f : %6u [%6.4f]", xmin + i*bin_width, hist[i], (float)hist[i] / (float)num_trials);
+
+        unsigned int k;
+        unsigned int n = round(60 * (float)hist[i] / (float)hist_max);
+        for (k=0; k<n; k++)
+            printf("#");
+        printf("\n");
+    }
+
+    // print distribution info, statistics
+    printf("statistics:\n");
+    switch (distribution) {
+    case UNIFORM:
+        printf("    distribution            :   %s\n", "uniform");
+        break;
+    case NORMAL:
+        printf("    distribution            :   %s\n", "normal (Gauss)");
+        printf("    eta                     :   %f\n", eta);
+        printf("    sigma                   :   %f\n", sigma);
+        break;
+    case EXPONENTIAL:
+        printf("    distribution            :   %s\n", "exponential");
+        printf("    lambda                  :   %f\n", lambda);
+        break;
+    case WEIBULL:
+        printf("    distribution            :   %s\n", "Weibull");
+        printf("    alpha                   :   %f\n", alphaw);
+        printf("    beta                    :   %f\n", betaw);
+        printf("    gamma                   :   %f\n", gammaw);
+        break;
+    case GAMMA:
+        printf("    distribution            :   %s\n", "gamma");
+        printf("    alpha                   :   %f\n", alphag);
+        printf("    beta                    :   %f\n", betag);
+        break;
+    case NAKAGAMIM:
+        printf("    distribution            :   %s\n", "Nakagami-m");
+        printf("    m                       :   %f\n", m);
+        printf("    omega                   :   %f\n", omeganak);
+        break;
+    case RICEK:
+        printf("    distribution            :   %s\n", "Rice-K");
+        printf("    K                       :   %f\n", K);
+        printf("    omega                   :   %f\n", omegarice);
+        break;
+    default:
+        fprintf(stderr,"error: %s, unknown/unsupported distribution\n", argv[0]);
+        exit(1);
+    }
+    printf("\n");
+    printf("    samples                 :   %8lu\n", num_trials);
+    printf("    first moment,  E( x }   :   %8.3f\n", m1);
+    printf("    second moment, E{x^2}   :   %8.3f\n", m2);
+    printf("    variance                :   %8.3f\n", m2 - m1*m1);
+    printf("    standard deviation      :   %8.3f\n", sqrtf(m2 - m1*m1));
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"xmin = %12.4e;\n", xmin);
+    fprintf(fid,"xmax = %12.4e;\n", xmax);
+    fprintf(fid,"num_bins = %u;\n", num_bins);
+    fprintf(fid,"xspan = xmax - xmin;\n");
+
+    float F_hat = 0.0f;
+    for (i=0; i<num_bins; i++) {
+        x = xmin + ((float)i + 0.5f)*bin_width;
+        float h = (float)(hist[i]) / (num_trials * bin_width);
+        fprintf(fid,"xh(%3lu) = %12.4e; h(%3lu) = %12.4e;\n", i+1, x, i+1, h);
+
+        x = xmin + ((float)i + 1.0f)*bin_width;
+        F_hat += h;
+        fprintf(fid,"xH(%3lu) = %12.4e; H(%3lu) = %12.4e;\n", i+1, x, i+1, F_hat);
+    }
+    fprintf(fid,"H = H/H(end);\n");
+
+    for (i=0; i<num_steps; i++) {
+        x = xmin + i*xstep;
+        fprintf(fid,"xf(%3lu) = %12.4e; f(%3lu) = %12.4e; F(%3lu) = %12.4e;\n", i+1, x, i+1, f[i], i+1, F[i]);
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(xh,h,'x', xf,f,'-');\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('f_x(x)');\n");
+    fprintf(fid,"axis([(xmin-0.1*xspan) (xmax+0.1*xspan) 0 1.1*max([h f])]);\n");
+    fprintf(fid,"legend('histogram','true PDF',1);\n");
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(xH,H,'x', xf,F,'-');\n");
+    fprintf(fid,"xlabel('x');\n");
+    fprintf(fid,"ylabel('f_x(x)');\n");
+    //fprintf(fid,"axis([(xmin-0.1*xspan) (xmax+0.1*xspan) 0 1]);\n");
+    fprintf(fid,"legend('histogram','true CDF',0);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/repack_bytes_example.c b/examples/repack_bytes_example.c
new file mode 100644
index 0000000..a9ab043
--- /dev/null
+++ b/examples/repack_bytes_example.c
@@ -0,0 +1,65 @@
+//
+// repack_bytes_example.c
+//
+// This example demonstrates the repack_bytes() interface by packing a
+// sequence of three 3-bit symbols into five 2-bit symbols.  The results
+// are printed to the screen.  Because the total number of bits in the
+// input is 9 and not evenly divisible by 2, the last of the 5 output
+// symbols has a zero explicitly padded to the end.
+//
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+// print symbol to screen, one bit at a time
+void print_symbol(unsigned char _sym,
+                  unsigned int _bits_per_symbol)
+{
+    unsigned int i;
+    unsigned int n; // shift amount
+    for (i=0; i<_bits_per_symbol; i++) {
+        n = _bits_per_symbol - i - 1;
+        printf("%c", (_sym >> n) & 0x01 ? '1' : '0');
+    }
+}
+
+// print symbol array to screen
+void print_symbol_array(unsigned char * _sym,
+                        unsigned int _bits_per_symbol,
+                        unsigned int _num_symbols)
+{
+    unsigned int i;
+    for (i=0; i<_num_symbols; i++) {
+        print_symbol(_sym[i], _bits_per_symbol);
+        printf("%c", i < _num_symbols-1 ? ',' : '\n');
+    }
+}
+
+int main() {
+    // input symbols:   111 000 111
+    // expected output: 11 10 00 11 1(0)
+    unsigned char input[3] = {
+        0x07,   // 111
+        0x00,   // 000
+        0x07    // 111(0)
+    };
+    
+    // allocate memory for output array
+    unsigned char output[5];
+    unsigned int N;
+
+    // print input symbol array
+    printf("input symbols:  ");
+    print_symbol_array(input,3,3);
+
+    // repack bytes into output array
+    liquid_repack_bytes( input, 3, 3, output, 2, 5, &N );
+
+    // print output array
+    printf("output symbols: ");
+    print_symbol_array(output,2,5);
+
+    return 0;
+}
+
diff --git a/examples/resamp2_cccf_example.c b/examples/resamp2_cccf_example.c
new file mode 100644
index 0000000..75f560c
--- /dev/null
+++ b/examples/resamp2_cccf_example.c
@@ -0,0 +1,131 @@
+//
+// resamp2_cccf_example.c
+//
+// This example demonstrates the halfband resampler cenetered at the
+// quarter sample rate to split the signal into positive and negative
+// frequency bands. Two distinct narrow-band signals are generated; one
+// at a positive frequency and one at a negative frequency. The resamp2
+// object is run as a filter to separate the two about the zero-
+// frequency center point.
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_cccf_example.m"
+
+int main() {
+    unsigned int m           = 12;      // filter semi-length (actual length: 4*m+1)
+    float        As          = 60.0f;   // stop-band attenuation [dB]
+    unsigned int num_samples = 400;     // number of input samples
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x [num_samples];  // input signal
+    float complex y0[num_samples];  //
+    float complex y1[num_samples];  //
+
+    // generate the two signals
+    iirfilt_crcf lowpass = iirfilt_crcf_create_lowpass(6,0.02);
+    for (i=0; i<num_samples; i++) {
+        // signal at negative frequency: tone
+        float complex x_neg = cexpf(-_Complex_I*2*M_PI*0.059f*i);
+
+        // signal at positive frequency: filtered noise
+        float complex v;
+        iirfilt_crcf_execute(lowpass, 4*randnf(), &v);
+        float complex x_pos = v * cexpf(_Complex_I*2*M_PI*0.073f*i);
+
+        // compsite
+        x[i] = (x_neg + x_pos) * hamming(i,num_samples);
+    }
+    iirfilt_crcf_destroy(lowpass);
+
+    // create/print the half-band resampler, with a specified
+    // stopband attenuation level
+    resamp2_cccf q = resamp2_cccf_create(m,-0.25f,As);
+    resamp2_cccf_print(q);
+
+    // run filter
+    for (i=0; i<num_samples; i++)
+        resamp2_cccf_filter_execute(q,x[i],&y0[i],&y1[i]);
+
+    // 
+    // print results to file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+
+    // output results
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x( %3u) = %12.4e + j*%12.4e;\n", i+1, crealf( x[i]), cimagf( x[i]));
+        fprintf(fid,"y0(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y0[i]), cimagf(y0[i]));
+        fprintf(fid,"y1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y1[i]), cimagf(y1[i]));
+    }
+
+    // plot temporal results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,real(x),'Color',[1 1 1]*0.7,'LineWidth',1);\n");
+    fprintf(fid,"  plot(t,imag(x),'Color',[1 1 1]*0.5,'LineWidth',2);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  axis([0 num_samples -2 2]);\n");
+    fprintf(fid,"  ylabel('original');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,real(y0),'Color',[1 1 1]*0.7,'LineWidth',1);\n");
+    fprintf(fid,"  plot(t,imag(y0),'Color',[0 0.5 0.2],'LineWidth',2);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  axis([0 num_samples -2 2]);\n");
+    fprintf(fid,"  ylabel('negative band');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,real(y1),'Color',[1 1 1]*0.7,'LineWidth',1);\n");
+    fprintf(fid,"  plot(t,imag(y1),'Color',[0 0.2 0.5],'LineWidth',2);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('real','imag','location','northeast');\n");
+    fprintf(fid,"  axis([0 num_samples -2 2]);\n");
+    fprintf(fid,"  ylabel('positive band');\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+
+    // plot spectrum results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"nfft=2400;\n");
+    fprintf(fid,"f  = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"w  = hamming(num_samples)' / num_samples;\n");
+    fprintf(fid,"X  = 20*log10(abs(fftshift(fft( x.*w, nfft))));\n");
+    fprintf(fid,"Y0 = 20*log10(abs(fftshift(fft(y0.*w, nfft))));\n");
+    fprintf(fid,"Y1 = 20*log10(abs(fftshift(fft(y1.*w, nfft))));\n");
+    fprintf(fid,"figure('Color','white');\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(f,X, 'Color',[1 1 1]*0.7,'LineWidth',2);\n");
+    fprintf(fid,"  plot(f,Y0,'Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  plot(f,Y1,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"legend('original','negative','positive','location','northeast');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/resamp2_crcf_decim_example.c b/examples/resamp2_crcf_decim_example.c
new file mode 100644
index 0000000..adb1129
--- /dev/null
+++ b/examples/resamp2_crcf_decim_example.c
@@ -0,0 +1,147 @@
+//
+// resamp2_crcf_decim_example.c
+//
+// Halfband decimator.  This example demonstrates the interface to the
+// decimating halfband resampler.  A low-frequency input sinusoid is
+// generated and fed into the decimator two samples at a time,
+// producing one output at each iteration.  The results are written to
+// an output file.
+//
+// SEE ALSO: resamp2_crcf_interp_example.c
+//           decim_rrrf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_decim_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     :   print help\n");
+    printf("  n     :   number of output samples,     default: 64\n");
+    printf("  m     :   filter semi-length,           default: 5\n");
+    printf("  s     :   filter stop-band attenuation, default: 60 dB\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int m=5;               // filter semi-length
+    float fc=0.037f;                // input tone frequency
+    unsigned int num_samples = 64;  // number of output samples
+    float As=60.0f;                 // stop-band attenuation [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:m:s:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                    return 0;
+        case 'n':   num_samples = atoi(optarg); break;
+        case 'm':   m           = atoi(optarg); break;
+        case 's':   As          = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+    unsigned int i;
+
+    // allocate arrays
+    float complex x[2*num_samples]; // input array
+    float complex y[  num_samples]; // output array
+
+    // generate input
+    unsigned int w_len = 2*num_samples - 4*m;   // window length
+    float beta = 8.0f;                          // kaiser window factor
+    float w_sum = 0.0f;                         // gain due to window
+    for (i=0; i<2*num_samples; i++) {
+        // compute windowing function and keep track of gain
+        float w = (i < w_len ? kaiser(i,w_len,beta,0) : 0.0f);
+        w_sum += w;
+
+        // compute windowed complex sinusoid
+        x[i] = w * cexpf(_Complex_I*2*M_PI*fc*i);
+    }
+
+    // create/print the half-band resampler
+    resamp2_crcf q = resamp2_crcf_create(m,0,As);
+    resamp2_crcf_print(q);
+    unsigned int delay = resamp2_crcf_get_delay(q);
+
+    // run the resampler
+    for (i=0; i<num_samples; i++) {
+        // execute the decimator
+        resamp2_crcf_decim_execute(q, &x[2*i], &y[i]);
+
+        // print results to screen
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // destroy half-band resampler
+    resamp2_crcf_destroy(q);
+
+    // 
+    // export results
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"h_len=%u;\n", 4*m+1);
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+    fprintf(fid,"delay      =%u;\n", delay);
+    fprintf(fid,"w_sum      =%12.8f;\n", w_sum);
+        
+    for (i=0; i<num_samples; i++) {
+        // save results to output file
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", 2*i+1,      crealf(x[2*i+0]),      cimagf(x[2*i+0]));
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", 2*i+2,      crealf(x[2*i+1]),      cimagf(x[2*i+1]));
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1,   0.5f*crealf(y[i    ]), 0.5f*cimagf(y[i    ]));
+    }
+
+    // plot time series
+    fprintf(fid,"tx =  0:(2*num_samples-1);\n");
+    fprintf(fid,"ty = [0:(  num_samples-1)]*2 - delay;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0.0 0.0],'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','decimated','location','northeast');");
+    fprintf(fid,"  axis([-delay 2*num_samples -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Time [t/T_s]');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0.0 0.5 0.0],'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','decimated','location','northeast');");
+    fprintf(fid,"  axis([-delay 2*num_samples -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Time [t/T_s]');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    // plot spectrum
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"g = 1/w_sum;\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(  x*g,nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(2*y*g,nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,  X,'LineWidth',1,  'Color',[0.5 0.5 0.5],...\n");
+    fprintf(fid,"     f/2,Y,'LineWidth',1.5,'Color',[0.1 0.3 0.5]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','decimated','location','northeast');");
+    fprintf(fid,"axis([-0.5 0.5 -100 10]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/resamp2_crcf_example.c b/examples/resamp2_crcf_example.c
new file mode 100644
index 0000000..5df80b8
--- /dev/null
+++ b/examples/resamp2_crcf_example.c
@@ -0,0 +1,126 @@
+//
+// resamp2_crcf_example.c
+//
+// This example demonstrates the halfband resampler running as both an
+// interpolator and a decimator. A narrow-band signal is first
+// interpolated by a factor of 2, and then decimated. The resulting RMS
+// error between the final signal and original is computed and printed
+// to the screen.
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_example.m"
+
+int main() {
+    unsigned int m=5;               // filter semi-length (actual length: 4*m+1)
+    float bw=0.13f;                 // input signal bandwidth
+    float fc=-0.597f;               // input signal carrier frequency (radians/sample)
+    unsigned int num_samples=37;    // number of input samples
+    float As=60.0f;                 // stop-band attenuation [dB]
+
+    unsigned int i;
+
+    // derived values
+    unsigned int n = num_samples + 2*m + 1; // adjusted input sequence length
+
+    // allocate memory for data arrays
+    float complex x[  n];
+    float complex y[2*n];
+    float complex z[  n];
+
+    // generate the baseband signal (filter pulse)
+    float h[num_samples];
+    liquid_firdes_kaiser(num_samples,bw,60.0f,0.0f,h);
+    for (i=0; i<n; i++)
+        x[i] = i < num_samples ? h[i] * cexpf(_Complex_I*fc*i) : 0.0f;
+
+    // create/print the half-band resampler, with a specified
+    // stopband attenuation level
+    resamp2_crcf q = resamp2_crcf_create(m,0,As);
+    resamp2_crcf_print(q);
+
+    // run interpolation stage
+    for (i=0; i<n; i++)
+        resamp2_crcf_interp_execute(q, x[i], &y[2*i]);
+
+    // clear resamp2 object
+    resamp2_crcf_clear(q);
+
+    // execute decimation stage
+    for (i=0; i<n; i++)
+        resamp2_crcf_interp_execute(q, y[2*i], &z[i]);
+
+    // clean up allocated objects
+    resamp2_crcf_destroy(q);
+
+    // compute RMS error
+    float rmse = 0.0f;
+    for (i=2*m; i<n; i++) {
+        float e = cabsf(x[i-2*m] - z[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf( rmse / (float)(n-2*m) );
+    printf("rms error : %12.4e\n", rmse);
+
+    // 
+    // print results to file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"bw=%12.8f;\n", bw);
+    fprintf(fid,"n=%u;\n", n);
+
+    // output results
+    for (i=0; i<n; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<2*n; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    for (i=0; i<n; i++)
+        fprintf(fid,"z(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+
+    // print results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"X = 20*log10(abs(fftshift(fft(x*bw*2,nfft))));\n");
+    fprintf(fid,"Y = 20*log10(abs(fftshift(fft(y*bw,  nfft))));\n");
+    fprintf(fid,"Z = 20*log10(abs(fftshift(fft(z*bw*2,nfft))));\n");
+    fprintf(fid,"plot(f,X,f,Y,f,Z);\n");
+    fprintf(fid,"legend('original','up-converted','down-converted',1);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t0 = 0:[n-1];\n");
+    fprintf(fid,"t1 = 0:[n*2-1];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t0,real(x),t0,imag(x));\n");
+    fprintf(fid,"  legend('I','Q',0);\n");
+    fprintf(fid,"  axis([0 n -1 1]);\n");
+    fprintf(fid,"  ylabel('original');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(t1,real(y),t1,imag(y));\n");
+    fprintf(fid,"  axis([0 n*2 -1 1]);\n");
+    fprintf(fid,"  ylabel('interpolated');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t0,real(z),t0,imag(z));\n");
+    fprintf(fid,"  axis([0 n -1 1]);\n");
+    fprintf(fid,"  ylabel('interp/decim');\n");
+
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/resamp2_crcf_filter_example.c b/examples/resamp2_crcf_filter_example.c
new file mode 100644
index 0000000..4a33b3d
--- /dev/null
+++ b/examples/resamp2_crcf_filter_example.c
@@ -0,0 +1,110 @@
+//
+// resamp2_crcf_filter_example.c
+//
+// Halfband filter example.
+//
+// SEE ALSO: resamp2_crcf_decim_example.c
+//           resamp2_crcf_interp_example.c
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_filter_example.m"
+
+int main() {
+    unsigned int m=9;               // filter semi-length
+    unsigned int num_samples=128;   // number of input samples
+    float As=60.0f;                 // stop-band attenuation [dB]
+
+    // derived values
+    unsigned int h_len = 4*m+1;     // half-band filter length
+    unsigned int N = num_samples + h_len;
+
+    // arrays
+    float complex x[N];             // input time series
+    float complex y0[N];            // output time series (lower band)
+    float complex y1[N];            // output time series (upper band)
+
+    // generate input sequence
+    unsigned int i;
+    for (i=0; i<N; i++) {
+        //x[i] = randnf() * cexpf(_Complex_I*M_PI*randf());
+        x[i] = cexpf(_Complex_I*2*M_PI*0.072f*i) + 0.6f*cexpf(_Complex_I*2*M_PI*0.37f*i);
+        x[i] *= (i < num_samples) ? hamming(i,num_samples) : 0.0f;
+    }
+
+    // create/print the half-band filter with a specified
+    // stop-band attenuation
+    resamp2_crcf q = resamp2_crcf_create(m,0.0f,As);
+    resamp2_crcf_print(q);
+
+    for (i=0; i<N; i++) {
+        // run the filter
+        resamp2_crcf_filter_execute(q, x[i], &y0[i], &y1[i]);
+
+        //printf("y(%3u) = %8.4f + j*%8.4f;\n", i+1, crealf(y), cimagf(y));
+    }
+
+    // clean up allocated objects
+    resamp2_crcf_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"N = %u;\n", N);
+    fprintf(fid,"t = 0:(N-1);\n");
+
+    // save results to output file
+    for (i=0; i<N; i++) {
+        fprintf(fid,"x(%3u)  = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]),  cimagf(x[i]));
+        fprintf(fid,"y0(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y0[i]), cimagf(y0[i]));
+        fprintf(fid,"y1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y1[i]), cimagf(y1[i]));
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,    real(x), 'LineWidth',2,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"       t-2*m,real(y0),'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    fprintf(fid,"       t-2*m,real(y1),'LineWidth',1,'Color',[0.00 0.50 0.25]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('original','lower band','upper band',1);");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,    imag(x), 'LineWidth',2,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"       t-2*m,imag(y0),'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    fprintf(fid,"       t-2*m,imag(y1),'LineWidth',1,'Color',[0.00 0.50 0.25]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('original','lower band','upper band',1);");
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"g = 1 / sqrt( real(x*x') );\n");
+    fprintf(fid,"X =20*log10(abs(fftshift(fft(x*g, nfft))));\n");
+    fprintf(fid,"Y0=20*log10(abs(fftshift(fft(y0*g,nfft))));\n");
+    fprintf(fid,"Y1=20*log10(abs(fftshift(fft(y1*g,nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,X, 'LineWidth',2,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"     f,Y0,'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    fprintf(fid,"     f,Y1,'LineWidth',1,'Color',[0.00 0.50 0.25]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','lower band','upper band',2);");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/resamp2_crcf_interp_example.c b/examples/resamp2_crcf_interp_example.c
new file mode 100644
index 0000000..c966f63
--- /dev/null
+++ b/examples/resamp2_crcf_interp_example.c
@@ -0,0 +1,146 @@
+//
+// resamp2_crcf_interp_example.c
+//
+// Halfband interpolator.  This example demonstrates the interface to the
+// interpolating halfband resampler.  A low-frequency input sinusoid is
+// generated and fed into the interpolator one sample at a time,
+// producing two outputs at each iteration.  The results are written to
+// an output file.
+//
+// SEE ALSO: resamp2_crcf_interp_example.c
+//           interp_crcf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_interp_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     :   print help\n");
+    printf("  n     :   number of input samples,      default: 64\n");
+    printf("  m     :   filter semi-length,           default: 5\n");
+    printf("  s     :   filter stop-band attenuation, default: 60 dB\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int m=5;               // filter semi-length
+    float fc=0.074f;                // input tone frequency
+    unsigned int num_samples = 64;  // number of input samples
+    float As=60.0f;                 // stop-band attenuation [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:m:s:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                    return 0;
+        case 'n':   num_samples = atoi(optarg); break;
+        case 'm':   m           = atoi(optarg); break;
+        case 's':   As          = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+    unsigned int i;
+
+    // allocate arrays
+    float complex x[  num_samples]; // input array
+    float complex y[2*num_samples]; // output array
+
+    // generate input
+    unsigned int w_len = num_samples - 2*m; // window length
+    float beta = 8.0f;                      // kaiser window factor
+    float w_sum = 0.0f;                     // gain due to window
+    for (i=0; i<num_samples; i++) {
+        // compute windowing function and keep track of gain
+        float w = (i < w_len ? kaiser(i,w_len,beta,0) : 0.0f);
+        w_sum += w;
+
+        // compute windowed complex sinusoid
+        x[i] = w * cexpf(_Complex_I*2*M_PI*fc*i);
+    }
+
+    // create/print the half-band resampler
+    resamp2_crcf q = resamp2_crcf_create(m,0,As);
+    resamp2_crcf_print(q);
+    unsigned int delay = resamp2_crcf_get_delay(q);
+
+    // run the resampler
+    for (i=0; i<num_samples; i++) {
+        // execute the interpolator
+        resamp2_crcf_interp_execute(q, x[i], &y[2*i]);
+
+        // print results to screen
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // destroy half-band resampler
+    resamp2_crcf_destroy(q);
+
+    // 
+    // export results
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+    fprintf(fid,"delay      =%u;\n", delay);
+    fprintf(fid,"w_sum     =%12.8f;\n", w_sum);
+        
+    for (i=0; i<num_samples; i++) {
+        // save results to output file
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", i+1,   crealf(x[i    ]), cimagf(x[i    ]));
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", 2*i+1,      crealf(y[2*i+0]),      cimagf(y[2*i+0]));
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", 2*i+2,      crealf(y[2*i+1]),      cimagf(y[2*i+1]));
+    }
+
+    // plot time series
+    fprintf(fid,"tx =  0:(  num_samples-1);\n");
+    fprintf(fid,"ty = [0:(2*num_samples-1)]/2 - (delay+1)/2;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0.0 0.0],'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','interpolated','location','northeast');");
+    fprintf(fid,"  axis([-delay num_samples -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Time [t/T_s]');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0.0 0.5 0.0],'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','interpolated','location','northeast');");
+    fprintf(fid,"  axis([-delay num_samples -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Time [t/T_s]');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+
+    // plot spectrum
+    fprintf(fid,"nfft=max([1024, 2^(1+nextpow2(num_samples))]);\n");
+    fprintf(fid,"g = 1/w_sum;\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x*g,  nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y*g/2,nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(  f,X,'LineWidth',1,  'Color',[0.5 0.5 0.5],...\n");
+    fprintf(fid,"     2*f,Y,'LineWidth',1.5,'Color',[0.1 0.3 0.5]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','interpolated','location','northeast');");
+    fprintf(fid,"axis([-1 1 -100 10]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/resamp_crcf_example.c b/examples/resamp_crcf_example.c
new file mode 100644
index 0000000..0ee8129
--- /dev/null
+++ b/examples/resamp_crcf_example.c
@@ -0,0 +1,246 @@
+//
+// resamp_crcf_example.c
+//
+// Demonstration of arbitrary resampler object whereby an input signal
+// is resampled at an arbitrary rate.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: %s [OPTION]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  r     : resampling rate (output/input),    default: 1.1\n");
+    printf("  m     : filter semi-length (delay),        default: 13\n");
+    printf("  b     : filter bandwidth, 0 < b < 0.5,     default: 0.4\n");
+    printf("  s     : filter stop-band attenuation [dB], default: 60\n");
+    printf("  p     : filter bank size,                  default: 64\n");
+    printf("  n     : number of input samples,           default: 400\n");
+    printf("  f     : input signal frequency,            default: 0.044\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float r           = 1.1f;   // resampling rate (output/input)
+    unsigned int m    = 13;     // resampling filter semi-length (filter delay)
+    float As          = 60.0f;  // resampling filter stop-band attenuation [dB]
+    float bw          = 0.45f;  // resampling filter bandwidth
+    unsigned int npfb = 64;     // number of filters in bank (timing resolution)
+    unsigned int n    = 400;    // number of input samples
+    float fc          = 0.044f; // complex sinusoid frequency
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hr:m:b:s:p:n:f:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();            return 0;
+        case 'r':   r    = atof(optarg); break;
+        case 'm':   m    = atoi(optarg); break;
+        case 'b':   bw   = atof(optarg); break;
+        case 's':   As   = atof(optarg); break;
+        case 'p':   npfb = atoi(optarg); break;
+        case 'n':   n    = atoi(optarg); break;
+        case 'f':   fc   = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (r <= 0.0f) {
+        fprintf(stderr,"error: %s, resampling rate must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (bw == 0.0f || bw >= 0.5f) {
+        fprintf(stderr,"error: %s, filter bandwidth must be in (0,0.5)\n", argv[0]);
+        exit(1);
+    } else if (As < 0.0f) {
+        fprintf(stderr,"error: %s, filter stop-band attenuation must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (npfb == 0) {
+        fprintf(stderr,"error: %s, filter bank size must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (n == 0) {
+        fprintf(stderr,"error: %s, number of input samples must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // number of input samples (zero-padded)
+    unsigned int nx = n + m;
+
+    // output buffer with extra padding for good measure
+    unsigned int y_len = (unsigned int) ceilf(1.1 * nx * r) + 4;
+
+    // arrays
+    float complex x[nx];
+    float complex y[y_len];
+
+    // create resampler
+    resamp_crcf q = resamp_crcf_create(r,m,bw,As,npfb);
+
+    // generate input signal
+    float wsum = 0.0f;
+    for (i=0; i<nx; i++) {
+        // compute window
+        float w = i < n ? kaiser(i, n, 10.0f, 0.0f) : 0.0f;
+
+        // apply window to complex sinusoid
+        x[i] = cexpf(_Complex_I*2*M_PI*fc*i) * w;
+
+        // accumulate window
+        wsum += w;
+    }
+
+    // resample
+    unsigned int ny=0;
+#if 0
+    // execute one sample at a time
+    unsigned int nw;
+    for (i=0; i<nx; i++) {
+        // execute resampler, storing in output buffer
+        resamp_crcf_execute(q, x[i], &y[ny], &nw);
+
+        // increment output size
+        ny += nw;
+    }
+#else
+    // execute on block of samples
+    resamp_crcf_execute_block(q, x, nx, y, &ny);
+#endif
+
+    // clean up allocated objects
+    resamp_crcf_destroy(q);
+
+    // 
+    // analyze resulting signal
+    //
+
+    // check that the actual resampling rate is close to the target
+    float r_actual = (float)ny / (float)nx;
+    float fy = fc / r;      // expected output frequency
+
+    // run FFT and ensure that carrier has moved and that image
+    // frequencies and distortion have been adequately suppressed
+    unsigned int nfft = 1 << liquid_nextpow2(ny);
+    float complex yfft[nfft];   // fft input
+    float complex Yfft[nfft];   // fft output
+    for (i=0; i<nfft; i++)
+        yfft[i] = i < ny ? y[i] : 0.0f;
+    fft_run(nfft, yfft, Yfft, LIQUID_FFT_FORWARD, 0);
+    fft_shift(Yfft, nfft);  // run FFT shift
+
+    // find peak frequency
+    float Ypeak = 0.0f;
+    float fpeak = 0.0f;
+    float max_sidelobe = -1e9f;     // maximum side-lobe [dB]
+    float main_lobe_width = 0.07f;  // TODO: figure this out from Kaiser's equations
+    for (i=0; i<nfft; i++) {
+        // normalized output frequency
+        float f = (float)i/(float)nfft - 0.5f;
+
+        // scale FFT output appropriately
+        float Ymag = 20*log10f( cabsf(Yfft[i] / (r * wsum)) );
+
+        // find frequency location of maximum magnitude
+        if (Ymag > Ypeak || i==0) {
+            Ypeak = Ymag;
+            fpeak = f;
+        }
+
+        // find peak side-lobe value, ignoring frequencies
+        // within a certain range of signal frequency
+        if ( fabsf(f-fy) > main_lobe_width )
+            max_sidelobe = Ymag > max_sidelobe ? Ymag : max_sidelobe;
+    }
+
+    // print results and check frequency location
+    printf("  desired resampling rate   :   %12.8f\n", r);
+    printf("  measured resampling rate  :   %12.8f    (%u/%u)\n", r_actual, ny, nx);
+    printf("  peak spectrum             :   %12.8f dB (expected 0.0 dB)\n", Ypeak);
+    printf("  peak frequency            :   %12.8f    (expected %-12.8f)\n", fpeak, fy);
+    printf("  max sidelobe              :   %12.8f dB (expected at least %.2f dB)\n", max_sidelobe, -As);
+
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"m=%u;\n", m);
+    fprintf(fid,"npfb=%u;\n",  npfb);
+    fprintf(fid,"r=%12.8f;\n", r);
+    fprintf(fid,"n=%u;\n", n);
+
+    fprintf(fid,"nx = %u;\n", nx);
+    fprintf(fid,"x = zeros(1,nx);\n");
+    for (i=0; i<nx; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    fprintf(fid,"ny = %u;\n", ny);
+    fprintf(fid,"y = zeros(1,ny);\n");
+    for (i=0; i<ny; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot time-domain result\n");
+    fprintf(fid,"tx=[0:(length(x)-1)];\n");
+    fprintf(fid,"ty=[0:(length(y)-1)]/r-m;\n");
+    fprintf(fid,"figure('Color','white','position',[500 500 500 900]);\n");
+    fprintf(fid,"subplot(4,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  axis([0 n -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(4,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0 0.5 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  axis([0 n -1.2 1.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"nfft=2^nextpow2(max(nx,ny));\n");
+    fprintf(fid,"%% estimate PSD, normalize by array length\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x,nfft)/length(x))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y,nfft)/length(y))));\n");
+    fprintf(fid,"G=max(X);\n");
+    fprintf(fid,"X=X-G;\n");
+    fprintf(fid,"Y=Y-G;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"if r>1, fx = f/r; fy = f;   %% interpolated\n");
+    fprintf(fid,"else,   fx = f;   fy = f*r; %% decimated\n");
+    fprintf(fid,"end;\n");
+    fprintf(fid,"subplot(4,1,3:4);\n");
+    fprintf(fid,"  plot(fx,X,'Color',[0.5 0.5 0.5],fy,Y,'LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('normalized frequency');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/ricek_channel_example.c b/examples/ricek_channel_example.c
new file mode 100644
index 0000000..a94030b
--- /dev/null
+++ b/examples/ricek_channel_example.c
@@ -0,0 +1,178 @@
+//
+// Rice-K fading generator example.
+//
+// This example generates correlated circular complex Gauss random variables
+// using an approximation to the ideal Doppler filter. The resulting Gauss
+// random variables are converted to Rice-K random variables using a simple
+// transformation. The resulting output file plots the filter's power
+// spectral density, the fading power envelope as a function of time, and the
+// distribution of Rice-K random variables alongside the theoretical PDF to
+// demonstrate that the technique is valid.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ricek_channel_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  h     : print help\n");
+    printf("  n     : number of samples\n");
+    printf("  f     : maximum doppler frequency (0,0.5)\n");
+    printf("  K     : Rice K factor, linear, greater than zero\n");
+    printf("  O     : mean power, linear (Rice-K distribution 'omega')\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int h_len = 51;        // doppler filter length
+    float fd           = 0.1f;      // maximum doppler frequency
+    float K            = 2.0f;      // Rice fading factor
+    float omega        = 1.0f;      // mean power
+    float theta        = 0.0f;      // angle of arrival
+    unsigned int num_samples=1200;  // number of samples
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:f:K:O:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                    return 0;
+        case 'n':   num_samples = atoi(optarg); break;
+        case 'f':   fd          = atof(optarg); break;
+        case 'K':   K           = atof(optarg); break;
+        case 'O':   omega       = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (K < 0.0f) {
+        fprintf(stderr,"error: %s, fading factor K must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (omega < 0.0f) {
+        fprintf(stderr,"error: %s, signal power Omega must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (fd <= 0.0f || fd >= 0.5f) {
+        fprintf(stderr,"error: %s, Doppler frequency must be in (0,0.5)\n", argv[0]);
+        exit(1);
+    } else if (h_len < 4) {
+        fprintf(stderr,"error: %s, Doppler filter length too small\n", argv[0]);
+        exit(1);
+    } else if (num_samples == 0) {
+        fprintf(stderr,"error: %s, number of samples must be greater than zero\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // allocate array for output samples
+    float complex * y = (float complex*) malloc(num_samples*sizeof(float complex));
+
+    // generate Doppler filter coefficients
+    float h[h_len];
+    liquid_firdes_doppler(h_len, fd, K, theta, h);
+
+    // normalize filter coefficients such that output Gauss random
+    // variables have unity variance
+    float std = 0.0f;
+    for (i=0; i<h_len; i++)
+        std += h[i]*h[i];
+    std = sqrtf(std);
+    for (i=0; i<h_len; i++)
+        h[i] /= std;
+
+    // create Doppler filter from coefficients
+    firfilt_crcf fdoppler = firfilt_crcf_create(h,h_len);
+
+    // generate complex circular Gauss random variables
+    float complex v;    // circular Gauss random variable (uncorrelated)
+    float complex x;    // circular Gauss random variable (correlated w/ Doppler filter)
+    float s   = sqrtf((omega*K)/(K+1.0));
+    float sig = sqrtf(0.5f*omega/(K+1.0));
+    for (i=0; i<num_samples; i++) {
+        // generate complex Gauss random variable
+        crandnf(&v);
+
+        // push through Doppler filter
+        firfilt_crcf_push(fdoppler, v);
+        firfilt_crcf_execute(fdoppler, &x);
+
+        // convert result to random variable with Rice-K distribution
+        y[i] = _Complex_I*( crealf(x)*sig + s ) +
+                          ( cimagf(x)*sig     );
+    }
+
+    // destroy filter object
+    firfilt_crcf_destroy(fdoppler);
+
+    // export results to file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"h_len       = %u;\n", h_len);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    // save filter coefficients
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%6u) = %12.4e;\n", i+1, h[i]);
+
+    // save samples
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // plot power spectral density of filter
+    fprintf(fid,"nfft = min(1024, 2^(ceil(log2(h_len))+4));\n");
+    fprintf(fid,"f    = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H    = 20*log10(abs(fftshift(fft(h,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H);\n");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Filter Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot fading profile
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"plot(t,20*log10(abs(y)));\n");
+    fprintf(fid,"xlabel('Normalized Time [t F_s]');\n");
+    fprintf(fid,"ylabel('Fading Power Envelope [dB]');\n");
+    fprintf(fid,"axis([0 num_samples -40 10]);\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot distribution
+    fprintf(fid,"[nn xx]   = hist(abs(y),15);\n");
+    fprintf(fid,"bin_width = xx(2) - xx(1);\n");
+    fprintf(fid,"ymax = max(abs(y));\n");
+    fprintf(fid,"s    = %12.4e;\n", s);
+    fprintf(fid,"sig  = %12.4e;\n", sig);
+    fprintf(fid,"yp   = 1.1*ymax*[1:500]/500;\n");
+    fprintf(fid,"pdf  = (yp/sig^2) .* exp(-(yp.^2+s^2)/(2*sig^2)) .* besseli(0,yp*s/sig^2);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(yp,pdf,'-', xx,nn/(num_samples*bin_width),'x');\n");
+    fprintf(fid,"xlabel('Fading Magnitude');\n");
+    fprintf(fid,"ylabel('Probability Density');\n");
+    fprintf(fid,"legend('theory','data','location','northeast');\n");
+
+    // close output file
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    // clean up allocated arrays
+    free(y);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/scramble_example.c b/examples/scramble_example.c
new file mode 100644
index 0000000..fdf4222
--- /dev/null
+++ b/examples/scramble_example.c
@@ -0,0 +1,135 @@
+//
+// scramble_example.c
+//
+// Data-scrambling example.  Physical layer synchronization of
+// received waveforms relies on independent and identically
+// distributed underlying data symbols.  If the message sequence,
+// however, is '00000....' and the modulation scheme is BPSK,
+// the synchronizer probably won't be able to recover the symbol
+// timing.  It is imperative to increase the entropy of the data
+// for this to happen.  The data scrambler routine attempts to
+// 'whiten' the data sequence with a bit mask in order to achieve
+// maximum entropy.  This example demonstrates the interface.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.h"
+
+float compute_entropy(unsigned char * _x,
+                      unsigned int _n);
+
+int main() {
+    unsigned int n=32;  // number of data bytes
+
+    unsigned char x[n]; // input data
+    unsigned char y[n]; // scrambled data
+    unsigned char z[n]; // unscrambled data
+
+    unsigned int i;
+
+    // generate data
+    for (i=0; i<n; i++)
+        x[i] = rand() % 2 ? 0x0f : 0xc8;
+
+    // scramble input
+    memmove(y,x,n);
+    scramble_data(y,n);
+
+    // unscramble result
+    memmove(z,y,n);
+    unscramble_data(z,n);
+
+    float Hx = compute_entropy(x,n);
+    float Hy = compute_entropy(y,n);
+
+    // print results
+    printf("i\tx\ty\tz\n");
+    printf("--\t--\t--\t--\n");
+    for (i=0; i<n; i++)
+        printf("%u\t%.2x\t%.2x\t%.2x\n", i, x[i], y[i], z[i]);
+
+    printf("H(x) = %12.8f\n", Hx);
+    printf("H(y) = %12.8f\n", Hy);
+
+    printf("done.\n");
+    return 0;
+}
+
+// raw entropy calculation, orders 1, 2, 4, 8
+float compute_entropy(unsigned char * _x,
+                      unsigned int _n)
+{
+    unsigned int i;
+    unsigned int j;
+
+    // initialize counter arrays
+    unsigned int c1[2];     // 1-bit symbols
+    unsigned int c2[4];     // 2-bit symbols
+    unsigned int c4[16];    // 4-bit symbols
+    unsigned int c8[256];   // 8-bit symbols
+
+    for (i=0; i<2;   i++) c1[i] = 0;
+    for (i=0; i<4;   i++) c2[i] = 0;
+    for (i=0; i<16;  i++) c4[i] = 0;
+    for (i=0; i<256; i++) c8[i] = 0;
+
+    for (i=0; i<_n; i++) {
+        // b:1
+        for (j=0; j<8; j++)  c1[ (_x[i] >> j) & 0x01 ]++;
+
+        // b:2
+        for (j=0; j<8; j+=2) c2[ (_x[i] >> j) & 0x03 ]++;
+
+        // b:4
+        for (j=0; j<8; j+=4) c4[ (_x[i] >> j) & 0x0f ]++;
+
+        // b:8
+        c8[ _x[i] ]++;
+    }
+
+    // compute entropy
+    float H1 = 0.0f;
+    float H2 = 0.0f;
+    float H4 = 0.0f;
+    float H8 = 0.0f;
+
+    float p;
+    for (i=0; i<2; i++) {
+        p = (float) c1[i] / (float)(8*_n);
+        H1 -= p * log2f(p+1e-12f);
+    }
+    H1 /= 1.0f;
+
+    for (i=0; i<4; i++) {
+        p = (float) c2[i] / (float)(4*_n);
+        H2 -= p * log2f(p+1e-12f);
+    }
+    H2 /= 2.0f;
+
+    for (i=0; i<16; i++) {
+        p = (float) c4[i] / (float)(2*_n);
+        H4 -= p * log2f(p+1e-12f);
+    }
+    H4 /= 4.0f;
+
+    for (i=0; i<256; i++) {
+        p = (float) c8[i] / (float)(_n);
+        H8 -= p * log2f(p+1e-12f);
+    }
+    H8 /= 8.0f;
+
+#if 0
+    printf("H1 : %12.8f\n", H1);
+    printf("H2 : %12.8f\n", H2);
+    printf("H4 : %12.8f\n", H4);
+    printf("H8 : %12.8f\n", H8);
+#endif
+
+    // not sure if product is truly the entropy, but
+    // it is a fair assessment
+    return H1 * H2 * H4 * H8;
+}
+
diff --git a/examples/smatrix_example.c b/examples/smatrix_example.c
new file mode 100644
index 0000000..a12db46
--- /dev/null
+++ b/examples/smatrix_example.c
@@ -0,0 +1,210 @@
+//
+// smatrix_example.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+int main(int argc, char*argv[])
+{
+#if 0
+    unsigned int M = 12;
+    unsigned int N = 16;
+
+    // create empty list
+    smatrixb q = smatrixb_create(M,N);
+
+    // set value(s)
+    smatrixb_set(q,1,2,  1);
+    smatrixb_set(q,2,2,  1);
+    smatrixb_set(q,2,3,  1);
+    smatrixb_set(q,2,7,  1);
+    smatrixb_set(q,2,8,  1);
+    smatrixb_set(q,2,11, 1);
+    smatrixb_delete(q,2,2);
+
+    printf("\ncompact form:\n");
+    smatrixb_print(q);
+
+    printf("\nexpanded form:\n");
+    smatrixb_print_expanded(q);
+
+    printf("\ncertain values:\n");
+    printf("  A[%2u,%2u] = %1u\n", 1, 1, smatrixb_get(q,1,1));
+    printf("  A[%2u,%2u] = %1u\n", 1, 2, smatrixb_get(q,1,2));
+
+    // generate vectors
+    unsigned char x[N];
+    unsigned char y[M];
+    unsigned int i;
+    unsigned int j;
+    for (j=0; j<N; j++)
+        x[j] = rand() % 2 ? 1 : 0;
+
+    smatrixb_vmul(q,x,y);
+
+    // print results
+    printf("x = [");
+    for (j=0; j<N; j++) printf("%2u", x[j]);
+    printf(" ];\n");
+
+    printf("y = [");
+    for (i=0; i<M; i++) printf("%2u", y[i]);
+    printf(" ];\n");
+
+    smatrixb_destroy(q);
+//#else
+    unsigned int M = 12;
+    unsigned int N = 12;
+
+    // create empty list
+    smatrixf q = smatrixf_create(M,N);
+
+    // set value(s)
+    smatrixf_set(q,1,2,  1.0f);
+    smatrixf_set(q,2,2,  2.0f);
+    smatrixf_set(q,2,3,  3.0f);
+    smatrixf_set(q,2,7,  4.0f);
+    smatrixf_set(q,2,8,  5.0f);
+    smatrixf_set(q,2,11, 6.0f);
+    smatrixf_delete(q,2,2);
+
+    printf("\ncompact form:\n");
+    smatrixf_print(q);
+
+    printf("\nexpanded form:\n");
+    smatrixf_print_expanded(q);
+
+    printf("\ncertain values:\n");
+    printf("  A[%2u,%2u] = %6.2f\n", 1, 1, smatrixf_get(q,1,1));
+    printf("  A[%2u,%2u] = %6.2f\n", 1, 2, smatrixf_get(q,1,2));
+
+    smatrixf_destroy(q);
+#endif
+
+#if 1
+    // 
+    // test matrix multiplication
+    //
+    smatrixb a = smatrixb_create( 8,12);
+    smatrixb b = smatrixb_create(12, 5);
+    smatrixb c = smatrixb_create( 8, 5);
+
+    // initialize 'a'
+    // 0 0 0 0 1 0 0 0 0 0 0 0
+    // 0 0 0 0 0 0 0 0 0 0 0 0
+    // 0 0 0 1 0 0 0 1 0 0 0 0
+    // 0 0 0 0 1 0 0 0 0 0 0 0
+    // 0 0 0 1 0 0 1 1 0 0 0 0
+    // 0 0 0 0 0 0 0 0 0 0 0 0
+    // 0 1 0 0 0 0 0 0 0 0 0 0
+    // 0 0 0 0 0 0 0 1 1 0 0 0
+    smatrixb_set(a,0,4, 1);
+    smatrixb_set(a,2,3, 1);
+    smatrixb_set(a,2,7, 1);
+    smatrixb_set(a,3,4, 1);
+    smatrixb_set(a,4,3, 1);
+    smatrixb_set(a,4,6, 1);
+    smatrixb_set(a,4,7, 1);
+    smatrixb_set(a,6,1, 1);
+    smatrixb_set(a,7,7, 1);
+    smatrixb_set(a,7,8, 1);
+
+    // initialize 'b'
+    // 1 1 0 0 0
+    // 0 0 0 0 1
+    // 0 0 0 0 0
+    // 0 0 0 0 0
+    // 0 0 0 0 0
+    // 0 0 0 0 1
+    // 0 0 0 1 0
+    // 0 0 0 1 0
+    // 0 0 0 0 0
+    // 0 1 0 0 1
+    // 1 0 0 1 0
+    // 0 1 0 0 0
+    smatrixb_set(b,0,0,  1);
+    smatrixb_set(b,0,1,  1);
+    smatrixb_set(b,1,4,  1);
+    smatrixb_set(b,5,4,  1);
+    smatrixb_set(b,6,3,  1);
+    smatrixb_set(b,7,3,  1);
+    smatrixb_set(b,9,1,  1);
+    smatrixb_set(b,9,4,  1);
+    smatrixb_set(b,10,0, 1);
+    smatrixb_set(b,11,1, 1);
+
+    // compute 'c'
+    // 0 0 0 0 0
+    // 0 0 0 0 0
+    // 0 0 0 1 0
+    // 0 0 0 0 0
+    // 0 0 0(0)0
+    // 0 0 0 0 0
+    // 0 0 0 0 1
+    // 0 0 0 1 0
+    smatrixb_mul(a,b,c);
+
+    // print results
+    printf("a:\n"); smatrixb_print_expanded(a);
+    printf("b:\n"); smatrixb_print_expanded(b);
+    printf("c:\n"); smatrixb_print_expanded(c);
+
+    smatrixb_destroy(a);
+    smatrixb_destroy(b);
+    smatrixb_destroy(c);
+#else
+    // 
+    // test matrix multiplication
+    //
+    smatrixf a = smatrixf_create(4, 5);
+    smatrixf b = smatrixf_create(5, 3);
+    smatrixf c = smatrixf_create(4, 3);
+
+    // initialize 'a'
+    // 0 0 0 0 4
+    // 0 0 0 0 0
+    // 0 0 0 3 0
+    // 2 0 0 0 1
+    smatrixf_set(a, 0,4, 4);
+    smatrixf_set(a, 2,3, 3);
+    smatrixf_set(a, 3,0, 2);
+    smatrixf_set(a, 3,4, 0);
+    smatrixf_set(a, 3,4, 1);
+
+    // initialize 'b'
+    // 7 6 0
+    // 0 0 0
+    // 0 0 0
+    // 0 5 0
+    // 2 0 0
+    smatrixf_set(b, 0,0, 7);
+    smatrixf_set(b, 0,1, 6);
+    smatrixf_set(b, 3,1, 5);
+    smatrixf_set(b, 4,0, 2);
+
+    printf("a:\n"); smatrixf_print(a); //smatrixf_print_expanded(a);
+    printf("b:\n"); smatrixf_print(b); //smatrixf_print_expanded(b);
+    // compute 'c'
+    //  8   0   0
+    //  0   0   0
+    //  0  15   0
+    // 16  12   0
+    smatrixf_mul(a,b,c);
+
+    // print results
+    printf("c:\n"); smatrixf_print_expanded(c);
+    printf("c:\n"); smatrixf_print(c);
+
+    smatrixf_destroy(a);
+    smatrixf_destroy(b);
+    smatrixf_destroy(c);
+#endif
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/spgramcf_example.c b/examples/spgramcf_example.c
new file mode 100644
index 0000000..1ec82da
--- /dev/null
+++ b/examples/spgramcf_example.c
@@ -0,0 +1,96 @@
+//
+// spgramcf_example.c
+//
+// Spectral periodogram example with complex inputs.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "spgramcf_example.m"
+
+int main() {
+    // spectral periodogram options
+    unsigned int nfft        =   1024;  // spectral periodogram FFT size
+    unsigned int num_samples =    2e6;  // number of samples
+    float        noise_floor = -60.0f;  // noise floor [dB]
+
+    unsigned int i;
+
+    // derived values
+    float nstd = powf(10.0f, noise_floor/20.0f);
+
+    // create spectral periodogram
+    spgramcf q = spgramcf_create_default(nfft);
+    spgramcf_print(q);
+
+    // generate signal (band-pass filter with Hilbert transform)
+    iirfilt_rrrf filter = iirfilt_rrrf_create_prototype(
+            LIQUID_IIRDES_BUTTER,
+            LIQUID_IIRDES_BANDPASS,
+            LIQUID_IIRDES_SOS,
+            9, 0.17f, 0.20f, 0.1f, 60.0f);
+    firhilbf ht = firhilbf_create(13,60.0f);
+
+    for (i=0; i<num_samples; i++) {
+        // filter input noise signal
+        float v = 0;
+        iirfilt_rrrf_execute(filter, randnf(), &v);
+
+        // filter off negative image (gain of 2)
+        float complex y = 0;
+        firhilbf_r2c_execute(ht, v, &y);
+
+        // scale and add noise
+        y = 0.5f*y + nstd * ( randnf() + _Complex_I*randnf() ) * M_SQRT1_2;
+
+        // push resulting sample through periodogram
+        spgramcf_push(q, y);
+    }
+
+    // explort to gnuplot
+    spgramcf_export_gnuplot(q,"spgramcf_example.gnu");
+
+    // compute power spectral density output
+    float psd[nfft];
+    spgramcf_get_psd(q, psd);
+
+    // destroy objects
+    iirfilt_rrrf_destroy(filter);
+    firhilbf_destroy(ht);
+    spgramcf_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f    = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"psd  = zeros(1,nfft);\n");
+    fprintf(fid,"noise_floor = %12.6f;\n", noise_floor);
+    
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%6u) = %12.4e;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f, psd, '-', 'LineWidth',1.5);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"ymin = 10*floor([noise_floor-20]/10);\n");
+    fprintf(fid,"ymax = 10*floor([noise_floor+80]/10);\n");
+    fprintf(fid,"axis([-0.5 0.5 ymin ymax]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/spgramcf_waterfall_example.c b/examples/spgramcf_waterfall_example.c
new file mode 100644
index 0000000..62e25b2
--- /dev/null
+++ b/examples/spgramcf_waterfall_example.c
@@ -0,0 +1,152 @@
+// waterfall example
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "spgramcf_waterfall_example.gnu"
+
+int main()
+{
+    // spectral periodogram options
+    unsigned int nfft        =    1200; // spectral periodogram FFT size
+    unsigned int num_samples = 2000000; // number of samples
+
+    // derived values
+    unsigned int num_estimates = 800;  // target number of PSD estimates
+    unsigned int samples_per_estimate = (unsigned int)(num_samples/num_estimates);
+
+    unsigned int i;
+
+    // create time-varying multi-path channel object
+    unsigned int c_len = 17;
+    float        std   = 0.1f;
+    float        tau   = 1e-4f;
+    tvmpch_cccf channel = tvmpch_cccf_create(c_len, std, tau);
+
+    unsigned int buf_len = 64;
+    float complex buf[buf_len];
+
+    // create spectral periodogram
+    spgramcf periodogram = spgramcf_create_default(nfft);
+
+    // create stream generator
+    msourcecf gen = msourcecf_create();
+    
+    // add noise source (narrow-band)
+    int id_noise = msourcecf_add_noise(gen, 0.10f);
+    msourcecf_set_frequency(gen, id_noise, 0.4*2*M_PI);
+    msourcecf_set_gain     (gen, id_noise, -20.0f);
+
+    // add tone
+    int id_tone = msourcecf_add_tone(gen);
+    msourcecf_set_frequency(gen, id_tone, -0.4*2*M_PI);
+    msourcecf_set_gain     (gen, id_tone, -10.0f);
+
+    // add modulated data
+    int id_modem = msourcecf_add_modem(gen,LIQUID_MODEM_QPSK,4,12,0.30f);
+    msourcecf_set_frequency(gen, id_modem, -0.1*2*M_PI);
+    msourcecf_set_gain     (gen, id_modem, 0.0f);
+
+    FILE * fid = fopen("waterfall.bin","wb");
+    // write header
+    float nfftf = (float)nfft;
+    fwrite(&nfftf, sizeof(float), 1, fid);
+    for (i=0; i<nfft; i++) {
+        float f = (float)i/nfftf - 0.5f;
+        fwrite(&f, sizeof(float), 1, fid);
+    }
+
+    float psd[nfft];
+    unsigned int total_samples   = 0;
+    unsigned int total_estimates = 0;
+    int state = 1;
+    while (total_samples < num_samples) {
+        // write samples to buffer
+        msourcecf_write_samples(gen, buf, buf_len);
+
+        // run through channel
+        tvmpch_cccf_execute_block(channel, buf, buf_len, buf);
+
+        // push resulting sample through periodogram
+        spgramcf_write(periodogram, buf, buf_len);
+
+        if (spgramcf_get_num_samples(periodogram) > samples_per_estimate) {
+            // compute power spectral density output
+            spgramcf_get_psd(periodogram, psd);
+
+            // write output
+            float n = (float)total_samples;
+            fwrite(&n, sizeof(float), 1, fid);
+            fwrite(psd, sizeof(float), nfft, fid);
+
+            // soft reset of internal state, counters
+            spgramcf_reset(periodogram);
+
+            // update counter for total number of PSD estimates taken
+            total_estimates++;
+        }
+
+        // accumulated samples
+        total_samples += buf_len;
+
+        // update state for noise source
+        if (state == 0 && randf() < 0.01f) {
+            state = 1;
+            msourcecf_enable(gen, id_noise);
+        } else if (state == 1 && randf() < 0.01f) {
+            state = 0;
+            msourcecf_disable(gen, id_noise);
+        }
+    }
+    printf("samples per PSD estimate : %u\n", samples_per_estimate);
+    printf("total samples            : %u (%u)\n", total_samples,   num_samples);
+    printf("total estimates          : %u (%u)\n", total_estimates, num_estimates);
+    fclose(fid);
+
+    // destroy objects
+    msourcecf_destroy(gen);
+    spgramcf_destroy(periodogram);
+    tvmpch_cccf_destroy(channel);
+
+    // export output file
+    fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"#!/usr/bin/gnuplot\n");
+    fprintf(fid,"reset\n");
+    fprintf(fid,"set terminal png size 800,800 enhanced font 'Verdana,10'\n");
+    fprintf(fid,"set output '%s.png'\n", OUTPUT_FILENAME);
+    fprintf(fid,"unset key\n");
+    fprintf(fid,"set style line 11 lc rgb '#808080' lt 1\n");
+    fprintf(fid,"set border 3 front ls 11\n");
+    fprintf(fid,"set style line 12 lc rgb '#888888' lt 0 lw 1\n");
+    fprintf(fid,"set grid front ls 12\n");
+    fprintf(fid,"set tics nomirror out scale 0.75\n");
+    fprintf(fid,"set xrange [-0.5:0.5]\n");
+    fprintf(fid,"set yrange [0:%f]\n", (float)(num_samples-1)*1e-3f);
+    fprintf(fid,"set xlabel 'Normalized Frequency [f/F_s]'\n");
+    fprintf(fid,"set ylabel 'Sample Index'\n");
+    fprintf(fid,"set format y '%%.0f k'\n");
+    fprintf(fid,"# disable colorbar tics\n");
+    fprintf(fid,"set cbtics scale 0\n");
+    fprintf(fid,"set palette negative defined ( \\\n");
+    fprintf(fid,"    0 '#D53E4F',\\\n");
+    fprintf(fid,"    1 '#F46D43',\\\n");
+    fprintf(fid,"    2 '#FDAE61',\\\n");
+    fprintf(fid,"    3 '#FEE08B',\\\n");
+    fprintf(fid,"    4 '#E6F598',\\\n");
+    fprintf(fid,"    5 '#ABDDA4',\\\n");
+    fprintf(fid,"    6 '#66C2A5',\\\n");
+    fprintf(fid,"    7 '#3288BD' )\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"plot 'waterfall.bin' u 1:($2*1e-3):3 binary matrix with image\n");
+    fclose(fid);
+    printf("results written to %s.\n", "waterfall.bin");
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+
diff --git a/examples/spgramf_example.c b/examples/spgramf_example.c
new file mode 100644
index 0000000..fb0a13d
--- /dev/null
+++ b/examples/spgramf_example.c
@@ -0,0 +1,87 @@
+//
+// spgramf_example.c
+//
+// Spectral periodogram example with real inputs.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "spgramf_example.m"
+
+int main() {
+    // spectral periodogram options
+    unsigned int nfft        =   1024;  // spectral periodogram FFT size
+    unsigned int num_samples =    1e6;  // number of samples
+    float        noise_floor = -60.0f;  // noise floor [dB]
+
+    unsigned int i;
+
+    // derived values
+    float nstd = powf(10.0f, noise_floor/20.0f);
+
+    // create spectral periodogram
+    spgramf q = spgramf_create_default(nfft);
+    spgramf_print(q);
+
+    // generate signal (band-pass filter)
+    iirfilt_rrrf filter = iirfilt_rrrf_create_prototype(
+            LIQUID_IIRDES_BUTTER,
+            LIQUID_IIRDES_BANDPASS,
+            LIQUID_IIRDES_SOS,
+            9, 0.17f, 0.20f, 0.1f, 60.0f);
+
+    for (i=0; i<num_samples; i++) {
+        // filter input noise signal
+        float y = 0;
+        iirfilt_rrrf_execute(filter, randnf(), &y);
+
+        // add noise
+        y += nstd * randnf();
+
+        // push resulting sample through periodogram
+        spgramf_push(q, y);
+    }
+
+    // compute power spectral density output
+    float psd[nfft];
+    spgramf_get_psd(q, psd);
+
+    // destroy objects
+    iirfilt_rrrf_destroy(filter);
+    spgramf_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f    = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H    = zeros(1,nfft);\n");
+    fprintf(fid,"noise_floor = %12.6f;\n", noise_floor);
+    
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%6u) = %12.4e;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f, H, '-', 'LineWidth',1.5);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"ymin = 10*floor([noise_floor-20]/10);\n");
+    fprintf(fid,"ymax = 10*floor([noise_floor+80]/10);\n");
+    fprintf(fid,"axis([-0.5 0.5 ymin ymax]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/symstreamcf_example.c b/examples/symstreamcf_example.c
new file mode 100644
index 0000000..c3a4b74
--- /dev/null
+++ b/examples/symstreamcf_example.c
@@ -0,0 +1,85 @@
+//
+// symstreamcf_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symstreamcf_example.m"
+
+int main()
+{
+    // symstream parameters
+    int          ftype       = LIQUID_FIRFILT_ARKAISER;
+    unsigned int k           =     4;
+    unsigned int m           =     9;
+    float        beta        = 0.30f;
+    int          ms          = LIQUID_MODEM_QPSK;
+
+    // spectral periodogram options
+    unsigned int nfft        =   2400;  // spectral periodogram FFT size
+    unsigned int num_samples =  80000;  // number of samples
+
+    unsigned int i;
+
+    // create spectral periodogram
+    spgramcf periodogram = spgramcf_create_default(nfft);
+
+    unsigned int buf_len = 1024;
+    float complex buf[buf_len];
+
+    // create stream generator
+    symstreamcf gen = symstreamcf_create_linear(ftype,k,m,beta,ms);
+
+    unsigned int total_samples = 0;
+    while (total_samples < num_samples) {
+        // write samples to buffer
+        symstreamcf_write_samples(gen, buf, buf_len);
+
+        // push resulting sample through periodogram
+        spgramcf_write(periodogram, buf, buf_len);
+        
+        // accumulated samples
+        total_samples += buf_len;
+    }
+    printf("total samples: %u\n", total_samples);
+
+    // compute power spectral density output
+    float psd[nfft];
+    spgramcf_get_psd(periodogram, psd);
+
+    // destroy objects
+    symstreamcf_destroy(gen);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f    = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H    = zeros(1,nfft);\n");
+    
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"H(%6u) = %12.4e;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f, H, '-', 'LineWidth',1.5);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/examples/symsync_crcf_example.c b/examples/symsync_crcf_example.c
new file mode 100644
index 0000000..b3e4d13
--- /dev/null
+++ b/examples/symsync_crcf_example.c
@@ -0,0 +1,160 @@
+//
+// symsync_crcf_example.c
+//
+// This example demonstrates the basic principles of the symbol timing
+// recovery family of objects, specifically symsync_crcf. A set of random
+// QPSK symbols are generated and interpolated with a timing offset. The
+// resulting signal is run through the symsync_crcf object which applies a
+// matched filter and recovers timing producing a clean constellation.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symsync_crcf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("symsync_crcf_example [options]\n");
+    printf("  h     : print help\n");
+    printf("  k     : filter samples/symbol, default: 2\n");
+    printf("  m     : filter delay (symbols), default: 5\n");
+    printf("  b     : filter excess bandwidth, default: 0.5\n");
+    printf("  B     : filter polyphase banks, default: 32\n");
+    printf("  n     : number of symbols, default: 400\n");
+}
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int k           = 2;       // samples/symbol (input)
+    unsigned int m           = 5;       // filter delay (symbols)
+    float        beta        = 0.5f;    // filter excess bandwidth factor
+    unsigned int num_filters = 32;      // number of filters in the bank
+    unsigned int num_symbols = 400;     // number of data symbols
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hk:m:b:B:s:n:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'k':   k           = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'B':   num_filters = atoi(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: k (samples/symbol) must be at least 2\n");
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (num_filters == 0) {
+        fprintf(stderr,"error: number of polyphase filters must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // static values
+    liquid_firfilt_type ftype = LIQUID_FIRFILT_ARKAISER;
+    float bandwidth = 0.02f;    // loop filter bandwidth
+    float dt = -0.5f;           // fractional sample offset
+
+    // derived values
+    unsigned int num_samples = k*num_symbols;
+
+    float complex s[num_symbols];       // data symbols
+    float complex x[num_samples];       // interpolated samples
+    float complex y[num_symbols + 64];  // synchronized symbols
+
+    // generate random QPSK symbols
+    for (i=0; i<num_symbols; i++) {
+        // random signal (QPSK)
+        s[i] = (rand() % 2 ? -M_SQRT1_2 : M_SQRT1_2) +
+               (rand() % 2 ? -M_SQRT1_2 : M_SQRT1_2) * _Complex_I;
+    }
+
+    // design interpolating filter with 'dt' samples of delay
+    firinterp_crcf interp = firinterp_crcf_create_prototype(ftype,k,m,beta,dt);
+
+    // run interpolator
+    firinterp_crcf_execute_block(interp, s, num_symbols, x);
+
+    // destroy interpolator
+    firinterp_crcf_destroy(interp);
+
+    // create symbol synchronizer
+    symsync_crcf sync = symsync_crcf_create_rnyquist(ftype, k, m, beta, num_filters);
+    
+    // set bandwidth
+    symsync_crcf_set_lf_bw(sync,bandwidth);
+
+    // execute on entire block of samples
+    unsigned int ny=0;
+    symsync_crcf_execute(sync, x, num_samples, y, &ny);
+
+    // destroy synchronizer
+    symsync_crcf_destroy(sync);
+
+    // print last several symbols to screen
+    printf("output symbols:\n");
+    printf("  ...\n");
+    for (i=ny-10; i<ny; i++)
+        printf("  sym_out(%2u) = %8.4f + j*%8.4f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    //
+    // export output file
+    //
+
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"clear all;\n");
+
+    fprintf(fid,"ny=%u;\n",ny);
+
+    for (i=0; i<ny; i++)
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        
+    fprintf(fid,"i0 = 1:round(0.5*ny);\n");
+    fprintf(fid,"i1 = round(0.5*ny):ny;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(real(y(i0)),imag(y(i0)),'x','MarkerSize',4,'Color',[1 1 1]*0.7);\n");
+    fprintf(fid,"plot(real(y(i1)),imag(y(i1)),'o','MarkerSize',4,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"xlabel('In-phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"legend(['first 50%%'],['last 50%%'],'location','northeast');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/symsync_crcf_full_example.c b/examples/symsync_crcf_full_example.c
new file mode 100644
index 0000000..a53f336
--- /dev/null
+++ b/examples/symsync_crcf_full_example.c
@@ -0,0 +1,314 @@
+//
+// symsync_crcf_full_example.c
+//
+// This example extends that of `symsync_crcf_example.c` by including options
+// for simulating a timing rate offset in addition to just a timing phase
+// error. The resulting output file shows not just the constellation but the
+// time domain sequence as well as the timing phase estimate over time.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symsync_crcf_full_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("symsync_crcf_full_example [options]\n");
+    printf("  h     : print usage\n");
+    printf("  T     : filter type: [rrcos], rkaiser, arkaiser, hM3, gmsk\n");
+    printf("  k     : filter samples/symbol,   default: 2\n");
+    printf("  K     : output samples/symbol,   default: 2\n");
+    printf("  m     : filter delay (symbols),  default: 5\n");
+    printf("  b     : filter excess bandwidth, default: 0.5\n");
+    printf("  B     : filter polyphase banks,  default: 32\n");
+    printf("  s     : signal-to-noise ratio,   default: 30dB\n");
+    printf("  w     : timing pll bandwidth,    default: 0.02\n");
+    printf("  n     : number of symbols,       default: 400\n");
+    printf("  t     : timing phase offset [%% symbol], t in [-0.5,0.5], default: -0.2\n");
+    printf("  r     : timing freq. offset [%% symbol], default: 1.000\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int k           =   2;     // samples/symbol (input)
+    unsigned int k_out       =   2;     // samples/symbol (output)
+    unsigned int m           =   5;     // filter delay (symbols)
+    float        beta        =   0.5f;  // filter excess bandwidth factor
+    unsigned int num_filters =  32;     // number of filters in the bank
+    unsigned int num_symbols = 400;     // number of data symbols
+    float        SNRdB       =  30.0f;  // signal-to-noise ratio
+
+    // transmit/receive filter types
+    liquid_firfilt_type ftype_tx = LIQUID_FIRFILT_RRC;
+    liquid_firfilt_type ftype_rx = LIQUID_FIRFILT_RRC;
+
+    float bt    =  0.02f;       // loop filter bandwidth
+    float tau   = -0.2f;        // fractional symbol offset
+    float r     =    1.00f;     // resampled rate
+    
+    // use random data or 101010 phasing pattern
+    int random_data=1;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hT:k:K:m:b:B:s:w:n:t:r:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'T':
+            if (strcmp(optarg,"gmsk")==0) {
+                ftype_tx = LIQUID_FIRFILT_GMSKTX;
+                ftype_rx = LIQUID_FIRFILT_GMSKRX;
+            } else {
+                ftype_tx = liquid_getopt_str2firfilt(optarg);
+                ftype_rx = liquid_getopt_str2firfilt(optarg);
+            }
+            if (ftype_tx == LIQUID_FIRFILT_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'k':   k           = atoi(optarg);     break;
+        case 'K':   k_out       = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'B':   num_filters = atoi(optarg);     break;
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'w':   bt          = atof(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        case 'r':   r           = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: k (samples/symbol) must be at least 2\n");
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (num_filters == 0) {
+        fprintf(stderr,"error: number of polyphase filters must be greater than 0\n");
+        exit(1);
+    } else if (bt <= 0.0f) {
+        fprintf(stderr,"error: timing PLL bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: timing phase offset must be in [-1,1]\n");
+        exit(1);
+    } else if (r < 0.5f || r > 2.0f) {
+        fprintf(stderr,"error: timing frequency offset must be in [0.5,2]\n");
+        exit(1);
+    }
+
+    // compute delay
+    while (tau < 0) tau += 1.0f;    // ensure positive tau
+    float g = k*tau;                // number of samples offset
+    int ds=floorf(g);               // additional symbol delay
+    float dt = (g - (float)ds);     // fractional sample offset
+    if (dt > 0.5f) {                // force dt to be in [0.5,0.5]
+        dt -= 1.0f;
+        ds++;
+    }
+
+    unsigned int i, n=0;
+
+    unsigned int num_samples = k*num_symbols;
+    unsigned int num_samples_resamp = (unsigned int) ceilf(num_samples*r*1.1f) + 4;
+    float complex s[num_symbols];           // data symbols
+    float complex x[num_samples];           // interpolated samples
+    float complex y[num_samples_resamp];    // resampled data (resamp_crcf)
+    float complex z[k_out*num_symbols + 64];// synchronized samples
+    float complex sym_out[num_symbols + 64];// synchronized symbols
+
+    for (i=0; i<num_symbols; i++) {
+        if (random_data) {
+            // random signal (QPSK)
+            s[i]  = cexpf(_Complex_I*0.5f*M_PI*((rand() % 4) + 0.5f));
+        } else {
+            s[i] = (i%2) ? 1.0f : -1.0f;  // 101010 phasing pattern
+        }
+    }
+
+    // 
+    // create and run interpolator
+    //
+
+    // design interpolating filter
+    unsigned int h_len = 2*k*m+1;
+    float h[h_len];
+    liquid_firdes_prototype(ftype_tx,k,m,beta,dt,h);
+    firinterp_crcf q = firinterp_crcf_create(k,h,h_len);
+    for (i=0; i<num_symbols; i++) {
+        firinterp_crcf_execute(q, s[i], &x[n]);
+        n+=k;
+    }
+    assert(n == num_samples);
+    firinterp_crcf_destroy(q);
+
+    // 
+    // run resampler
+    //
+    unsigned int resamp_len = 10*k; // resampling filter semi-length (filter delay)
+    float resamp_bw = 0.45f;        // resampling filter bandwidth
+    float resamp_As = 60.0f;        // resampling filter stop-band attenuation
+    unsigned int resamp_npfb = 64;  // number of filters in bank
+    resamp_crcf f = resamp_crcf_create(r, resamp_len, resamp_bw, resamp_As, resamp_npfb);
+    unsigned int num_samples_resampled = 0;
+    unsigned int num_written;
+    for (i=0; i<num_samples; i++) {
+#if 0
+        // bypass arbitrary resampler
+        y[i] = x[i];
+        num_samples_resampled = num_samples;
+#else
+        // TODO : compensate for resampler filter delay
+        resamp_crcf_execute(f, x[i], &y[num_samples_resampled], &num_written);
+        num_samples_resampled += num_written;
+#endif
+    }
+    resamp_crcf_destroy(f);
+
+    // 
+    // add noise
+    //
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples_resampled; i++)
+        y[i] += nstd*(randnf() + _Complex_I*randnf());
+
+
+    // 
+    // create and run symbol synchronizer
+    //
+
+    symsync_crcf d = symsync_crcf_create_rnyquist(ftype_rx, k, m, beta, num_filters);
+    symsync_crcf_set_lf_bw(d,bt);
+    symsync_crcf_set_output_rate(d,k_out);
+
+    unsigned int num_samples_sync=0;
+    unsigned int nn;
+    unsigned int num_symbols_sync = 0;
+    float tau_hat[num_samples];
+    for (i=ds; i<num_samples_resampled; i++) {
+        tau_hat[num_samples_sync] = symsync_crcf_get_tau(d);
+        symsync_crcf_execute(d, &y[i], 1, &z[num_samples_sync], &nn);
+
+        // decimate
+        unsigned int j;
+        for (j=0; j<nn; j++) {
+            if ( (num_samples_sync%k_out)==0 )
+                sym_out[num_symbols_sync++] = z[num_samples_sync];
+            num_samples_sync++;
+        }
+    }
+    symsync_crcf_destroy(d);
+
+    // print last several symbols to screen
+    printf("output symbols:\n");
+    printf("  ...\n");
+    for (i=num_symbols_sync-10; i<num_symbols_sync; i++)
+        printf("  sym_out(%2u) = %8.4f + j*%8.4f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+    //
+    // export output file
+    //
+
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"k=%u;\n",k);
+    fprintf(fid,"m=%u;\n",m);
+    fprintf(fid,"beta=%12.8f;\n",beta);
+    fprintf(fid,"k_out=%u;\n",k_out);
+    fprintf(fid,"num_filters=%u;\n",num_filters);
+    fprintf(fid,"num_symbols=%u;\n",num_symbols);
+
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"h(%3u) = %12.5f;\n", i+1, h[i]);
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"s(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(s[i]), cimagf(s[i]));
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        
+    for (i=0; i<num_samples_resampled; i++)
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        
+    for (i=0; i<num_samples_sync; i++)
+        fprintf(fid,"z(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(z[i]), cimagf(z[i]));
+        
+    for (i=0; i<num_symbols_sync; i++)
+        fprintf(fid,"sym_out(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+        
+    for (i=0; i<num_samples_sync; i++)
+        fprintf(fid,"tau_hat(%3u) = %12.8f;\n", i+1, tau_hat[i]);
+
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% scale QPSK in-phase by sqrt(2)\n");
+    fprintf(fid,"z = z*sqrt(2);\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tz = [0:length(z)-1]/k_out;\n");
+    fprintf(fid,"iz = 1:k_out:length(z);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(tz,     real(z),    '-',...\n");
+    fprintf(fid,"     tz(iz), real(z(iz)),'or');\n");
+    fprintf(fid,"xlabel('Time');\n");
+    fprintf(fid,"ylabel('Output Signal (real)');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"legend('output time series','optimum timing','location','northeast');\n");
+
+    fprintf(fid,"iz0 = iz( 1:round(length(iz)*0.5) );\n");
+    fprintf(fid,"iz1 = iz( round(length(iz)*0.5):length(iz) );\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(real(z(iz0)),imag(z(iz0)),'x','MarkerSize',4,'Color',[0.6 0.6 0.6]);\n");
+    fprintf(fid,"plot(real(z(iz1)),imag(z(iz1)),'o','MarkerSize',4,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*2.0);\n");
+    fprintf(fid,"xlabel('In-phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"legend(['first 50%%'],['last 50%%'],'location','northeast');\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"tt = 0:(length(tau_hat)-1);\n");
+    fprintf(fid,"b = floor(num_filters*tau_hat + 0.5);\n");
+    fprintf(fid,"stairs(tt,tau_hat*num_filters);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(tt,b,'-k','Color',[0 0 0]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('filterbank index');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([0 length(tau_hat) -1 num_filters]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/symsync_crcf_kaiser_example.c b/examples/symsync_crcf_kaiser_example.c
new file mode 100644
index 0000000..8fb0b40
--- /dev/null
+++ b/examples/symsync_crcf_kaiser_example.c
@@ -0,0 +1,208 @@
+//
+// symsync_crcf_kaiser_example.c
+//
+// This is a simplified example of the symync family of objects to show how
+// symbol timing can be recovered after the matched filter output.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symsync_crcf_kaiser_example.m"
+
+// print usage/help message
+void usage(void);
+void usage(void)
+{
+    printf("symsync_crcf_example [options]\n");
+    printf(" -h            : print help\n");
+    printf(" -k <samp/sym> : filter samples/symbol,   default:   2\n");
+    printf(" -m <delay>    : filter delay (symbols),  default:   3\n");
+    printf(" -b <beta>     : filter excess bandwidth, default:   0.5\n");
+    printf(" -B <n>        : filter polyphase banks,  default:  32\n");
+    printf(" -s <snr>      : signal-to-noise ratio,   default:  30 dB\n");
+    printf(" -w <bw>       : timing pll bandwidth,    default:   0.02\n");
+    printf(" -n <n>        : number of symbols,       default: 400\n");
+    printf(" -t <tau>      : timing offset,           default:  -0.2\n");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int k           =   2;     // samples/symbol (input)
+    unsigned int m           =   3;     // filter delay (symbols)
+    float        beta        =   0.5f;  // filter excess bandwidth factor
+    unsigned int num_filters =  32;     // number of filters in the bank
+    float        SNRdB       =  30.0f;  // signal-to-noise ratio
+    float        bt          =   0.02f; // loop filter bandwidth
+    unsigned int num_symbols = 400;     // number of data symbols
+    float        tau         =  -0.20f; // fractional symbol offset
+
+    // Nyquist filter type
+    liquid_firfilt_type ftype = LIQUID_FIRFILT_KAISER;
+    
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:B:s:w:n:t:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'k':   k           = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'B':   num_filters = atoi(optarg);     break;
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'w':   bt          = atof(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: k (samples/symbol) must be at least 2\n");
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (num_filters == 0) {
+        fprintf(stderr,"error: number of polyphase filters must be greater than 0\n");
+        exit(1);
+    } else if (bt <= 0.0f) {
+        fprintf(stderr,"error: timing PLL bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: timing phase offset must be in [-1,1]\n");
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = k*num_symbols;
+    float complex x[num_samples];           // interpolated samples
+    float complex y[num_samples];           // received signal (with noise)
+    float         tau_hat[num_samples];     // instantaneous timing offset estimate
+    float complex sym_out[num_symbols + 64];// synchronized symbols
+
+    // create sequence of Nyquist-interpolated QPSK symbols
+    firinterp_crcf interp = firinterp_crcf_create_prototype(ftype,k,m,beta,tau);
+    for (i=0; i<num_symbols; i++) {
+        // generate random QPSK symbol
+        float complex s = ( rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2 ) +
+                          ( rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2 ) * _Complex_I;
+
+        // interpolate symbol
+        firinterp_crcf_execute(interp, s, &x[i*k]);
+    }
+    firinterp_crcf_destroy(interp);
+
+
+    // add noise
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf());
+
+
+    // create and run symbol synchronizer
+    symsync_crcf decim = symsync_crcf_create_kaiser(k, m, beta, num_filters);
+    symsync_crcf_set_lf_bw(decim,bt);   // set loop filter bandwidth
+
+    // NOTE: we could just synchronize entire block (see following line);
+    //       however we would like to save the instantaneous timing offset
+    //       estimate for plotting purposes
+    //symsync_crcf_execute(d, y, num_samples, sym_out, &num_symbols_sync);
+
+    unsigned int num_symbols_sync = 0;
+    unsigned int num_written=0;
+    for (i=0; i<num_samples; i++) {
+        // save instantaneous timing offset estimate
+        tau_hat[i] = symsync_crcf_get_tau(decim);
+
+        // execute one sample at a time
+        symsync_crcf_execute(decim, &y[i], 1, &sym_out[num_symbols_sync], &num_written);
+
+        // increment number of symbols synchronized
+        num_symbols_sync += num_written;
+    }
+    symsync_crcf_destroy(decim);
+
+    // print last several symbols to screen
+    printf("output symbols:\n");
+    for (i=num_symbols_sync-10; i<num_symbols_sync; i++)
+        printf("  sym_out(%2u) = %8.4f + j*%8.4f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+
+    //
+    // export output file
+    //
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"k=%u;\n",k);
+    fprintf(fid,"m=%u;\n",m);
+    fprintf(fid,"beta=%12.8f;\n",beta);
+    fprintf(fid,"num_filters=%u;\n",num_filters);
+    fprintf(fid,"num_symbols=%u;\n",num_symbols);
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"tau_hat(%3u) = %12.8f;\n", i+1, tau_hat[i]);
+        
+    for (i=0; i<num_symbols_sync; i++)
+        fprintf(fid,"sym_out(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+        
+    fprintf(fid,"i0 = 1:round( 0.5*num_symbols );\n");
+    fprintf(fid,"i1 = round( 0.5*num_symbols ):num_symbols;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(real(sym_out(i0)),imag(sym_out(i0)),'x','MarkerSize',4,'Color',[0.6 0.6 0.6]);\n");
+    fprintf(fid,"plot(real(sym_out(i1)),imag(sym_out(i1)),'o','MarkerSize',4,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"xlabel('In-phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"legend(['first 50%%'],['last 50%%'],1);\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"tt = 0:(length(tau_hat)-1);\n");
+    fprintf(fid,"b = floor(num_filters*tau_hat + 0.5);\n");
+    fprintf(fid,"stairs(tt,tau_hat*num_filters);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"plot(tt,b,'-k','Color',[0 0 0]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('filterbank index');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([0 length(tau_hat) -1 num_filters]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/symtrack_cccf_example.c b/examples/symtrack_cccf_example.c
new file mode 100644
index 0000000..5824f94
--- /dev/null
+++ b/examples/symtrack_cccf_example.c
@@ -0,0 +1,213 @@
+//
+// symtrack_cccf_example.c
+//
+// This example demonstrates how to recover data symbols using the symtrack
+// object. A stream of modulated and interpolated symbols are generated using
+// the symstream object. The resulting samples are passed through a channel
+// to add various impairments. The symtrack object recovers timing, carrier,
+// and other information imparted by the channel and returns data symbols
+// ready for demodulation.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symtrack_cccf_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("symtrack_cccf_example [options]\n");
+    printf("  h     : print this help file\n");
+    printf("  k     : filter samples/symbol,   default: 2\n");
+    printf("  m     : filter delay (symbols),  default: 3\n");
+    printf("  b     : filter excess bandwidth, default: 0.5\n");
+    printf("  s     : signal-to-noise ratio,   default: 30 dB\n");
+    printf("  w     : timing pll bandwidth,    default: 0.02\n");
+    printf("  n     : number of symbols,       default: 4000\n");
+    printf("  t     : timing phase offset [%% symbol], t in [-0.5,0.5], default: -0.2\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    int          ftype       = LIQUID_FIRFILT_ARKAISER;
+    int          ms          = LIQUID_MODEM_QPSK;
+    unsigned int k           = 2;       // samples per symbol
+    unsigned int m           = 7;       // filter delay (symbols)
+    float        beta        = 0.20f;   // filter excess bandwidth factor
+    unsigned int num_symbols = 4000;    // number of data symbols
+    unsigned int hc_len      =   4;     // channel filter length
+    float        noise_floor = -60.0f;  // noise floor [dB]
+    float        SNRdB       = 30.0f;   // signal-to-noise ratio [dB]
+    float        bandwidth   =  0.02f;  // loop filter bandwidth
+    float        tau         = -0.2f;   // fractional symbol offset
+    float        rate        = 1.001f;  // sample rate offset
+    float        dphi        =  0.01f;  // carrier frequency offset [radians/sample]
+    float        phi         =  2.1f;   // carrier phase offset [radians]
+
+    unsigned int nfft        =   2400;  // spectral periodogram FFT size
+    unsigned int num_samples = 200000;  // number of samples
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hk:m:b:s:w:n:t:r:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                        return 0;
+        case 'k':   k           = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'w':   bandwidth   = atof(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        case 'r':   rate        = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: k (samples/symbol) must be greater than 1\n");
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (bandwidth <= 0.0f) {
+        fprintf(stderr,"error: timing PLL bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: timing phase offset must be in [-1,1]\n");
+        exit(1);
+    } else if (rate > 1.02f || rate < 0.98f) {
+        fprintf(stderr,"error: timing rate offset must be in [1.02,0.98]\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // buffers
+    unsigned int    buf_len = 400;      // buffer size
+    float complex   x   [buf_len];      // original signal
+    float complex   y   [buf_len*2];    // channel output (larger to accommodate resampler)
+    float complex   syms[buf_len];      // recovered symbols
+    // window for saving last few symbols
+    windowcf sym_buf = windowcf_create(buf_len);
+
+    // create stream generator
+    symstreamcf gen = symstreamcf_create_linear(ftype,k,m,beta,ms);
+
+    // create channel emulator and add impairments
+    channel_cccf channel = channel_cccf_create();
+    channel_cccf_add_awgn          (channel, noise_floor, SNRdB);
+    channel_cccf_add_carrier_offset(channel, dphi, phi);
+    channel_cccf_add_multipath     (channel, NULL, hc_len);
+    channel_cccf_add_resamp        (channel, 0.0f, rate);
+
+    // create symbol tracking synchronizer
+    symtrack_cccf symtrack = symtrack_cccf_create(ftype,k,m,beta,ms);
+    symtrack_cccf_set_bandwidth(symtrack,0.05f);
+
+    // create spectral periodogram for estimating spectrum
+    spgramcf periodogram = spgramcf_create_default(nfft);
+
+    unsigned int total_samples = 0;
+    unsigned int ny;
+    unsigned int total_symbols = 0;
+    while (total_samples < num_samples)
+    {
+        // write samples to buffer
+        symstreamcf_write_samples(gen, x, buf_len);
+
+        // apply channel
+        channel_cccf_execute(channel, x, buf_len, y, &ny);
+
+        // push resulting sample through periodogram
+        spgramcf_write(periodogram, y, ny);
+
+        // run resulting stream through synchronizer
+        unsigned int num_symbols_sync;
+        symtrack_cccf_execute_block(symtrack, y, ny, syms, &num_symbols_sync);
+        total_symbols += num_symbols_sync;
+
+        // write resulting symbols to window buffer for plotting
+        windowcf_write(sym_buf, syms, num_symbols_sync);
+
+        // accumulated samples
+        total_samples += buf_len;
+    }
+    printf("total samples: %u\n", total_samples);
+    printf("total symbols: %u\n", total_symbols);
+
+    // write accumulated power spectral density estimate
+    float psd[nfft];
+    spgramcf_get_psd(periodogram, psd);
+
+    //
+    // export output file
+    //
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    // read buffer and write last symbols to file
+    float complex * rc;
+    windowcf_read(sym_buf, &rc);
+    fprintf(fid,"syms = zeros(1,%u);\n", buf_len);
+    for (i=0; i<buf_len; i++)
+        fprintf(fid,"syms(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // power spectral density estimate
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f=[0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%3u) = %12.8f;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure('Color','white','position',[500 500 1400 400]);\n");
+    fprintf(fid,"subplot(1,3,1);\n");
+    fprintf(fid,"plot(real(syms),imag(syms),'x','MarkerSize',4);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"  xlabel('In-phase');\n");
+    fprintf(fid,"  ylabel('Quadrature');\n");
+    fprintf(fid,"  title('Last %u symbols');\n", buf_len);
+    fprintf(fid,"subplot(1,3,2:3);\n");
+    fprintf(fid,"  plot(f, psd, 'LineWidth',1.5,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  pmin = 10*floor(0.1*min(psd - 5));\n");
+    fprintf(fid,"  pmax = 10*ceil (0.1*max(psd + 5));\n");
+    fprintf(fid,"  axis([-0.5 0.5 pmin pmax]);\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('Power Spectral Density [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // destroy objects
+    symstreamcf_destroy  (gen);
+    spgramcf_destroy     (periodogram);
+    channel_cccf_destroy (channel);
+    symtrack_cccf_destroy(symtrack);
+    windowcf_destroy     (sym_buf);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/examples/wdelayf_example.c b/examples/wdelayf_example.c
new file mode 100644
index 0000000..d4b1f24
--- /dev/null
+++ b/examples/wdelayf_example.c
@@ -0,0 +1,57 @@
+//
+// wdelayf_example.c
+//
+// SEE ALSO: bufferf_example.c
+//           windowf_example.c
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "wdelayf_example.m"
+
+int main() {
+    unsigned int delay = 10;
+    unsigned int num_samples = 64;
+
+    // create wdelay, all elements initialized to 0
+    wdelayf w = wdelayf_create(delay);
+    float y; // output
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"delay = %u;\n", delay);
+
+    // push several elements
+    unsigned int i;
+    float x;
+    for (i=0; i<num_samples; i++) {
+        if (i==0)   x = 1.0f;
+        else        x = 0.0f;
+
+        wdelayf_push(w, x);
+        wdelayf_read(w, &y);
+
+        printf("%4u : %12.8f\n", i, y);
+        fprintf(fid,"x(%4u) = %12.8f; y(%4u) = %12.8f;\n", i+1, x, i+1, y);
+    }
+    wdelayf_print(w);
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,x,t,y);\n");
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    wdelayf_destroy(w);
+
+    printf("done.\n");
+    return 0;
+}
+
+
diff --git a/examples/windowf_example.c b/examples/windowf_example.c
new file mode 100644
index 0000000..ba0522d
--- /dev/null
+++ b/examples/windowf_example.c
@@ -0,0 +1,63 @@
+//
+// windowf_example.c
+//
+// This example demonstrates the functionality of a window buffer (also
+// known as a circular or ring buffer) of floating-point values.  Values
+// are written to and read from the buffer using several different
+// methods.
+//
+// SEE ALSO: bufferf_example.c
+//           wdelayf_example.c
+//
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+int main() {
+    // initialize vector of data for testing
+    float v[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+    float *r; // reader
+    unsigned int i;
+
+    // create window: 10 elements, initialized to 0
+    // w: 0 0 0 0 0 0 0 0 0 0
+    windowf w = windowf_create(10);
+
+    // push 4 elements
+    // w: 0 0 0 0 0 0 1 1 1 1
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+
+    // push 4 more elements
+    // w: 0 0 1 1 1 1 9 8 7 6
+    windowf_write(w, v, 4);
+
+    // push 4 more elements
+    // w: 1 1 9 8 7 6 3 3 3 3
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+
+    // read the buffer by assigning the pointer
+    // appropriately
+    windowf_read(w, &r);
+
+    // manual print
+    printf("manual output:\n");
+    for (i=0; i<10; i++)
+        printf("%6u : %f\n", i, r[i]);
+
+    windowf_debug_print(w);
+
+    // clean it up
+    windowf_destroy(w);
+
+    printf("done.\n");
+    return 0;
+}
+
+
diff --git a/include/liquid.h b/include/liquid.h
new file mode 100644
index 0000000..0a08bab
--- /dev/null
+++ b/include/liquid.h
@@ -0,0 +1,6867 @@
+/*
+ * Copyright (c) 2007 - 2016 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef __LIQUID_H__
+#define __LIQUID_H__
+
+#ifdef __cplusplus
+extern "C" {
+#   define LIQUID_USE_COMPLEX_H 0
+#else
+#   define LIQUID_USE_COMPLEX_H 1
+#endif // __cplusplus
+
+// common headers
+#include <inttypes.h>
+
+//
+// Make sure the version and version number macros weren't defined by
+// some prevoiusly included header file.
+//
+#ifdef LIQUID_VERSION
+#  undef LIQUID_VERSION
+#endif
+#ifdef LIQUID_VERSION_NUMBER
+#  undef LIQUID_VERSION_NUMBER
+#endif
+
+//
+// Compile-time version numbers
+// 
+// LIQUID_VERSION = "X.Y.Z"
+// LIQUID_VERSION_NUMBER = (X*1000000 + Y*1000 + Z)
+//
+#define LIQUID_VERSION          "1.2.0"
+#define LIQUID_VERSION_NUMBER   1002000
+
+//
+// Run-time library version numbers
+//
+extern const char liquid_version[];
+const char * liquid_libversion(void);
+int liquid_libversion_number(void);
+
+// run-time library validation
+#define LIQUID_VALIDATE_LIBVERSION                              \
+  if (LIQUID_VERSION_NUMBER != liquid_libversion_number()) {    \
+    fprintf(stderr,"%s:%u: ", __FILE__,__LINE__);               \
+    fprintf(stderr,"error: invalid liquid runtime library\n");  \
+    exit(1);                                                    \
+  }                                                             \
+
+#define LIQUID_CONCAT(prefix, name) prefix ## name
+#define LIQUID_VALIDATE_INPUT
+
+/* 
+ * Compile-time complex data type definitions
+ *
+ * Default: use the C99 complex data type, otherwise
+ * define complex type compatible with the C++ complex standard,
+ * otherwise resort to defining binary compatible array.
+ */
+#if LIQUID_USE_COMPLEX_H==1
+#   include <complex.h>
+#   define LIQUID_DEFINE_COMPLEX(R,C) typedef R _Complex C
+#elif defined _GLIBCXX_COMPLEX || defined _LIBCPP_COMPLEX
+#   define LIQUID_DEFINE_COMPLEX(R,C) typedef std::complex<R> C
+#else
+#   define LIQUID_DEFINE_COMPLEX(R,C) typedef struct {R real; R imag;} C;
+#endif
+//#   define LIQUID_DEFINE_COMPLEX(R,C) typedef R C[2]
+
+LIQUID_DEFINE_COMPLEX(float,  liquid_float_complex);
+LIQUID_DEFINE_COMPLEX(double, liquid_double_complex);
+
+// 
+// MODULE : agc (automatic gain control)
+//
+
+#define AGC_MANGLE_CRCF(name)   LIQUID_CONCAT(agc_crcf, name)
+#define AGC_MANGLE_RRRF(name)   LIQUID_CONCAT(agc_rrrf, name)
+
+// large macro
+//   AGC    : name-mangling macro
+//   T      : primitive data type
+//   TC     : input/output data type
+#define LIQUID_AGC_DEFINE_API(AGC,T,TC)                         \
+typedef struct AGC(_s) * AGC();                                 \
+                                                                \
+/* create automatic gain control object                     */  \
+AGC() AGC(_create)(void);                                       \
+                                                                \
+/* destroy object, freeing all internally-allocated memory  */  \
+void AGC(_destroy)(AGC() _q);                                   \
+                                                                \
+/* print object properties to stdout                        */  \
+void AGC(_print)(AGC() _q);                                     \
+                                                                \
+/* reset object's internal state                            */  \
+void AGC(_reset)(AGC() _q);                                     \
+                                                                \
+/* execute automatic gain control on an single input sample */  \
+/*  _q      : automatic gain control object                 */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample                                 */  \
+void AGC(_execute)(AGC() _q,                                    \
+                   TC    _x,                                    \
+                   TC *  _y);                                   \
+                                                                \
+/* execute automatic gain control on block of samples       */  \
+/*  _q      : automatic gain control object                 */  \
+/*  _x      : input data array, [size: _n x 1]              */  \
+/*  _n      : number of input, output samples               */  \
+/*  _y      : output data array, [size: _n x 1]             */  \
+void AGC(_execute_block)(AGC()        _q,                       \
+                         TC *         _x,                       \
+                         unsigned int _n,                       \
+                         TC *         _y);                      \
+                                                                \
+/* lock/unlock gain control */                                  \
+void AGC(_lock)(  AGC() _q);                                    \
+void AGC(_unlock)(AGC() _q);                                    \
+                                                                \
+/* get/set loop filter bandwidth; attack/release time       */  \
+float AGC(_get_bandwidth)(AGC() _q);                            \
+void  AGC(_set_bandwidth)(AGC() _q, float _bt);                 \
+                                                                \
+/* get/set signal level (linear) relative to unity energy   */  \
+float AGC(_get_signal_level)(AGC() _q);                         \
+void  AGC(_set_signal_level)(AGC() _q, float _signal_level);    \
+                                                                \
+/* get/set signal level (dB) relative to unity energy       */  \
+float AGC(_get_rssi)(AGC() _q);                                 \
+void  AGC(_set_rssi)(AGC() _q, float _rssi);                    \
+                                                                \
+/* get/set gain value (linear) relative to unity energy     */  \
+float AGC(_get_gain)(AGC() _q);                                 \
+void  AGC(_set_gain)(AGC() _q, float _gain);                    \
+                                                                \
+/* initialize internal gain on input array                  */  \
+/*  _q      : automatic gain control object                 */  \
+/*  _x      : input data array, [size: _n x 1]              */  \
+/*  _n      : number of input, output samples               */  \
+void AGC(_init)(AGC()        _q,                                \
+                TC *         _x,                                \
+                unsigned int _n);                               \
+
+// Define agc APIs
+LIQUID_AGC_DEFINE_API(AGC_MANGLE_CRCF, float, liquid_float_complex)
+LIQUID_AGC_DEFINE_API(AGC_MANGLE_RRRF, float, float)
+
+
+
+//
+// MODULE : audio
+//
+
+// CVSD: continuously variable slope delta
+typedef struct cvsd_s * cvsd;
+
+// create cvsd object
+//  _num_bits   :   number of adjacent bits to observe (4 recommended)
+//  _zeta       :   slope adjustment multiplier (1.5 recommended)
+//  _alpha      :   pre-/post-emphasis filter coefficient (0.9 recommended)
+// NOTE: _alpha must be in [0,1]
+cvsd cvsd_create(unsigned int _num_bits,
+                 float _zeta,
+                 float _alpha);
+
+// destroy cvsd object
+void cvsd_destroy(cvsd _q);
+
+// print cvsd object parameters
+void cvsd_print(cvsd _q);
+
+// encode/decode single sample
+unsigned char   cvsd_encode(cvsd _q, float _audio_sample);
+float           cvsd_decode(cvsd _q, unsigned char _bit);
+
+// encode/decode 8 samples at a time
+void cvsd_encode8(cvsd _q, float * _audio, unsigned char * _data);
+void cvsd_decode8(cvsd _q, unsigned char _data, float * _audio);
+
+
+//
+// MODULE : buffer
+//
+
+// circular buffer
+#define CBUFFER_MANGLE_FLOAT(name)  LIQUID_CONCAT(cbufferf,  name)
+#define CBUFFER_MANGLE_CFLOAT(name) LIQUID_CONCAT(cbuffercf, name)
+
+// large macro
+//   CBUFFER : name-mangling macro
+//   T       : data type
+#define LIQUID_CBUFFER_DEFINE_API(CBUFFER,T)                    \
+typedef struct CBUFFER(_s) * CBUFFER();                         \
+                                                                \
+/* create circular buffer object of a particular size       */  \
+CBUFFER() CBUFFER(_create)(unsigned int _max_size);             \
+                                                                \
+/* create circular buffer object of a particular size and   */  \
+/* specify the maximum number of elements that can be read  */  \
+/* at any given time.                                       */  \
+CBUFFER() CBUFFER(_create_max)(unsigned int _max_size,          \
+                               unsigned int _max_read);         \
+                                                                \
+/* destroy cbuffer object, freeing all internal memory      */  \
+void CBUFFER(_destroy)(CBUFFER() _q);                           \
+                                                                \
+/* print cbuffer object properties                          */  \
+void CBUFFER(_print)(CBUFFER() _q);                             \
+                                                                \
+/* print cbuffer object properties and internal state       */  \
+void CBUFFER(_debug_print)(CBUFFER() _q);                       \
+                                                                \
+/* clear internal buffer                                    */  \
+void CBUFFER(_clear)(CBUFFER() _q);                             \
+                                                                \
+/* get the number of elements currently in the buffer       */  \
+unsigned int CBUFFER(_size)(CBUFFER() _q);                      \
+                                                                \
+/* get the maximum number of elements the buffer can hold   */  \
+unsigned int CBUFFER(_max_size)(CBUFFER() _q);                  \
+                                                                \
+/* get the maximum number of elements you may read at once  */  \
+unsigned int CBUFFER(_max_read)(CBUFFER() _q);                  \
+                                                                \
+/* get the number of available slots (max_size - size)      */  \
+unsigned int CBUFFER(_space_available)(CBUFFER() _q);           \
+                                                                \
+/* is buffer full?                                          */  \
+int CBUFFER(_is_full)(CBUFFER() _q);                            \
+                                                                \
+/* write a single sample into the buffer                    */  \
+/*  _q  : circular buffer object                            */  \
+/*  _v  : input sample                                      */  \
+void CBUFFER(_push)(CBUFFER() _q,                               \
+                    T         _v);                              \
+                                                                \
+/* write samples to the buffer                              */  \
+/*  _q  : circular buffer object                            */  \
+/*  _v  : output array                                      */  \
+/*  _n  : number of samples to write                        */  \
+void CBUFFER(_write)(CBUFFER()    _q,                           \
+                     T *          _v,                           \
+                     unsigned int _n);                          \
+                                                                \
+/* remove and return a single element from the buffer       */  \
+/*  _q  : circular buffer object                            */  \
+/*  _v  : pointer to sample output                          */  \
+void CBUFFER(_pop)(CBUFFER() _q,                                \
+                   T *       _v);                               \
+                                                                \
+/* read buffer contents                                     */  \
+/*  _q              : circular buffer object                */  \
+/*  _num_requested  : number of elements requested          */  \
+/*  _v              : output pointer                        */  \
+/*  _nr             : number of elements referenced by _v   */  \
+void CBUFFER(_read)(CBUFFER()      _q,                          \
+                    unsigned int   _num_requested,              \
+                    T **           _v,                          \
+                    unsigned int * _num_read);                  \
+                                                                \
+/* release _n samples from the buffer                       */  \
+void CBUFFER(_release)(CBUFFER()    _q,                         \
+                       unsigned int _n);                        \
+
+// Define buffer APIs
+LIQUID_CBUFFER_DEFINE_API(CBUFFER_MANGLE_FLOAT,  float)
+LIQUID_CBUFFER_DEFINE_API(CBUFFER_MANGLE_CFLOAT, liquid_float_complex)
+
+
+
+// Windowing functions
+#define WINDOW_MANGLE_FLOAT(name)  LIQUID_CONCAT(windowf,  name)
+#define WINDOW_MANGLE_CFLOAT(name) LIQUID_CONCAT(windowcf, name)
+
+// large macro
+//   WINDOW : name-mangling macro
+//   T      : data type
+#define LIQUID_WINDOW_DEFINE_API(WINDOW,T)                      \
+                                                                \
+typedef struct WINDOW(_s) * WINDOW();                           \
+                                                                \
+/* create window buffer object of length _n                 */  \
+WINDOW() WINDOW(_create)(unsigned int _n);                      \
+                                                                \
+/* recreate window buffer object with new length            */  \
+/*  _q      : old window object                             */  \
+/*  _n      : new window length                             */  \
+WINDOW() WINDOW(_recreate)(WINDOW() _q, unsigned int _n);       \
+                                                                \
+/* destroy window object, freeing all internally memory     */  \
+void WINDOW(_destroy)(WINDOW() _q);                             \
+                                                                \
+/* print window object to stdout                            */  \
+void WINDOW(_print)(WINDOW() _q);                               \
+                                                                \
+/* print window object to stdout (with extra information)   */  \
+void WINDOW(_debug_print)(WINDOW() _q);                         \
+                                                                \
+/* clear/reset window object (initialize to zeros)          */  \
+void WINDOW(_clear)(WINDOW() _q);                               \
+                                                                \
+/* read window buffer contents                              */  \
+/*  _q      : window object                                 */  \
+/*  _v      : output pointer (set to internal array)        */  \
+void WINDOW(_read)(WINDOW() _q, T ** _v);                       \
+                                                                \
+/* index single element in buffer at a particular index     */  \
+/*  _q      : window object                                 */  \
+/*  _i      : index of element to read                      */  \
+/*  _v      : output value pointer                          */  \
+void WINDOW(_index)(WINDOW()     _q,                            \
+                    unsigned int _i,                            \
+                    T *          _v);                           \
+                                                                \
+/* push single element onto window buffer                   */  \
+/*  _q      : window object                                 */  \
+/*  _v      : single input element                          */  \
+void WINDOW(_push)(WINDOW() _q,                                 \
+                   T        _v);                                \
+                                                                \
+/* write array of elements onto window buffer               */  \
+/*  _q      : window object                                 */  \
+/*  _v      : input array of values to write                */  \
+/*  _n      : number of input values to write               */  \
+void WINDOW(_write)(WINDOW()     _q,                            \
+                    T *          _v,                            \
+                    unsigned int _n);                           \
+
+// Define window APIs
+LIQUID_WINDOW_DEFINE_API(WINDOW_MANGLE_FLOAT,  float)
+LIQUID_WINDOW_DEFINE_API(WINDOW_MANGLE_CFLOAT, liquid_float_complex)
+//LIQUID_WINDOW_DEFINE_API(WINDOW_MANGLE_UINT,   unsigned int)
+
+
+// wdelay functions : windowed-delay
+// Implements an efficient z^-k delay with minimal memory
+#define WDELAY_MANGLE_FLOAT(name)   LIQUID_CONCAT(wdelayf,  name)
+#define WDELAY_MANGLE_CFLOAT(name)  LIQUID_CONCAT(wdelaycf, name)
+#define WDELAY_MANGLE_UINT(name)    LIQUID_CONCAT(wdelayui, name)
+
+// large macro
+//   WDELAY : name-mangling macro
+//   T      : data type
+#define LIQUID_WDELAY_DEFINE_API(WDELAY,T)                      \
+typedef struct WDELAY(_s) * WDELAY();                           \
+                                                                \
+/* create delay buffer object with '_delay' samples         */  \
+WDELAY() WDELAY(_create)(unsigned int _delay);                  \
+                                                                \
+/* re-create delay buffer object with '_delay' samples      */  \
+/*  _q      :   old delay buffer object                     */  \
+/*  _delay  :   delay for new object                        */  \
+WDELAY() WDELAY(_recreate)(WDELAY()     _q,                     \
+                           unsigned int _delay);                \
+                                                                \
+/* destroy delay buffer object, freeing internal memory     */  \
+void WDELAY(_destroy)(WDELAY() _q);                             \
+                                                                \
+/* print delay buffer object's state to stdout              */  \
+void WDELAY(_print)(WDELAY() _q);                               \
+                                                                \
+/* clear/reset state of object                              */  \
+void WDELAY(_clear)(WDELAY() _q);                               \
+                                                                \
+/* read delayed sample from delay buffer object             */  \
+/*  _q  :   delay buffer object                             */  \
+/*  _v  :   value of delayed element                        */  \
+void WDELAY(_read)(WDELAY() _q,                                 \
+                   T *      _v);                                \
+                                                                \
+/* push new sample into delay buffer object                 */  \
+/*  _q  :   delay buffer object                             */  \
+/*  _v  :   new value to be added to buffer                 */  \
+void WDELAY(_push)(WDELAY() _q,                                 \
+                   T        _v);                                \
+
+// Define wdelay APIs
+LIQUID_WDELAY_DEFINE_API(WDELAY_MANGLE_FLOAT,  float)
+LIQUID_WDELAY_DEFINE_API(WDELAY_MANGLE_CFLOAT, liquid_float_complex)
+//LIQUID_WDELAY_DEFINE_API(WDELAY_MANGLE_UINT,   unsigned int)
+
+
+
+//
+// MODULE : channel
+//
+
+#define CHANNEL_MANGLE_CCCF(name)   LIQUID_CONCAT(channel_cccf,name)
+
+// large macro
+//   CHANNEL    : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_CHANNEL_DEFINE_API(CHANNEL,TO,TC,TI)             \
+                                                                \
+typedef struct CHANNEL(_s) * CHANNEL();                         \
+                                                                \
+/* create channel object with default parameters            */  \
+CHANNEL() CHANNEL(_create)(void);                               \
+                                                                \
+/* create channel object with particular delay              */  \
+/*  _m  :   resampling filter semi-length                   */  \
+CHANNEL() CHANNEL(_create_delay)(unsigned int _m);              \
+                                                                \
+/* destroy channel object, freeing all internal memory      */  \
+void CHANNEL(_destroy)(CHANNEL() _q);                           \
+                                                                \
+/* print channel object internals to standard output        */  \
+void CHANNEL(_print)(CHANNEL() _q);                             \
+                                                                \
+/* apply additive white Gausss noise impairment             */  \
+/*  _q              : channel object                        */  \
+/*  _noise_floor_dB : noise floor power spectral density    */  \
+/*  _SNR_dB         : signal-to-noise ratio [dB]            */  \
+void CHANNEL(_add_awgn)(CHANNEL() _q,                           \
+                        float     _noise_floor_dB,              \
+                        float     _SNRdB);                      \
+                                                                \
+/* apply additive white Gausss noise impairment             */  \
+/*  _q              : channel object                        */  \
+/*  _delay          : resampling delay                      */  \
+/*  _rate           : resampling rate                       */  \
+void CHANNEL(_add_resamp)(CHANNEL() _q,                         \
+                          float     _delay,                     \
+                          float     _rate);                     \
+                                                                \
+/* apply carrier offset impairment                          */  \
+/*  _q          : channel object                            */  \
+/*  _frequency  : carrier frequency offse [radians/sample   */  \
+/*  _phase      : carrier phase offset    [radians]         */  \
+void CHANNEL(_add_carrier_offset)(CHANNEL() _q,                 \
+                                  float     _frequency,         \
+                                  float     _phase);            \
+                                                                \
+/* apply multi-path channel impairment                      */  \
+/*  _q          : channel object                            */  \
+/*  _h          : channel coefficients (NULL for random)    */  \
+/*  _h_len      : number of channel coefficients            */  \
+void CHANNEL(_add_multipath)(CHANNEL()    _q,                   \
+                             TC *         _h,                   \
+                             unsigned int _h_len);              \
+                                                                \
+/* apply slowly-varying shadowing impairment                */  \
+/*  _q          : channel object                            */  \
+/*  _sigma      : std. deviation for log-normal shadowing   */  \
+/*  _fd         : Doppler frequency, _fd in (0,0.5)         */  \
+void CHANNEL(_add_shadowing)(CHANNEL()    _q,                   \
+                             float        _sigma,               \
+                             float        _fd);                 \
+                                                                \
+/* get nominal delay [samples]                              */  \
+unsigned int CHANNEL(_get_delay)(CHANNEL() _q);                 \
+                                                                \
+/* apply channel impairments on input array                 */  \
+/*  _q      : channel object                                */  \
+/*  _x      : input array [size: _nx x 1]                   */  \
+/*  _nx     : input array length                            */  \
+/*  _y      : output array                                  */  \
+/*  _ny     : output array length                           */  \
+void CHANNEL(_execute)(CHANNEL()      _q,                       \
+                       TI *           _x,                       \
+                       unsigned int   _nx,                      \
+                       TO *           _y,                       \
+                       unsigned int * _ny);                     \
+
+LIQUID_CHANNEL_DEFINE_API(CHANNEL_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+
+//
+// time-varying multi-path channel
+//
+#define TVMPCH_MANGLE_CCCF(name)    LIQUID_CONCAT(tvmpch_cccf,name)
+
+// large macro
+//   TVMPCH    : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_TVMPCH_DEFINE_API(TVMPCH,TO,TC,TI)               \
+                                                                \
+typedef struct TVMPCH(_s) * TVMPCH();                           \
+                                                                \
+/* create channel object with default parameters            */  \
+/* create time-varying multi-path channel emulator object   */  \
+/*  _n      :   number of coefficients, _n > 0              */  \
+/*  _std    :   standard deviation                          */  \
+/*  _tau    :   coherence time                              */  \
+TVMPCH() TVMPCH(_create)(unsigned int _n,                       \
+                         float        _std,                     \
+                         float        _tau);                    \
+                                                                \
+/* destroy channel object, freeing all internal memory      */  \
+void TVMPCH(_destroy)(TVMPCH() _q);                             \
+                                                                \
+/* reset object                                             */  \
+void TVMPCH(_reset)(TVMPCH() _q);                               \
+                                                                \
+/* print channel object internals to standard output        */  \
+void TVMPCH(_print)(TVMPCH() _q);                               \
+                                                                \
+/* push sample into emulator                                */  \
+/*  _q      : channel object                                */  \
+/*  _x      : input sample                                  */  \
+void TVMPCH(_push)(TVMPCH() _q,                                 \
+                   TI       _x);                                \
+                                                                \
+/* compute output sample                                    */  \
+/*  _q      : channel object                                */  \
+/*  _y      : output sample                                 */  \
+void TVMPCH(_execute)(TVMPCH()      _q,                         \
+                      TO *          _y);                        \
+                                                                \
+/* apply channel impairments on a block of samples          */  \
+/*  _q      : channel object                                */  \
+/*  _x      : input array [size: _nx x 1]                   */  \
+/*  _nx     : input array length                            */  \
+/*  _y      : output array                                  */  \
+void TVMPCH(_execute_block)(TVMPCH()     _q,                    \
+                            TI *         _x,                    \
+                            unsigned int _nx,                   \
+                            TO *         _y);                   \
+
+LIQUID_TVMPCH_DEFINE_API(TVMPCH_MANGLE_CCCF,
+                         liquid_float_complex,
+                         liquid_float_complex,
+                         liquid_float_complex)
+
+
+//
+// MODULE : dotprod (vector dot product)
+//
+
+#define DOTPROD_MANGLE_RRRF(name)   LIQUID_CONCAT(dotprod_rrrf,name)
+#define DOTPROD_MANGLE_CCCF(name)   LIQUID_CONCAT(dotprod_cccf,name)
+#define DOTPROD_MANGLE_CRCF(name)   LIQUID_CONCAT(dotprod_crcf,name)
+
+// large macro
+//   DOTPROD    : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_DOTPROD_DEFINE_API(DOTPROD,TO,TC,TI)             \
+                                                                \
+/* run dot product without creating object [unrolled loop]  */  \
+/*  _v      : coefficients array [size: _n x 1]             */  \
+/*  _x      : input array [size: _n x 1]                    */  \
+/*  _n      : dotprod length, _n > 0                        */  \
+/*  _y      : output sample pointer                         */  \
+void DOTPROD(_run)( TC *_v, TI *_x, unsigned int _n, TO *_y);   \
+void DOTPROD(_run4)(TC *_v, TI *_x, unsigned int _n, TO *_y);   \
+                                                                \
+typedef struct DOTPROD(_s) * DOTPROD();                         \
+                                                                \
+/* create dot product object                                */  \
+/*  _v      : coefficients array [size: _n x 1]             */  \
+/*  _n      : dotprod length, _n > 0                        */  \
+DOTPROD() DOTPROD(_create)(TC *         _v,                     \
+                           unsigned int _n);                    \
+                                                                \
+/* re-create dot product object                             */  \
+/*  _q      : old dotprod object                            */  \
+/*  _v      : coefficients array [size: _n x 1]             */  \
+/*  _n      : dotprod length, _n > 0                        */  \
+DOTPROD() DOTPROD(_recreate)(DOTPROD()    _q,                   \
+                             TC *         _v,                   \
+                             unsigned int _n);                  \
+                                                                \
+/* destroy dotprod object, freeing all internal memory      */  \
+void DOTPROD(_destroy)(DOTPROD() _q);                           \
+                                                                \
+/* print dotprod object internals to standard output        */  \
+void DOTPROD(_print)(DOTPROD() _q);                             \
+                                                                \
+/* execute dot product                                      */  \
+/*  _q      : dotprod object                                */  \
+/*  _x      : input array [size: _n x 1]                    */  \
+/*  _y      : output sample pointer                         */  \
+void DOTPROD(_execute)(DOTPROD() _q,                            \
+                       TI *      _x,                            \
+                       TO *      _y);                           \
+
+LIQUID_DOTPROD_DEFINE_API(DOTPROD_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_DOTPROD_DEFINE_API(DOTPROD_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+LIQUID_DOTPROD_DEFINE_API(DOTPROD_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+// 
+// sum squared methods
+//
+
+float liquid_sumsqf(float *      _v,
+                    unsigned int _n);
+
+float liquid_sumsqcf(liquid_float_complex * _v,
+                     unsigned int           _n);
+
+
+//
+// MODULE : equalization
+//
+
+// least mean-squares (LMS)
+#define EQLMS_MANGLE_RRRF(name)     LIQUID_CONCAT(eqlms_rrrf,name)
+#define EQLMS_MANGLE_CCCF(name)     LIQUID_CONCAT(eqlms_cccf,name)
+
+// large macro
+//   EQLMS  : name-mangling macro
+//   T      : data type
+#define LIQUID_EQLMS_DEFINE_API(EQLMS,T)                        \
+typedef struct EQLMS(_s) * EQLMS();                             \
+                                                                \
+/* create LMS EQ initialized with external coefficients     */  \
+/*  _h      : filter coefficients (NULL for {1,0,0...})     */  \
+/*  _h_len  : filter length                                 */  \
+EQLMS() EQLMS(_create)(T *          _h,                         \
+                       unsigned int _h_len);                    \
+                                                                \
+/* create LMS EQ initialized with square-root Nyquist       */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _k      : samples/symbol                                */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : rolloff factor (0 < beta <= 1)                */  \
+/*  _dt     : fractional sample delay                       */  \
+EQLMS() EQLMS(_create_rnyquist)(int          _type,             \
+                                unsigned int _k,                \
+                                unsigned int _m,                \
+                                float        _beta,             \
+                                float        _dt);              \
+                                                                \
+/* create LMS EQ initialized with low-pass filter           */  \
+/*  _h_len  : filter length                                 */  \
+/*  _fc     : filter cut-off, _fc in (0,0.5]                */  \
+EQLMS() EQLMS(_create_lowpass)(unsigned int _h_len,             \
+                               float        _fc);               \
+                                                                \
+/* re-create EQ initialized with external coefficients      */  \
+/*  _q      :   equalizer object                            */  \
+/*  _h      :   filter coefficients (NULL for {1,0,0...})   */  \
+/*  _h_len  :   filter length                               */  \
+EQLMS() EQLMS(_recreate)(EQLMS()      _q,                       \
+                         T *          _h,                       \
+                         unsigned int _h_len);                  \
+                                                                \
+/* destroy equalizer object, freeing all internal memory    */  \
+void EQLMS(_destroy)(EQLMS() _q);                               \
+                                                                \
+/* reset equalizer object, clearing internal state          */  \
+void EQLMS(_reset)(EQLMS() _q);                                 \
+                                                                \
+/* print equalizer internal state                           */  \
+void EQLMS(_print)(EQLMS() _q);                                 \
+                                                                \
+/* get/set equalizer learning rate                          */  \
+float EQLMS(_get_bw)(EQLMS() _q);                               \
+void  EQLMS(_set_bw)(EQLMS() _q,                                \
+                     float   _lambda);                          \
+                                                                \
+/* push sample into equalizer internal buffer               */  \
+void EQLMS(_push)(EQLMS() _q,                                   \
+                  T       _x);                                  \
+                                                                \
+/* push sample into equalizer internal buffer as block      */  \
+/*  _q      :   equalizer object                            */  \
+/*  _x      :   input sample array                          */  \
+/*  _n      :   input sample array length                   */  \
+void EQLMS(_push_block)(EQLMS()      _q,                        \
+                        T *          _x,                        \
+                        unsigned int _n);                       \
+                                                                \
+/* execute internal dot product and return result           */  \
+/*  _q      :   equalizer object                            */  \
+/*  _y      :   output sample                               */  \
+void EQLMS(_execute)(EQLMS() _q,                                \
+                     T *     _y);                               \
+                                                                \
+/* execute equalizer with block of samples using constant   */  \
+/* modulus algorithm, operating on a decimation rate of _k  */  \
+/* samples.                                                 */  \
+/*  _q      :   equalizer object                            */  \
+/*  _k      :   down-sampling rate                          */  \
+/*  _x      :   input sample array [size: _n x 1]           */  \
+/*  _n      :   input sample array length                   */  \
+/*  _y      :   output sample array [size: _n x 1]          */  \
+void EQLMS(_execute_block)(EQLMS()      _q,                     \
+                           unsigned int _k,                     \
+                           T *          _x,                     \
+                           unsigned int _n,                     \
+                           T *          _y);                    \
+                                                                \
+/* step through one cycle of equalizer training             */  \
+/*  _q      :   equalizer object                            */  \
+/*  _d      :   desired output                              */  \
+/*  _d_hat  :   actual output                               */  \
+void EQLMS(_step)(EQLMS() _q,                                   \
+                  T       _d,                                   \
+                  T       _d_hat);                              \
+                                                                \
+/* step through one cycle of equalizer training (blind)     */  \
+/*  _q      :   equalizer object                            */  \
+/*  _d_hat  :   actual output                               */  \
+void EQLMS(_step_blind)(EQLMS() _q,                             \
+                        T       _d_hat);                        \
+                                                                \
+/* get equalizer's internal coefficients                    */  \
+/*  _q      :   equalizer object                            */  \
+/*  _w      :   weights [size: _p x 1]                      */  \
+void EQLMS(_get_weights)(EQLMS() _q,                            \
+                         T *     _w);                           \
+                                                                \
+/* train equalizer object on group of samples               */  \
+/*  _q      :   equalizer object                            */  \
+/*  _w      :   input/output weights   [size: _p x 1]       */  \
+/*  _x      :   received sample vector [size: _n x 1]       */  \
+/*  _d      :   desired output vector  [size: _n x 1]       */  \
+/*  _n      :   input, output vector length                 */  \
+void EQLMS(_train)(EQLMS()      _q,                             \
+                   T *          _w,                             \
+                   T *          _x,                             \
+                   T *          _d,                             \
+                   unsigned int _n);                            \
+
+LIQUID_EQLMS_DEFINE_API(EQLMS_MANGLE_RRRF, float);
+LIQUID_EQLMS_DEFINE_API(EQLMS_MANGLE_CCCF, liquid_float_complex);
+
+
+// recursive least-squares (RLS)
+#define EQRLS_MANGLE_RRRF(name)     LIQUID_CONCAT(eqrls_rrrf,name)
+#define EQRLS_MANGLE_CCCF(name)     LIQUID_CONCAT(eqrls_cccf,name)
+
+// large macro
+//   EQRLS  : name-mangling macro
+//   T      : data type
+#define LIQUID_EQRLS_DEFINE_API(EQRLS,T)                        \
+typedef struct EQRLS(_s) * EQRLS();                             \
+                                                                \
+/* create RLS EQ initialized with external coefficients     */  \
+/*  _h  : filter coefficients (NULL for {1,0,0...})         */  \
+/*  _p  : filter length                                     */  \
+EQRLS() EQRLS(_create)(T *          _h,                         \
+                       unsigned int _p);                        \
+                                                                \
+/* re-create RLS EQ initialized with external coefficients  */  \
+/*  _q  : initial equalizer object                          */  \
+/*  _h  : filter coefficients (NULL for {1,0,0...})         */  \
+/*  _p  : filter length                                     */  \
+EQRLS() EQRLS(_recreate)(EQRLS()      _q,                       \
+                         T *          _h,                       \
+                         unsigned int _p);                      \
+                                                                \
+/* destroy equalizer object, freeing all internal memory    */  \
+void EQRLS(_destroy)(EQRLS() _q);                               \
+                                                                \
+/* print equalizer internal state                           */  \
+void EQRLS(_print)(EQRLS() _q);                                 \
+                                                                \
+/* reset equalizer object, clearing internal state          */  \
+void EQRLS(_reset)(EQRLS() _q);                                 \
+                                                                \
+/* get/set equalizer learning rate                          */  \
+float EQRLS(_get_bw)(EQRLS() _q);                               \
+void  EQRLS(_set_bw)(EQRLS() _q,                                \
+                     float   _mu);                              \
+                                                                \
+/* push sample into equalizer internal buffer               */  \
+void EQRLS(_push)(EQRLS() _q, T _x);                            \
+                                                                \
+/* execute internal dot product and return result           */  \
+/*  _q      :   equalizer object                            */  \
+/*  _y      :   output sample                               */  \
+void EQRLS(_execute)(EQRLS() _q, T * _y);                       \
+                                                                \
+/* step through one cycle of equalizer training             */  \
+/*  _q      :   equalizer object                            */  \
+/*  _d      :   desired output                              */  \
+/*  _d_hat  :   actual output                               */  \
+void EQRLS(_step)(EQRLS() _q, T _d, T _d_hat);                  \
+                                                                \
+/* retrieve internal filter coefficients                    */  \
+/*  _q      :   equalizer object                            */  \
+/*  _w      :   weights [size: _p x 1]                      */  \
+void EQRLS(_get_weights)(EQRLS() _q,                            \
+                         T *     _w);                           \
+                                                                \
+/* train equalizer object on group of samples               */  \
+/*  _q      :   equalizer object                            */  \
+/*  _w      :   input/output weights   [size: _p x 1]       */  \
+/*  _x      :   received sample vector [size: _n x 1]       */  \
+/*  _d      :   desired output vector  [size: _n x 1]       */  \
+/*  _n      :   input, output vector length                 */  \
+void EQRLS(_train)(EQRLS()      _q,                             \
+                   T *          _w,                             \
+                   T *          _x,                             \
+                   T *          _d,                             \
+                   unsigned int _n);
+
+LIQUID_EQRLS_DEFINE_API(EQRLS_MANGLE_RRRF, float);
+LIQUID_EQRLS_DEFINE_API(EQRLS_MANGLE_CCCF, liquid_float_complex);
+
+
+
+
+//
+// MODULE : fec (forward error-correction)
+//
+
+// soft bit values
+#define LIQUID_SOFTBIT_0        (0)
+#define LIQUID_SOFTBIT_1        (255)
+#define LIQUID_SOFTBIT_ERASURE  (127)
+
+// available CRC schemes
+#define LIQUID_CRC_NUM_SCHEMES  7
+typedef enum {
+    LIQUID_CRC_UNKNOWN=0,   // unknown/unavailable CRC scheme
+    LIQUID_CRC_NONE,        // no error-detection
+    LIQUID_CRC_CHECKSUM,    // 8-bit checksum
+    LIQUID_CRC_8,           // 8-bit CRC
+    LIQUID_CRC_16,          // 16-bit CRC
+    LIQUID_CRC_24,          // 24-bit CRC
+    LIQUID_CRC_32           // 32-bit CRC
+} crc_scheme;
+
+// pretty names for crc schemes
+extern const char * crc_scheme_str[LIQUID_CRC_NUM_SCHEMES][2];
+
+// Print compact list of existing and available CRC schemes
+void liquid_print_crc_schemes();
+
+// returns crc_scheme based on input string
+crc_scheme liquid_getopt_str2crc(const char * _str);
+
+// get length of CRC (bytes)
+unsigned int crc_get_length(crc_scheme _scheme);
+
+// generate error-detection key
+//  _scheme     :   error-detection scheme
+//  _msg        :   input data message, [size: _n x 1]
+//  _n          :   input data message size
+unsigned int crc_generate_key(crc_scheme      _scheme,
+                              unsigned char * _msg,
+                              unsigned int    _n);
+
+// generate error-detection key and append to end of message
+//  _scheme     :   error-detection scheme (resulting in 'p' bytes)
+//  _msg        :   input data message, [size: _n+p x 1]
+//  _n          :   input data message size (excluding key at end)
+void crc_append_key(crc_scheme      _scheme,
+                    unsigned char * _msg,
+                    unsigned int    _n);
+
+// validate message using error-detection key
+//  _scheme     :   error-detection scheme
+//  _msg        :   input data message, [size: _n x 1]
+//  _n          :   input data message size
+//  _key        :   error-detection key
+int crc_validate_message(crc_scheme      _scheme,
+                         unsigned char * _msg,
+                         unsigned int    _n,
+                         unsigned int    _key);
+
+// check message with key appended to end of array
+//  _scheme     :   error-detection scheme (resulting in 'p' bytes)
+//  _msg        :   input data message, [size: _n+p x 1]
+//  _n          :   input data message size (excluding key at end)
+int crc_check_key(crc_scheme      _scheme,
+                  unsigned char * _msg,
+                  unsigned int    _n);
+
+// get size of key (bytes)
+unsigned int crc_sizeof_key(crc_scheme _scheme);
+
+
+// available FEC schemes
+#define LIQUID_FEC_NUM_SCHEMES  28
+typedef enum {
+    LIQUID_FEC_UNKNOWN=0,       // unknown/unsupported scheme
+    LIQUID_FEC_NONE,            // no error-correction
+    LIQUID_FEC_REP3,            // simple repeat code, r1/3
+    LIQUID_FEC_REP5,            // simple repeat code, r1/5
+    LIQUID_FEC_HAMMING74,       // Hamming (7,4) block code, r1/2 (really 4/7)
+    LIQUID_FEC_HAMMING84,       // Hamming (7,4) with extra parity bit, r1/2
+    LIQUID_FEC_HAMMING128,      // Hamming (12,8) block code, r2/3
+    
+    LIQUID_FEC_GOLAY2412,       // Golay (24,12) block code, r1/2
+    LIQUID_FEC_SECDED2216,      // SEC-DED (22,16) block code, r8/11
+    LIQUID_FEC_SECDED3932,      // SEC-DED (39,32) block code
+    LIQUID_FEC_SECDED7264,      // SEC-DED (72,64) block code, r8/9
+
+    // codecs not defined internally (see http://www.ka9q.net/code/fec/)
+    LIQUID_FEC_CONV_V27,        // r1/2, K=7, dfree=10
+    LIQUID_FEC_CONV_V29,        // r1/2, K=9, dfree=12
+    LIQUID_FEC_CONV_V39,        // r1/3, K=9, dfree=18
+    LIQUID_FEC_CONV_V615,       // r1/6, K=15, dfree<=57 (Heller 1968)
+
+    // punctured (perforated) codes
+    LIQUID_FEC_CONV_V27P23,     // r2/3, K=7, dfree=6
+    LIQUID_FEC_CONV_V27P34,     // r3/4, K=7, dfree=5
+    LIQUID_FEC_CONV_V27P45,     // r4/5, K=7, dfree=4
+    LIQUID_FEC_CONV_V27P56,     // r5/6, K=7, dfree=4
+    LIQUID_FEC_CONV_V27P67,     // r6/7, K=7, dfree=3
+    LIQUID_FEC_CONV_V27P78,     // r7/8, K=7, dfree=3
+
+    LIQUID_FEC_CONV_V29P23,     // r2/3, K=9, dfree=7
+    LIQUID_FEC_CONV_V29P34,     // r3/4, K=9, dfree=6
+    LIQUID_FEC_CONV_V29P45,     // r4/5, K=9, dfree=5
+    LIQUID_FEC_CONV_V29P56,     // r5/6, K=9, dfree=5
+    LIQUID_FEC_CONV_V29P67,     // r6/7, K=9, dfree=4
+    LIQUID_FEC_CONV_V29P78,     // r7/8, K=9, dfree=4
+
+    // Reed-Solomon codes
+    LIQUID_FEC_RS_M8            // m=8, n=255, k=223
+} fec_scheme;
+
+// pretty names for fec schemes
+extern const char * fec_scheme_str[LIQUID_FEC_NUM_SCHEMES][2];
+
+// Print compact list of existing and available FEC schemes
+void liquid_print_fec_schemes();
+
+// returns fec_scheme based on input string
+fec_scheme liquid_getopt_str2fec(const char * _str);
+
+// fec object (pointer to fec structure)
+typedef struct fec_s * fec;
+
+// return the encoded message length using a particular error-
+// correction scheme (object-independent method)
+//  _scheme     :   forward error-correction scheme
+//  _msg_len    :   raw, uncoded message length
+unsigned int fec_get_enc_msg_length(fec_scheme _scheme,
+                                    unsigned int _msg_len);
+
+// get the theoretical rate of a particular forward error-
+// correction scheme (object-independent method)
+float fec_get_rate(fec_scheme _scheme);
+
+// create a fec object of a particular scheme
+//  _scheme     :   error-correction scheme
+//  _opts       :   (ignored)
+fec fec_create(fec_scheme _scheme,
+               void *_opts);
+
+// recreate fec object
+//  _q          :   old fec object
+//  _scheme     :   new error-correction scheme
+//  _opts       :   (ignored)
+fec fec_recreate(fec _q,
+                 fec_scheme _scheme,
+                 void *_opts);
+
+// destroy fec object
+void fec_destroy(fec _q);
+
+// print fec object internals
+void fec_print(fec _q);
+
+// encode a block of data using a fec scheme
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_dec        :   decoded message
+//  _msg_enc        :   encoded message
+void fec_encode(fec _q,
+                unsigned int _dec_msg_len,
+                unsigned char * _msg_dec,
+                unsigned char * _msg_enc);
+
+// decode a block of data using a fec scheme
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_enc        :   encoded message
+//  _msg_dec        :   decoded message
+void fec_decode(fec _q,
+                unsigned int _dec_msg_len,
+                unsigned char * _msg_enc,
+                unsigned char * _msg_dec);
+
+// decode a block of data using a fec scheme (soft decision)
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_enc        :   encoded message (soft bits)
+//  _msg_dec        :   decoded message
+void fec_decode_soft(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_enc,
+                     unsigned char * _msg_dec);
+
+// 
+// Packetizer
+//
+
+// computes the number of encoded bytes after packetizing
+//
+//  _n      :   number of uncoded input bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+unsigned int packetizer_compute_enc_msg_len(unsigned int _n,
+                                            int _crc,
+                                            int _fec0,
+                                            int _fec1);
+
+// computes the number of decoded bytes before packetizing
+//
+//  _k      :   number of encoded bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+unsigned int packetizer_compute_dec_msg_len(unsigned int _k,
+                                            int _crc,
+                                            int _fec0,
+                                            int _fec1);
+
+typedef struct packetizer_s * packetizer;
+
+// create packetizer object
+//
+//  _n      :   number of uncoded input bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+packetizer packetizer_create(unsigned int _dec_msg_len,
+                             int _crc,
+                             int _fec0,
+                             int _fec1);
+
+// re-create packetizer object
+//
+//  _p      :   initialz packetizer object
+//  _n      :   number of uncoded input bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+packetizer packetizer_recreate(packetizer _p,
+                               unsigned int _dec_msg_len,
+                               int _crc,
+                               int _fec0,
+                               int _fec1);
+
+// destroy packetizer object
+void packetizer_destroy(packetizer _p);
+
+// print packetizer object internals
+void packetizer_print(packetizer _p);
+
+// access methods
+unsigned int packetizer_get_dec_msg_len(packetizer _p);
+unsigned int packetizer_get_enc_msg_len(packetizer _p);
+crc_scheme   packetizer_get_crc        (packetizer _p);
+fec_scheme   packetizer_get_fec0       (packetizer _p);
+fec_scheme   packetizer_get_fec1       (packetizer _p);
+
+
+// Execute the packetizer on an input message
+//
+//  _p      :   packetizer object
+//  _msg    :   input message (uncoded bytes)
+//  _pkt    :   encoded output message
+void packetizer_encode(packetizer            _p,
+                       const unsigned char * _msg,
+                       unsigned char *       _pkt);
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded bytes)
+//  _msg    :   decoded output message
+int  packetizer_decode(packetizer            _p,
+                       const unsigned char * _pkt,
+                       unsigned char *       _msg);
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded soft bits)
+//  _msg    :   decoded output message
+int packetizer_decode_soft(packetizer            _p,
+                           const unsigned char * _pkt,
+                           unsigned char *       _msg);
+
+
+//
+// interleaver
+//
+typedef struct interleaver_s * interleaver;
+
+// create interleaver
+//   _n     : number of bytes
+interleaver interleaver_create(unsigned int _n);
+
+// destroy interleaver object
+void interleaver_destroy(interleaver _q);
+
+// print interleaver object internals
+void interleaver_print(interleaver _q);
+
+// set depth (number of internal iterations)
+//  _q      :   interleaver object
+//  _depth  :   depth
+void interleaver_set_depth(interleaver _q,
+                           unsigned int _depth);
+
+// execute forward interleaver (encoder)
+//  _q          :   interleaver object
+//  _msg_dec    :   decoded (un-interleaved) message
+//  _msg_enc    :   encoded (interleaved) message
+void interleaver_encode(interleaver _q,
+                        unsigned char * _msg_dec,
+                        unsigned char * _msg_enc);
+
+// execute forward interleaver (encoder) on soft bits
+//  _q          :   interleaver object
+//  _msg_dec    :   decoded (un-interleaved) message
+//  _msg_enc    :   encoded (interleaved) message
+void interleaver_encode_soft(interleaver _q,
+                             unsigned char * _msg_dec,
+                             unsigned char * _msg_enc);
+
+// execute reverse interleaver (decoder)
+//  _q          :   interleaver object
+//  _msg_enc    :   encoded (interleaved) message
+//  _msg_dec    :   decoded (un-interleaved) message
+void interleaver_decode(interleaver _q,
+                        unsigned char * _msg_enc,
+                        unsigned char * _msg_dec);
+
+// execute reverse interleaver (decoder) on soft bits
+//  _q          :   interleaver object
+//  _msg_enc    :   encoded (interleaved) message
+//  _msg_dec    :   decoded (un-interleaved) message
+void interleaver_decode_soft(interleaver _q,
+                             unsigned char * _msg_enc,
+                             unsigned char * _msg_dec);
+
+
+
+//
+// MODULE : fft (fast Fourier transform)
+//
+
+// type of transform
+typedef enum {
+    LIQUID_FFT_UNKNOWN  =   0,  // unknown transform type
+
+    // regular complex one-dimensional transforms
+    LIQUID_FFT_FORWARD  =  +1,  // complex one-dimensional FFT 
+    LIQUID_FFT_BACKWARD =  -1,  // complex one-dimensional inverse FFT 
+
+    // discrete cosine transforms
+    LIQUID_FFT_REDFT00  =  10,  // real one-dimensional DCT-I
+    LIQUID_FFT_REDFT10  =  11,  // real one-dimensional DCT-II
+    LIQUID_FFT_REDFT01  =  12,  // real one-dimensional DCT-III
+    LIQUID_FFT_REDFT11  =  13,  // real one-dimensional DCT-IV
+
+    // discrete sine transforms
+    LIQUID_FFT_RODFT00  =  20,  // real one-dimensional DST-I
+    LIQUID_FFT_RODFT10  =  21,  // real one-dimensional DST-II
+    LIQUID_FFT_RODFT01  =  22,  // real one-dimensional DST-III
+    LIQUID_FFT_RODFT11  =  23,  // real one-dimensional DST-IV
+
+    // modified discrete cosine transform
+    LIQUID_FFT_MDCT     =  30,  // MDCT
+    LIQUID_FFT_IMDCT    =  31,  // IMDCT
+} liquid_fft_type;
+
+#define LIQUID_FFT_MANGLE_FLOAT(name)   LIQUID_CONCAT(fft,name)
+
+// Macro    :   FFT
+//  FFT     :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_FFT_DEFINE_API(FFT,T,TC)                         \
+                                                                \
+typedef struct FFT(plan_s) * FFT(plan);                         \
+                                                                \
+/* create regular complex one-dimensional transform         */  \
+/*  _n      :   transform size                              */  \
+/*  _x      :   pointer to input array  [size: _n x 1]      */  \
+/*  _y      :   pointer to output array [size: _n x 1]      */  \
+/*  _dir    :   direction (e.g. LIQUID_FFT_FORWARD)         */  \
+/*  _flags  :   options, optimization                       */  \
+FFT(plan) FFT(_create_plan)(unsigned int _n,                    \
+                            TC *         _x,                    \
+                            TC *         _y,                    \
+                            int          _dir,                  \
+                            int          _flags);               \
+                                                                \
+/* create real-to-real transform                            */  \
+/*  _n      :   transform size                              */  \
+/*  _x      :   pointer to input array  [size: _n x 1]      */  \
+/*  _y      :   pointer to output array [size: _n x 1]      */  \
+/*  _type   :   transform type (e.g. LIQUID_FFT_REDFT00)    */  \
+/*  _flags  :   options, optimization                       */  \
+FFT(plan) FFT(_create_plan_r2r_1d)(unsigned int _n,             \
+                                   T *          _x,             \
+                                   T *          _y,             \
+                                   int          _type,          \
+                                   int          _flags);        \
+                                                                \
+/* destroy transform                                        */  \
+void FFT(_destroy_plan)(FFT(plan) _p);                          \
+                                                                \
+/* print transform plan and internal strategy               */  \
+void FFT(_print_plan)(FFT(plan) _p);                            \
+                                                                \
+/* run the transform                                        */  \
+void FFT(_execute)(FFT(plan) _p);                               \
+                                                                \
+/* object-independent methods */                                \
+                                                                \
+/* perform n-point FFT allocating plan internally           */  \
+/*  _nfft   : fft size                                      */  \
+/*  _x      : input array [size: _nfft x 1]                 */  \
+/*  _y      : output array [size: _nfft x 1]                */  \
+/*  _dir    : fft direction: LIQUID_FFT_{FORWARD,BACKWARD}  */  \
+/*  _flags  : fft flags                                     */  \
+void FFT(_run)(unsigned int _n,                                 \
+               TC *         _x,                                 \
+               TC *         _y,                                 \
+               int          _dir,                               \
+               int          _flags);                            \
+                                                                \
+/* perform n-point real FFT allocating plan internally      */  \
+/*  _nfft   : fft size                                      */  \
+/*  _x      : input array [size: _nfft x 1]                 */  \
+/*  _y      : output array [size: _nfft x 1]                */  \
+/*  _type   : fft type, e.g. LIQUID_FFT_REDFT10             */  \
+/*  _flags  : fft flags                                     */  \
+void FFT(_r2r_1d_run)(unsigned int _n,                          \
+                      T *          _x,                          \
+                      T *          _y,                          \
+                      int          _type,                       \
+                      int          _flags);                     \
+                                                                \
+/* perform _n-point fft shift                               */  \
+void FFT(_shift)(TC *         _x,                               \
+                 unsigned int _n);                              \
+
+
+LIQUID_FFT_DEFINE_API(LIQUID_FFT_MANGLE_FLOAT,float,liquid_float_complex)
+
+// antiquated fft methods
+// FFT(plan) FFT(_create_plan_mdct)(unsigned int _n,
+//                                  T * _x,
+//                                  T * _y,
+//                                  int _kind,
+//                                  int _flags);
+
+
+// 
+// spectral periodogram
+//
+
+#define LIQUID_SPGRAM_MANGLE_CFLOAT(name) LIQUID_CONCAT(spgramcf,name)
+#define LIQUID_SPGRAM_MANGLE_FLOAT(name)  LIQUID_CONCAT(spgramf, name)
+
+// Macro    :   SPGRAM
+//  SPGRAM  :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+//  TI      :   primitive data type (input)
+#define LIQUID_SPGRAM_DEFINE_API(SPGRAM,T,TC,TI)                \
+                                                                \
+typedef struct SPGRAM(_s) * SPGRAM();                           \
+                                                                \
+/* create spgram object                                     */  \
+/*  _nfft       : FFT size                                  */  \
+/*  _window     : window [size: _window_len x 1]            */  \
+/*  _window_len : window length, _window_len in [1,_nfft]   */  \
+/*  _delay      : delay between transforms, _delay > 0      */  \
+SPGRAM() SPGRAM(_create)(unsigned int _nfft,                    \
+                         int          _wtype,                   \
+                         unsigned int _window_len,              \
+                         unsigned int _delay);                  \
+                                                                \
+/* create default spgram object (Kaiser-Bessel window)      */  \
+SPGRAM() SPGRAM(_create_default)(unsigned int _nfft);           \
+                                                                \
+/* destroy spgram object                                    */  \
+void SPGRAM(_destroy)(SPGRAM() _q);                             \
+                                                                \
+/* resets the internal state of the spgram object           */  \
+void SPGRAM(_reset)(SPGRAM() _q);                               \
+                                                                \
+/* print internal state of the spgram object                */  \
+void SPGRAM(_print)(SPGRAM() _q);                               \
+                                                                \
+/* set methods                                              */  \
+int          SPGRAM(_set_alpha)(SPGRAM() _q, float _alpha);     \
+                                                                \
+/* access methods                                           */  \
+unsigned int SPGRAM(_get_nfft)                (SPGRAM() _q);    \
+unsigned int SPGRAM(_get_window_len)          (SPGRAM() _q);    \
+unsigned int SPGRAM(_get_delay)               (SPGRAM() _q);    \
+uint64_t     SPGRAM(_get_num_samples)         (SPGRAM() _q);    \
+uint64_t     SPGRAM(_get_num_samples_total)   (SPGRAM() _q);    \
+uint64_t     SPGRAM(_get_num_transforms)      (SPGRAM() _q);    \
+uint64_t     SPGRAM(_get_num_transforms_total)(SPGRAM() _q);    \
+float        SPGRAM(_get_alpha)               (SPGRAM() _q);    \
+                                                                \
+/* push a single sample into the spgram object              */  \
+/*  _q      :   spgram object                               */  \
+/*  _x      :   input sample                                */  \
+void SPGRAM(_push)(SPGRAM() _q,                                 \
+                   TI       _x);                                \
+                                                                \
+/* write a block of samples to the spgram object            */  \
+/*  _q      :   spgram object                               */  \
+/*  _x      :   input buffer [size: _n x 1]                 */  \
+/*  _n      :   input buffer length                         */  \
+void SPGRAM(_write)(SPGRAM()     _q,                            \
+                    TI *         _x,                            \
+                    unsigned int _n);                           \
+                                                                \
+/* compute spectral periodogram output (fft-shifted values  */  \
+/* in dB) from current buffer contents                      */  \
+/*  _q      :   spgram object                               */  \
+/*  _X      :   output spectrum (dB) [size: _nfft x 1]      */  \
+void SPGRAM(_get_psd)(SPGRAM() _q,                              \
+                      T *      _X);                             \
+                                                                \
+/* export gnuplot file                                      */  \
+/*  _q        : spgram object                               */  \
+/*  _filename : input buffer [size: _n x 1]                 */  \
+int SPGRAM(_export_gnuplot)(SPGRAM()     _q,                    \
+                            const char * _filename);            \
+                                                                \
+/* object-independent methods */                                \
+                                                                \
+/* estimate spectrum on input signal                        */  \
+/*  _nfft   :   FFT size                                    */  \
+/*  _x      :   input signal [size: _n x 1]                 */  \
+/*  _n      :   input signal length                         */  \
+/*  _psd    :   output spectrum, [size: _nfft x 1]          */  \
+void SPGRAM(_estimate_psd)(unsigned int _nfft,                  \
+                           TI *         _x,                     \
+                           unsigned int _n,                     \
+                           T *          _psd);                  \
+
+LIQUID_SPGRAM_DEFINE_API(LIQUID_SPGRAM_MANGLE_CFLOAT,
+                         float,
+                         liquid_float_complex,
+                         liquid_float_complex)
+
+LIQUID_SPGRAM_DEFINE_API(LIQUID_SPGRAM_MANGLE_FLOAT,
+                         float,
+                         liquid_float_complex,
+                         float)
+
+// 
+// asgram : ascii spectral periodogram
+//
+
+#define LIQUID_ASGRAM_MANGLE_CFLOAT(name) LIQUID_CONCAT(asgramcf,name)
+#define LIQUID_ASGRAM_MANGLE_FLOAT(name)  LIQUID_CONCAT(asgramf, name)
+
+// Macro    :   ASGRAM
+//  ASGRAM  :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+//  TI      :   primitive data type (input)
+#define LIQUID_ASGRAM_DEFINE_API(ASGRAM,T,TC,TI)                \
+                                                                \
+typedef struct ASGRAM(_s) * ASGRAM();                           \
+                                                                \
+/* create asgram object with size _nfft                     */  \
+ASGRAM() ASGRAM(_create)(unsigned int _nfft);                   \
+                                                                \
+/* destroy asgram object                                    */  \
+void ASGRAM(_destroy)(ASGRAM() _q);                             \
+                                                                \
+/* resets the internal state of the asgram object           */  \
+void ASGRAM(_reset)(ASGRAM() _q);                               \
+                                                                \
+/* set scale and offset for spectrogram                     */  \
+/*  _q      :   asgram object                               */  \
+/*  _offset :   signal offset level [dB]                    */  \
+/*  _scale  :   signal scale [dB]                           */  \
+void ASGRAM(_set_scale)(ASGRAM() _q,                            \
+                        float    _offset,                       \
+                        float    _scale);                       \
+                                                                \
+/* push a single sample into the asgram object              */  \
+/*  _q      :   asgram object                               */  \
+/*  _x      :   input sample                                */  \
+void ASGRAM(_push)(ASGRAM() _q,                                 \
+                   TI       _x);                                \
+                                                                \
+/* write a block of samples to the asgram object            */  \
+/*  _q      :   asgram object                               */  \
+/*  _x      :   input buffer [size: _n x 1]                 */  \
+/*  _n      :   input buffer length                         */  \
+void ASGRAM(_write)(ASGRAM()     _q,                            \
+                    TI *         _x,                            \
+                    unsigned int _n);                           \
+                                                                \
+/* compute spectral periodogram output from current buffer  */  \
+/* contents                                                 */  \
+/*  _q          :   spgram object                           */  \
+/*  _ascii      :   output ASCII string [size: _nfft x 1]   */  \
+/*  _peakval    :   peak power spectral density value [dB]  */  \
+/*  _peakfreq   :   peak power spectral density frequency   */  \
+void ASGRAM(_execute)(ASGRAM() _q,                              \
+                      char *  _ascii,                           \
+                      float * _peakval,                         \
+                      float * _peakfreq);                       \
+                                                                \
+/* compute spectral periodogram output from current buffer  */  \
+/* contents and print standard format to stdout             */  \
+void ASGRAM(_print)(ASGRAM() _q);                               \
+
+LIQUID_ASGRAM_DEFINE_API(LIQUID_ASGRAM_MANGLE_CFLOAT,
+                         float,
+                         liquid_float_complex,
+                         liquid_float_complex)
+
+LIQUID_ASGRAM_DEFINE_API(LIQUID_ASGRAM_MANGLE_FLOAT,
+                         float,
+                         liquid_float_complex,
+                         float)
+
+
+//
+// MODULE : filter
+//
+
+//
+// firdes: finite impulse response filter design
+//
+
+// prototypes
+typedef enum {
+    LIQUID_FIRFILT_UNKNOWN=0,   // unknown filter type
+
+    // Nyquist filter prototypes
+    LIQUID_FIRFILT_KAISER,      // Nyquist Kaiser filter
+    LIQUID_FIRFILT_PM,          // Parks-McClellan filter
+    LIQUID_FIRFILT_RCOS,        // raised-cosine filter
+    LIQUID_FIRFILT_FEXP,        // flipped exponential
+    LIQUID_FIRFILT_FSECH,       // flipped hyperbolic secant
+    LIQUID_FIRFILT_FARCSECH,    // flipped arc-hyperbolic secant
+
+    // root-Nyquist filter prototypes
+    LIQUID_FIRFILT_ARKAISER,    // root-Nyquist Kaiser (approximate optimum)
+    LIQUID_FIRFILT_RKAISER,     // root-Nyquist Kaiser (true optimum)
+    LIQUID_FIRFILT_RRC,         // root raised-cosine
+    LIQUID_FIRFILT_hM3,         // harris-Moerder-3 filter
+    LIQUID_FIRFILT_GMSKTX,      // GMSK transmit filter
+    LIQUID_FIRFILT_GMSKRX,      // GMSK receive filter
+    LIQUID_FIRFILT_RFEXP,       // flipped exponential
+    LIQUID_FIRFILT_RFSECH,      // flipped hyperbolic secant
+    LIQUID_FIRFILT_RFARCSECH,   // flipped arc-hyperbolic secant
+} liquid_firfilt_type;
+
+// Design (root-)Nyquist filter from prototype
+//  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)
+//  _k      : samples/symbol,          _k > 1
+//  _m      : symbol delay,            _m > 0
+//  _beta   : excess bandwidth factor, _beta in [0,1)
+//  _dt     : fractional sample delay, _dt in [-1,1]
+//  _h      : output coefficient buffer (length: 2*_k*_m+1)
+void liquid_firdes_prototype(liquid_firfilt_type _type,
+                             unsigned int        _k,
+                             unsigned int        _m,
+                             float               _beta,
+                             float               _dt,
+                             float *             _h);
+
+// returns filter type based on input string
+int liquid_getopt_str2firfilt(const char * _str);
+
+// estimate required filter length given
+//  _df     :   transition bandwidth (0 < _b < 0.5)
+//  _As     :   stop-band attenuation [dB], _As > 0
+unsigned int estimate_req_filter_len(float _df,
+                                     float _As);
+
+// estimate filter stop-band attenuation given
+//  _df     :   transition bandwidth (0 < _b < 0.5)
+//  _N      :   filter length
+float estimate_req_filter_As(float        _df,
+                             unsigned int _N);
+
+// estimate filter transition bandwidth given
+//  _As     :   stop-band attenuation [dB], _As > 0
+//  _N      :   filter length
+float estimate_req_filter_df(float        _As,
+                             unsigned int _N);
+
+
+// returns the Kaiser window beta factor give the filter's target
+// stop-band attenuation (As) [Vaidyanathan:1993]
+//  _As     :   target filter's stop-band attenuation [dB], _As > 0
+float kaiser_beta_As(float _As);
+
+
+// Design FIR filter using Parks-McClellan algorithm
+
+// band type specifier
+typedef enum {
+    LIQUID_FIRDESPM_BANDPASS=0,     // regular band-pass filter
+    LIQUID_FIRDESPM_DIFFERENTIATOR, // differentiating filter
+    LIQUID_FIRDESPM_HILBERT         // Hilbert transform
+} liquid_firdespm_btype;
+
+// weighting type specifier
+typedef enum {
+    LIQUID_FIRDESPM_FLATWEIGHT=0,   // flat weighting
+    LIQUID_FIRDESPM_EXPWEIGHT,      // exponential weighting
+    LIQUID_FIRDESPM_LINWEIGHT,      // linear weighting
+} liquid_firdespm_wtype;
+
+// run filter design (full life cycle of object)
+//  _h_len      :   length of filter (number of taps)
+//  _num_bands  :   number of frequency bands
+//  _bands      :   band edges, f in [0,0.5], [size: _num_bands x 2]
+//  _des        :   desired response [size: _num_bands x 1]
+//  _weights    :   response weighting [size: _num_bands x 1]
+//  _wtype      :   weight types (e.g. LIQUID_FIRDESPM_FLATWEIGHT) [size: _num_bands x 1]
+//  _btype      :   band type (e.g. LIQUID_FIRDESPM_BANDPASS)
+//  _h          :   output coefficients array [size: _h_len x 1]
+void firdespm_run(unsigned int _h_len,
+                  unsigned int _num_bands,
+                  float * _bands,
+                  float * _des,
+                  float * _weights,
+                  liquid_firdespm_wtype * _wtype,
+                  liquid_firdespm_btype _btype,
+                  float * _h);
+
+// structured object
+typedef struct firdespm_s * firdespm;
+
+// create firdespm object
+//  _h_len      :   length of filter (number of taps)
+//  _num_bands  :   number of frequency bands
+//  _bands      :   band edges, f in [0,0.5], [size: _num_bands x 2]
+//  _des        :   desired response [size: _num_bands x 1]
+//  _weights    :   response weighting [size: _num_bands x 1]
+//  _wtype      :   weight types (e.g. LIQUID_FIRDESPM_FLATWEIGHT) [size: _num_bands x 1]
+//  _btype      :   band type (e.g. LIQUID_FIRDESPM_BANDPASS)
+firdespm firdespm_create(unsigned int _h_len,
+                         unsigned int _num_bands,
+                         float * _bands,
+                         float * _des,
+                         float * _weights,
+                         liquid_firdespm_wtype * _wtype,
+                         liquid_firdespm_btype _btype);
+
+// destroy firdespm object
+void firdespm_destroy(firdespm _q);
+
+// print firdespm object internals
+void firdespm_print(firdespm _q);
+
+// execute filter design, storing result in _h
+void firdespm_execute(firdespm _q, float * _h);
+
+
+// Design FIR using kaiser window
+//  _n      : filter length, _n > 0
+//  _fc     : cutoff frequency, 0 < _fc < 0.5
+//  _As     : stop-band attenuation [dB], _As > 0
+//  _mu     : fractional sample offset, -0.5 < _mu < 0.5
+//  _h      : output coefficient buffer, [size: _n x 1]
+void liquid_firdes_kaiser(unsigned int _n,
+                          float _fc,
+                          float _As,
+                          float _mu,
+                          float *_h);
+
+// Design FIR doppler filter
+//  _n      : filter length
+//  _fd     : normalized doppler frequency (0 < _fd < 0.5)
+//  _K      : Rice fading factor (K >= 0)
+//  _theta  : LoS component angle of arrival
+//  _h      : output coefficient buffer
+void liquid_firdes_doppler(unsigned int _n,
+                           float        _fd,
+                           float        _K,
+                           float        _theta,
+                           float *      _h);
+
+
+// Design Nyquist raised-cosine filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rcos(unsigned int _k,
+                        unsigned int _m,
+                        float _beta,
+                        float _dt,
+                        float * _h);
+
+// Design root-Nyquist raised-cosine filter
+void liquid_firdes_rrcos(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design root-Nyquist Kaiser filter
+void liquid_firdes_rkaiser(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design (approximate) root-Nyquist Kaiser filter
+void liquid_firdes_arkaiser(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design root-Nyquist harris-Moerder filter
+void liquid_firdes_hM3(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design GMSK transmit and receive filters
+void liquid_firdes_gmsktx(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+void liquid_firdes_gmskrx(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design flipped exponential Nyquist/root-Nyquist filters
+void liquid_firdes_fexp( unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+void liquid_firdes_rfexp(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design flipped hyperbolic secand Nyquist/root-Nyquist filters
+void liquid_firdes_fsech( unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+void liquid_firdes_rfsech(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Design flipped arc-hyperbolic secand Nyquist/root-Nyquist filters
+void liquid_firdes_farcsech( unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+void liquid_firdes_rfarcsech(unsigned int _k, unsigned int _m, float _beta, float _dt, float * _h);
+
+// Compute group delay for an FIR filter
+//  _h      : filter coefficients array
+//  _n      : filter length
+//  _fc     : frequency at which delay is evaluated (-0.5 < _fc < 0.5)
+float fir_group_delay(float * _h,
+                      unsigned int _n,
+                      float _fc);
+
+// Compute group delay for an IIR filter
+//  _b      : filter numerator coefficients
+//  _nb     : filter numerator length
+//  _a      : filter denominator coefficients
+//  _na     : filter denominator length
+//  _fc     : frequency at which delay is evaluated (-0.5 < _fc < 0.5)
+float iir_group_delay(float * _b,
+                      unsigned int _nb,
+                      float * _a,
+                      unsigned int _na,
+                      float _fc);
+
+
+// liquid_filter_autocorr()
+//
+// Compute auto-correlation of filter at a specific lag.
+//
+//  _h      :   filter coefficients [size: _h_len x 1]
+//  _h_len  :   filter length
+//  _lag    :   auto-correlation lag (samples)
+float liquid_filter_autocorr(float *      _h,
+                             unsigned int _h_len,
+                             int          _lag);
+
+// liquid_filter_crosscorr()
+//
+// Compute cross-correlation of two filters at a specific lag.
+//
+//  _h      :   filter coefficients [size: _h_len]
+//  _h_len  :   filter length
+//  _g      :   filter coefficients [size: _g_len]
+//  _g_len  :   filter length
+//  _lag    :   cross-correlation lag (samples)
+float liquid_filter_crosscorr(float *      _h,
+                              unsigned int _h_len,
+                              float *      _g,
+                              unsigned int _g_len,
+                              int          _lag);
+
+// liquid_filter_isi()
+//
+// Compute inter-symbol interference (ISI)--both RMS and
+// maximum--for the filter _h.
+//
+//  _h      :   filter coefficients [size: 2*_k*_m+1 x 1]
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _rms    :   output root mean-squared ISI
+//  _max    :   maximum ISI
+void liquid_filter_isi(float *      _h,
+                       unsigned int _k,
+                       unsigned int _m,
+                       float *      _rms,
+                       float *      _max);
+
+// Compute relative out-of-band energy
+//
+//  _h      :   filter coefficients [size: _h_len x 1]
+//  _h_len  :   filter length
+//  _fc     :   analysis cut-off frequency
+//  _nfft   :   fft size
+float liquid_filter_energy(float *      _h,
+                           unsigned int _h_len,
+                           float        _fc,
+                           unsigned int _nfft);
+
+
+//
+// IIR filter design
+//
+
+// IIR filter design filter type
+typedef enum {
+    LIQUID_IIRDES_BUTTER=0,
+    LIQUID_IIRDES_CHEBY1,
+    LIQUID_IIRDES_CHEBY2,
+    LIQUID_IIRDES_ELLIP,
+    LIQUID_IIRDES_BESSEL
+} liquid_iirdes_filtertype;
+
+// IIR filter design band type
+typedef enum {
+    LIQUID_IIRDES_LOWPASS=0,
+    LIQUID_IIRDES_HIGHPASS,
+    LIQUID_IIRDES_BANDPASS,
+    LIQUID_IIRDES_BANDSTOP
+} liquid_iirdes_bandtype;
+
+// IIR filter design coefficients format
+typedef enum {
+    LIQUID_IIRDES_SOS=0,
+    LIQUID_IIRDES_TF
+} liquid_iirdes_format;
+
+// IIR filter design template
+//  _ftype      :   filter type (e.g. LIQUID_IIRDES_BUTTER)
+//  _btype      :   band type (e.g. LIQUID_IIRDES_BANDPASS)
+//  _format     :   coefficients format (e.g. LIQUID_IIRDES_SOS)
+//  _n          :   filter order
+//  _fc         :   low-pass prototype cut-off frequency
+//  _f0         :   center frequency (band-pass, band-stop)
+//  _Ap         :   pass-band ripple in dB
+//  _As         :   stop-band ripple in dB
+//  _B          :   numerator
+//  _A          :   denominator
+void liquid_iirdes(liquid_iirdes_filtertype _ftype,
+                   liquid_iirdes_bandtype   _btype,
+                   liquid_iirdes_format     _format,
+                   unsigned int _n,
+                   float _fc,
+                   float _f0,
+                   float _Ap,
+                   float _As,
+                   float * _B,
+                   float * _A);
+
+// compute analog zeros, poles, gain for specific filter types
+void butter_azpkf(unsigned int _n,
+                  liquid_float_complex * _za,
+                  liquid_float_complex * _pa,
+                  liquid_float_complex * _ka);
+void cheby1_azpkf(unsigned int _n,
+                  float _ep,
+                  liquid_float_complex * _z,
+                  liquid_float_complex * _p,
+                  liquid_float_complex * _k);
+void cheby2_azpkf(unsigned int _n,
+                  float _es,
+                  liquid_float_complex * _z,
+                  liquid_float_complex * _p,
+                  liquid_float_complex * _k);
+void ellip_azpkf(unsigned int _n,
+                 float _ep,
+                 float _es,
+                 liquid_float_complex * _z,
+                 liquid_float_complex * _p,
+                 liquid_float_complex * _k);
+void bessel_azpkf(unsigned int _n,
+                  liquid_float_complex * _z,
+                  liquid_float_complex * _p,
+                  liquid_float_complex * _k);
+
+// compute frequency pre-warping factor
+float iirdes_freqprewarp(liquid_iirdes_bandtype _btype,
+                         float _fc,
+                         float _f0);
+
+// convert analog z/p/k form to discrete z/p/k form (bilinear z-transform)
+//  _za     :   analog zeros [length: _nza]
+//  _nza    :   number of analog zeros
+//  _pa     :   analog poles [length: _npa]
+//  _npa    :   number of analog poles
+//  _m      :   frequency pre-warping factor
+//  _zd     :   output digital zeros [length: _npa]
+//  _pd     :   output digital poles [length: _npa]
+//  _kd     :   output digital gain (should actually be real-valued)
+void bilinear_zpkf(liquid_float_complex * _za,
+                   unsigned int _nza,
+                   liquid_float_complex * _pa,
+                   unsigned int _npa,
+                   liquid_float_complex _ka,
+                   float _m,
+                   liquid_float_complex * _zd,
+                   liquid_float_complex * _pd,
+                   liquid_float_complex * _kd);
+
+// digital z/p/k low-pass to high-pass
+//  _zd     :   digital zeros (low-pass prototype), [length: _n]
+//  _pd     :   digital poles (low-pass prototype), [length: _n]
+//  _n      :   low-pass filter order
+//  _zdt    :   output digital zeros transformed [length: _n]
+//  _pdt    :   output digital poles transformed [length: _n]
+void iirdes_dzpk_lp2hp(liquid_float_complex * _zd,
+                       liquid_float_complex * _pd,
+                       unsigned int _n,
+                       liquid_float_complex * _zdt,
+                       liquid_float_complex * _pdt);
+
+// digital z/p/k low-pass to band-pass
+//  _zd     :   digital zeros (low-pass prototype), [length: _n]
+//  _pd     :   digital poles (low-pass prototype), [length: _n]
+//  _n      :   low-pass filter order
+//  _f0     :   center frequency
+//  _zdt    :   output digital zeros transformed [length: 2*_n]
+//  _pdt    :   output digital poles transformed [length: 2*_n]
+void iirdes_dzpk_lp2bp(liquid_float_complex * _zd,
+                       liquid_float_complex * _pd,
+                       unsigned int _n,
+                       float _f0,
+                       liquid_float_complex * _zdt,
+                       liquid_float_complex * _pdt);
+
+// convert discrete z/p/k form to transfer function
+//  _zd     :   digital zeros [length: _n]
+//  _pd     :   digital poles [length: _n]
+//  _n      :   filter order
+//  _kd     :   digital gain
+//  _b      :   output numerator [length: _n+1]
+//  _a      :   output denominator [length: _n+1]
+void iirdes_dzpk2tff(liquid_float_complex * _zd,
+                     liquid_float_complex * _pd,
+                     unsigned int _n,
+                     liquid_float_complex _kd,
+                     float * _b,
+                     float * _a);
+
+// convert discrete z/p/k form to second-order sections
+//  _zd     :   digital zeros [length: _n]
+//  _pd     :   digital poles [length: _n]
+//  _n      :   filter order
+//  _kd     :   digital gain
+//  _B      :   output numerator [size: 3 x L+r]
+//  _A      :   output denominator [size: 3 x L+r]
+//  where r = _n%2, L = (_n-r)/2
+void iirdes_dzpk2sosf(liquid_float_complex * _zd,
+                      liquid_float_complex * _pd,
+                      unsigned int _n,
+                      liquid_float_complex _kd,
+                      float * _B,
+                      float * _A);
+
+// additional IIR filter design templates
+
+// design 2nd-order IIR filter (active lag)
+//          1 + t2 * s
+//  F(s) = ------------
+//          1 + t1 * s
+//
+//  _w      :   filter bandwidth
+//  _zeta   :   damping factor (1/sqrt(2) suggested)
+//  _K      :   loop gain (1000 suggested)
+//  _b      :   output feed-forward coefficients [size: 3 x 1]
+//  _a      :   output feed-back coefficients [size: 3 x 1]
+void iirdes_pll_active_lag(float _w,
+                           float _zeta,
+                           float _K,
+                           float * _b,
+                           float * _a);
+
+// design 2nd-order IIR filter (active PI)
+//          1 + t2 * s
+//  F(s) = ------------
+//           t1 * s
+//
+//  _w      :   filter bandwidth
+//  _zeta   :   damping factor (1/sqrt(2) suggested)
+//  _K      :   loop gain (1000 suggested)
+//  _b      :   output feed-forward coefficients [size: 3 x 1]
+//  _a      :   output feed-back coefficients [size: 3 x 1]
+void iirdes_pll_active_PI(float _w,
+                          float _zeta,
+                          float _K,
+                          float * _b,
+                          float * _a);
+
+// checks stability of iir filter
+//  _b      :   feed-forward coefficients [size: _n x 1]
+//  _a      :   feed-back coefficients [size: _n x 1]
+//  _n      :   number of coefficients
+int iirdes_isstable(float * _b,
+                    float * _a,
+                    unsigned int _n);
+
+//
+// linear prediction
+//
+
+// compute the linear prediction coefficients for an input signal _x
+//  _x      :   input signal [size: _n x 1]
+//  _n      :   input signal length
+//  _p      :   prediction filter order
+//  _a      :   prediction filter [size: _p+1 x 1]
+//  _e      :   prediction error variance [size: _p+1 x 1]
+void liquid_lpc(float * _x,
+                unsigned int _n,
+                unsigned int _p,
+                float * _a,
+                float * _g);
+
+// solve the Yule-Walker equations using Levinson-Durbin recursion
+// for _symmetric_ autocorrelation
+//  _r      :   autocorrelation array [size: _p+1 x 1]
+//  _p      :   filter order
+//  _a      :   output coefficients [size: _p+1 x 1]
+//  _e      :   error variance [size: _p+1 x 1]
+//
+// NOTES:
+//  By definition _a[0] = 1.0
+void liquid_levinson(float * _r,
+                     unsigned int _p,
+                     float * _a,
+                     float * _e);
+
+//
+// auto-correlator (delay cross-correlation)
+//
+
+#define AUTOCORR_MANGLE_CCCF(name)  LIQUID_CONCAT(autocorr_cccf,name)
+#define AUTOCORR_MANGLE_RRRF(name)  LIQUID_CONCAT(autocorr_rrrf,name)
+
+// Macro:
+//   AUTOCORR   : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_AUTOCORR_DEFINE_API(AUTOCORR,TO,TC,TI)           \
+                                                                \
+typedef struct AUTOCORR(_s) * AUTOCORR();                       \
+                                                                \
+/* create auto-correlator object                            */  \
+/*  _window_size    : size of the correlator window         */  \
+/*  _delay          : correlator delay [samples]            */  \
+AUTOCORR() AUTOCORR(_create)(unsigned int _window_size,         \
+                             unsigned int _delay);              \
+                                                                \
+/* destroy auto-correlator object, freeing internal memory  */  \
+void AUTOCORR(_destroy)(AUTOCORR() _q);                         \
+                                                                \
+/* reset auto-correlator object's internals                 */  \
+void AUTOCORR(_reset)(AUTOCORR() _q);                           \
+                                                                \
+/* print auto-correlator parameters to stdout               */  \
+void AUTOCORR(_print)(AUTOCORR() _q);                           \
+                                                                \
+/* push sample into auto-correlator object                  */  \
+void AUTOCORR(_push)(AUTOCORR() _q,                             \
+                     TI         _x);                            \
+                                                                \
+/* compute single auto-correlation output                   */  \
+void AUTOCORR(_execute)(AUTOCORR() _q,                          \
+                        TO *       _rxx);                       \
+                                                                \
+/* compute auto-correlation on block of samples; the input  */  \
+/* and output arrays may have the same pointer              */  \
+/*  _q      :   auto-correlation object                     */  \
+/*  _x      :   input array [size: _n x 1]                  */  \
+/*  _n      :   number of input, output samples             */  \
+/*  _rxx    :   input array [size: _n x 1]                  */  \
+void AUTOCORR(_execute_block)(AUTOCORR()   _q,                  \
+                              TI *         _x,                  \
+                              unsigned int _n,                  \
+                              TO *         _rxx);               \
+                                                                \
+/* return sum of squares of buffered samples                */  \
+float AUTOCORR(_get_energy)(AUTOCORR() _q);                     \
+
+LIQUID_AUTOCORR_DEFINE_API(AUTOCORR_MANGLE_CCCF,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+LIQUID_AUTOCORR_DEFINE_API(AUTOCORR_MANGLE_RRRF,
+                           float,
+                           float,
+                           float)
+
+
+//
+// Finite impulse response filter
+//
+
+#define FIRFILT_MANGLE_RRRF(name)  LIQUID_CONCAT(firfilt_rrrf,name)
+#define FIRFILT_MANGLE_CRCF(name)  LIQUID_CONCAT(firfilt_crcf,name)
+#define FIRFILT_MANGLE_CCCF(name)  LIQUID_CONCAT(firfilt_cccf,name)
+
+// Macro:
+//   FIRFILT : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_FIRFILT_DEFINE_API(FIRFILT,TO,TC,TI)             \
+typedef struct FIRFILT(_s) * FIRFILT();                         \
+                                                                \
+FIRFILT() FIRFILT(_create)(TC * _h, unsigned int _n);           \
+                                                                \
+/* create using Kaiser-Bessel windowed sinc method          */  \
+/*  _n      : filter length, _n > 0                         */  \
+/*  _fc     : filter cut-off frequency 0 < _fc < 0.5        */  \
+/*  _As     : filter stop-band attenuation [dB], _As > 0    */  \
+/*  _mu     : fractional sample offset, -0.5 < _mu < 0.5    */  \
+FIRFILT() FIRFILT(_create_kaiser)(unsigned int _n,              \
+                                  float        _fc,             \
+                                  float        _As,             \
+                                  float        _mu);            \
+                                                                \
+/* create from square-root Nyquist prototype                */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _k      : nominal samples/symbol, _k > 1                */  \
+/*  _m      : filter delay [symbols], _m > 0                */  \
+/*  _beta   : rolloff factor, 0 < beta <= 1                 */  \
+/*  _mu     : fractional sample offset,-0.5 < _mu < 0.5     */  \
+FIRFILT() FIRFILT(_create_rnyquist)(int          _type,         \
+                                    unsigned int _k,            \
+                                    unsigned int _m,            \
+                                    float        _beta,         \
+                                    float        _mu);          \
+                                                                \
+/* create rectangular filter prototype                      */  \
+FIRFILT() FIRFILT(_create_rect)(unsigned int _n);               \
+                                                                \
+/* re-create filter                                         */  \
+/*  _q      : original filter object                        */  \
+/*  _h      : pointer to filter coefficients [size: _n x 1] */  \
+/*  _n      : filter length, _n > 0                         */  \
+FIRFILT() FIRFILT(_recreate)(FIRFILT()    _q,                   \
+                             TC *         _h,                   \
+                             unsigned int _n);                  \
+                                                                \
+/* destroy filter object and free all internal memory       */  \
+void FIRFILT(_destroy)(FIRFILT() _q);                           \
+                                                                \
+/* reset filter object's internal buffer                    */  \
+void FIRFILT(_reset)(FIRFILT() _q);                             \
+                                                                \
+/* print filter object information                          */  \
+void FIRFILT(_print)(FIRFILT() _q);                             \
+                                                                \
+/* set output scaling for filter                            */  \
+void FIRFILT(_set_scale)(FIRFILT() _q,                          \
+                         TC        _scale);                     \
+                                                                \
+/* push sample into filter object's internal buffer         */  \
+/*  _q      : filter object                                 */  \
+/*  _x      : single input sample                           */  \
+void FIRFILT(_push)(FIRFILT() _q,                               \
+                    TI        _x);                              \
+                                                                \
+/* execute the filter on internal buffer and coefficients   */  \
+/*  _q      : filter object                                 */  \
+/*  _y      : pointer to single output sample               */  \
+void FIRFILT(_execute)(FIRFILT() _q,                            \
+                       TO *      _y);                           \
+                                                                \
+/* execute the filter on a block of input samples; the      */  \
+/* input and output buffers may be the same                 */  \
+/*  _q      : filter object                                 */  \
+/*  _x      : pointer to input array [size: _n x 1]         */  \
+/*  _n      : number of input, output samples               */  \
+/*  _y      : pointer to output array [size: _n x 1]        */  \
+void FIRFILT(_execute_block)(FIRFILT()    _q,                   \
+                             TI *         _x,                   \
+                             unsigned int _n,                   \
+                             TO *         _y);                  \
+                                                                \
+/* return length of filter object                           */  \
+unsigned int FIRFILT(_get_length)(FIRFILT() _q);                \
+                                                                \
+/* compute complex frequency response of filter object      */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency to evaluate                         */  \
+/*  _H      : pointer to output complex frequency response  */  \
+void FIRFILT(_freqresponse)(FIRFILT()              _q,          \
+                            float                  _fc,         \
+                            liquid_float_complex * _H);         \
+                                                                \
+/* compute and return group delay of filter object          */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency to evaluate                         */  \
+float FIRFILT(_groupdelay)(FIRFILT() _q,                        \
+                           float     _fc);                      \
+
+LIQUID_FIRFILT_DEFINE_API(FIRFILT_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_FIRFILT_DEFINE_API(FIRFILT_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+LIQUID_FIRFILT_DEFINE_API(FIRFILT_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+//
+// FIR Hilbert transform
+//  2:1 real-to-complex decimator
+//  1:2 complex-to-real interpolator
+//
+
+#define FIRHILB_MANGLE_FLOAT(name)  LIQUID_CONCAT(firhilbf, name)
+//#define FIRHILB_MANGLE_DOUBLE(name) LIQUID_CONCAT(firhilb, name)
+
+// NOTES:
+//   Although firhilb is a placeholder for both decimation and
+//   interpolation, separate objects should be used for each task.
+#define LIQUID_FIRHILB_DEFINE_API(FIRHILB,T,TC)                 \
+typedef struct FIRHILB(_s) * FIRHILB();                         \
+                                                                \
+/* create finite impulse reponse Hilbert transform          */  \
+/*  _m      : filter semi-length, delay is 2*m+1            */  \
+/*  _As     : filter stop-band attenuation [dB]             */  \
+FIRHILB() FIRHILB(_create)(unsigned int _m,                     \
+                           float        _As);                   \
+                                                                \
+/* destroy finite impulse reponse Hilbert transform         */  \
+void FIRHILB(_destroy)(FIRHILB() _q);                           \
+                                                                \
+/* print firhilb object internals to stdout                 */  \
+void FIRHILB(_print)(FIRHILB() _q);                             \
+                                                                \
+/* reset firhilb object internal state                      */  \
+void FIRHILB(_reset)(FIRHILB() _q);                             \
+                                                                \
+/* execute Hilbert transform (real to complex)              */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   real-valued input sample                    */  \
+/*  _y      :   complex-valued output sample                */  \
+void FIRHILB(_r2c_execute)(FIRHILB() _q,                        \
+                           T         _x,                        \
+                           TC *      _y);                       \
+                                                                \
+/* execute Hilbert transform (complex to real)              */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   complex-valued input sample                 */  \
+/*  _y      :   real-valued output sample                   */  \
+void FIRHILB(_c2r_execute)(FIRHILB() _q,                        \
+                           TC        _x,                        \
+                           T *       _y);                       \
+                                                                \
+/* execute Hilbert transform decimator (real to complex)    */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   real-valued input array [size: 2 x 1]       */  \
+/*  _y      :   complex-valued output sample                */  \
+void FIRHILB(_decim_execute)(FIRHILB() _q,                      \
+                             T *       _x,                      \
+                             TC *      _y);                     \
+                                                                \
+/* execute Hilbert transform decimator (real to complex) on */  \
+/* a block of samples                                       */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   real-valued input array [size: 2*_n x 1]    */  \
+/*  _n      :   number of *output* samples                  */  \
+/*  _y      :   complex-valued output array [size: _n x 1]  */  \
+void FIRHILB(_decim_execute_block)(FIRHILB()    _q,             \
+                                   T *          _x,             \
+                                   unsigned int _n,             \
+                                   TC *         _y);            \
+                                                                \
+/* execute Hilbert transform interpolator (real to complex) */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   complex-valued input sample                 */  \
+/*  _y      :   real-valued output array [size: 2 x 1]      */  \
+void FIRHILB(_interp_execute)(FIRHILB() _q,                     \
+                              TC        _x,                     \
+                              T *       _y);                    \
+                                                                \
+/* execute Hilbert transform interpolator (complex to real) */  \
+/* on a block of samples                                    */  \
+/*  _q      :   Hilbert transform object                    */  \
+/*  _x      :   complex-valued input array [size: _n x 1]   */  \
+/*  _n      :   number of *input* samples                   */  \
+/*  _y      :   real-valued output array [size: 2*_n x 1]   */  \
+void FIRHILB(_interp_execute_block)(FIRHILB()    _q,            \
+                                    TC *         _x,            \
+                                    unsigned int _n,            \
+                                    T *          _y);           \
+
+LIQUID_FIRHILB_DEFINE_API(FIRHILB_MANGLE_FLOAT, float, liquid_float_complex)
+//LIQUID_FIRHILB_DEFINE_API(FIRHILB_MANGLE_DOUBLE, double, liquid_double_complex)
+
+
+//
+// FFT-based finite impulse response filter
+//
+
+#define FFTFILT_MANGLE_RRRF(name)  LIQUID_CONCAT(fftfilt_rrrf,name)
+#define FFTFILT_MANGLE_CRCF(name)  LIQUID_CONCAT(fftfilt_crcf,name)
+#define FFTFILT_MANGLE_CCCF(name)  LIQUID_CONCAT(fftfilt_cccf,name)
+
+// Macro:
+//   FFTFILT : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_FFTFILT_DEFINE_API(FFTFILT,TO,TC,TI)             \
+typedef struct FFTFILT(_s) * FFTFILT();                         \
+                                                                \
+/* create FFT-based FIR filter using external coefficients  */  \
+/*  _h      : filter coefficients [size: _h_len x 1]        */  \
+/*  _h_len  : filter length, _h_len > 0                     */  \
+/*  _n      : block size = nfft/2, at least _h_len-1        */  \
+FFTFILT() FFTFILT(_create)(TC *         _h,                     \
+                           unsigned int _h_len,                 \
+                           unsigned int _n);                    \
+                                                                \
+/* destroy filter object and free all internal memory       */  \
+void FFTFILT(_destroy)(FFTFILT() _q);                           \
+                                                                \
+/* reset filter object's internal buffer                    */  \
+void FFTFILT(_reset)(FFTFILT() _q);                             \
+                                                                \
+/* print filter object information                          */  \
+void FFTFILT(_print)(FFTFILT() _q);                             \
+                                                                \
+/* set output scaling for filter                            */  \
+void FFTFILT(_set_scale)(FFTFILT() _q,                          \
+                         TC        _scale);                     \
+                                                                \
+/* execute the filter on internal buffer and coefficients   */  \
+/*  _q      : filter object                                 */  \
+/*  _x      : pointer to input data array  [size: _n x 1]   */  \
+/*  _y      : pointer to output data array [size: _n x 1]   */  \
+void FFTFILT(_execute)(FFTFILT() _q,                            \
+                       TI *      _x,                            \
+                       TO *      _y);                           \
+                                                                \
+/* return length of filter object's internal coefficients   */  \
+unsigned int FFTFILT(_get_length)(FFTFILT() _q);                \
+
+LIQUID_FFTFILT_DEFINE_API(FFTFILT_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_FFTFILT_DEFINE_API(FFTFILT_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+LIQUID_FFTFILT_DEFINE_API(FFTFILT_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+
+//
+// Infinite impulse response filter
+//
+
+#define IIRFILT_MANGLE_RRRF(name)  LIQUID_CONCAT(iirfilt_rrrf,name)
+#define IIRFILT_MANGLE_CRCF(name)  LIQUID_CONCAT(iirfilt_crcf,name)
+#define IIRFILT_MANGLE_CCCF(name)  LIQUID_CONCAT(iirfilt_cccf,name)
+
+// Macro:
+//   IIRFILT : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_IIRFILT_DEFINE_API(IIRFILT,TO,TC,TI)             \
+                                                                \
+typedef struct IIRFILT(_s) * IIRFILT();                         \
+                                                                \
+/* create infinite impulse reponse filter                   */  \
+/*  _b      : feed-forward coefficients [size: _nb x 1]     */  \
+/*  _nb     : number of feed-forward coefficients           */  \
+/*  _a      : feed-back coefficients [size: _na x 1]        */  \
+/*  _na     : number of feed-back coefficients              */  \
+IIRFILT() IIRFILT(_create)(TC *         _b,                     \
+                           unsigned int _nb,                    \
+                           TC *         _a,                     \
+                           unsigned int _na);                   \
+                                                                \
+/* create IIR filter using 2nd-order secitons               */  \
+/*  _B      : feed-forward coefficients [size: _nsos x 3]   */  \
+/*  _A      : feed-back coefficients    [size: _nsos x 3]   */  \
+IIRFILT() IIRFILT(_create_sos)(TC *         _B,                 \
+                               TC *         _A,                 \
+                               unsigned int _nsos);             \
+                                                                \
+/* create IIR filter from design template                   */  \
+/*  _ftype  : filter type (e.g. LIQUID_IIRDES_BUTTER)       */  \
+/*  _btype  : band type (e.g. LIQUID_IIRDES_BANDPASS)       */  \
+/*  _format : coefficients format (e.g. LIQUID_IIRDES_SOS)  */  \
+/*  _n      : filter order                                  */  \
+/*  _fc     : low-pass prototype cut-off frequency          */  \
+/*  _f0     : center frequency (band-pass, band-stop)       */  \
+/*  _Ap     : pass-band ripple in dB                        */  \
+/*  _As     : stop-band ripple in dB                        */  \
+IIRFILT() IIRFILT(_create_prototype)(                           \
+            liquid_iirdes_filtertype _ftype,                    \
+            liquid_iirdes_bandtype   _btype,                    \
+            liquid_iirdes_format     _format,                   \
+            unsigned int             _order,                    \
+            float _fc,                                          \
+            float _f0,                                          \
+            float _Ap,                                          \
+            float _As);                                         \
+                                                                \
+/* create simplified low-pass Butterworth IIR filter */         \
+/*  _n      : filter order                                  */  \
+/*  _fc     : low-pass prototype cut-off frequency          */  \
+IIRFILT() IIRFILT(_create_lowpass)(                             \
+            unsigned int _order,                                \
+            float        _fc);                                  \
+                                                                \
+/* create 8th-order integrator filter                       */  \
+IIRFILT() IIRFILT(_create_integrator)();                        \
+                                                                \
+/* create 8th-order differentiator filter                   */  \
+IIRFILT() IIRFILT(_create_differentiator)();                    \
+                                                                \
+/* create simple DC-blocking filter                         */  \
+IIRFILT() IIRFILT(_create_dc_blocker)(float _alpha);            \
+                                                                \
+/* create phase-locked loop iirfilt object                  */  \
+/*  _w      : filter bandwidth                              */  \
+/*  _zeta   : damping factor (1/sqrt(2) suggested)          */  \
+/*  _K      : loop gain (1000 suggested)                    */  \
+IIRFILT() IIRFILT(_create_pll)(float _w,                        \
+                               float _zeta,                     \
+                               float _K);                       \
+                                                                \
+/* destroy iirfilt object, freeing all internal memory      */  \
+void IIRFILT(_destroy)(IIRFILT() _q);                           \
+                                                                \
+/* print iirfilt object properties to stdout                */  \
+void IIRFILT(_print)(IIRFILT() _q);                             \
+                                                                \
+/* clear/reset iirfilt object internals                     */  \
+void IIRFILT(_reset)(IIRFILT() _q);                             \
+                                                                \
+/* compute filter output                                    */  \
+/*  _q      : iirfilt object                                */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample pointer                         */  \
+void IIRFILT(_execute)(IIRFILT() _q,                            \
+                       TI        _x,                            \
+                       TO *      _y);                           \
+                                                                \
+/* execute the filter on a block of input samples; the      */  \
+/* input and output buffers may be the same                 */  \
+/*  _q      : filter object                                 */  \
+/*  _x      : pointer to input array [size: _n x 1]         */  \
+/*  _n      : number of input, output samples               */  \
+/*  _y      : pointer to output array [size: _n x 1]        */  \
+void IIRFILT(_execute_block)(IIRFILT()    _q,                   \
+                             TI *         _x,                   \
+                             unsigned int _n,                   \
+                             TO *         _y);                  \
+                                                                \
+/* return iirfilt object's filter length (order + 1)        */  \
+unsigned int IIRFILT(_get_length)(IIRFILT() _q);                \
+                                                                \
+/* compute complex frequency response of filter object      */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency to evaluate                         */  \
+/*  _H      : pointer to output complex frequency response  */  \
+void IIRFILT(_freqresponse)(IIRFILT()              _q,          \
+                            float                  _fc,         \
+                            liquid_float_complex * _H);         \
+                                                                \
+/* compute and return group delay of filter object          */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency to evaluate                         */  \
+float IIRFILT(_groupdelay)(IIRFILT() _q, float _fc);            \
+
+LIQUID_IIRFILT_DEFINE_API(IIRFILT_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_IIRFILT_DEFINE_API(IIRFILT_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+LIQUID_IIRFILT_DEFINE_API(IIRFILT_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+
+//
+// FIR Polyphase filter bank
+//
+#define FIRPFB_MANGLE_RRRF(name)  LIQUID_CONCAT(firpfb_rrrf,name)
+#define FIRPFB_MANGLE_CRCF(name)  LIQUID_CONCAT(firpfb_crcf,name)
+#define FIRPFB_MANGLE_CCCF(name)  LIQUID_CONCAT(firpfb_cccf,name)
+
+// Macro:
+//   FIRPFB : name-mangling macro
+//   TO     : output data type
+//   TC     : coefficients data type
+//   TI     : input data type
+#define LIQUID_FIRPFB_DEFINE_API(FIRPFB,TO,TC,TI)               \
+                                                                \
+typedef struct FIRPFB(_s) * FIRPFB();                           \
+                                                                \
+/* create firpfb from external coefficients                 */  \
+/*  _M      : number of filters in the bank                 */  \
+/*  _h      : coefficients [size: _M*_h_len x 1]            */  \
+/*  _h_len  : filter delay (symbols)                        */  \
+FIRPFB() FIRPFB(_create)(unsigned int _M,                       \
+                         TC *         _h,                       \
+                         unsigned int _h_len);                  \
+                                                                \
+/* create firpfb from external coefficients                 */  \
+/*  _M      : number of filters in the bank                 */  \
+/*  _m      : filter semi-length [samples]                  */  \
+/*  _fc     : filter cut-off frequency 0 < _fc < 0.5        */  \
+/*  _As     : filter stop-band suppression [dB]             */  \
+FIRPFB() FIRPFB(_create_kaiser)(unsigned int _M,                \
+                                unsigned int _m,                \
+                                float        _fc,               \
+                                float        _As);              \
+                                                                \
+/* create firpfb from square-root Nyquist prototype         */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _npfb   : number of filters in the bank                 */  \
+/*  _k      : nominal samples/symbol                        */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : rolloff factor (0 < beta <= 1)                */  \
+FIRPFB() FIRPFB(_create_rnyquist)(int          _type,           \
+                                  unsigned int _npfb,           \
+                                  unsigned int _k,              \
+                                  unsigned int _m,              \
+                                  float        _beta);          \
+                                                                \
+/* create from square-root derivative Nyquist prototype     */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _npfb   : number of filters in the bank                 */  \
+/*  _k      : nominal samples/symbol                        */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : rolloff factor (0 < beta <= 1)                */  \
+FIRPFB() FIRPFB(_create_drnyquist)(int          _type,          \
+                                   unsigned int _npfb,          \
+                                   unsigned int _k,             \
+                                   unsigned int _m,             \
+                                   float        _beta);         \
+                                                                \
+/* re-create filterbank object                              */  \
+/*  _q      : original firpfb object                        */  \
+/*  _M      : number of filters in the bank                 */  \
+/*  _h      : coefficients [size: _M x _h_len]              */  \
+/*  _h_len  : length of each filter                         */  \
+FIRPFB() FIRPFB(_recreate)(FIRPFB()     _q,                     \
+                           unsigned int _M,                     \
+                           TC *         _h,                     \
+                           unsigned int _h_len);                \
+                                                                \
+/* destroy firpfb object, freeing all internal memory       */  \
+void FIRPFB(_destroy)(FIRPFB() _q);                             \
+                                                                \
+/* print firpfb object's parameters                         */  \
+void FIRPFB(_print)(FIRPFB() _q);                               \
+                                                                \
+/* set output scaling for filter                            */  \
+void FIRPFB(_set_scale)(FIRPFB() _q,                            \
+                        TC       _g);                           \
+                                                                \
+/* clear/reset firpfb object internal state                 */  \
+void FIRPFB(_reset)(FIRPFB() _q);                               \
+                                                                \
+/* push sample into firpfb internal buffer                  */  \
+void FIRPFB(_push)(FIRPFB() _q, TI _x);                         \
+                                                                \
+/* execute the filter on internal buffer and coefficients   */  \
+/*  _q      : firpfb object                                 */  \
+/*  _i      : index of filter to use                        */  \
+/*  _y      : pointer to output sample                      */  \
+void FIRPFB(_execute)(FIRPFB()     _q,                          \
+                      unsigned int _i,                          \
+                      TO *         _y);                         \
+
+LIQUID_FIRPFB_DEFINE_API(FIRPFB_MANGLE_RRRF,
+                         float,
+                         float,
+                         float)
+
+LIQUID_FIRPFB_DEFINE_API(FIRPFB_MANGLE_CRCF,
+                         liquid_float_complex,
+                         float,
+                         liquid_float_complex)
+
+LIQUID_FIRPFB_DEFINE_API(FIRPFB_MANGLE_CCCF,
+                         liquid_float_complex,
+                         liquid_float_complex,
+                         liquid_float_complex)
+
+// 
+// Interpolators
+//
+
+// firinterp : finite impulse response interpolator
+#define FIRINTERP_MANGLE_RRRF(name)  LIQUID_CONCAT(firinterp_rrrf,name)
+#define FIRINTERP_MANGLE_CRCF(name)  LIQUID_CONCAT(firinterp_crcf,name)
+#define FIRINTERP_MANGLE_CCCF(name)  LIQUID_CONCAT(firinterp_cccf,name)
+
+#define LIQUID_FIRINTERP_DEFINE_API(FIRINTERP,TO,TC,TI)         \
+                                                                \
+typedef struct FIRINTERP(_s) * FIRINTERP();                     \
+                                                                \
+/* create interpolator from external coefficients           */  \
+/*  _M      : interpolation factor                          */  \
+/*  _h      : filter coefficients [size: _h_len x 1]        */  \
+/*  _h_len  : filter length                                 */  \
+FIRINTERP() FIRINTERP(_create)(unsigned int _M,                 \
+                               TC *         _h,                 \
+                               unsigned int _h_len);            \
+                                                                \
+/* create interpolator from Kaiser prototype                */  \
+/*  _M      : interpolation factor                          */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _As     : stop-band attenuation [dB]                    */  \
+FIRINTERP() FIRINTERP(_create_kaiser)(unsigned int _M,          \
+                                      unsigned int _m,          \
+                                      float        _As);        \
+                                                                \
+/* create prorotype (root-)Nyquist interpolator             */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RCOS)        */  \
+/*  _k      :   samples/symbol,          _k > 1             */  \
+/*  _m      :   filter delay (symbols),  _m > 0             */  \
+/*  _beta   :   excess bandwidth factor, _beta < 1          */  \
+/*  _dt     :   fractional sample delay, _dt in (-1, 1)     */  \
+FIRINTERP() FIRINTERP(_create_prototype)(int          _type,    \
+                                         unsigned int _k,       \
+                                         unsigned int _m,       \
+                                         float        _beta,    \
+                                         float        _dt);     \
+                                                                \
+/* destroy firinterp object, freeing all internal memory    */  \
+void FIRINTERP(_destroy)(FIRINTERP() _q);                       \
+                                                                \
+/* print firinterp object's internal properties to stdout   */  \
+void FIRINTERP(_print)(FIRINTERP() _q);                         \
+                                                                \
+/* reset internal state                                     */  \
+void FIRINTERP(_reset)(FIRINTERP() _q);                         \
+                                                                \
+/* execute interpolation on single input sample             */  \
+/*  _q      : firinterp object                              */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample array [size: _M x 1]            */  \
+void FIRINTERP(_execute)(FIRINTERP() _q,                        \
+                         TI          _x,                        \
+                         TO *        _y);                       \
+                                                                \
+/* execute interpolation on block of input samples          */  \
+/*  _q      : firinterp object                              */  \
+/*  _x      : input array [size: _n x 1]                    */  \
+/*  _n      : size of input array                           */  \
+/*  _y      : output sample array [size: _M*_n x 1]         */  \
+void FIRINTERP(_execute_block)(FIRINTERP()  _q,                 \
+                               TI *         _x,                 \
+                               unsigned int _n,                 \
+                               TO *         _y);                \
+
+LIQUID_FIRINTERP_DEFINE_API(FIRINTERP_MANGLE_RRRF,
+                            float,
+                            float,
+                            float)
+
+LIQUID_FIRINTERP_DEFINE_API(FIRINTERP_MANGLE_CRCF,
+                            liquid_float_complex,
+                            float,
+                            liquid_float_complex)
+
+LIQUID_FIRINTERP_DEFINE_API(FIRINTERP_MANGLE_CCCF,
+                            liquid_float_complex,
+                            liquid_float_complex,
+                            liquid_float_complex)
+
+// iirinterp : infinite impulse response interpolator
+#define IIRINTERP_MANGLE_RRRF(name)  LIQUID_CONCAT(iirinterp_rrrf,name)
+#define IIRINTERP_MANGLE_CRCF(name)  LIQUID_CONCAT(iirinterp_crcf,name)
+#define IIRINTERP_MANGLE_CCCF(name)  LIQUID_CONCAT(iirinterp_cccf,name)
+
+#define LIQUID_IIRINTERP_DEFINE_API(IIRINTERP,TO,TC,TI)         \
+typedef struct IIRINTERP(_s) * IIRINTERP();                     \
+                                                                \
+/* create interpolator from external coefficients           */  \
+/*  _M      : interpolation factor                          */  \
+/*  _b      : feed-back coefficients [size: _nb x 1]        */  \
+/*  _nb     : feed-back coefficients length                 */  \
+/*  _a      : feed-forward coefficients [size: _na x 1]     */  \
+/*  _na     : feed-forward coefficients length              */  \
+IIRINTERP() IIRINTERP(_create)(unsigned int _M,                 \
+                               TC *         _b,                 \
+                               unsigned int _nb,                \
+                               TC *         _a,                 \
+                               unsigned int _na);               \
+                                                                \
+/* create decimator with default Butterworth prototype      */  \
+/*  _M      : decimation factor                             */  \
+/*  _order  : filter order                                  */  \
+IIRINTERP() IIRINTERP(_create_default)(                         \
+                unsigned int _M,                                \
+                unsigned int _order);                           \
+                                                                \
+/* create interpolator from prototype                       */  \
+/*  _M      : interpolation factor                          */  \
+IIRINTERP() IIRINTERP(_create_prototype)(                       \
+                unsigned int _M,                                \
+                liquid_iirdes_filtertype _ftype,                \
+                liquid_iirdes_bandtype   _btype,                \
+                liquid_iirdes_format     _format,               \
+                unsigned int _order,                            \
+                float _fc,                                      \
+                float _f0,                                      \
+                float _Ap,                                      \
+                float _As);                                     \
+                                                                \
+/* destroy interpolator object and free internal memory     */  \
+void IIRINTERP(_destroy)(IIRINTERP() _q);                       \
+                                                                \
+/* print interpolator object internals                      */  \
+void IIRINTERP(_print)(IIRINTERP() _q);                         \
+                                                                \
+/* reset interpolator object                                */  \
+void IIRINTERP(_reset)(IIRINTERP() _q);                         \
+                                                                \
+/* execute interpolation on single input sample             */  \
+/*  _q      : iirinterp object                              */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample array [size: _M x 1]            */  \
+void IIRINTERP(_execute)(IIRINTERP() _q,                        \
+                         TI          _x,                        \
+                         TO *        _y);                       \
+                                                                \
+/* execute interpolation on block of input samples          */  \
+/*  _q      : iirinterp object                              */  \
+/*  _x      : input array [size: _n x 1]                    */  \
+/*  _n      : size of input array                           */  \
+/*  _y      : output sample array [size: _M*_n x 1]         */  \
+void IIRINTERP(_execute_block)(IIRINTERP()  _q,                 \
+                               TI *         _x,                 \
+                               unsigned int _n,                 \
+                               TO *         _y);                \
+                                                                \
+/* get system group delay at frequency _fc                  */  \
+float IIRINTERP(_groupdelay)(IIRINTERP() _q, float _fc);        \
+
+LIQUID_IIRINTERP_DEFINE_API(IIRINTERP_MANGLE_RRRF,
+                            float,
+                            float,
+                            float)
+
+LIQUID_IIRINTERP_DEFINE_API(IIRINTERP_MANGLE_CRCF,
+                            liquid_float_complex,
+                            float,
+                            liquid_float_complex)
+
+LIQUID_IIRINTERP_DEFINE_API(IIRINTERP_MANGLE_CCCF,
+                            liquid_float_complex,
+                            liquid_float_complex,
+                            liquid_float_complex)
+
+// 
+// Decimators
+//
+
+// firdecim : finite impulse response decimator
+#define FIRDECIM_MANGLE_RRRF(name) LIQUID_CONCAT(firdecim_rrrf,name)
+#define FIRDECIM_MANGLE_CRCF(name) LIQUID_CONCAT(firdecim_crcf,name)
+#define FIRDECIM_MANGLE_CCCF(name) LIQUID_CONCAT(firdecim_cccf,name)
+
+#define LIQUID_FIRDECIM_DEFINE_API(FIRDECIM,TO,TC,TI)           \
+typedef struct FIRDECIM(_s) * FIRDECIM();                       \
+                                                                \
+/* create decimator from external coefficients              */  \
+/*  _M      : decimation factor                             */  \
+/*  _h      : filter coefficients [size: _h_len x 1]        */  \
+/*  _h_len  : filter coefficients length                    */  \
+FIRDECIM() FIRDECIM(_create)(unsigned int _M,                   \
+                             TC *         _h,                   \
+                             unsigned int _h_len);              \
+                                                                \
+/* create decimator from Kaiser prototype                   */  \
+/*  _M      : decimation factor                             */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _As     : stop-band attenuation [dB]                    */  \
+FIRDECIM() FIRDECIM(_create_kaiser)(unsigned int _M,            \
+                                    unsigned int _m,            \
+                                    float        _As);          \
+                                                                \
+/* create square-root Nyquist decimator                     */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _M      : samples/symbol (decimation factor)            */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : rolloff factor (0 < beta <= 1)                */  \
+/*  _dt     : fractional sample delay                       */  \
+FIRDECIM() FIRDECIM(_create_prototype)(int          _type,      \
+                                       unsigned int _M,         \
+                                       unsigned int _m,         \
+                                       float        _beta,      \
+                                       float        _dt);       \
+                                                                \
+/* destroy decimator object                                 */  \
+void FIRDECIM(_destroy)(FIRDECIM() _q);                         \
+                                                                \
+/* print decimator object propreties to stdout              */  \
+void FIRDECIM(_print)(FIRDECIM() _q);                           \
+                                                                \
+/* reset decimator object internal state                    */  \
+void FIRDECIM(_clear)(FIRDECIM() _q);                           \
+                                                                \
+/* execute decimator on _M input samples                    */  \
+/*  _q      : decimator object                              */  \
+/*  _x      : input samples [size: _M x 1]                  */  \
+/*  _y      : output sample pointer                         */  \
+void FIRDECIM(_execute)(FIRDECIM() _q,                          \
+                        TI *       _x,                          \
+                        TO *       _y);                         \
+                                                                \
+/* execute decimator on block of _n*_M input samples        */  \
+/*  _q      : decimator object                              */  \
+/*  _x      : input array [size: _n*_M x 1]                 */  \
+/*  _n      : number of _output_ samples                    */  \
+/*  _y      : output array [_size: _n x 1]                  */  \
+void FIRDECIM(_execute_block)(FIRDECIM()   _q,                  \
+                              TI *         _x,                  \
+                              unsigned int _n,                  \
+                              TO *         _y);                 \
+
+LIQUID_FIRDECIM_DEFINE_API(FIRDECIM_MANGLE_RRRF,
+                           float,
+                           float,
+                           float)
+
+LIQUID_FIRDECIM_DEFINE_API(FIRDECIM_MANGLE_CRCF,
+                           liquid_float_complex,
+                           float,
+                           liquid_float_complex)
+
+LIQUID_FIRDECIM_DEFINE_API(FIRDECIM_MANGLE_CCCF,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+
+// iirdecim : infinite impulse response decimator
+#define IIRDECIM_MANGLE_RRRF(name)  LIQUID_CONCAT(iirdecim_rrrf,name)
+#define IIRDECIM_MANGLE_CRCF(name)  LIQUID_CONCAT(iirdecim_crcf,name)
+#define IIRDECIM_MANGLE_CCCF(name)  LIQUID_CONCAT(iirdecim_cccf,name)
+
+#define LIQUID_IIRDECIM_DEFINE_API(IIRDECIM,TO,TC,TI)           \
+typedef struct IIRDECIM(_s) * IIRDECIM();                       \
+                                                                \
+/* create decimator from external coefficients              */  \
+/*  _M      : decimation factor                             */  \
+/*  _b      : feed-back coefficients [size: _nb x 1]        */  \
+/*  _nb     : feed-back coefficients length                 */  \
+/*  _a      : feed-forward coefficients [size: _na x 1]     */  \
+/*  _na     : feed-forward coefficients length              */  \
+IIRDECIM() IIRDECIM(_create)(unsigned int _M,                   \
+                             TC *         _b,                   \
+                             unsigned int _nb,                  \
+                             TC *         _a,                   \
+                             unsigned int _na);                 \
+                                                                \
+/* create decimator with default Butterworth prototype      */  \
+/*  _M      : decimation factor                             */  \
+/*  _order  : filter order                                  */  \
+IIRDECIM() IIRDECIM(_create_default)(                           \
+                unsigned int _M,                                \
+                unsigned int _order);                           \
+                                                                \
+/* create decimator from prototype                          */  \
+/*  _M      : decimation factor                             */  \
+/*  _ftype  : filter type (e.g. LIQUID_IIRDES_BUTTER)       */  \
+/*  _btype  : band type (e.g. LIQUID_IIRDES_BANDPASS)       */  \
+/*  _format : coefficients format (e.g. LIQUID_IIRDES_SOS)  */  \
+/*  _n      : filter order                                  */  \
+/*  _fc     : low-pass prototype cut-off frequency          */  \
+/*  _f0     : center frequency (band-pass, band-stop)       */  \
+/*  _Ap     : pass-band ripple in dB                        */  \
+/*  _As     : stop-band ripple in dB                        */  \
+IIRDECIM() IIRDECIM(_create_prototype)(                         \
+                unsigned int             _M,                    \
+                liquid_iirdes_filtertype _ftype,                \
+                liquid_iirdes_bandtype   _btype,                \
+                liquid_iirdes_format     _format,               \
+                unsigned int             _order,                \
+                float                    _fc,                   \
+                float                    _f0,                   \
+                float                    _Ap,                   \
+                float                    _As);                  \
+                                                                \
+/* destroy decimator object and free internal memory        */  \
+void IIRDECIM(_destroy)(IIRDECIM() _q);                         \
+                                                                \
+/* print decimator object internals                         */  \
+void IIRDECIM(_print)(IIRDECIM() _q);                           \
+                                                                \
+/* reset decimator object                                   */  \
+void IIRDECIM(_reset)(IIRDECIM() _q);                           \
+                                                                \
+/* execute decimator on _M input samples                    */  \
+/*  _q      : decimator object                              */  \
+/*  _x      : input samples [size: _M x 1]                  */  \
+/*  _y      : output sample pointer                         */  \
+void IIRDECIM(_execute)(IIRDECIM() _q,                          \
+                        TI *       _x,                          \
+                        TO *       _y);                         \
+                                                                \
+/* execute decimator on block of _n*_M input samples        */  \
+/*  _q      : decimator object                              */  \
+/*  _x      : input array [size: _n*_M x 1]                 */  \
+/*  _n      : number of _output_ samples                    */  \
+/*  _y      : output array [_sze: _n x 1]                   */  \
+void IIRDECIM(_execute_block)(IIRDECIM()   _q,                  \
+                              TI *         _x,                  \
+                              unsigned int _n,                  \
+                              TO *         _y);                 \
+                                                                \
+/* get system group delay at frequency _fc                  */  \
+float IIRDECIM(_groupdelay)(IIRDECIM() _q, float _fc);          \
+
+LIQUID_IIRDECIM_DEFINE_API(IIRDECIM_MANGLE_RRRF,
+                           float,
+                           float,
+                           float)
+
+LIQUID_IIRDECIM_DEFINE_API(IIRDECIM_MANGLE_CRCF,
+                           liquid_float_complex,
+                           float,
+                           liquid_float_complex)
+
+LIQUID_IIRDECIM_DEFINE_API(IIRDECIM_MANGLE_CCCF,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+
+
+// 
+// Half-band resampler
+//
+#define RESAMP2_MANGLE_RRRF(name)   LIQUID_CONCAT(resamp2_rrrf,name)
+#define RESAMP2_MANGLE_CRCF(name)   LIQUID_CONCAT(resamp2_crcf,name)
+#define RESAMP2_MANGLE_CCCF(name)   LIQUID_CONCAT(resamp2_cccf,name)
+
+#define LIQUID_RESAMP2_DEFINE_API(RESAMP2,TO,TC,TI)             \
+typedef struct RESAMP2(_s) * RESAMP2();                         \
+                                                                \
+/* create half-band resampler                               */  \
+/*  _m      :   filter semi-length (h_len = 4*m+1)          */  \
+/*  _f0     :   filter center frequency                     */  \
+/*  _As     :   stop-band attenuation [dB]                  */  \
+RESAMP2() RESAMP2(_create)(unsigned int _m,                     \
+                           float        _f0,                    \
+                           float        _As);                   \
+                                                                \
+/* re-create half-band resampler with new properties        */  \
+/*  _q      :   original half-band resampler object         */  \
+/*  _m      :   filter semi-length (h_len = 4*m+1)          */  \
+/*  _f0     :   filter center frequency                     */  \
+/*  _As     :   stop-band attenuation [dB]                  */  \
+RESAMP2() RESAMP2(_recreate)(RESAMP2()    _q,                   \
+                             unsigned int _m,                   \
+                             float        _f0,                  \
+                             float        _As);                 \
+                                                                \
+/* destroy half-band resampler                              */  \
+void RESAMP2(_destroy)(RESAMP2() _q);                           \
+                                                                \
+/* print resamp2 object's internals                         */  \
+void RESAMP2(_print)(RESAMP2() _q);                             \
+                                                                \
+/* reset internal buffer                                    */  \
+void RESAMP2(_clear)(RESAMP2() _q);                             \
+                                                                \
+/* get resampler filter delay (semi-length m)               */  \
+unsigned int RESAMP2(_get_delay)(RESAMP2() _q);                 \
+                                                                \
+/* execute resamp2 as half-band filter                      */  \
+/*  _q      :   resamp2 object                              */  \
+/*  _x      :   input sample                                */  \
+/*  _y0     :   output sample pointer (low frequency)       */  \
+/*  _y1     :   output sample pointer (high frequency)      */  \
+void RESAMP2(_filter_execute)(RESAMP2() _q,                     \
+                              TI        _x,                     \
+                              TO *      _y0,                    \
+                              TO *      _y1);                   \
+                                                                \
+/* execute resamp2 as half-band analysis filterbank         */  \
+/*  _q      :   resamp2 object                              */  \
+/*  _x      :   input array  [size: 2 x 1]                  */  \
+/*  _y      :   output array [size: 2 x 1]                  */  \
+void RESAMP2(_analyzer_execute)(RESAMP2() _q,                   \
+                                TI *      _x,                   \
+                                TO *      _y);                  \
+                                                                \
+/* execute resamp2 as half-band synthesis filterbank        */  \
+/*  _q      :   resamp2 object                              */  \
+/*  _x      :   input array  [size: 2 x 1]                  */  \
+/*  _y      :   output array [size: 2 x 1]                  */  \
+void RESAMP2(_synthesizer_execute)(RESAMP2() _q,                \
+                                   TI *      _x,                \
+                                   TO *      _y);               \
+                                                                \
+/* execute resamp2 as half-band decimator                   */  \
+/*  _q      :   resamp2 object                              */  \
+/*  _x      :   input array  [size: 2 x 1]                  */  \
+/*  _y      :   output sample pointer                       */  \
+void RESAMP2(_decim_execute)(RESAMP2() _q,                      \
+                             TI *      _x,                      \
+                             TO *      _y);                     \
+                                                                \
+/* execute resamp2 as half-band interpolator                */  \
+/*  _q      :   resamp2 object                              */  \
+/*  _x      :   input sample                                */  \
+/*  _y      :   output array [size: 2 x 1]                  */  \
+void RESAMP2(_interp_execute)(RESAMP2() _q,                     \
+                              TI        _x,                     \
+                              TO *      _y);                    \
+
+LIQUID_RESAMP2_DEFINE_API(RESAMP2_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_RESAMP2_DEFINE_API(RESAMP2_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+LIQUID_RESAMP2_DEFINE_API(RESAMP2_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+
+// 
+// Arbitrary resampler
+//
+#define RESAMP_MANGLE_RRRF(name)    LIQUID_CONCAT(resamp_rrrf,name)
+#define RESAMP_MANGLE_CRCF(name)    LIQUID_CONCAT(resamp_crcf,name)
+#define RESAMP_MANGLE_CCCF(name)    LIQUID_CONCAT(resamp_cccf,name)
+
+#define LIQUID_RESAMP_DEFINE_API(RESAMP,TO,TC,TI)               \
+typedef struct RESAMP(_s) * RESAMP();                           \
+                                                                \
+/* create arbitrary resampler object                        */  \
+/*  _rate   : arbitrary resampling rate                     */  \
+/*  _m      : filter semi-length (delay)                    */  \
+/*  _fc     : filter cutoff frequency, 0 < _fc < 0.5        */  \
+/*  _As     : filter stop-band attenuation [dB]             */  \
+/*  _npfb   : number of filters in the bank                 */  \
+RESAMP() RESAMP(_create)(float        _rate,                    \
+                         unsigned int _m,                       \
+                         float        _fc,                      \
+                         float        _As,                      \
+                         unsigned int _npfb);                   \
+                                                                \
+/* create arbitrary resampler object with a specified input */  \
+/* resampling rate and default parameters                   */  \
+/*  m (filter semi-length) = 7                              */  \
+/*  fc (filter cutoff frequency) = 0.25                     */  \
+/*  As (filter stop-band attenuation) = 60 dB               */  \
+/*  npfb (number of filters in the bank) = 64               */  \
+RESAMP() RESAMP(_create_default)(float _rate);                  \
+                                                                \
+/* destroy arbitrary resampler object                       */  \
+void RESAMP(_destroy)(RESAMP() _q);                             \
+                                                                \
+/* print resamp object internals to stdout                  */  \
+void RESAMP(_print)(RESAMP() _q);                               \
+                                                                \
+/* reset resamp object internals                            */  \
+void RESAMP(_reset)(RESAMP() _q);                               \
+                                                                \
+/* get resampler delay (output samples)                     */  \
+unsigned int RESAMP(_get_delay)(RESAMP() _q);                   \
+                                                                \
+/* set rate of arbitrary resampler                          */  \
+void RESAMP(_set_rate)(RESAMP() _q, float _rate);               \
+                                                                \
+/* adjust rate of arbitrary resampler                       */  \
+void RESAMP(_adjust_rate)(RESAMP() _q, float _delta);           \
+                                                                \
+/* execute arbitrary resampler                              */  \
+/*  _q              :   resamp object                       */  \
+/*  _x              :   single input sample                 */  \
+/*  _y              :   output sample array (pointer)       */  \
+/*  _num_written    :   number of samples written to _y     */  \
+void RESAMP(_execute)(RESAMP()       _q,                        \
+                      TI             _x,                        \
+                      TO *           _y,                        \
+                      unsigned int * _num_written);             \
+                                                                \
+/* execute arbitrary resampler on a block of samples        */  \
+/*  _q              :   resamp object                       */  \
+/*  _x              :   input buffer [size: _nx x 1]        */  \
+/*  _nx             :   input buffer                        */  \
+/*  _y              :   output sample array (pointer)       */  \
+/*  _ny             :   number of samples written to _y     */  \
+void RESAMP(_execute_block)(RESAMP()       _q,                  \
+                            TI *           _x,                  \
+                            unsigned int   _nx,                 \
+                            TO *           _y,                  \
+                            unsigned int * _ny);                \
+
+LIQUID_RESAMP_DEFINE_API(RESAMP_MANGLE_RRRF,
+                         float,
+                         float,
+                         float)
+
+LIQUID_RESAMP_DEFINE_API(RESAMP_MANGLE_CRCF,
+                         liquid_float_complex,
+                         float,
+                         liquid_float_complex)
+
+LIQUID_RESAMP_DEFINE_API(RESAMP_MANGLE_CCCF,
+                         liquid_float_complex,
+                         liquid_float_complex,
+                         liquid_float_complex)
+
+
+// 
+// Multi-stage half-band resampler
+//
+
+// resampling type (interpolator/decimator)
+typedef enum {
+    LIQUID_RESAMP_INTERP=0, // interpolator
+    LIQUID_RESAMP_DECIM,    // decimator
+} liquid_resamp_type;
+
+#define MSRESAMP2_MANGLE_RRRF(name) LIQUID_CONCAT(msresamp2_rrrf,name)
+#define MSRESAMP2_MANGLE_CRCF(name) LIQUID_CONCAT(msresamp2_crcf,name)
+#define MSRESAMP2_MANGLE_CCCF(name) LIQUID_CONCAT(msresamp2_cccf,name)
+
+#define LIQUID_MSRESAMP2_DEFINE_API(MSRESAMP2,TO,TC,TI)         \
+typedef struct MSRESAMP2(_s) * MSRESAMP2();                     \
+                                                                \
+/* create multi-stage half-band resampler                   */  \
+/*  _type       : resampler type (e.g. LIQUID_RESAMP_DECIM) */  \
+/*  _num_stages : number of resampling stages               */  \
+/*  _fc         : filter cut-off frequency 0 < _fc < 0.5    */  \
+/*  _f0         : filter center frequency                   */  \
+/*  _As         : stop-band attenuation [dB]                */  \
+MSRESAMP2() MSRESAMP2(_create)(int          _type,              \
+                               unsigned int _num_stages,        \
+                               float        _fc,                \
+                               float        _f0,                \
+                               float        _As);               \
+                                                                \
+/* destroy multi-stage half-bandresampler                   */  \
+void MSRESAMP2(_destroy)(MSRESAMP2() _q);                       \
+                                                                \
+/* print msresamp object internals to stdout                */  \
+void MSRESAMP2(_print)(MSRESAMP2() _q);                         \
+                                                                \
+/* reset msresamp object internal state                     */  \
+void MSRESAMP2(_reset)(MSRESAMP2() _q);                         \
+                                                                \
+/* get group delay (number of output samples)               */  \
+float MSRESAMP2(_get_delay)(MSRESAMP2() _q);                    \
+                                                                \
+/* execute multi-stage resampler, M = 2^num_stages          */  \
+/*  LIQUID_RESAMP_INTERP:   input: 1,   output: M           */  \
+/*  LIQUID_RESAMP_DECIM:    input: M,   output: 1           */  \
+/*  _q      : msresamp object                               */  \
+/*  _x      : input sample array                            */  \
+/*  _y      : output sample array                           */  \
+void MSRESAMP2(_execute)(MSRESAMP2() _q,                        \
+                         TI *        _x,                        \
+                         TO *        _y);                       \
+
+LIQUID_MSRESAMP2_DEFINE_API(MSRESAMP2_MANGLE_RRRF,
+                            float,
+                            float,
+                            float)
+
+LIQUID_MSRESAMP2_DEFINE_API(MSRESAMP2_MANGLE_CRCF,
+                            liquid_float_complex,
+                            float,
+                            liquid_float_complex)
+
+LIQUID_MSRESAMP2_DEFINE_API(MSRESAMP2_MANGLE_CCCF,
+                            liquid_float_complex,
+                            liquid_float_complex,
+                            liquid_float_complex)
+
+
+// 
+// Multi-stage arbitrary resampler
+//
+#define MSRESAMP_MANGLE_RRRF(name)    LIQUID_CONCAT(msresamp_rrrf,name)
+#define MSRESAMP_MANGLE_CRCF(name)    LIQUID_CONCAT(msresamp_crcf,name)
+#define MSRESAMP_MANGLE_CCCF(name)    LIQUID_CONCAT(msresamp_cccf,name)
+
+#define LIQUID_MSRESAMP_DEFINE_API(MSRESAMP,TO,TC,TI)           \
+typedef struct MSRESAMP(_s) * MSRESAMP();                       \
+                                                                \
+/* create multi-stage arbitrary resampler                   */  \
+/*  _r      :   resampling rate [output/input]              */  \
+/*  _As     :   stop-band attenuation [dB]                  */  \
+MSRESAMP() MSRESAMP(_create)(float _r,                          \
+                             float _As);                        \
+                                                                \
+/* destroy multi-stage arbitrary resampler                  */  \
+void MSRESAMP(_destroy)(MSRESAMP() _q);                         \
+                                                                \
+/* print msresamp object internals to stdout                */  \
+void MSRESAMP(_print)(MSRESAMP() _q);                           \
+                                                                \
+/* reset msresamp object internal state                     */  \
+void MSRESAMP(_reset)(MSRESAMP() _q);                           \
+                                                                \
+/* get filter delay (output samples)                        */  \
+float MSRESAMP(_get_delay)(MSRESAMP() _q);                      \
+                                                                \
+/* execute multi-stage resampler                            */  \
+/*  _q      :   msresamp object                             */  \
+/*  _x      :   input sample array  [size: _nx x 1]         */  \
+/*  _nx     :   input sample array size                     */  \
+/*  _y      :   output sample array [size: variable]        */  \
+/*  _ny     :   number of samples written to _y             */  \
+void MSRESAMP(_execute)(MSRESAMP()     _q,                      \
+                        TI *           _x,                      \
+                        unsigned int   _nx,                     \
+                        TO *           _y,                      \
+                        unsigned int * _ny);                    \
+
+LIQUID_MSRESAMP_DEFINE_API(MSRESAMP_MANGLE_RRRF,
+                           float,
+                           float,
+                           float)
+
+LIQUID_MSRESAMP_DEFINE_API(MSRESAMP_MANGLE_CRCF,
+                           liquid_float_complex,
+                           float,
+                           liquid_float_complex)
+
+LIQUID_MSRESAMP_DEFINE_API(MSRESAMP_MANGLE_CCCF,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+
+// 
+// Symbol timing recovery (symbol synchronizer)
+//
+#define SYMSYNC_MANGLE_RRRF(name)   LIQUID_CONCAT(symsync_rrrf,name)
+#define SYMSYNC_MANGLE_CRCF(name)   LIQUID_CONCAT(symsync_crcf,name)
+
+#define LIQUID_SYMSYNC_DEFINE_API(SYMSYNC,TO,TC,TI)             \
+                                                                \
+typedef struct SYMSYNC(_s) * SYMSYNC();                         \
+                                                                \
+/* create synchronizer object from external coefficients    */  \
+/*  _k      : samples per symbol                            */  \
+/*  _M      : number of filters in the bank                 */  \
+/*  _h      : matched filter coefficients [size:            */  \
+/*  _h_len  : length of matched filter                      */  \
+SYMSYNC() SYMSYNC(_create)(unsigned int _k,                     \
+                           unsigned int _M,                     \
+                           TC *         _h,                     \
+                           unsigned int _h_len);                \
+                                                                \
+/* create square-root Nyquist symbol synchronizer           */  \
+/*  _type   : filter type (e.g. LIQUID_FIRFILT_RRC)         */  \
+/*  _k      : samples/symbol                                */  \
+/*  _m      : symbol delay                                  */  \
+/*  _beta   : rolloff factor, beta in (0,1]                 */  \
+/*  _M      : number of filters in the bank                 */  \
+SYMSYNC() SYMSYNC(_create_rnyquist)(int          _type,         \
+                                    unsigned int _k,            \
+                                    unsigned int _m,            \
+                                    float        _beta,         \
+                                    unsigned int _M);           \
+                                                                \
+/* create symsync using Kaiser filter interpolator; useful  */  \
+/* when the input signal has matched filter applied already */  \
+/*  _k      : input samples/symbol                          */  \
+/*  _m      : symbol delay                                  */  \
+/*  _beta   : rolloff factor, beta in (0,1]                 */  \
+/*  _M      : number of filters in the bank                 */  \
+SYMSYNC() SYMSYNC(_create_kaiser)(unsigned int _k,              \
+                                  unsigned int _m,              \
+                                  float        _beta,           \
+                                  unsigned int _M);             \
+                                                                \
+/* destroy symsync object, freeing all internal memory      */  \
+void SYMSYNC(_destroy)(SYMSYNC() _q);                           \
+                                                                \
+/* print symsync object's parameters                        */  \
+void SYMSYNC(_print)(SYMSYNC() _q);                             \
+                                                                \
+/* reset symsync internal state                             */  \
+void SYMSYNC(_reset)(SYMSYNC() _q);                             \
+                                                                \
+/* lock/unlock loop control                                 */  \
+void SYMSYNC(_lock)(  SYMSYNC() _q);                            \
+void SYMSYNC(_unlock)(SYMSYNC() _q);                            \
+                                                                \
+/* set synchronizer output rate (samples/symbol)            */  \
+/*  _q      : synchronizer object                           */  \
+/*  _k_out  : output samples/symbol                         */  \
+void SYMSYNC(_set_output_rate)(SYMSYNC()    _q,                 \
+                               unsigned int _k_out);            \
+                                                                \
+/* set loop-filter bandwidth                                */  \
+/*  _q      : synchronizer object                           */  \
+/*  _bt     : loop bandwidth                                */  \
+void SYMSYNC(_set_lf_bw)(SYMSYNC() _q,                          \
+                         float     _bt);                        \
+                                                                \
+/* return instantaneous fractional timing offset estimate   */  \
+float SYMSYNC(_get_tau)(SYMSYNC() _q);                          \
+                                                                \
+/* execute synchronizer on input data array                 */  \
+/*  _q      : synchronizer object                           */  \
+/*  _x      : input data array                              */  \
+/*  _nx     : number of input samples                       */  \
+/*  _y      : output data array                             */  \
+/*  _ny     : number of samples written to output buffer    */  \
+void SYMSYNC(_execute)(SYMSYNC()      _q,                       \
+                       TI *           _x,                       \
+                       unsigned int   _nx,                      \
+                       TO *           _y,                       \
+                       unsigned int * _ny);                     \
+
+LIQUID_SYMSYNC_DEFINE_API(SYMSYNC_MANGLE_RRRF,
+                          float,
+                          float,
+                          float)
+
+LIQUID_SYMSYNC_DEFINE_API(SYMSYNC_MANGLE_CRCF,
+                          liquid_float_complex,
+                          float,
+                          liquid_float_complex)
+
+
+//
+// Finite impulse response Farrow filter
+//
+
+#define FIRFARROW_MANGLE_RRRF(name)     LIQUID_CONCAT(firfarrow_rrrf,name)
+#define FIRFARROW_MANGLE_CRCF(name)     LIQUID_CONCAT(firfarrow_crcf,name)
+//#define FIRFARROW_MANGLE_CCCF(name)     LIQUID_CONCAT(firfarrow_cccf,name)
+
+// Macro:
+//   FIRFARROW  : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_FIRFARROW_DEFINE_API(FIRFARROW,TO,TC,TI)         \
+                                                                \
+typedef struct FIRFARROW(_s) * FIRFARROW();                     \
+                                                                \
+/* create firfarrow object                                  */  \
+/*  _h_len      : filter length                             */  \
+/*  _p          : polynomial order                          */  \
+/*  _fc         : filter cutoff frequency                   */  \
+/*  _As         : stopband attenuation [dB]                 */  \
+FIRFARROW() FIRFARROW(_create)(unsigned int _h_len,             \
+                               unsigned int _p,                 \
+                               float        _fc,                \
+                               float        _As);               \
+                                                                \
+/* destroy firfarrow object, freeing all internal memory    */  \
+void FIRFARROW(_destroy)(FIRFARROW() _q);                       \
+                                                                \
+/* print firfarrow object's internal properties             */  \
+void FIRFARROW(_print)(FIRFARROW() _q);                         \
+                                                                \
+/* reset firfarrow object's internal state                  */  \
+void FIRFARROW(_reset)(FIRFARROW() _q);                         \
+                                                                \
+/* push sample into firfarrow object                        */  \
+/*  _q      : firfarrow object                              */  \
+/*  _x      : input sample                                  */  \
+void FIRFARROW(_push)(FIRFARROW() _q,                           \
+                      TI          _x);                          \
+                                                                \
+/* set fractional delay of firfarrow object                 */  \
+/*  _q      : firfarrow object                              */  \
+/*  _mu     : fractional sample delay                       */  \
+void FIRFARROW(_set_delay)(FIRFARROW() _q,                      \
+                           float       _mu);                    \
+                                                                \
+/* execute firfarrow internal dot product                   */  \
+/*  _q      : firfarrow object                              */  \
+/*  _y      : output sample pointer                         */  \
+void FIRFARROW(_execute)(FIRFARROW() _q,                        \
+                         TO *        _y);                       \
+                                                                \
+/* compute firfarrow filter on block of samples; the input  */  \
+/* and output arrays may have the same pointer              */  \
+/*  _q      : firfarrow object                              */  \
+/*  _x      : input array [size: _n x 1]                    */  \
+/*  _n      : input, output array size                      */  \
+/*  _y      : output array [size: _n x 1]                   */  \
+void FIRFARROW(_execute_block)(FIRFARROW()  _q,                 \
+                               TI *         _x,                 \
+                               unsigned int _n,                 \
+                               TO *         _y);                \
+                                                                \
+/* get length of firfarrow object (number of filter taps)   */  \
+unsigned int FIRFARROW(_get_length)(FIRFARROW() _q);            \
+                                                                \
+/* get coefficients of firfarrow object                     */  \
+/*  _q      : firfarrow object                              */  \
+/*  _h      : output coefficients pointer                   */  \
+void FIRFARROW(_get_coefficients)(FIRFARROW() _q,               \
+                                  float *     _h);              \
+                                                                \
+/* compute complex frequency response                       */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency                                     */  \
+/*  _H      : output frequency response                     */  \
+void FIRFARROW(_freqresponse)(FIRFARROW()            _q,        \
+                              float                  _fc,       \
+                              liquid_float_complex * _H);       \
+                                                                \
+/* compute group delay [samples]                            */  \
+/*  _q      :   filter object                               */  \
+/*  _fc     :   frequency                                   */  \
+float FIRFARROW(_groupdelay)(FIRFARROW() _q,                    \
+                             float       _fc);                  \
+
+LIQUID_FIRFARROW_DEFINE_API(FIRFARROW_MANGLE_RRRF,
+                            float,
+                            float,
+                            float)
+
+LIQUID_FIRFARROW_DEFINE_API(FIRFARROW_MANGLE_CRCF,
+                            liquid_float_complex,
+                            float,
+                            liquid_float_complex)
+
+
+
+//
+// MODULE : framing
+//
+
+// framesyncstats : generic frame synchronizer statistic structure
+
+typedef struct {
+    // signal quality
+    float evm;      // error vector magnitude [dB]
+    float rssi;     // received signal strength indicator [dB]
+    float cfo;      // carrier frequency offset (f/Fs)
+
+    // demodulated frame symbols
+    liquid_float_complex * framesyms;   // pointer to array [size: framesyms x 1]
+    unsigned int num_framesyms;         // length of framesyms
+
+    // modulation/coding scheme etc.
+    unsigned int mod_scheme;    // modulation scheme
+    unsigned int mod_bps;       // modulation depth (bits/symbol)
+    unsigned int check;         // data validity check (crc, checksum)
+    unsigned int fec0;          // forward error-correction (inner)
+    unsigned int fec1;          // forward error-correction (outer)
+} framesyncstats_s;
+
+// external framesyncstats default object
+extern framesyncstats_s framesyncstats_default;
+
+// initialize framesyncstats object on default
+void framesyncstats_init_default(framesyncstats_s * _stats);
+
+// print framesyncstats object
+void framesyncstats_print(framesyncstats_s * _stats);
+
+
+// framedatastats : gather frame data
+typedef struct {
+    unsigned int      num_frames_detected;
+    unsigned int      num_headers_valid;
+    unsigned int      num_payloads_valid;
+    unsigned long int num_bytes_received;
+} framedatastats_s;
+
+// reset framedatastats object
+void framedatastats_reset(framedatastats_s * _stats);
+
+// print framedatastats object
+void framedatastats_print(framedatastats_s * _stats);
+
+
+// Generic frame synchronizer callback function type
+//  _header         :   header data [size: 8 bytes]
+//  _header_valid   :   is header valid? (0:no, 1:yes)
+//  _payload        :   payload data [size: _payload_len]
+//  _payload_len    :   length of payload (bytes)
+//  _payload_valid  :   is payload valid? (0:no, 1:yes)
+//  _stats          :   frame statistics object
+//  _userdata       :   pointer to userdata
+typedef int (*framesync_callback)(unsigned char *  _header,
+                                  int              _header_valid,
+                                  unsigned char *  _payload,
+                                  unsigned int     _payload_len,
+                                  int              _payload_valid,
+                                  framesyncstats_s _stats,
+                                  void *           _userdata);
+
+// framesync csma callback functions invoked when signal levels is high or low
+//  _userdata       :   user-defined data pointer
+typedef void (*framesync_csma_callback)(void * _userdata);
+
+//
+// packet encoder/decoder
+//
+
+typedef struct qpacketmodem_s * qpacketmodem;
+
+// create packet encoder
+qpacketmodem qpacketmodem_create ();
+void         qpacketmodem_destroy(qpacketmodem _q);
+void         qpacketmodem_reset  (qpacketmodem _q);
+void         qpacketmodem_print  (qpacketmodem _q);
+
+int qpacketmodem_configure(qpacketmodem _q,
+                           unsigned int _payload_len,
+                           crc_scheme   _check,
+                           fec_scheme   _fec0,
+                           fec_scheme   _fec1,
+                           int          _ms);
+
+// get length of encoded frame in symbols
+unsigned int qpacketmodem_get_frame_len(qpacketmodem _q);
+
+// get unencoded/decoded payload length (bytes)
+unsigned int qpacketmodem_get_payload_len(qpacketmodem _q);
+
+// regular access methods
+unsigned int qpacketmodem_get_crc      (qpacketmodem _q);
+unsigned int qpacketmodem_get_fec0     (qpacketmodem _q);
+unsigned int qpacketmodem_get_fec1     (qpacketmodem _q);
+unsigned int qpacketmodem_get_modscheme(qpacketmodem _q);
+
+// encode packet into un-modulated frame symbol indices
+//  _q          :   qpacketmodem object
+//  _payload    :   unencoded payload bytes
+//  _syms       :   encoded but un-modulated payload symbol indices
+void qpacketmodem_encode_syms(qpacketmodem          _q,
+                              const unsigned char * _payload,
+                              unsigned char *       _syms);
+
+// decode packet from demodulated frame symbol indices (hard-decision decoding)
+//  _q          :   qpacketmodem object
+//  _syms       :   received hard-decision symbol indices [size: frame_len x 1]
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_syms(qpacketmodem    _q,
+                             unsigned char * _syms,
+                             unsigned char * _payload);
+
+// decode packet from demodulated frame bits (soft-decision decoding)
+//  _q          :   qpacketmodem object
+//  _bits       :   received soft-decision bits, [size: bps*frame_len x 1]
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_bits(qpacketmodem    _q,
+                             unsigned char * _bits,
+                             unsigned char * _payload);
+
+// encode and modulate packet into modulated frame samples
+//  _q          :   qpacketmodem object
+//  _payload    :   unencoded payload bytes
+//  _frame      :   encoded/modulated payload symbols
+void qpacketmodem_encode(qpacketmodem           _q,
+                         const unsigned char *  _payload,
+                         liquid_float_complex * _frame);
+
+// decode packet from modulated frame samples, returning flag if CRC passed
+// NOTE: hard-decision decoding
+//  _q          :   qpacketmodem object
+//  _frame      :   encoded/modulated payload symbols
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode(qpacketmodem           _q,
+                        liquid_float_complex * _frame,
+                        unsigned char *        _payload);
+
+// decode packet from modulated frame samples, returning flag if CRC passed
+// NOTE: soft-decision decoding
+//  _q          :   qpacketmodem object
+//  _frame      :   encoded/modulated payload symbols
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_soft(qpacketmodem           _q,
+                             liquid_float_complex * _frame,
+                             unsigned char *        _payload);
+
+//
+// pilot generator for streaming applications
+//
+typedef struct qpilotgen_s * qpilotgen;
+
+// create packet encoder
+qpilotgen qpilotgen_create(unsigned int _payload_len,
+                           unsigned int _pilot_spacing);
+
+qpilotgen qpilotgen_recreate(qpilotgen    _q,
+                             unsigned int _payload_len,
+                             unsigned int _pilot_spacing);
+
+void qpilotgen_destroy(qpilotgen _q);
+void qpilotgen_reset(  qpilotgen _q);
+void qpilotgen_print(  qpilotgen _q);
+
+unsigned int qpilotgen_get_frame_len(qpilotgen _q);
+
+// insert pilot symbols
+void qpilotgen_execute(qpilotgen              _q,
+                       liquid_float_complex * _payload,
+                       liquid_float_complex * _frame);
+
+//
+// pilot synchronizer for streaming applications
+//
+typedef struct qpilotsync_s * qpilotsync;
+
+// create packet encoder
+qpilotsync qpilotsync_create(unsigned int _payload_len,
+                             unsigned int _pilot_spacing);
+
+qpilotsync qpilotsync_recreate(qpilotsync   _q,
+                               unsigned int _payload_len,
+                               unsigned int _pilot_spacing);
+
+void qpilotsync_destroy(qpilotsync _q);
+void qpilotsync_reset(  qpilotsync _q);
+void qpilotsync_print(  qpilotsync _q);
+
+unsigned int qpilotsync_get_frame_len(qpilotsync _q);
+
+// recover frame symbols from received frame
+void qpilotsync_execute(qpilotsync             _q,
+                        liquid_float_complex * _frame,
+                        liquid_float_complex * _payload);
+
+// get estimates
+float qpilotsync_get_dphi(qpilotsync _q);
+float qpilotsync_get_phi (qpilotsync _q);
+float qpilotsync_get_gain(qpilotsync _q);
+
+
+//
+// Basic frame generator (64 bytes data payload)
+//
+
+// frame length in samples
+#define LIQUID_FRAME64_LEN (1440)
+
+typedef struct framegen64_s * framegen64;
+
+// create frame generator
+framegen64 framegen64_create();
+
+// destroy frame generator
+void framegen64_destroy(framegen64 _q);
+
+// print frame generator internal properties
+void framegen64_print(framegen64 _q);
+
+// generate frame
+//  _q          :   frame generator object
+//  _header     :   8-byte header data
+//  _payload    :   64-byte payload data
+//  _frame      :   output frame samples [size: LIQUID_FRAME64_LEN x 1]
+void framegen64_execute(framegen64             _q,
+                        unsigned char *        _header,
+                        unsigned char *        _payload,
+                        liquid_float_complex * _frame);
+
+typedef struct framesync64_s * framesync64;
+
+// create framesync64 object
+//  _callback   :   callback function
+//  _userdata   :   user data pointer passed to callback function
+framesync64 framesync64_create(framesync_callback _callback,
+                               void *             _userdata);
+
+// destroy frame synchronizer
+void framesync64_destroy(framesync64 _q);
+
+// print frame synchronizer internal properties
+void framesync64_print(framesync64 _q);
+
+// reset frame synchronizer internal state
+void framesync64_reset(framesync64 _q);
+
+// push samples through frame synchronizer
+//  _q      :   frame synchronizer object
+//  _x      :   input samples [size: _n x 1]
+//  _n      :   number of input samples
+void framesync64_execute(framesync64            _q,
+                         liquid_float_complex * _x,
+                         unsigned int           _n);
+
+// enable/disable debugging
+void framesync64_debug_enable(framesync64 _q);
+void framesync64_debug_disable(framesync64 _q);
+void framesync64_debug_print(framesync64 _q, const char * _filename);
+
+#if 0
+// advanced modes
+void framesync64_set_csma_callbacks(framesync64             _q,
+                                    framesync_csma_callback _csma_lock,
+                                    framesync_csma_callback _csma_unlock,
+                                    void *                  _csma_userdata);
+#endif
+
+//
+// Flexible frame : adjustable payload, mod scheme, etc., but bring
+//                  your own error correction, redundancy check
+//
+
+// frame generator
+typedef struct {
+    unsigned int check;         // data validity check
+    unsigned int fec0;          // forward error-correction scheme (inner)
+    unsigned int fec1;          // forward error-correction scheme (outer)
+    unsigned int mod_scheme;    // modulation scheme
+} flexframegenprops_s;
+
+void flexframegenprops_init_default(flexframegenprops_s * _fgprops);
+
+typedef struct flexframegen_s * flexframegen;
+
+// create flexframegen object
+//  _props  :   frame properties (modulation scheme, etc.)
+flexframegen flexframegen_create(flexframegenprops_s * _props);
+
+// destroy flexframegen object
+void flexframegen_destroy(flexframegen _q);
+
+// print flexframegen object internals
+void flexframegen_print(flexframegen _q);
+
+// reset flexframegen object internals
+void flexframegen_reset(flexframegen _q);
+
+// is frame assembled?
+int flexframegen_is_assembled(flexframegen _q);
+
+// get frame properties
+void flexframegen_getprops(flexframegen _q, flexframegenprops_s * _props);
+
+// set frame properties
+int flexframegen_setprops(flexframegen _q, flexframegenprops_s * _props);
+
+// get length of assembled frame (samples)
+unsigned int flexframegen_getframelen(flexframegen _q);
+
+// assemble a frame from an array of data
+//  _q              :   frame generator object
+//  _header         :   frame header
+//  _payload        :   payload data [size: _payload_len x 1]
+//  _payload_len    :   payload data length
+void flexframegen_assemble(flexframegen          _q,
+                           const unsigned char * _header,
+                           const unsigned char * _payload,
+                           unsigned int          _payload_len);
+
+// write samples of assembled frame, two samples at a time, returning
+// '1' when frame is complete, '0' otherwise. Zeros will be written
+// to the buffer if the frame is not assembled
+//  _q          :   frame generator object
+//  _buffer     :   output buffer [size: _buffer_len x 1]
+//  _buffer_len :   output buffer length
+int flexframegen_write_samples(flexframegen           _q,
+                               liquid_float_complex * _buffer,
+                               unsigned int           _buffer_len);
+
+// frame synchronizer
+
+typedef struct flexframesync_s * flexframesync;
+
+// create flexframesync object
+//  _callback   :   callback function
+//  _userdata   :   user data pointer passed to callback function
+flexframesync flexframesync_create(framesync_callback _callback,
+                                   void *             _userdata);
+
+// destroy frame synchronizer
+void flexframesync_destroy(flexframesync _q);
+
+// print frame synchronizer internal properties
+void flexframesync_print(flexframesync _q);
+
+// reset frame synchronizer internal state
+void flexframesync_reset(flexframesync _q);
+
+// push samples through frame synchronizer
+//  _q      :   frame synchronizer object
+//  _x      :   input samples [size: _n x 1]
+//  _n      :   number of input samples
+void flexframesync_execute(flexframesync          _q,
+                           liquid_float_complex * _x,
+                           unsigned int           _n);
+
+// frame data statistics
+void             flexframesync_reset_framedatastats(flexframesync _q);
+framedatastats_s flexframesync_get_framedatastats  (flexframesync _q);
+
+// enable/disable debugging
+void flexframesync_debug_enable(flexframesync _q);
+void flexframesync_debug_disable(flexframesync _q);
+void flexframesync_debug_print(flexframesync _q,
+                               const char *  _filename);
+
+//
+// bpacket : binary packet suitable for data streaming
+//
+
+// 
+// bpacket generator/encoder
+//
+typedef struct bpacketgen_s * bpacketgen;
+
+// create bpacketgen object
+//  _m              :   p/n sequence length (ignored)
+//  _dec_msg_len    :   decoded message length (original uncoded data)
+//  _crc            :   data validity check (e.g. cyclic redundancy check)
+//  _fec0           :   inner forward error-correction code scheme
+//  _fec1           :   outer forward error-correction code scheme
+bpacketgen bpacketgen_create(unsigned int _m,
+                             unsigned int _dec_msg_len,
+                             int _crc,
+                             int _fec0,
+                             int _fec1);
+
+// re-create bpacketgen object from old object
+//  _q              :   old bpacketgen object
+//  _m              :   p/n sequence length (ignored)
+//  _dec_msg_len    :   decoded message length (original uncoded data)
+//  _crc            :   data validity check (e.g. cyclic redundancy check)
+//  _fec0           :   inner forward error-correction code scheme
+//  _fec1           :   outer forward error-correction code scheme
+bpacketgen bpacketgen_recreate(bpacketgen _q,
+                               unsigned int _m,
+                               unsigned int _dec_msg_len,
+                               int _crc,
+                               int _fec0,
+                               int _fec1);
+
+// destroy bpacketgen object, freeing all internally-allocated memory
+void bpacketgen_destroy(bpacketgen _q);
+
+// print bpacketgen internals
+void bpacketgen_print(bpacketgen _q);
+
+// return length of full packet
+unsigned int bpacketgen_get_packet_len(bpacketgen _q);
+
+// encode packet
+void bpacketgen_encode(bpacketgen _q,
+                       unsigned char * _msg_dec,
+                       unsigned char * _packet);
+
+// 
+// bpacket synchronizer/decoder
+//
+typedef struct bpacketsync_s * bpacketsync;
+typedef int (*bpacketsync_callback)(unsigned char *  _payload,
+                                    int              _payload_valid,
+                                    unsigned int     _payload_len,
+                                    framesyncstats_s _stats,
+                                    void *           _userdata);
+bpacketsync bpacketsync_create(unsigned int _m,
+                               bpacketsync_callback _callback,
+                               void * _userdata);
+void bpacketsync_destroy(bpacketsync _q);
+void bpacketsync_print(bpacketsync _q);
+void bpacketsync_reset(bpacketsync _q);
+
+// run synchronizer on array of input bytes
+//  _q      :   bpacketsync object
+//  _bytes  :   input data array [size: _n x 1]
+//  _n      :   input array size
+void bpacketsync_execute(bpacketsync _q,
+                         unsigned char * _bytes,
+                         unsigned int _n);
+
+// run synchronizer on input byte
+//  _q      :   bpacketsync object
+//  _byte   :   input byte
+void bpacketsync_execute_byte(bpacketsync _q,
+                              unsigned char _byte);
+
+// run synchronizer on input symbol
+//  _q      :   bpacketsync object
+//  _sym    :   input symbol with _bps significant bits
+//  _bps    :   number of bits in input symbol
+void bpacketsync_execute_sym(bpacketsync _q,
+                             unsigned char _sym,
+                             unsigned int _bps);
+
+// execute one bit at a time
+void bpacketsync_execute_bit(bpacketsync _q,
+                             unsigned char _bit);
+
+//
+// GMSK frame generator
+//
+
+typedef struct gmskframegen_s * gmskframegen;
+
+// create GMSK frame generator
+gmskframegen gmskframegen_create();
+void gmskframegen_destroy       (gmskframegen _q);
+int  gmskframegen_is_assembled  (gmskframegen _q);
+void gmskframegen_print         (gmskframegen _q);
+void gmskframegen_reset         (gmskframegen _q);
+void gmskframegen_assemble      (gmskframegen          _q,
+                                 const unsigned char * _header,
+                                 const unsigned char * _payload,
+                                 unsigned int          _payload_len,
+                                 crc_scheme            _check,
+                                 fec_scheme            _fec0,
+                                 fec_scheme            _fec1);
+unsigned int gmskframegen_getframelen(gmskframegen _q);
+int gmskframegen_write_samples(gmskframegen _q,
+                               liquid_float_complex * _y);
+
+
+//
+// GMSK frame synchronizer
+//
+
+typedef struct gmskframesync_s * gmskframesync;
+
+// create GMSK frame synchronizer
+//  _callback   :   callback function
+//  _userdata   :   user data pointer passed to callback function
+gmskframesync gmskframesync_create(framesync_callback _callback,
+                                   void *             _userdata);
+void gmskframesync_destroy(gmskframesync _q);
+void gmskframesync_print(gmskframesync _q);
+void gmskframesync_reset(gmskframesync _q);
+void gmskframesync_execute(gmskframesync _q,
+                           liquid_float_complex * _x,
+                           unsigned int _n);
+
+// debugging
+void gmskframesync_debug_enable(gmskframesync _q);
+void gmskframesync_debug_disable(gmskframesync _q);
+void gmskframesync_debug_print(gmskframesync _q, const char * _filename);
+
+
+
+// 
+// OFDM flexframe generator
+//
+
+// ofdm frame generator properties
+typedef struct {
+    unsigned int check;         // data validity check
+    unsigned int fec0;          // forward error-correction scheme (inner)
+    unsigned int fec1;          // forward error-correction scheme (outer)
+    unsigned int mod_scheme;    // modulation scheme
+    //unsigned int block_size;  // framing block size
+} ofdmflexframegenprops_s;
+void ofdmflexframegenprops_init_default(ofdmflexframegenprops_s * _props);
+
+typedef struct ofdmflexframegen_s * ofdmflexframegen;
+
+// create OFDM flexible framing generator object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+//  _fgprops    :   frame properties (modulation scheme, etc.)
+ofdmflexframegen ofdmflexframegen_create(unsigned int              _M,
+                                         unsigned int              _cp_len,
+                                         unsigned int              _taper_len,
+                                         unsigned char *           _p,
+                                         ofdmflexframegenprops_s * _fgprops);
+
+// destroy ofdmflexframegen object
+void ofdmflexframegen_destroy(ofdmflexframegen _q);
+
+// print parameters, properties, etc.
+void ofdmflexframegen_print(ofdmflexframegen _q);
+
+// reset ofdmflexframegen object internals
+void ofdmflexframegen_reset(ofdmflexframegen _q);
+
+// is frame assembled?
+int ofdmflexframegen_is_assembled(ofdmflexframegen _q);
+
+// get properties
+void ofdmflexframegen_getprops(ofdmflexframegen _q,
+                               ofdmflexframegenprops_s * _props);
+
+// set properties
+void ofdmflexframegen_setprops(ofdmflexframegen _q,
+                               ofdmflexframegenprops_s * _props);
+
+// get length of frame (symbols)
+//  _q              :   OFDM frame generator object
+unsigned int ofdmflexframegen_getframelen(ofdmflexframegen _q);
+
+// assemble a frame from an array of data
+//  _q              :   OFDM frame generator object
+//  _header         :   frame header [8 bytes]
+//  _payload        :   payload data [size: _payload_len x 1]
+//  _payload_len    :   payload data length
+void ofdmflexframegen_assemble(ofdmflexframegen      _q,
+                               const unsigned char * _header,
+                               const unsigned char * _payload,
+                               unsigned int          _payload_len);
+
+// write symbols of assembled frame
+//  _q              :   OFDM frame generator object
+//  _buffer         :   output buffer [size: M+cp_len x 1]
+int ofdmflexframegen_writesymbol(ofdmflexframegen       _q,
+                                 liquid_float_complex * _buffer);
+
+// 
+// OFDM flex frame synchronizer
+//
+
+typedef struct ofdmflexframesync_s * ofdmflexframesync;
+
+// create OFDM flexible framing synchronizer object
+//  _M          :   number of subcarriers
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+//  _callback   :   user-defined callback function
+//  _userdata   :   user-defined data pointer
+ofdmflexframesync ofdmflexframesync_create(unsigned int       _M,
+                                           unsigned int       _cp_len,
+                                           unsigned int       _taper_len,
+                                           unsigned char *    _p,
+                                           framesync_callback _callback,
+                                           void *             _userdata);
+
+void ofdmflexframesync_destroy(ofdmflexframesync _q);
+void ofdmflexframesync_print(ofdmflexframesync _q);
+void ofdmflexframesync_reset(ofdmflexframesync _q);
+void ofdmflexframesync_execute(ofdmflexframesync _q,
+                               liquid_float_complex * _x,
+                               unsigned int _n);
+
+// query the received signal strength indication
+float ofdmflexframesync_get_rssi(ofdmflexframesync _q);
+
+// query the received carrier offset estimate
+float ofdmflexframesync_get_cfo(ofdmflexframesync _q);
+
+// enable/disable debugging
+void ofdmflexframesync_debug_enable(ofdmflexframesync _q);
+void ofdmflexframesync_debug_disable(ofdmflexframesync _q);
+void ofdmflexframesync_debug_print(ofdmflexframesync _q,
+                                   const char *      _filename);
+
+
+
+//
+// Binary P/N synchronizer
+//
+#define BSYNC_MANGLE_RRRF(name)     LIQUID_CONCAT(bsync_rrrf,name)
+#define BSYNC_MANGLE_CRCF(name)     LIQUID_CONCAT(bsync_crcf,name)
+#define BSYNC_MANGLE_CCCF(name)     LIQUID_CONCAT(bsync_cccf,name)
+
+// Macro:
+//   BSYNC  : name-mangling macro
+//   TO     : output data type
+//   TC     : coefficients data type
+//   TI     : input data type
+#define LIQUID_BSYNC_DEFINE_API(BSYNC,TO,TC,TI)                 \
+typedef struct BSYNC(_s) * BSYNC();                             \
+                                                                \
+BSYNC() BSYNC(_create)(unsigned int _n, TC * _v);               \
+                                                                \
+/* create binary synchronizer from m-sequence               */  \
+/*  _g      :   m-sequence generator polynomial             */  \
+/*  _k      :   samples/symbol (over-sampling factor)       */  \
+BSYNC() BSYNC(_create_msequence)(unsigned int _g,               \
+                                 unsigned int _k);              \
+void BSYNC(_destroy)(BSYNC() _fs);                              \
+void BSYNC(_print)(BSYNC() _fs);                                \
+void BSYNC(_correlate)(BSYNC() _fs, TI _sym, TO * _y);
+
+LIQUID_BSYNC_DEFINE_API(BSYNC_MANGLE_RRRF,
+                        float,
+                        float,
+                        float)
+
+LIQUID_BSYNC_DEFINE_API(BSYNC_MANGLE_CRCF,
+                        liquid_float_complex,
+                        float,
+                        liquid_float_complex)
+
+LIQUID_BSYNC_DEFINE_API(BSYNC_MANGLE_CCCF,
+                        liquid_float_complex,
+                        liquid_float_complex,
+                        liquid_float_complex)
+
+
+//
+// Pre-demodulation synchronizers (binary and otherwise)
+//
+#define  PRESYNC_MANGLE_CCCF(name)  LIQUID_CONCAT( presync_cccf,name)
+#define BPRESYNC_MANGLE_CCCF(name)  LIQUID_CONCAT(bpresync_cccf,name)
+
+// Macro:
+//   PRESYNC   : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_PRESYNC_DEFINE_API(PRESYNC,TO,TC,TI)             \
+typedef struct PRESYNC(_s) * PRESYNC();                         \
+                                                                \
+/* create pre-demod synchronizer                            */  \
+/*  _v          :   baseband sequence                       */  \
+/*  _n          :   baseband sequence length                */  \
+/*  _dphi_max   :   maximum absolute frequency deviation    */  \
+/*  _m          :   number of correlators                   */  \
+PRESYNC() PRESYNC(_create)(TC *         _v,                     \
+                           unsigned int _n,                     \
+                           float        _dphi_max,              \
+                           unsigned int _m);                    \
+                                                                \
+/* destroy pre-demod synchronizer                           */  \
+void PRESYNC(_destroy)(PRESYNC() _q);                           \
+                                                                \
+/* print pre-demod synchronizer internal state              */  \
+void PRESYNC(_print)(PRESYNC() _q);                             \
+                                                                \
+/* reset pre-demod synchronizer internal state              */  \
+void PRESYNC(_reset)(PRESYNC() _q);                             \
+                                                                \
+/* push input sample into pre-demod synchronizer            */  \
+/*  _q          :   pre-demod synchronizer object           */  \
+/*  _x          :   input sample                            */  \
+void PRESYNC(_push)(PRESYNC() _q,                               \
+                    TI        _x);                              \
+                                                                \
+/* correlate input sequence                                 */  \
+/*  _q          :   pre-demod synchronizer object           */  \
+/*  _rxy        :   output cross correlation                */  \
+/*  _dphi_hat   :   output frequency offset estiamte        */  \
+void PRESYNC(_correlate)(PRESYNC() _q,                          \
+                         TO *      _rxy,                        \
+                         float *   _dphi_hat);                  \
+
+// non-binary pre-demodulation synchronizer
+LIQUID_PRESYNC_DEFINE_API(PRESYNC_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+// binary pre-demodulation synchronizer
+LIQUID_PRESYNC_DEFINE_API(BPRESYNC_MANGLE_CCCF,
+                          liquid_float_complex,
+                          liquid_float_complex,
+                          liquid_float_complex)
+
+//
+// Frame detector
+//
+
+typedef struct qdetector_cccf_s * qdetector_cccf;
+
+// create detector with generic sequence
+//  _s      :   sample sequence
+//  _s_len  :   length of sample sequence
+qdetector_cccf qdetector_cccf_create(liquid_float_complex * _s,
+                                     unsigned int           _s_len);
+
+// create detector from sequence of symbols using internal linear interpolator
+//  _sequence       :   symbol sequence
+//  _sequence_len   :   length of symbol sequence
+//  _ftype          :   filter prototype (e.g. LIQUID_FIRFILT_RRC)
+//  _k              :   samples/symbol
+//  _m              :   filter delay
+//  _beta           :   excess bandwidth factor
+qdetector_cccf qdetector_cccf_create_linear(liquid_float_complex * _sequence,
+                                            unsigned int           _sequence_len,
+                                            int                    _ftype,
+                                            unsigned int           _k,
+                                            unsigned int           _m,
+                                            float                  _beta);
+
+// create detector from sequence of GMSK symbols
+//  _sequence       :   bit sequence
+//  _sequence_len   :   length of bit sequence
+//  _k              :   samples/symbol
+//  _m              :   filter delay
+//  _beta           :   excess bandwidth factor
+qdetector_cccf qdetector_cccf_create_gmsk(unsigned char * _sequence,
+                                          unsigned int    _sequence_len,
+                                          unsigned int    _k,
+                                          unsigned int    _m,
+                                          float           _beta);
+
+void qdetector_cccf_destroy(qdetector_cccf _q);
+void qdetector_cccf_print  (qdetector_cccf _q);
+void qdetector_cccf_reset  (qdetector_cccf _q);
+
+// run detector, looking for sequence; return pointer to aligned, buffered samples
+void * qdetector_cccf_execute(qdetector_cccf       _q,
+                              liquid_float_complex _x);
+
+// set detection threshold (should be between 0 and 1, good starting point is 0.5)
+void qdetector_cccf_set_threshold(qdetector_cccf _q,
+                                  float          _threshold);
+
+// set carrier offset search range
+void qdetector_cccf_set_range(qdetector_cccf _q,
+                              float          _dphi_max);
+
+// access methods
+unsigned int qdetector_cccf_get_seq_len (qdetector_cccf _q); // sequence length
+const void * qdetector_cccf_get_sequence(qdetector_cccf _q); // pointer to sequence
+unsigned int qdetector_cccf_get_buf_len (qdetector_cccf _q); // buffer length
+float        qdetector_cccf_get_tau     (qdetector_cccf _q); // fractional timing offset estimate
+float        qdetector_cccf_get_gamma   (qdetector_cccf _q); // channel gain
+float        qdetector_cccf_get_dphi    (qdetector_cccf _q); // carrier frequency offset estimate
+float        qdetector_cccf_get_phi     (qdetector_cccf _q); // carrier phase offset estimate
+
+//
+// Pre-demodulation detector
+//
+
+typedef struct detector_cccf_s * detector_cccf;
+
+// create pre-demod detector
+//  _s          :   sequence
+//  _n          :   sequence length
+//  _threshold  :   detection threshold (default: 0.7)
+//  _dphi_max   :   maximum carrier offset
+detector_cccf detector_cccf_create(liquid_float_complex * _s,
+                                   unsigned int           _n,
+                                   float                  _threshold,
+                                   float                  _dphi_max);
+
+// destroy pre-demo detector object
+void detector_cccf_destroy(detector_cccf _q);
+
+// print pre-demod detector internal state
+void detector_cccf_print(detector_cccf _q);
+
+// reset pre-demod detector internal state
+void detector_cccf_reset(detector_cccf _q);
+
+// Run sample through pre-demod detector's correlator.
+// Returns '1' if signal was detected, '0' otherwise
+//  _q          :   pre-demod detector
+//  _x          :   input sample
+//  _tau_hat    :   fractional sample offset estimate (set when detected)
+//  _dphi_hat   :   carrier frequency offset estimate (set when detected)
+//  _gamma_hat  :   channel gain estimate (set when detected)
+int detector_cccf_correlate(detector_cccf        _q,
+                            liquid_float_complex _x,
+                            float *              _tau_hat,
+                            float *              _dphi_hat,
+                            float *              _gamma_hat);
+
+
+// 
+// symbol streaming for testing (no meaningful data, just symbols)
+//
+#define SYMSTREAM_MANGLE_CFLOAT(name) LIQUID_CONCAT(symstreamcf,name)
+
+#define LIQUID_SYMSTREAM_DEFINE_API(SYMSTREAM,TO)               \
+                                                                \
+typedef struct SYMSTREAM(_s) * SYMSTREAM();                     \
+                                                                \
+/* create default symstream object                          */  \
+/* (LIQUID_RNYQUIST_ARKAISER, k=2, m=7, beta=0.3, QPSK)     */  \
+SYMSTREAM() SYMSTREAM(_create)(void);                           \
+                                                                \
+/* create symstream object with linear modulation           */  \
+/*  _ftype  : filter type (e.g. LIQUID_RNYQUIST_RRC)        */  \
+/*  _k      : samples per symbol                            */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : filter excess bandwidth                       */  \
+/*  _ms     : modulation scheme (e.g. LIQUID_MODEM_QPSK)    */  \
+SYMSTREAM() SYMSTREAM(_create_linear)(int          _ftype,      \
+                                      unsigned int _k,          \
+                                      unsigned int _m,          \
+                                      float        _beta,       \
+                                      int          _ms);        \
+                                                                \
+/* destroy symstream object, freeing all internal memory    */  \
+void SYMSTREAM(_destroy)(SYMSTREAM() _q);                       \
+                                                                \
+/* print symstream object's parameters                      */  \
+void SYMSTREAM(_print)(SYMSTREAM() _q);                         \
+                                                                \
+/* reset symstream internal state                           */  \
+void SYMSTREAM(_reset)(SYMSTREAM() _q);                         \
+                                                                \
+/* write block of samples to output buffer                  */  \
+/*  _q      : synchronizer object                           */  \
+/*  _buf    : output buffer [size: _buf_len x 1]            */  \
+/*  _buf_len: output buffer size                            */  \
+void SYMSTREAM(_write_samples)(SYMSTREAM()  _q,                 \
+                               TO *         _buf,               \
+                               unsigned int _buf_len);          \
+    
+LIQUID_SYMSTREAM_DEFINE_API(SYMSTREAM_MANGLE_CFLOAT, liquid_float_complex)
+
+
+
+//
+// multi-signal source for testing (no meaningful data, just signals)
+//
+#define MSOURCE_MANGLE_CFLOAT(name) LIQUID_CONCAT(msourcecf,name)
+
+#define LIQUID_MSOURCE_DEFINE_API(MSOURCE,TO)                   \
+                                                                \
+typedef struct MSOURCE(_s) * MSOURCE();                         \
+                                                                \
+/* create default msource object                            */  \
+MSOURCE() MSOURCE(_create)(void);                               \
+                                                                \
+/* destroy msource object                                   */  \
+void MSOURCE(_destroy)(MSOURCE() _q);                           \
+                                                                \
+/* print msrouce object                                     */  \
+void MSOURCE(_print)(MSOURCE() _q);                             \
+                                                                \
+/* reset msrouce object                                     */  \
+void MSOURCE(_reset)(MSOURCE() _q);                             \
+                                                                \
+/* add signal sources                                       */  \
+int MSOURCE(_add_tone) (MSOURCE() _q);                          \
+int MSOURCE(_add_noise)(MSOURCE() _q, float _bandwidth);        \
+int MSOURCE(_add_modem)(MSOURCE()    _q,                        \
+                        int          _ms,                       \
+                        unsigned int _k,                        \
+                        unsigned int _m,                        \
+                        float        _beta);                    \
+                                                                \
+/* remove signal                                            */  \
+void MSOURCE(_remove)(MSOURCE() _q, int _id);                   \
+                                                                \
+/* enable/disable signal                                    */  \
+void MSOURCE(_enable) (MSOURCE() _q, int _id);                  \
+void MSOURCE(_disable)(MSOURCE() _q, int _id);                  \
+                                                                \
+/* set signal gain                                          */  \
+/*  _q      :   msource object                              */  \
+/*  _id     :   source id                                   */  \
+/*  _gain   :   signal gain                                 */  \
+void MSOURCE(_set_gain)(MSOURCE() _q,                           \
+                        int       _id,                          \
+                        float     _gain_dB);                    \
+                                                                \
+/* set carrier offset to signal                             */  \
+/*  _q      :   msource object                              */  \
+/*  _id     :   source id                                   */  \
+/*  _fc     :   carrier offset, fc in [-0.5,0.5]            */  \
+void MSOURCE(_set_frequency)(MSOURCE() _q,                      \
+                             int       _id,                     \
+                             float     _dphi);                  \
+                                                                \
+/* write block of samples to output buffer                  */  \
+/*  _q      : synchronizer object                           */  \
+/*  _buf    : output buffer [size: _buf_len x 1]            */  \
+/*  _buf_len: output buffer size                            */  \
+void MSOURCE(_write_samples)(MSOURCE()    _q,                   \
+                             TO *         _buf,                 \
+                             unsigned int _buf_len);            \
+    
+LIQUID_MSOURCE_DEFINE_API(MSOURCE_MANGLE_CFLOAT, liquid_float_complex)
+
+
+
+
+// 
+// Symbol tracking: AGC > symsync > EQ > carrier recovery
+//
+#define SYMTRACK_MANGLE_RRRF(name) LIQUID_CONCAT(symtrack_rrrf,name)
+#define SYMTRACK_MANGLE_CCCF(name) LIQUID_CONCAT(symtrack_cccf,name)
+
+// large macro
+//   SYMTRACK   : name-mangling macro
+//   T          : data type, primitive
+//   TO         : data type, output
+//   TC         : data type, coefficients
+//   TI         : data type, input
+#define LIQUID_SYMTRACK_DEFINE_API(SYMTRACK,T,TO,TC,TI)         \
+                                                                \
+typedef struct SYMTRACK(_s) * SYMTRACK();                       \
+                                                                \
+/* create symtrack object with default parameters           */  \
+/*  _ftype  : filter type (e.g. LIQUID_RNYQUIST_RRC)        */  \
+/*  _k      : samples per symbol                            */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : filter excess bandwidth                       */  \
+/*  _ms     : modulation scheme (e.g. LIQUID_MODEM_QPSK)    */  \
+SYMTRACK() SYMTRACK(_create)(int          _ftype,               \
+                             unsigned int _k,                   \
+                             unsigned int _m,                   \
+                             float        _beta,                \
+                             int          _ms);                 \
+                                                                \
+/* create symtrack object using default parameters          */  \
+SYMTRACK() SYMTRACK(_create_default)();                         \
+                                                                \
+/* destroy symtrack object, freeing all internal memory     */  \
+void SYMTRACK(_destroy)(SYMTRACK() _q);                         \
+                                                                \
+/* print symtrack object's parameters                       */  \
+void SYMTRACK(_print)(SYMTRACK() _q);                           \
+                                                                \
+/* reset symtrack internal state                            */  \
+void SYMTRACK(_reset)(SYMTRACK() _q);                           \
+                                                                \
+/* set symtrack modulation scheme                           */  \
+void SYMTRACK(_set_modscheme)(SYMTRACK() _q, int _ms);          \
+                                                                \
+/* set symtrack internal bandwidth                          */  \
+void SYMTRACK(_set_bandwidth)(SYMTRACK() _q, float _bw);        \
+                                                                \
+/* adjust internal nco by requested phase                   */  \
+void SYMTRACK(_adjust_phase)(SYMTRACK() _q, T _dphi);           \
+                                                                \
+/* execute synchronizer on single input sample              */  \
+/*  _q      : synchronizer object                           */  \
+/*  _x      : input data sample                             */  \
+/*  _y      : output data array                             */  \
+/*  _ny     : number of samples written to output buffer    */  \
+void SYMTRACK(_execute)(SYMTRACK()     _q,                      \
+                        TI             _x,                      \
+                        TO *           _y,                      \
+                        unsigned int * _ny);                    \
+                                                                \
+/* execute synchronizer on input data array                 */  \
+/*  _q      : synchronizer object                           */  \
+/*  _x      : input data array                              */  \
+/*  _nx     : number of input samples                       */  \
+/*  _y      : output data array                             */  \
+/*  _ny     : number of samples written to output buffer    */  \
+void SYMTRACK(_execute_block)(SYMTRACK()     _q,                \
+                              TI *           _x,                \
+                              unsigned int   _nx,               \
+                              TO *           _y,                \
+                              unsigned int * _ny);              \
+    
+LIQUID_SYMTRACK_DEFINE_API(SYMTRACK_MANGLE_RRRF,
+                           float,
+                           float,
+                           float,
+                           float)
+
+LIQUID_SYMTRACK_DEFINE_API(SYMTRACK_MANGLE_CCCF,
+                           float,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+
+
+//
+// MODULE : math
+//
+
+// ln( Gamma(z) )
+float liquid_lngammaf(float _z);
+
+// Gamma(z)
+float liquid_gammaf(float _z);
+
+// ln( gamma(z,alpha) ) : lower incomplete gamma function
+float liquid_lnlowergammaf(float _z, float _alpha);
+
+// ln( Gamma(z,alpha) ) : upper incomplete gamma function
+float liquid_lnuppergammaf(float _z, float _alpha);
+
+// gamma(z,alpha) : lower incomplete gamma function
+float liquid_lowergammaf(float _z, float _alpha);
+
+// Gamma(z,alpha) : upper incomplete gamma function
+float liquid_uppergammaf(float _z, float _alpha);
+
+// n!
+float liquid_factorialf(unsigned int _n);
+
+
+
+// ln(I_v(z)) : log Modified Bessel function of the first kind
+float liquid_lnbesselif(float _nu, float _z);
+
+// I_v(z) : Modified Bessel function of the first kind
+float liquid_besselif(float _nu, float _z);
+
+// I_0(z) : Modified Bessel function of the first kind (order zero)
+float liquid_besseli0f(float _z);
+
+// J_v(z) : Bessel function of the first kind
+float liquid_besseljf(float _nu, float _z);
+
+// J_0(z) : Bessel function of the first kind (order zero)
+float liquid_besselj0f(float _z);
+
+
+// Q function
+float liquid_Qf(float _z);
+
+// Marcum Q-function
+float liquid_MarcumQf(int _M,
+                      float _alpha,
+                      float _beta);
+
+// Marcum Q-function (M=1)
+float liquid_MarcumQ1f(float _alpha,
+                       float _beta);
+
+// sin(pi x) / (pi x)
+float sincf(float _x);
+
+// next power of 2 : y = ceil(log2(_x))
+unsigned int liquid_nextpow2(unsigned int _x);
+
+// (n choose k) = n! / ( k! (n-k)! )
+float liquid_nchoosek(unsigned int _n, unsigned int _k);
+
+// 
+// Windowing functions
+//
+
+// Modulation schemes available
+#define LIQUID_WINDOW_NUM_FUNCTIONS (10)
+
+// prototypes
+typedef enum {
+    LIQUID_WINDOW_UNKNOWN=0,        // unknown/unsupported scheme
+
+    LIQUID_WINDOW_HAMMING,          // Hamming
+    LIQUID_WINDOW_HANN,             // Hann
+    LIQUID_WINDOW_BLACKMANHARRIS,   // Blackman-harris (4-term)
+    LIQUID_WINDOW_BLACKMANHARRIS7,  // Blackman-harris (7-term)
+    LIQUID_WINDOW_KAISER,           // Kaiser (beta factor unspecified)
+    LIQUID_WINDOW_FLATTOP,          // flat top (includes negative values)
+    LIQUID_WINDOW_TRIANGULAR,       // triangular
+    LIQUID_WINDOW_RCOSTAPER,        // raised-cosine taper (taper size unspecified)
+    LIQUID_WINDOW_KBD,              // Kaiser-Bessel derived window (beta factor unspecified)
+} liquid_window_type;
+
+// pretty names for window
+extern const char * liquid_window_str[LIQUID_WINDOW_NUM_FUNCTIONS][2];
+
+// Print compact list of existing and available windowing functions
+void liquid_print_windows();
+
+// returns modulation_scheme based on input string
+liquid_window_type liquid_getopt_str2window(const char * _str);
+
+// Kaiser-Bessel derived window (single sample)
+//  _n      :   index (0 <= _n < _N)
+//  _N      :   length of filter (must be even)
+//  _beta   :   Kaiser window parameter (_beta > 0)
+float liquid_kbd(unsigned int _n, unsigned int _N, float _beta);
+
+// Kaiser-Bessel derived window (full window)
+//  _n      :   length of filter (must be even)
+//  _beta   :   Kaiser window parameter (_beta > 0)
+//  _w      :   resulting window
+void liquid_kbd_window(unsigned int _n, float _beta, float * _w);
+
+// Kaiser window
+//  _n      :   window index
+//  _N      :   full window length
+//  _beta   :   Kaiser-Bessel window shape parameter
+//  _dt     :   fractional sample offset
+float kaiser(unsigned int _n,
+             unsigned int _N,
+             float        _beta,
+             float        _dt);
+
+// Hamming window
+//  _n      :   window index
+//  _N      :   full window length
+float hamming(unsigned int _n,
+              unsigned int _N);
+
+// Hann window
+//  _n      :   window index
+//  _N      :   full window length
+float hann(unsigned int _n,
+           unsigned int _N);
+
+// Blackman-harris window
+//  _n      :   window index
+//  _N      :   full window length
+float blackmanharris(unsigned int _n,
+                     unsigned int _N);
+
+// 7th order Blackman-harris window
+// _n			:	window index
+// _N			:	full window length
+float blackmanharris7(unsigned int _n,
+                      unsigned int _N);
+
+// Flat-top window
+// _n			:	window index
+// _N			:	full window length
+float flattop(unsigned int _n,
+              unsigned int _N);
+
+// Triangular window
+// _n			:	window index
+// _N			:	full window length
+// _L			:	triangle length, _L in {_N, _N+1, _N-1}
+float triangular(unsigned int _n,
+                 unsigned int _N,
+                 unsigned int _L);
+
+// raised-cosine tapering window
+//  _n      :   window index
+//  _t      :   taper length
+//  _N      :   full window length
+float liquid_rcostaper_windowf(unsigned int _n,
+                               unsigned int _t,
+                               unsigned int _N);
+
+
+// polynomials
+
+
+#define POLY_MANGLE_DOUBLE(name)    LIQUID_CONCAT(poly,   name)
+#define POLY_MANGLE_FLOAT(name)     LIQUID_CONCAT(polyf,  name)
+
+#define POLY_MANGLE_CDOUBLE(name)   LIQUID_CONCAT(polyc,  name)
+#define POLY_MANGLE_CFLOAT(name)    LIQUID_CONCAT(polycf, name)
+
+// large macro
+//   POLY   : name-mangling macro
+//   T      : data type
+//   TC     : data type (complex)
+#define LIQUID_POLY_DEFINE_API(POLY,T,TC)                       \
+/* evaluate polynomial _p (order _k-1) at value _x  */          \
+T POLY(_val)(T * _p, unsigned int _k, T _x);                    \
+                                                                \
+/* least-squares polynomial fit (order _k-1) */                 \
+void POLY(_fit)(T * _x,                                         \
+                T * _y,                                         \
+                unsigned int _n,                                \
+                T * _p,                                         \
+                unsigned int _k);                               \
+                                                                \
+/* Lagrange polynomial exact fit (order _n-1) */                \
+void POLY(_fit_lagrange)(T * _x,                                \
+                         T * _y,                                \
+                         unsigned int _n,                       \
+                         T * _p);                               \
+                                                                \
+/* Lagrange polynomial interpolation */                         \
+T POLY(_interp_lagrange)(T * _x,                                \
+                         T * _y,                                \
+                         unsigned int _n,                       \
+                         T   _x0);                              \
+                                                                \
+/* Lagrange polynomial fit (barycentric form) */                \
+void POLY(_fit_lagrange_barycentric)(T * _x,                    \
+                                     unsigned int _n,           \
+                                     T * _w);                   \
+                                                                \
+/* Lagrange polynomial interpolation (barycentric form) */      \
+T POLY(_val_lagrange_barycentric)(T * _x,                       \
+                                  T * _y,                       \
+                                  T * _w,                       \
+                                  T   _x0,                      \
+                                  unsigned int _n);             \
+                                                                \
+/* expands the polynomial:                                      \
+ *  P_n(x) = (1+x)^n                                            \
+ * as                                                           \
+ *  P_n(x) = p[0] + p[1]*x + p[2]*x^2 + ... + p[n]x^n           \
+ * NOTE: _p has order n=m+k (array is length n+1)               \
+ */                                                             \
+void POLY(_expandbinomial)(unsigned int _n,                     \
+                           T * _p);                             \
+                                                                \
+/* expands the polynomial:                                      \
+ *  P_n(x) = (1+x)^m * (1-x)^k                                  \
+ * as                                                           \
+ *  P_n(x) = p[0] + p[1]*x + p[2]*x^2 + ... + p[n]x^n           \
+ * NOTE: _p has order n=m+k (array is length n+1)               \
+ */                                                             \
+void POLY(_expandbinomial_pm)(unsigned int _m,                  \
+                              unsigned int _k,                  \
+                              T * _p);                          \
+                                                                \
+/* expands the polynomial:                                      \
+ *  P_n(x) = (x-r[0]) * (x-r[1]) * ... * (x-r[n-1])             \
+ * as                                                           \
+ *  P_n(x) = c[0] + c[1]*x + ... + c[n]*x^n                     \
+ * where r[0],r[1],...,r[n-1] are the roots of P_n(x)           \
+ * NOTE: _c has order _n (array is length _n+1)                 \
+ */                                                             \
+void POLY(_expandroots)(T * _a,                                 \
+                        unsigned int _n,                        \
+                        T * _c);                                \
+                                                                \
+/* expands the polynomial:                                      \
+ *  P_n(x) =                                                    \
+ *    (x*b[0]-a[0]) * (x*b[1]-a[1]) * ... * (x*b[n-1]-a[n-1])   \
+ * as                                                           \
+ *  P_n(x) = c[0] + c[1]*x + ... + c[n]*x^n                     \
+ * NOTE: _c has order _n (array is length _n+1)                 \
+ */                                                             \
+void POLY(_expandroots2)(T * _a,                                \
+                         T * _b,                                \
+                         unsigned int _n,                       \
+                         T * _c);                               \
+                                                                \
+/* find roots of the polynomial (complex)                   */  \
+/*  _poly   : poly array, ascending powers [size: _k x 1]   */  \
+/*  _k      : poly length (poly order = _k - 1)             */  \
+/*  _roots  : resulting complex roots [size: _k-1 x 1]      */  \
+void POLY(_findroots)(T *          _poly,                       \
+                      unsigned int _n,                          \
+                      TC *         _roots);                     \
+                                                                \
+/* find the complex roots of the polynomial using the       */  \
+/* Durand-Kerner method                                     */  \
+void POLY(_findroots_durandkerner)(T *          _poly,          \
+                                   unsigned int _k,             \
+                                   TC *         _roots);        \
+                                                                \
+/* find the complex roots of the polynomial using           */  \
+/* Bairstow's method                                        */  \
+void POLY(_findroots_bairstow)(T *          _poly,              \
+                               unsigned int _k,                 \
+                               TC *         _roots);            \
+                                                                \
+/* expands the multiplication of two polynomials */             \
+void POLY(_mul)(T *          _a,                                \
+                unsigned int _order_a,                          \
+                T *          _b,                                \
+                unsigned int _order_b,                          \
+                T *          _c);                               \
+
+LIQUID_POLY_DEFINE_API(POLY_MANGLE_DOUBLE,
+                       double,
+                       liquid_double_complex)
+
+LIQUID_POLY_DEFINE_API(POLY_MANGLE_FLOAT,
+                       float,
+                       liquid_float_complex)
+
+LIQUID_POLY_DEFINE_API(POLY_MANGLE_CDOUBLE,
+                       liquid_double_complex,
+                       liquid_double_complex)
+
+LIQUID_POLY_DEFINE_API(POLY_MANGLE_CFLOAT,
+                       liquid_float_complex,
+                       liquid_float_complex)
+
+#if 0
+// expands the polynomial: (1+x)^n
+void poly_binomial_expand(unsigned int _n, int * _c);
+
+// expands the polynomial: (1+x)^k * (1-x)^(n-k)
+void poly_binomial_expand_pm(unsigned int _n,
+                             unsigned int _k,
+                             int * _c);
+#endif
+
+// 
+// modular arithmetic, etc.
+//
+
+// maximum number of factors
+#define LIQUID_MAX_FACTORS (40)
+
+// is number prime?
+int liquid_is_prime(unsigned int _n);
+
+// compute number's prime factors
+//  _n          :   number to factor
+//  _factors    :   pre-allocated array of factors [size: LIQUID_MAX_FACTORS x 1]
+//  _num_factors:   number of factors found, sorted ascending
+void liquid_factor(unsigned int   _n,
+                   unsigned int * _factors,
+                   unsigned int * _num_factors);
+
+// compute number's unique prime factors
+//  _n          :   number to factor
+//  _factors    :   pre-allocated array of factors [size: LIQUID_MAX_FACTORS x 1]
+//  _num_factors:   number of unique factors found, sorted ascending
+void liquid_unique_factor(unsigned int   _n,
+                          unsigned int * _factors,
+                          unsigned int * _num_factors);
+
+// compute c = base^exp (mod n)
+unsigned int liquid_modpow(unsigned int _base,
+                           unsigned int _exp,
+                           unsigned int _n);
+
+// find smallest primitive root of _n
+unsigned int liquid_primitive_root(unsigned int _n);
+
+// find smallest primitive root of _n, assuming _n is prime
+unsigned int liquid_primitive_root_prime(unsigned int _n);
+
+// Euler's totient function
+unsigned int liquid_totient(unsigned int _n);
+
+
+//
+// MODULE : matrix
+//
+
+#define MATRIX_MANGLE_DOUBLE(name)  LIQUID_CONCAT(matrix,   name)
+#define MATRIX_MANGLE_FLOAT(name)   LIQUID_CONCAT(matrixf,  name)
+
+#define MATRIX_MANGLE_CDOUBLE(name) LIQUID_CONCAT(matrixc,  name)
+#define MATRIX_MANGLE_CFLOAT(name)  LIQUID_CONCAT(matrixcf, name)
+
+// large macro
+//   MATRIX : name-mangling macro
+//   T      : data type
+#define LIQUID_MATRIX_DEFINE_API(MATRIX,T)                      \
+                                                                \
+/* print array as matrix                                    */  \
+/*  _x      : input matrix [size: _r x _c]                  */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_print)(T *          _x,                            \
+                    unsigned int _r,                            \
+                    unsigned int _c);                           \
+                                                                \
+/* add two matrices _x and _y saving the result in _z       */  \
+/*  _x      : input matrix  [size: _r x _c]                 */  \
+/*  _y      : input matrix  [size: _r x _c]                 */  \
+/*  _z      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_add)(T *          _x,                              \
+                  T *          _y,                              \
+                  T *          _z,                              \
+                  unsigned int _r,                              \
+                  unsigned int _c);                             \
+                                                                \
+/* subtract two matrices _x and _y saving the result in _z  */  \
+/*  _x      : input matrix  [size: _r x _c]                 */  \
+/*  _y      : input matrix  [size: _r x _c]                 */  \
+/*  _z      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_sub)(T *          _x,                              \
+                  T *          _y,                              \
+                  T *          _z,                              \
+                  unsigned int _r,                              \
+                  unsigned int _c);                             \
+                                                                \
+/* perform point-wise multiplication of two matrices _x     */  \
+/* and _y saving the result in _z                           */  \
+/*  _x      : input matrix  [size: _r x _c]                 */  \
+/*  _y      : input matrix  [size: _r x _c]                 */  \
+/*  _z      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_pmul)(T *          _x,                             \
+                   T *          _y,                             \
+                   T *          _z,                             \
+                   unsigned int _r,                             \
+                   unsigned int _c);                            \
+                                                                \
+/* perform point-wise division of two matrices _x and _y    */  \
+/* saving the result in _z                                  */  \
+/*  _x      : input matrix  [size: _r x _c]                 */  \
+/*  _y      : input matrix  [size: _r x _c]                 */  \
+/*  _z      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_pdiv)(T *          _x,                             \
+                   T *          _y,                             \
+                   T *          _z,                             \
+                   unsigned int _r,                             \
+                   unsigned int _c);                            \
+                                                                \
+/* multiply two matrices _x and _y storing the result in _z */  \
+/* NOTE: _rz = _rx, _cz = _cy, and _cx = _ry                */  \
+/*  _x      : input matrix  [size: _rx x _cx]               */  \
+/*  _y      : input matrix  [size: _ry x _cy]               */  \
+/*  _z      : output matrix [size: _rz x _cz]               */  \
+void MATRIX(_mul)(T * _x, unsigned int _rx, unsigned int _cx,   \
+                  T * _y, unsigned int _ry, unsigned int _cy,   \
+                  T * _z, unsigned int _rz, unsigned int _cz);  \
+                                                                \
+/* solve _x = _y*_z for _z for square matrices of size _n   */  \
+/*  _x      : input matrix  [size: _n x _n]                 */  \
+/*  _y      : input matrix  [size: _n x _n]                 */  \
+/*  _z      : output matrix [size: _n x _n]                 */  \
+void MATRIX(_div)(T *          _x,                              \
+                  T *          _y,                              \
+                  T *          _z,                              \
+                  unsigned int _n);                             \
+                                                                \
+/* compute the determinant of a square matrix _x            */  \
+/*  _x      : input matrix [size: _r x _c]                  */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+T MATRIX(_det)(T *          _x,                                 \
+               unsigned int _r,                                 \
+               unsigned int _c);                                \
+                                                                \
+/* compute the in-place transpose of the matrix _x          */  \
+/*  _x      : input matrix [size: _r x _c]                  */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_trans)(T *          _x,                            \
+                    unsigned int _r,                            \
+                    unsigned int _c);                           \
+                                                                \
+/* compute the in-place Hermitian transpose of _x           */  \
+/*  _x      : input matrix [size: _r x _c]                  */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_hermitian)(T *          _x,                        \
+                        unsigned int _r,                        \
+                        unsigned int _c);                       \
+                                                                \
+/* compute x*x' on [m x n] matrix, result: [m x m]          */  \
+/*  _x      : input matrix [size: _m x _n]                  */  \
+/*  _m      : input rows                                    */  \
+/*  _n      : input columns                                 */  \
+/*  _xxT    : output matrix [size: _m x _m]                 */  \
+void MATRIX(_mul_transpose)(T *          _x,                    \
+                            unsigned int _m,                    \
+                            unsigned int _n,                    \
+                            T *          _xxT);                 \
+                                                                \
+/* compute x'*x on [m x n] matrix, result: [n x n]          */  \
+/*  _x      : input matrix [size: _m x _n]                  */  \
+/*  _m      : input rows                                    */  \
+/*  _n      : input columns                                 */  \
+/*  _xTx    : output matrix [size: _n x _n]                 */  \
+void MATRIX(_transpose_mul)(T *          _x,                    \
+                            unsigned int _m,                    \
+                            unsigned int _n,                    \
+                            T *          _xTx);                 \
+                                                                \
+/* compute x*x.' on [m x n] matrix, result: [m x m]         */  \
+/*  _x      : input matrix [size: _m x _n]                  */  \
+/*  _m      : input rows                                    */  \
+/*  _n      : input columns                                 */  \
+/*  _xxH    : output matrix [size: _m x _m]                 */  \
+void MATRIX(_mul_hermitian)(T *          _x,                    \
+                            unsigned int _m,                    \
+                            unsigned int _n,                    \
+                            T *          _xxH);                 \
+                                                                \
+/* compute x.'*x on [m x n] matrix, result: [n x n]         */  \
+/*  _x      : input matrix [size: _m x _n]                  */  \
+/*  _m      : input rows                                    */  \
+/*  _n      : input columns                                 */  \
+/*  _xHx    : output matrix [size: _n x _n]                 */  \
+void MATRIX(_hermitian_mul)(T *          _x,                    \
+                            unsigned int _m,                    \
+                            unsigned int _n,                    \
+                            T *          _xHx);                 \
+                                                                \
+                                                                \
+/* augment two matrices _x and _y storing the result in _z  */  \
+/* NOTE: _rz = _rx = _ry, _rx = _ry, and _cz = _cx + _cy    */  \
+/*  _x      : input matrix  [size: _rx x _cx]               */  \
+/*  _y      : input matrix  [size: _ry x _cy]               */  \
+/*  _z      : output matrix [size: _rz x _cz]               */  \
+void MATRIX(_aug)(T * _x, unsigned int _rx, unsigned int _cx,   \
+                  T * _y, unsigned int _ry, unsigned int _cy,   \
+                  T * _z, unsigned int _rz, unsigned int _cz);  \
+                                                                \
+/* compute the inverse of a square matrix _x                */  \
+/*  _x      : input/output matrix [size: _r x _c]           */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_inv)(T *          _x,                              \
+                  unsigned int _r,                              \
+                  unsigned int _c);                             \
+                                                                \
+/* generate the identity square matrix of size _n           */  \
+/*  _x      : output matrix [size: _n x _n]                 */  \
+/*  _n      : dimensions of _x                              */  \
+void MATRIX(_eye)(T *          _x,                              \
+                  unsigned int _n);                             \
+                                                                \
+/* generate the all-ones matrix of size _n                  */  \
+/*  _x      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_ones)(T *          _n,                             \
+                   unsigned int _r,                             \
+                   unsigned int _c);                            \
+                                                                \
+/* generate the all-zeros matrix of size _n                 */  \
+/*  _x      : output matrix [size: _r x _c]                 */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_zeros)(T *          _x,                            \
+                    unsigned int _r,                            \
+                    unsigned int _c);                           \
+                                                                \
+/* perform Gauss-Jordan elimination on matrix _x            */  \
+/*  _x      : input/output matrix [size: _r x _c]           */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+void MATRIX(_gjelim)(T *          _x,                           \
+                     unsigned int _r,                           \
+                     unsigned int _c);                          \
+                                                                \
+/* pivot on element _x[_r,_c]                               */  \
+/*  _x      : output matrix [size: _rx x _cx]               */  \
+/*  _rx     : rows of _x                                    */  \
+/*  _cx     : columns of _x                                 */  \
+/*  _r      : pivot row                                     */  \
+/*  _c      : pivot column                                  */  \
+void MATRIX(_pivot)(T *          _x,                            \
+                    unsigned int _rx,                           \
+                    unsigned int _cx,                           \
+                    unsigned int _r,                            \
+                    unsigned int _c);                           \
+                                                                \
+/* swap rows _r1 and _r2 of matrix _x                       */  \
+/*  _x      : input/output matrix [size: _rx x _cx]         */  \
+/*  _rx     : rows of _x                                    */  \
+/*  _cx     : columns of _x                                 */  \
+/*  _r1     : first row to swap                             */  \
+/*  _r2     : second row to swap                            */  \
+void MATRIX(_swaprows)(T *          _x,                         \
+                       unsigned int _rx,                        \
+                       unsigned int _cx,                        \
+                       unsigned int _r1,                        \
+                       unsigned int _r2);                       \
+                                                                \
+/* solve linear system of _n equations: _A*_x = _b          */  \
+/*  _A      :   system matrix [size: _n x _n]               */  \
+/*  _n      :   system size                                 */  \
+/*  _b      :   equality vector [size: _n x 1]              */  \
+/*  _x      :   solution vector [size: _n x 1]              */  \
+/*  _opts   :   options (ignored for now)                   */  \
+void MATRIX(_linsolve)(T *          _A,                         \
+                       unsigned int _n,                         \
+                       T *          _b,                         \
+                       T *          _x,                         \
+                       void *       _opts);                     \
+                                                                \
+/* solve linear system of equations using conjugate         */  \
+/* gradient method                                          */  \
+/*  _A      :   symmetric positive definite square matrix   */  \
+/*  _n      :   system dimension                            */  \
+/*  _b      :   equality [size: _n x 1]                     */  \
+/*  _x      :   solution estimate [size: _n x 1]            */  \
+/*  _opts   :   options (ignored for now)                   */  \
+void MATRIX(_cgsolve)(T *          _A,                          \
+                      unsigned int _n,                          \
+                      T *          _b,                          \
+                      T *          _x,                          \
+                      void *       _opts);                      \
+                                                                \
+/* L/U/P decomposition, Crout's method                      */  \
+/*  _x      : input/output matrix [size: _rx x _cx]         */  \
+/*  _rx     : rows of _x                                    */  \
+/*  _cx     : columns of _x                                 */  \
+/*  _L      : first row to swap                             */  \
+/*  _U      : first row to swap                             */  \
+/*  _P      : first row to swap                             */  \
+void MATRIX(_ludecomp_crout)(T *          _x,                   \
+                             unsigned int _rx,                  \
+                             unsigned int _cx,                  \
+                             T *          _L,                   \
+                             T *          _U,                   \
+                             T *          _P);                  \
+                                                                \
+/* L/U/P decomposition, Doolittle's method                  */  \
+/*  _x      : input/output matrix [size: _rx x _cx]         */  \
+/*  _rx     : rows of _x                                    */  \
+/*  _cx     : columns of _x                                 */  \
+/*  _L      : first row to swap                             */  \
+/*  _U      : first row to swap                             */  \
+/*  _P      : first row to swap                             */  \
+void MATRIX(_ludecomp_doolittle)(T *          _x,               \
+                                 unsigned int _rx,              \
+                                 unsigned int _cx,              \
+                                 T *          _L,               \
+                                 T *          _U,               \
+                                 T *          _P);              \
+                                                                \
+/* Orthnormalization using the Gram-Schmidt algorithm       */  \
+/*  _A      : input matrix [size: _r x _c]                  */  \
+/*  _r      : rows                                          */  \
+/*  _c      : columns                                       */  \
+/*  _v      : output matrix                                 */  \
+void MATRIX(_gramschmidt)(T *          _A,                      \
+                          unsigned int _r,                      \
+                          unsigned int _c,                      \
+                          T *          _v);                     \
+                                                                \
+/* Q/R decomposition using the Gram-Schmidt algorithm such  */  \
+/* that _A = _Q*_R and _Q^T * _Q = _In and _R is a diagonal */  \
+/* matrix                                                   */  \
+/* NOTE: all matrices are square                            */  \
+/*  _A      : input matrix [size: _m x _m]                  */  \
+/*  _m      : rows                                          */  \
+/*  _n      : columns (same as cols)                        */  \
+/*  _Q      : output matrix [size: _m x _m]                 */  \
+/*  _R      : output matrix [size: _m x _m]                 */  \
+void MATRIX(_qrdecomp_gramschmidt)(T *          _A,             \
+                                   unsigned int _m,             \
+                                   unsigned int _n,             \
+                                   T *          _Q,             \
+                                   T *          _R);            \
+                                                                \
+/* Compute Cholesky decomposition of a symmetric/Hermitian  */  \
+/* positive-definite matrix as A = L * L^T                  */  \
+/*  _A      :   input square matrix [size: _n x _n]         */  \
+/*  _n      :   input matrix dimension                      */  \
+/*  _L      :   output lower-triangular matrix              */  \
+void MATRIX(_chol)(T *          _A,                             \
+                   unsigned int _n,                             \
+                   T *          _L);                            \
+
+#define matrix_access(X,R,C,r,c) ((X)[(r)*(C)+(c)])
+
+#define matrixc_access(X,R,C,r,c)   matrix_access(X,R,C,r,c)
+#define matrixf_access(X,R,C,r,c)   matrix_access(X,R,C,r,c)
+#define matrixcf_access(X,R,C,r,c)  matrix_access(X,R,C,r,c)
+
+LIQUID_MATRIX_DEFINE_API(MATRIX_MANGLE_FLOAT,   float)
+LIQUID_MATRIX_DEFINE_API(MATRIX_MANGLE_DOUBLE,  double)
+
+LIQUID_MATRIX_DEFINE_API(MATRIX_MANGLE_CFLOAT,  liquid_float_complex)
+LIQUID_MATRIX_DEFINE_API(MATRIX_MANGLE_CDOUBLE, liquid_double_complex)
+
+
+#define SMATRIX_MANGLE_BOOL(name)   LIQUID_CONCAT(smatrixb,  name)
+#define SMATRIX_MANGLE_FLOAT(name)  LIQUID_CONCAT(smatrixf,  name)
+#define SMATRIX_MANGLE_INT(name)    LIQUID_CONCAT(smatrixi,  name)
+
+// sparse 'alist' matrix type (similar to MacKay, Davey Lafferty convention)
+// large macro
+//   SMATRIX    : name-mangling macro
+//   T          : primitive data type
+#define LIQUID_SMATRIX_DEFINE_API(SMATRIX,T)                    \
+typedef struct SMATRIX(_s) * SMATRIX();                         \
+                                                                \
+/* create _M x _N matrix, initialized with zeros */             \
+SMATRIX() SMATRIX(_create)(unsigned int _M,                     \
+                           unsigned int _N);                    \
+                                                                \
+/* create _M x _N matrix, initialized on array */               \
+SMATRIX() SMATRIX(_create_array)(T *          _x,               \
+                                 unsigned int _m,               \
+                                 unsigned int _n);              \
+                                                                \
+/* destroy object */                                            \
+void SMATRIX(_destroy)(SMATRIX() _q);                           \
+                                                                \
+/* print compact form */                                        \
+void SMATRIX(_print)(SMATRIX() _q);                             \
+                                                                \
+/* print expanded form */                                       \
+void SMATRIX(_print_expanded)(SMATRIX() _q);                    \
+                                                                \
+/* query properties methods */                                  \
+void SMATRIX(_size)(SMATRIX()      _q,                          \
+                    unsigned int * _m,                          \
+                    unsigned int * _n);                         \
+                                                                \
+/* zero all elements */                                         \
+void SMATRIX(_clear)(SMATRIX() _q); /* zero and keep memory  */ \
+void SMATRIX(_reset)(SMATRIX() _q); /* zero and clear memory */ \
+                                                                \
+/* determine if value has been set (allocated memory) */        \
+int SMATRIX(_isset)(SMATRIX()    _q,                            \
+                    unsigned int _m,                            \
+                    unsigned int _n);                           \
+                                                                \
+/* inserts/deletes element at index (memory allocation) */      \
+void SMATRIX(_insert)(SMATRIX()    _q,                          \
+                      unsigned int _m,                          \
+                      unsigned int _n,                          \
+                      T            _v);                         \
+void SMATRIX(_delete)(SMATRIX()    _q,                          \
+                      unsigned int _m,                          \
+                      unsigned int _n);                         \
+                                                                \
+/* sets/gets the value (with memory allocation if needed) */    \
+void SMATRIX(_set)(SMATRIX()    _q,                             \
+                   unsigned int _m,                             \
+                   unsigned int _n,                             \
+                   T            _v);                            \
+T SMATRIX(_get)(SMATRIX()    _q,                                \
+                unsigned int _m,                                \
+                unsigned int _n);                               \
+                                                                \
+/* initialize to identity matrix */                             \
+void SMATRIX(_eye)(SMATRIX() _q);                               \
+                                                                \
+/* multiply two sparse binary matrices */                       \
+void SMATRIX(_mul)(SMATRIX() _x,                                \
+                   SMATRIX() _y,                                \
+                   SMATRIX() _z);                               \
+                                                                \
+/* multiply sparse matrix by vector         */                  \
+/*  _q  :   sparse matrix                   */                  \
+/*  _x  :   input vector [size: _N x 1]     */                  \
+/*  _y  :   output vector [size: _M x 1]    */                  \
+void SMATRIX(_vmul)(SMATRIX() _q,                               \
+                    T *       _x,                               \
+                    T *       _y);                              \
+
+LIQUID_SMATRIX_DEFINE_API(SMATRIX_MANGLE_BOOL,  unsigned char)
+LIQUID_SMATRIX_DEFINE_API(SMATRIX_MANGLE_FLOAT, float)
+LIQUID_SMATRIX_DEFINE_API(SMATRIX_MANGLE_INT,   short int)
+
+// 
+// smatrix cross methods
+//
+
+// multiply sparse binary matrix by floating-point matrix
+//  _q  :   sparse matrix [size: A->M x A->N]
+//  _x  :   input vector  [size:  mx  x  nx ]
+//  _y  :   output vector [size:  my  x  ny ]
+void smatrixb_mulf(smatrixb     _A,
+                   float *      _x,
+                   unsigned int _mx,
+                   unsigned int _nx,
+                   float *      _y,
+                   unsigned int _my,
+                   unsigned int _ny);
+
+// multiply sparse binary matrix by floating-point vector
+//  _q  :   sparse matrix
+//  _x  :   input vector [size: _N x 1]
+//  _y  :   output vector [size: _M x 1]
+void smatrixb_vmulf(smatrixb _q,
+                    float *  _x,
+                    float *  _y);
+
+
+//
+// MODULE : modem (modulator/demodulator)
+//
+
+// Maximum number of allowed bits per symbol
+#define MAX_MOD_BITS_PER_SYMBOL 8
+
+// Modulation schemes available
+#define LIQUID_MODEM_NUM_SCHEMES      (52)
+
+typedef enum {
+    LIQUID_MODEM_UNKNOWN=0, // Unknown modulation scheme
+
+    // Phase-shift keying (PSK)
+    LIQUID_MODEM_PSK2,      LIQUID_MODEM_PSK4,
+    LIQUID_MODEM_PSK8,      LIQUID_MODEM_PSK16,
+    LIQUID_MODEM_PSK32,     LIQUID_MODEM_PSK64,
+    LIQUID_MODEM_PSK128,    LIQUID_MODEM_PSK256,
+
+    // Differential phase-shift keying (DPSK)
+    LIQUID_MODEM_DPSK2,     LIQUID_MODEM_DPSK4,
+    LIQUID_MODEM_DPSK8,     LIQUID_MODEM_DPSK16,
+    LIQUID_MODEM_DPSK32,    LIQUID_MODEM_DPSK64,
+    LIQUID_MODEM_DPSK128,   LIQUID_MODEM_DPSK256,
+
+    // amplitude-shift keying
+    LIQUID_MODEM_ASK2,      LIQUID_MODEM_ASK4,
+    LIQUID_MODEM_ASK8,      LIQUID_MODEM_ASK16,
+    LIQUID_MODEM_ASK32,     LIQUID_MODEM_ASK64,
+    LIQUID_MODEM_ASK128,    LIQUID_MODEM_ASK256,
+
+    // rectangular quadrature amplitude-shift keying (QAM)
+    LIQUID_MODEM_QAM4,
+    LIQUID_MODEM_QAM8,      LIQUID_MODEM_QAM16,
+    LIQUID_MODEM_QAM32,     LIQUID_MODEM_QAM64,
+    LIQUID_MODEM_QAM128,    LIQUID_MODEM_QAM256,
+
+    // amplitude phase-shift keying (APSK)
+    LIQUID_MODEM_APSK4,
+    LIQUID_MODEM_APSK8,     LIQUID_MODEM_APSK16,
+    LIQUID_MODEM_APSK32,    LIQUID_MODEM_APSK64,
+    LIQUID_MODEM_APSK128,   LIQUID_MODEM_APSK256,
+
+    // specific modem types
+    LIQUID_MODEM_BPSK,      // Specific: binary PSK
+    LIQUID_MODEM_QPSK,      // specific: quaternary PSK
+    LIQUID_MODEM_OOK,       // Specific: on/off keying
+    LIQUID_MODEM_SQAM32,    // 'square' 32-QAM
+    LIQUID_MODEM_SQAM128,   // 'square' 128-QAM
+    LIQUID_MODEM_V29,       // V.29 star constellation
+    LIQUID_MODEM_ARB16OPT,  // optimal 16-QAM
+    LIQUID_MODEM_ARB32OPT,  // optimal 32-QAM
+    LIQUID_MODEM_ARB64OPT,  // optimal 64-QAM
+    LIQUID_MODEM_ARB128OPT, // optimal 128-QAM
+    LIQUID_MODEM_ARB256OPT, // optimal 256-QAM
+    LIQUID_MODEM_ARB64VT,   // Virginia Tech logo
+
+    // arbitrary modem type
+    LIQUID_MODEM_ARB        // arbitrary QAM
+} modulation_scheme;
+
+// structure for holding full modulation type descriptor
+struct modulation_type_s {
+    const char * name;          // short name (e.g. 'bpsk')
+    const char * fullname;      // full name (e.g. 'binary phase-shift keying')
+    modulation_scheme scheme;   // modulation scheme (e.g. LIQUID_MODEM_BPSK)
+    unsigned int bps;           // modulation depth (e.g. 1)
+};
+
+// full modulation type descriptor
+extern const struct modulation_type_s modulation_types[LIQUID_MODEM_NUM_SCHEMES];
+
+// Print compact list of existing and available modulation schemes
+void liquid_print_modulation_schemes();
+
+// returns modulation_scheme based on input string
+modulation_scheme liquid_getopt_str2mod(const char * _str);
+
+// query basic modulation types
+int liquid_modem_is_psk(modulation_scheme _ms);
+int liquid_modem_is_dpsk(modulation_scheme _ms);
+int liquid_modem_is_ask(modulation_scheme _ms);
+int liquid_modem_is_qam(modulation_scheme _ms);
+int liquid_modem_is_apsk(modulation_scheme _ms);
+
+// useful functions
+
+// counts the number of different bits between two symbols
+unsigned int count_bit_errors(unsigned int _s1, unsigned int _s2);
+
+// counts the number of different bits between two arrays of symbols
+//  _msg0   :   original message [size: _n x 1]
+//  _msg1   :   copy of original message [size: _n x 1]
+//  _n      :   message size
+unsigned int count_bit_errors_array(unsigned char * _msg0,
+                                    unsigned char * _msg1,
+                                    unsigned int _n);
+
+// converts binary-coded decimal (BCD) to gray, ensuring successive values
+// differ by exactly one bit
+unsigned int gray_encode(unsigned int symbol_in);
+
+// converts a gray-encoded symbol to binary-coded decimal (BCD)
+unsigned int gray_decode(unsigned int symbol_in);
+
+// pack soft bits into symbol
+//  _soft_bits  :   soft input bits [size: _bps x 1]
+//  _bps        :   bits per symbol
+//  _sym_out    :   output symbol, value in [0,2^_bps)
+void liquid_pack_soft_bits(unsigned char * _soft_bits,
+                           unsigned int _bps,
+                           unsigned int * _sym_out);
+
+// unpack soft bits into symbol
+//  _sym_in     :   input symbol, value in [0,2^_bps)
+//  _bps        :   bits per symbol
+//  _soft_bits  :   soft output bits [size: _bps x 1]
+void liquid_unpack_soft_bits(unsigned int _sym_in,
+                             unsigned int _bps,
+                             unsigned char * _soft_bits);
+
+
+//
+// Linear modem
+//
+
+#define LIQUID_MODEM_MANGLE_FLOAT(name) LIQUID_CONCAT(modem,name)
+
+// Macro    :   MODEM
+//  MODEM   :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_MODEM_DEFINE_API(MODEM,T,TC)                     \
+                                                                \
+/* define struct pointer */                                     \
+typedef struct MODEM(_s) * MODEM();                             \
+                                                                \
+/* create digital modem object                              */  \
+MODEM() MODEM(_create)(modulation_scheme _scheme);              \
+                                                                \
+/* create arbitrary digital modem object                    */  \
+/*  _table  :   array of complex constellation points       */  \
+/*  _M      :   modulation order and table size             */  \
+MODEM() MODEM(_create_arbitrary)(TC *         _table,           \
+                                 unsigned int _M);              \
+                                                                \
+/* recreate modulation scheme, re-allocating memory as      */  \
+/* necessary                                                */  \
+MODEM() MODEM(_recreate)(MODEM()           _q,                  \
+                         modulation_scheme _scheme);            \
+                                                                \
+void MODEM(_destroy)(MODEM() _q);                               \
+void MODEM(_print)(  MODEM() _q);                               \
+void MODEM(_reset)(  MODEM() _q);                               \
+                                                                \
+/* generate random symbol                                   */  \
+unsigned int MODEM(_gen_rand_sym)(MODEM() _q);                  \
+                                                                \
+/* Accessor functions */                                        \
+unsigned int      MODEM(_get_bps)   (MODEM() _q);               \
+modulation_scheme MODEM(_get_scheme)(MODEM() _q);               \
+                                                                \
+/* generic modulate function; simply queries modem scheme   */  \
+/* and calls appropriate subroutine                         */  \
+/*  _q  :   modem object                                    */  \
+/*  _s  :   input symbol                                    */  \
+/*  _x  :   output sample                                   */  \
+void MODEM(_modulate)(MODEM()      _q,                          \
+                      unsigned int _s,                          \
+                      TC *         _y);                         \
+                                                                \
+/* generic hard-decision demodulation function              */  \
+/*  _q  :   modem object                                    */  \
+/*  _x  :   input sample                                    */  \
+/*  _s  :   output symbol                                   */  \
+void MODEM(_demodulate)(MODEM()        _q,                      \
+                        TC             _x,                      \
+                        unsigned int * _s);                     \
+                                                                \
+/* generic soft-decision demodulation function              */  \
+/*  _q          :   modem object                            */  \
+/*  _x          :   input sample                            */  \
+/*  _s          :   output hard symbol                      */  \
+/*  _soft_bits  :   output soft bits                        */  \
+void MODEM(_demodulate_soft)(MODEM()         _q,                \
+                             TC              _x,                \
+                             unsigned int  * _s,                \
+                             unsigned char * _soft_bits);       \
+                                                                \
+/* get demodulator's estimated transmit sample */               \
+void MODEM(_get_demodulator_sample)(MODEM() _q,                 \
+                                    TC *    _x_hat);            \
+                                                                \
+/* get demodulator phase error */                               \
+float MODEM(_get_demodulator_phase_error)(MODEM() _q);          \
+                                                                \
+/* get demodulator error vector magnitude */                    \
+float MODEM(_get_demodulator_evm)(MODEM() _q);                  \
+
+// define modem APIs
+LIQUID_MODEM_DEFINE_API(LIQUID_MODEM_MANGLE_FLOAT,float,liquid_float_complex)
+
+
+//
+// continuous-phase modulation
+//
+
+// gmskmod : GMSK modulator
+typedef struct gmskmod_s * gmskmod;
+
+// create gmskmod object
+//  _k      :   samples/symbol
+//  _m      :   filter delay (symbols)
+//  _BT     :   excess bandwidth factor
+gmskmod gmskmod_create(unsigned int _k,
+                       unsigned int _m,
+                       float        _BT);
+void gmskmod_destroy(gmskmod _q);
+void gmskmod_print(gmskmod _q);
+void gmskmod_reset(gmskmod _q);
+void gmskmod_modulate(gmskmod _q,
+                      unsigned int _sym,
+                      liquid_float_complex * _y);
+
+
+// gmskdem : GMSK demodulator
+typedef struct gmskdem_s * gmskdem;
+
+// create gmskdem object
+//  _k      :   samples/symbol
+//  _m      :   filter delay (symbols)
+//  _BT     :   excess bandwidth factor
+gmskdem gmskdem_create(unsigned int _k,
+                       unsigned int _m,
+                       float        _BT);
+void gmskdem_destroy(gmskdem _q);
+void gmskdem_print(gmskdem _q);
+void gmskdem_reset(gmskdem _q);
+void gmskdem_set_eq_bw(gmskdem _q, float _bw);
+void gmskdem_demodulate(gmskdem _q,
+                        liquid_float_complex * _y,
+                        unsigned int * _sym);
+
+// 
+// Analog frequency modulator
+//
+#define LIQUID_FREQMOD_MANGLE_FLOAT(name) LIQUID_CONCAT(freqmod,name)
+
+// Macro    :   FREQMOD (analog frequency modulator)
+//  FREQMOD :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_FREQMOD_DEFINE_API(FREQMOD,T,TC)                 \
+                                                                \
+/* define struct pointer */                                     \
+typedef struct FREQMOD(_s) * FREQMOD();                         \
+                                                                \
+/* create freqmod object (frequency modulator)              */  \
+/*  _kf     :   modulation factor                           */  \
+FREQMOD() FREQMOD(_create)(float _kf);                          \
+                                                                \
+/* destroy freqmod object                                   */  \
+void FREQMOD(_destroy)(FREQMOD() _q);                           \
+                                                                \
+/* print freqmod object internals                           */  \
+void FREQMOD(_print)(FREQMOD() _q);                             \
+                                                                \
+/* reset state                                              */  \
+void FREQMOD(_reset)(FREQMOD() _q);                             \
+                                                                \
+/* modulate single sample                                   */  \
+/*  _q      :   frequency modulator object                  */  \
+/*  _m      :   message signal m(t)                         */  \
+/*  _s      :   complex baseband signal s(t)                */  \
+void FREQMOD(_modulate)(FREQMOD() _q,                           \
+                        T         _m,                           \
+                        TC *      _s);                          \
+                                                                \
+/* modulate block of samples                                */  \
+/*  _q      :   frequency modulator object                  */  \
+/*  _m      :   message signal m(t), [size: _n x 1]         */  \
+/*  _n      :   number of input, output samples             */  \
+/*  _s      :   complex baseband signal s(t) [size: _n x 1] */  \
+void FREQMOD(_modulate_block)(FREQMOD()    _q,                  \
+                              T *          _m,                  \
+                              unsigned int _n,                  \
+                              TC *         _s);                 \
+
+// define freqmod APIs
+LIQUID_FREQMOD_DEFINE_API(LIQUID_FREQMOD_MANGLE_FLOAT,float,liquid_float_complex)
+
+//
+// continuous phase frequency-shift keying (CP-FSK) modems
+//
+
+// CP-FSK filter prototypes
+typedef enum {
+    LIQUID_CPFSK_SQUARE=0,      // square pulse
+    LIQUID_CPFSK_RCOS_FULL,     // raised-cosine (full response)
+    LIQUID_CPFSK_RCOS_PARTIAL,  // raised-cosine (partial response)
+    LIQUID_CPFSK_GMSK,          // Gauss minimum-shift keying pulse
+} liquid_cpfsk_filter;
+
+// CP-FSK modulator
+typedef struct cpfskmod_s * cpfskmod;
+
+// create cpfskmod object (frequency modulator)
+//  _bps    :   bits per symbol, _bps > 0
+//  _h      :   modulation index, _h > 0
+//  _k      :   samples/symbol, _k > 1, _k even
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   filter bandwidth parameter, _beta > 0
+//  _type   :   filter type (e.g. LIQUID_CPFSK_SQUARE)
+cpfskmod cpfskmod_create(unsigned int _bps,
+                         float        _h,
+                         unsigned int _k,
+                         unsigned int _m,
+                         float        _beta,
+                         int          _type);
+//cpfskmod cpfskmod_create_msk(unsigned int _k);
+//cpfskmod cpfskmod_create_gmsk(unsigned int _k, float _BT);
+
+// destroy cpfskmod object
+void cpfskmod_destroy(cpfskmod _q);
+
+// print cpfskmod object internals
+void cpfskmod_print(cpfskmod _q);
+
+// reset state
+void cpfskmod_reset(cpfskmod _q);
+
+// get transmit delay [symbols]
+unsigned int cpfskmod_get_delay(cpfskmod _q);
+
+// modulate sample
+//  _q      :   frequency modulator object
+//  _s      :   input symbol
+//  _y      :   output sample array [size: _k x 1]
+void cpfskmod_modulate(cpfskmod               _q,
+                       unsigned int           _s,
+                       liquid_float_complex * _y);
+
+
+
+// CP-FSK demodulator
+typedef struct cpfskdem_s * cpfskdem;
+
+// create cpfskdem object (frequency modulator)
+//  _bps    :   bits per symbol, _bps > 0
+//  _h      :   modulation index, _h > 0
+//  _k      :   samples/symbol, _k > 1, _k even
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   filter bandwidth parameter, _beta > 0
+//  _type   :   filter type (e.g. LIQUID_CPFSK_SQUARE)
+cpfskdem cpfskdem_create(unsigned int _bps,
+                         float        _h,
+                         unsigned int _k,
+                         unsigned int _m,
+                         float        _beta,
+                         int          _type);
+//cpfskdem cpfskdem_create_msk(unsigned int _k);
+//cpfskdem cpfskdem_create_gmsk(unsigned int _k, float _BT);
+
+// destroy cpfskdem object
+void cpfskdem_destroy(cpfskdem _q);
+
+// print cpfskdem object internals
+void cpfskdem_print(cpfskdem _q);
+
+// reset state
+void cpfskdem_reset(cpfskdem _q);
+
+// get receive delay [symbols]
+unsigned int cpfskdem_get_delay(cpfskdem _q);
+
+#if 0
+// demodulate array of samples
+//  _q      :   continuous-phase frequency demodulator object
+//  _y      :   input sample array [size: _n x 1]
+//  _n      :   input sample array length
+//  _s      :   output symbol array
+//  _nw     :   number of output symbols written
+void cpfskdem_demodulate(cpfskdem               _q,
+                         liquid_float_complex * _y,
+                         unsigned int           _n,
+                         unsigned int         * _s,
+                         unsigned int         * _nw);
+#else
+// demodulate array of samples, assuming perfect timing
+//  _q      :   continuous-phase frequency demodulator object
+//  _y      :   input sample array [size: _k x 1]
+unsigned int cpfskdem_demodulate(cpfskdem               _q,
+                                 liquid_float_complex * _y);
+#endif
+
+
+
+//
+// M-ary frequency-shift keying (MFSK) modems
+//
+
+// FSK modulator
+typedef struct fskmod_s * fskmod;
+
+// create fskmod object (frequency modulator)
+//  _m          :   bits per symbol, _bps > 0
+//  _k          :   samples/symbol, _k >= 2^_m
+//  _bandwidth  :   total signal bandwidth, (0,0.5)
+fskmod fskmod_create(unsigned int _m,
+                     unsigned int _k,
+                     float        _bandwidth);
+
+// destroy fskmod object
+void fskmod_destroy(fskmod _q);
+
+// print fskmod object internals
+void fskmod_print(fskmod _q);
+
+// reset state
+void fskmod_reset(fskmod _q);
+
+// modulate sample
+//  _q      :   frequency modulator object
+//  _s      :   input symbol
+//  _y      :   output sample array [size: _k x 1]
+void fskmod_modulate(fskmod                 _q,
+                     unsigned int           _s,
+                     liquid_float_complex * _y);
+
+
+
+// CP-FSK demodulator
+typedef struct fskdem_s * fskdem;
+
+// create fskdem object (frequency demodulator)
+//  _m          :   bits per symbol, _bps > 0
+//  _k          :   samples/symbol, _k >= 2^_m
+//  _bandwidth  :   total signal bandwidth, (0,0.5)
+fskdem fskdem_create(unsigned int _m,
+                     unsigned int _k,
+                     float        _bandwidth);
+
+// destroy fskdem object
+void fskdem_destroy(fskdem _q);
+
+// print fskdem object internals
+void fskdem_print(fskdem _q);
+
+// reset state
+void fskdem_reset(fskdem _q);
+
+// demodulate symbol, assuming perfect symbol timing
+//  _q      :   fskdem object
+//  _y      :   input sample array [size: _k x 1]
+unsigned int fskdem_demodulate(fskdem                 _q,
+                               liquid_float_complex * _y);
+
+// get demodulator frequency error
+float fskdem_get_frequency_error(fskdem _q);
+
+
+// 
+// Analog frequency demodulator
+//
+
+#define LIQUID_FREQDEM_MANGLE_FLOAT(name) LIQUID_CONCAT(freqdem,name)
+
+// Macro    :   FREQDEM (analog frequency modulator)
+//  FREQDEM :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_FREQDEM_DEFINE_API(FREQDEM,T,TC)                 \
+                                                                \
+/* define struct pointer */                                     \
+typedef struct FREQDEM(_s) * FREQDEM();                         \
+                                                                \
+/* create freqdem object (frequency modulator)              */  \
+/*  _kf      :   modulation factor                          */  \
+FREQDEM() FREQDEM(_create)(float _kf);                          \
+                                                                \
+/* destroy freqdem object                                   */  \
+void FREQDEM(_destroy)(FREQDEM() _q);                           \
+                                                                \
+/* print freqdem object internals                           */  \
+void FREQDEM(_print)(FREQDEM() _q);                             \
+                                                                \
+/* reset state                                              */  \
+void FREQDEM(_reset)(FREQDEM() _q);                             \
+                                                                \
+/* demodulate sample                                        */  \
+/*  _q      :   frequency modulator object                  */  \
+/*  _r      :   received signal r(t)                        */  \
+/*  _m      :   output message signal m(t)                  */  \
+void FREQDEM(_demodulate)(FREQDEM() _q,                         \
+                          TC        _r,                         \
+                          T *       _m);                        \
+                                                                \
+/* demodulate block of samples                              */  \
+/*  _q      :   frequency demodulator object                */  \
+/*  _r      :   received signal r(t) [size: _n x 1]         */  \
+/*  _n      :   number of input, output samples             */  \
+/*  _m      :   message signal m(t), [size: _n x 1]         */  \
+void FREQDEM(_demodulate_block)(FREQDEM()    _q,                \
+                                TC *         _r,                \
+                                unsigned int _n,                \
+                                T *          _m);               \
+
+// define freqdem APIs
+LIQUID_FREQDEM_DEFINE_API(LIQUID_FREQDEM_MANGLE_FLOAT,float,liquid_float_complex)
+
+
+
+// amplitude modulation types
+typedef enum {
+    LIQUID_AMPMODEM_DSB=0,  // double side-band
+    LIQUID_AMPMODEM_USB,    // single side-band (upper)
+    LIQUID_AMPMODEM_LSB     // single side-band (lower)
+} liquid_ampmodem_type;
+
+typedef struct ampmodem_s * ampmodem;
+
+// create ampmodem object
+//  _m                  :   modulation index
+//  _fc                 :   carrier frequency, range: [-0.5,0.5]
+//  _type               :   AM type (e.g. LIQUID_AMPMODEM_DSB)
+//  _suppressed_carrier :   carrier suppression flag
+ampmodem ampmodem_create(float _m,
+                         float _fc,
+                         liquid_ampmodem_type _type,
+                         int _suppressed_carrier);
+
+// destroy ampmodem object
+void ampmodem_destroy(ampmodem _fm);
+
+// print ampmodem object internals
+void ampmodem_print(ampmodem _fm);
+
+// reset ampmodem object state
+void ampmodem_reset(ampmodem _fm);
+
+// modulate sample
+void ampmodem_modulate(ampmodem _fm,
+                       float _x,
+                       liquid_float_complex *_y);
+
+void ampmodem_modulate_block(ampmodem _q,
+                             float * _m,
+                             unsigned int _n,
+                             liquid_float_complex *_s);
+
+// demodulate sample
+void ampmodem_demodulate(ampmodem _fm,
+                         liquid_float_complex _y,
+                         float *_x);
+
+void ampmodem_demodulate_block(ampmodem _q,
+                               liquid_float_complex * _r,
+                               unsigned int _n,
+                               float * _m);
+
+//
+// MODULE : multichannel
+//
+
+
+#define FIRPFBCH_NYQUIST        0
+#define FIRPFBCH_ROOTNYQUIST    1
+
+#define LIQUID_ANALYZER         0
+#define LIQUID_SYNTHESIZER      1
+
+
+//
+// Finite impulse response polyphase filterbank channelizer
+//
+
+#define FIRPFBCH_MANGLE_CRCF(name)  LIQUID_CONCAT(firpfbch_crcf,name)
+#define FIRPFBCH_MANGLE_CCCF(name)  LIQUID_CONCAT(firpfbch_cccf,name)
+
+// Macro:
+//   FIRPFBCH   : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_FIRPFBCH_DEFINE_API(FIRPFBCH,TO,TC,TI)           \
+typedef struct FIRPFBCH(_s) * FIRPFBCH();                       \
+                                                                \
+/* create finite impulse response polyphase filter-bank     */  \
+/* channelizer object from external coefficients            */  \
+/*  _type   : channelizer type, e.g. LIQUID_ANALYZER        */  \
+/*  _M      : number of channels                            */  \
+/*  _p      : number of coefficients for each channel       */  \
+/*  _h      : coefficients [size: _M*_p x 1]                */  \
+FIRPFBCH() FIRPFBCH(_create)(int          _type,                \
+                             unsigned int _M,                   \
+                             unsigned int _p,                   \
+                             TC *         _h);                  \
+                                                                \
+/* create FIR polyphase filterbank channelizer object with  */  \
+/* prototype filter based on windowed Kaiser design         */  \
+/*  _type   : type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER)   */  \
+/*  _M      : number of channels                            */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _As     : stop-band attentuation [dB]                   */  \
+FIRPFBCH() FIRPFBCH(_create_kaiser)(int          _type,         \
+                                    unsigned int _M,            \
+                                    unsigned int _m,            \
+                                    float        _As);          \
+                                                                \
+/* create FIR polyphase filterbank channelizer object with  */  \
+/* prototype root-Nyquist filter                            */  \
+/*  _type   : type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER)   */  \
+/*  _M      : number of channels                            */  \
+/*  _m      : filter delay (symbols)                        */  \
+/*  _beta   : filter excess bandwidth factor, in [0,1]      */  \
+/*  _ftype  : filter prototype (rrcos, rkaiser, etc.)       */  \
+FIRPFBCH() FIRPFBCH(_create_rnyquist)(int          _type,       \
+                                      unsigned int _M,          \
+                                      unsigned int _m,          \
+                                      float        _beta,       \
+                                      int          _ftype);     \
+                                                                \
+/* destroy firpfbch object                                  */  \
+void FIRPFBCH(_destroy)(FIRPFBCH() _q);                         \
+                                                                \
+/* clear/reset firpfbch internal state                      */  \
+void FIRPFBCH(_reset)(FIRPFBCH() _q);                           \
+                                                                \
+/* print firpfbch internal parameters to stdout             */  \
+void FIRPFBCH(_print)(FIRPFBCH() _q);                           \
+                                                                \
+/* execute filterbank as synthesizer on block of samples    */  \
+/*  _q      : filterbank channelizer object                 */  \
+/*  _x      : channelized input, [size: num_channels x 1]   */  \
+/*  _y      : output time series, [size: num_channels x 1]  */  \
+void FIRPFBCH(_synthesizer_execute)(FIRPFBCH() _q,              \
+                                    TI *       _x,              \
+                                    TO *       _y);             \
+                                                                \
+/* execute filterbank as analyzer on block of samples       */  \
+/*  _q      : filterbank channelizer object                 */  \
+/*  _x      : input time series, [size: num_channels x 1]   */  \
+/*  _y      : channelized output, [size: num_channels x 1]  */  \
+void FIRPFBCH(_analyzer_execute)(FIRPFBCH() _q,                 \
+                                 TI *       _x,                 \
+                                 TO *       _y);                \
+
+
+LIQUID_FIRPFBCH_DEFINE_API(FIRPFBCH_MANGLE_CRCF,
+                           liquid_float_complex,
+                           float,
+                           liquid_float_complex)
+
+LIQUID_FIRPFBCH_DEFINE_API(FIRPFBCH_MANGLE_CCCF,
+                           liquid_float_complex,
+                           liquid_float_complex,
+                           liquid_float_complex)
+
+
+//
+// Finite impulse response polyphase filterbank channelizer
+// with output rate 2 Fs / M
+//
+
+#define FIRPFBCH2_MANGLE_CRCF(name) LIQUID_CONCAT(firpfbch2_crcf,name)
+
+// Macro:
+//   FIRPFBCH2  : name-mangling macro
+//   TO         : output data type
+//   TC         : coefficients data type
+//   TI         : input data type
+#define LIQUID_FIRPFBCH2_DEFINE_API(FIRPFBCH2,TO,TC,TI)         \
+typedef struct FIRPFBCH2(_s) * FIRPFBCH2();                     \
+                                                                \
+/* create firpfbch2 object                                  */  \
+/*  _type   :   channelizer type (e.g. LIQUID_ANALYZER)     */  \
+/*  _M      :   number of channels (must be even)           */  \
+/*  _m      :   prototype filter semi-lenth, length=2*M*m   */  \
+/*  _h      :   prototype filter coefficient array          */  \
+FIRPFBCH2() FIRPFBCH2(_create)(int          _type,              \
+                               unsigned int _M,                 \
+                               unsigned int _m,                 \
+                               TC *         _h);                \
+                                                                \
+/* create firpfbch2 object using Kaiser window prototype    */  \
+/*  _type   :   channelizer type (e.g. LIQUID_ANALYZER)     */  \
+/*  _M      :   number of channels (must be even)           */  \
+/*  _m      :   prototype filter semi-lenth, length=2*M*m+1 */  \
+/*  _As     :   filter stop-band attenuation [dB]           */  \
+FIRPFBCH2() FIRPFBCH2(_create_kaiser)(int          _type,       \
+                                      unsigned int _M,          \
+                                      unsigned int _m,          \
+                                      float        _As);        \
+                                                                \
+/* destroy firpfbch2 object, freeing internal memory        */  \
+void FIRPFBCH2(_destroy)(FIRPFBCH2() _q);                       \
+                                                                \
+/* reset firpfbch2 object internals                         */  \
+void FIRPFBCH2(_reset)(FIRPFBCH2() _q);                         \
+                                                                \
+/* print firpfbch2 object internals                         */  \
+void FIRPFBCH2(_print)(FIRPFBCH2() _q);                         \
+                                                                \
+/* execute filterbank channelizer                           */  \
+/* LIQUID_ANALYZER:     input: M/2, output: M               */  \
+/* LIQUID_SYNTHESIZER:  input: M,   output: M/2             */  \
+/*  _x      :   channelizer input                           */  \
+/*  _y      :   channelizer output                          */  \
+void FIRPFBCH2(_execute)(FIRPFBCH2() _q,                        \
+                         TI *        _x,                        \
+                         TO *        _y);                       \
+
+
+LIQUID_FIRPFBCH2_DEFINE_API(FIRPFBCH2_MANGLE_CRCF,
+                            liquid_float_complex,
+                            float,
+                            liquid_float_complex)
+
+
+
+#define OFDMFRAME_SCTYPE_NULL   0
+#define OFDMFRAME_SCTYPE_PILOT  1
+#define OFDMFRAME_SCTYPE_DATA   2
+
+// initialize default subcarrier allocation
+//  _M      :   number of subcarriers
+//  _p      :   output subcarrier allocation array, [size: _M x 1]
+void ofdmframe_init_default_sctype(unsigned int _M,
+                                   unsigned char * _p);
+
+// validate subcarrier type (count number of null, pilot, and data
+// subcarriers in the allocation)
+//  _p          :   subcarrier allocation array, [size: _M x 1]
+//  _M          :   number of subcarriers
+//  _M_null     :   output number of null subcarriers
+//  _M_pilot    :   output number of pilot subcarriers
+//  _M_data     :   output number of data subcarriers
+void ofdmframe_validate_sctype(unsigned char * _p,
+                               unsigned int _M,
+                               unsigned int * _M_null,
+                               unsigned int * _M_pilot,
+                               unsigned int * _M_data);
+
+// print subcarrier allocation to screen
+//  _p      :   output subcarrier allocation array, [size: _M x 1]
+//  _M      :   number of subcarriers
+void ofdmframe_print_sctype(unsigned char * _p,
+                            unsigned int    _M);
+
+
+// 
+// OFDM frame (symbol) generator
+//
+typedef struct ofdmframegen_s * ofdmframegen;
+
+// create OFDM framing generator object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+ofdmframegen ofdmframegen_create(unsigned int    _M,
+                                 unsigned int    _cp_len,
+                                 unsigned int    _taper_len,
+                                 unsigned char * _p);
+
+void ofdmframegen_destroy(ofdmframegen _q);
+
+void ofdmframegen_print(ofdmframegen _q);
+
+void ofdmframegen_reset(ofdmframegen _q);
+
+// write first S0 symbol
+void ofdmframegen_write_S0a(ofdmframegen _q,
+                            liquid_float_complex *_y);
+
+// write second S0 symbol
+void ofdmframegen_write_S0b(ofdmframegen _q,
+                            liquid_float_complex *_y);
+
+// write S1 symbol
+void ofdmframegen_write_S1(ofdmframegen _q,
+                           liquid_float_complex *_y);
+
+// write data symbol
+void ofdmframegen_writesymbol(ofdmframegen _q,
+                              liquid_float_complex * _x,
+                              liquid_float_complex *_y);
+
+// write tail
+void ofdmframegen_writetail(ofdmframegen _q,
+                            liquid_float_complex * _x);
+
+// 
+// OFDM frame (symbol) synchronizer
+//
+typedef int (*ofdmframesync_callback)(liquid_float_complex * _y,
+                                      unsigned char * _p,
+                                      unsigned int _M,
+                                      void * _userdata);
+typedef struct ofdmframesync_s * ofdmframesync;
+
+// create OFDM framing synchronizer object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+//  _callback   :   user-defined callback function
+//  _userdata   :   user-defined data pointer
+ofdmframesync ofdmframesync_create(unsigned int           _M,
+                                   unsigned int           _cp_len,
+                                   unsigned int           _taper_len,
+                                   unsigned char *        _p,
+                                   ofdmframesync_callback _callback,
+                                   void *                 _userdata);
+void ofdmframesync_destroy(ofdmframesync _q);
+void ofdmframesync_print(ofdmframesync _q);
+void ofdmframesync_reset(ofdmframesync _q);
+void ofdmframesync_execute(ofdmframesync _q,
+                           liquid_float_complex * _x,
+                           unsigned int _n);
+
+// query methods
+float ofdmframesync_get_rssi(ofdmframesync _q); // received signal strength indication
+float ofdmframesync_get_cfo(ofdmframesync _q);  // carrier offset estimate
+
+// debugging
+void ofdmframesync_debug_enable(ofdmframesync _q);
+void ofdmframesync_debug_disable(ofdmframesync _q);
+void ofdmframesync_debug_print(ofdmframesync _q, const char * _filename);
+
+
+// 
+// MODULE : nco (numerically-controlled oscillator)
+//
+
+// oscillator type
+//  LIQUID_NCO  :   numerically-controlled oscillator (fast)
+//  LIQUID_VCO  :   "voltage"-controlled oscillator (precise)
+typedef enum {
+    LIQUID_NCO=0,
+    LIQUID_VCO
+} liquid_ncotype;
+
+#define NCO_MANGLE_FLOAT(name)  LIQUID_CONCAT(nco_crcf, name)
+
+// large macro
+//   NCO    : name-mangling macro
+//   T      : primitive data type
+//   TC     : input/output data type
+#define LIQUID_NCO_DEFINE_API(NCO,T,TC)                         \
+typedef struct NCO(_s) * NCO();                                 \
+                                                                \
+NCO() NCO(_create)(liquid_ncotype _type);                       \
+void NCO(_destroy)(NCO() _q);                                   \
+void NCO(_print)(NCO() _q);                                     \
+                                                                \
+/* set phase/frequency to zero, reset pll filter        */      \
+void NCO(_reset)(NCO() _q);                                     \
+                                                                \
+/* get/set/adjust internal frequency/phase              */      \
+T    NCO(_get_frequency)(   NCO() _q);                          \
+void NCO(_set_frequency)(   NCO() _q, T _f);                    \
+void NCO(_adjust_frequency)(NCO() _q, T _df);                   \
+T    NCO(_get_phase)(       NCO() _q);                          \
+void NCO(_set_phase)(       NCO() _q, T _phi);                  \
+void NCO(_adjust_phase)(    NCO() _q, T _dphi);                 \
+                                                                \
+/* increment phase by internal phase step (frequency)   */      \
+void NCO(_step)(NCO() _q);                                      \
+                                                                \
+/* compute trigonometric functions                      */      \
+T NCO(_sin)(NCO() _q);                                          \
+T NCO(_cos)(NCO() _q);                                          \
+void NCO(_sincos)(NCO() _q, T* _s, T* _c);                      \
+void NCO(_cexpf)(NCO() _q, TC * _y);                            \
+                                                                \
+/* pll : phase-locked loop                              */      \
+void NCO(_pll_set_bandwidth)(NCO() _q, T _bandwidth);           \
+void NCO(_pll_step)(NCO() _q, T _dphi);                         \
+                                                                \
+/* Rotate input sample up by NCO angle (no stepping)    */      \
+void NCO(_mix_up)(NCO() _q, TC _x, TC *_y);                     \
+                                                                \
+/* Rotate input sample down by NCO angle (no stepping)  */      \
+void NCO(_mix_down)(NCO() _q, TC _x, TC *_y);                   \
+                                                                \
+/* Rotate input vector up by NCO angle (stepping)       */      \
+/*  _q      :   nco object                              */      \
+/*  _x      :   input vector [size: _N x 1]             */      \
+/*  _y      :   output vector [size: _N x 1]            */      \
+/*  _N      :   vector size                             */      \
+void NCO(_mix_block_up)(NCO() _q,                               \
+                        TC *_x,                                 \
+                        TC *_y,                                 \
+                        unsigned int _N);                       \
+                                                                \
+/* Rotate input vector down by NCO angle (stepping)     */      \
+/*  _q      :   nco object                              */      \
+/*  _x      :   input vector [size: _N x 1]             */      \
+/*  _y      :   output vector [size: _N x 1]            */      \
+/*  _N      :   vector size                             */      \
+void NCO(_mix_block_down)(NCO() _q,                             \
+                          TC *_x,                               \
+                          TC *_y,                               \
+                          unsigned int _N);                     \
+
+// Define nco APIs
+LIQUID_NCO_DEFINE_API(NCO_MANGLE_FLOAT, float, liquid_float_complex)
+
+
+// nco utilities
+
+// unwrap phase of array (basic)
+void liquid_unwrap_phase(float * _theta, unsigned int _n);
+
+// unwrap phase of array (advanced)
+void liquid_unwrap_phase2(float * _theta, unsigned int _n);
+
+
+
+//
+// MODULE : optimization
+//
+
+// utility function pointer definition
+typedef float (*utility_function)(void *       _userdata,
+                                  float *      _v,
+                                  unsigned int _n);
+
+// n-dimensional Rosenbrock utility function (minimum at _v = {1,1,1...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_rosenbrock(void *       _userdata,
+                        float *      _v,
+                        unsigned int _n);
+
+// n-dimensional inverse Gauss utility function (minimum at _v = {0,0,0...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_invgauss(void *       _userdata,
+                      float *      _v,
+                      unsigned int _n);
+
+// n-dimensional multimodal utility function (minimum at _v = {0,0,0...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_multimodal(void *       _userdata,
+                        float *      _v,
+                        unsigned int _n);
+
+// n-dimensional spiral utility function (minimum at _v = {0,0,0...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_spiral(void *       _userdata,
+                    float *      _v,
+                    unsigned int _n);
+
+
+//
+// Gradient search
+//
+
+#define LIQUID_OPTIM_MINIMIZE (0)
+#define LIQUID_OPTIM_MAXIMIZE (1)
+
+typedef struct gradsearch_s * gradsearch;
+
+// Create a gradient search object
+//   _userdata          :   user data object pointer
+//   _v                 :   array of parameters to optimize
+//   _num_parameters    :   array length (number of parameters to optimize)
+//   _u                 :   utility function pointer
+//   _direction         :   search direction (e.g. LIQUID_OPTIM_MAXIMIZE)
+gradsearch gradsearch_create(void *           _userdata,
+                             float *          _v,
+                             unsigned int     _num_parameters,
+                             utility_function _utility,
+                             int              _direction);
+
+// Destroy a gradsearch object
+void gradsearch_destroy(gradsearch _q);
+
+// Prints current status of search
+void gradsearch_print(gradsearch _q);
+
+// Iterate once
+float gradsearch_step(gradsearch _q);
+
+// Execute the search
+float gradsearch_execute(gradsearch   _q,
+                         unsigned int _max_iterations,
+                         float        _target_utility);
+
+
+// quasi-Newton search
+typedef struct qnsearch_s * qnsearch;
+
+// Create a simple qnsearch object; parameters are specified internally
+//   _userdata          :   userdata
+//   _v                 :   array of parameters to optimize
+//   _num_parameters    :   array length
+//   _get_utility       :   utility function pointer
+//   _direction         :   search direction (e.g. LIQUID_OPTIM_MAXIMIZE)
+qnsearch qnsearch_create(void *           _userdata,
+                         float *          _v,
+                         unsigned int     _num_parameters,
+                         utility_function _u,
+                         int              _direction);
+
+// Destroy a qnsearch object
+void qnsearch_destroy(qnsearch _g);
+
+// Prints current status of search
+void qnsearch_print(qnsearch _g);
+
+// Resets internal state
+void qnsearch_reset(qnsearch _g);
+
+// Iterate once
+void qnsearch_step(qnsearch _g);
+
+// Execute the search
+float qnsearch_execute(qnsearch _g,
+                       unsigned int _max_iterations,
+                       float _target_utility);
+
+// 
+// chromosome (for genetic algorithm search)
+//
+typedef struct chromosome_s * chromosome;
+
+// create a chromosome object, variable bits/trait
+chromosome chromosome_create(unsigned int * _bits_per_trait,
+                             unsigned int _num_traits);
+
+// create a chromosome object, all traits same resolution
+chromosome chromosome_create_basic(unsigned int _num_traits,
+                                   unsigned int _bits_per_trait);
+
+// create a chromosome object, cloning a parent
+chromosome chromosome_create_clone(chromosome _parent);
+
+// copy existing chromosomes' internal traits (all other internal
+// parameters must be equal)
+void chromosome_copy(chromosome _parent, chromosome _child);
+
+// Destroy a chromosome object
+void chromosome_destroy(chromosome _c);
+
+// get number of traits in chromosome
+unsigned int chromosome_get_num_traits(chromosome _c);
+
+// Print chromosome values to screen (binary representation)
+void chromosome_print(chromosome _c);
+
+// Print chromosome values to screen (floating-point representation)
+void chromosome_printf(chromosome _c);
+
+// clear chromosome (set traits to zero)
+void chromosome_clear(chromosome _c);
+
+// initialize chromosome on integer values
+void chromosome_init(chromosome _c,
+                     unsigned int * _v);
+
+// initialize chromosome on floating-point values
+void chromosome_initf(chromosome _c,
+                      float * _v);
+
+// Mutates chromosome _c at _index
+void chromosome_mutate(chromosome _c, unsigned int _index);
+
+// Resulting chromosome _c is a crossover of parents _p1 and _p2 at _threshold
+void chromosome_crossover(chromosome _p1,
+                          chromosome _p2,
+                          chromosome _c,
+                          unsigned int _threshold);
+
+// Initializes chromosome to random value
+void chromosome_init_random(chromosome _c);
+
+// Returns integer representation of chromosome
+unsigned int chromosome_value(chromosome _c,
+                              unsigned int _index);
+
+// Returns floating-point representation of chromosome
+float chromosome_valuef(chromosome _c,
+                        unsigned int _index);
+
+// 
+// genetic algorithm search
+//
+typedef struct gasearch_s * gasearch;
+
+typedef float (*gasearch_utility)(void * _userdata, chromosome _c);
+
+// Create a simple gasearch object; parameters are specified internally
+//  _utility            :   chromosome fitness utility function
+//  _userdata           :   user data, void pointer passed to _get_utility() callback
+//  _parent             :   initial population parent chromosome, governs precision, etc.
+//  _minmax             :   search direction
+gasearch gasearch_create(gasearch_utility _u,
+                         void * _userdata,
+                         chromosome _parent,
+                         int _minmax);
+
+// Create a gasearch object, specifying search parameters
+//  _utility            :   chromosome fitness utility function
+//  _userdata           :   user data, void pointer passed to _get_utility() callback
+//  _parent             :   initial population parent chromosome, governs precision, etc.
+//  _minmax             :   search direction
+//  _population_size    :   number of chromosomes in population
+//  _mutation_rate      :   probability of mutating chromosomes
+gasearch gasearch_create_advanced(gasearch_utility _utility,
+                                  void * _userdata,
+                                  chromosome _parent,
+                                  int _minmax,
+                                  unsigned int _population_size,
+                                  float _mutation_rate);
+
+
+// Destroy a gasearch object
+void gasearch_destroy(gasearch _q);
+
+// print search parameter internals
+void gasearch_print(gasearch _q);
+
+// set mutation rate
+void gasearch_set_mutation_rate(gasearch _q,
+                                float _mutation_rate);
+
+// set population/selection size
+//  _q                  :   ga search object
+//  _population_size    :   new population size (number of chromosomes)
+//  _selection_size     :   selection size (number of parents for new generation)
+void gasearch_set_population_size(gasearch _q,
+                                  unsigned int _population_size,
+                                  unsigned int _selection_size);
+
+// Execute the search
+//  _q              :   ga search object
+//  _max_iterations :   maximum number of iterations to run before bailing
+//  _target_utility :   target utility
+float gasearch_run(gasearch _q,
+                    unsigned int _max_iterations,
+                    float _target_utility);
+
+// iterate over one evolution of the search algorithm
+void gasearch_evolve(gasearch _q);
+
+// get optimal chromosome
+//  _q              :   ga search object
+//  _c              :   output optimal chromosome
+//  _utility_opt    :   fitness of _c
+void gasearch_getopt(gasearch _q,
+                     chromosome _c,
+                     float * _utility_opt);
+
+//
+// MODULE : quantization
+//
+
+float compress_mulaw(float _x, float _mu);
+float expand_mulaw(float _x, float _mu);
+
+void compress_cf_mulaw(liquid_float_complex _x, float _mu, liquid_float_complex * _y);
+void expand_cf_mulaw(liquid_float_complex _y, float _mu, liquid_float_complex * _x);
+
+//float compress_alaw(float _x, float _a);
+//float expand_alaw(float _x, float _a);
+
+// inline quantizer: 'analog' signal in [-1, 1]
+unsigned int quantize_adc(float _x, unsigned int _num_bits);
+float quantize_dac(unsigned int _s, unsigned int _num_bits);
+
+// structured quantizer
+
+typedef enum {
+    LIQUID_COMPANDER_NONE=0,
+    LIQUID_COMPANDER_LINEAR,
+    LIQUID_COMPANDER_MULAW,
+    LIQUID_COMPANDER_ALAW
+} liquid_compander_type;
+
+#define QUANTIZER_MANGLE_FLOAT(name)    LIQUID_CONCAT(quantizerf,  name)
+#define QUANTIZER_MANGLE_CFLOAT(name)   LIQUID_CONCAT(quantizercf, name)
+
+// large macro
+//   QUANTIZER  : name-mangling macro
+//   T          : data type
+#define LIQUID_QUANTIZER_DEFINE_API(QUANTIZER,T)                \
+typedef struct QUANTIZER(_s) * QUANTIZER();                     \
+QUANTIZER() QUANTIZER(_create)(liquid_compander_type _ctype,    \
+                               float _range,                    \
+                               unsigned int _num_bits);         \
+void QUANTIZER(_destroy)(QUANTIZER() _q);                       \
+void QUANTIZER(_print)(QUANTIZER() _q);                         \
+void QUANTIZER(_execute_adc)(QUANTIZER() _q,                    \
+                             T _x,                              \
+                             unsigned int * _sample);           \
+void QUANTIZER(_execute_dac)(QUANTIZER() _q,                    \
+                             unsigned int _sample,              \
+                             T * _x);
+
+LIQUID_QUANTIZER_DEFINE_API(QUANTIZER_MANGLE_FLOAT,  float)
+LIQUID_QUANTIZER_DEFINE_API(QUANTIZER_MANGLE_CFLOAT, liquid_float_complex)
+
+
+//
+// MODULE : random (number generators)
+//
+
+
+// Uniform random number generator, (0,1]
+float randf();
+float randf_pdf(float _x);
+float randf_cdf(float _x);
+
+// Gauss random number generator, N(0,1)
+//   f(x) = 1/sqrt(2*pi*sigma^2) * exp{-(x-eta)^2/(2*sigma^2)}
+//
+//   where
+//     eta   = mean
+//     sigma = standard deviation
+//
+float randnf();
+void awgn(float *_x, float _nstd);
+void crandnf(liquid_float_complex *_y);
+void cawgn(liquid_float_complex *_x, float _nstd);
+float randnf_pdf(float _x, float _eta, float _sig);
+float randnf_cdf(float _x, float _eta, float _sig);
+
+// Exponential
+//  f(x) = lambda exp{ -lambda x }
+// where
+//  lambda = spread parameter, lambda > 0
+//  x >= 0
+float randexpf(float _lambda);
+float randexpf_pdf(float _x, float _lambda);
+float randexpf_cdf(float _x, float _lambda);
+
+// Weibull
+//   f(x) = (a/b) (x/b)^(a-1) exp{ -(x/b)^a }
+//   where
+//     a = alpha : shape parameter
+//     b = beta  : scaling parameter
+//     g = gamma : location (threshold) parameter
+//
+float randweibf(float _alpha, float _beta, float _gamma);
+float randweibf_pdf(float _x, float _a, float _b, float _g);
+float randweibf_cdf(float _x, float _a, float _b, float _g);
+
+// Gamma
+//          x^(a-1) exp(-x/b)
+//  f(x) = -------------------
+//            Gamma(a) b^a
+//  where
+//      a = alpha : shape parameter, a > 0
+//      b = beta  : scale parameter, b > 0
+//      Gamma(z) = regular gamma function
+//      x >= 0
+float randgammaf(float _alpha, float _beta);
+float randgammaf_pdf(float _x, float _alpha, float _beta);
+float randgammaf_cdf(float _x, float _alpha, float _beta);
+
+// Nakagami-m
+//  f(x) = (2/Gamma(m)) (m/omega)^m x^(2m-1) exp{-(m/omega)x^2}
+// where
+//      m       : shape parameter, m >= 0.5
+//      omega   : spread parameter, omega > 0
+//      Gamma(z): regular complete gamma function
+//      x >= 0
+float randnakmf(float _m, float _omega);
+float randnakmf_pdf(float _x, float _m, float _omega);
+float randnakmf_cdf(float _x, float _m, float _omega);
+
+// Rice-K
+//  f(x) = (x/sigma^2) exp{ -(x^2+s^2)/(2sigma^2) } I0( x s / sigma^2 )
+// where
+//  s     = sqrt( omega*K/(K+1) )
+//  sigma = sqrt(0.5 omega/(K+1))
+// and
+//  K     = shape parameter
+//  omega = spread parameter
+//  I0    = modified Bessel function of the first kind
+//  x >= 0
+float randricekf(float _K, float _omega);
+float randricekf_cdf(float _x, float _K, float _omega);
+float randricekf_pdf(float _x, float _K, float _omega);
+
+
+// Data scrambler : whiten data sequence
+void scramble_data(unsigned char * _x, unsigned int _len);
+void unscramble_data(unsigned char * _x, unsigned int _len);
+void unscramble_data_soft(unsigned char * _x, unsigned int _len);
+
+//
+// MODULE : sequence
+//
+
+// Binary sequence (generic)
+
+typedef struct bsequence_s * bsequence;
+
+// Create a binary sequence of a specific length (number of bits)
+bsequence bsequence_create(unsigned int num_bits);
+
+// Free memory in a binary sequence
+void bsequence_destroy(bsequence _bs);
+
+// Clear binary sequence (set to 0's)
+void bsequence_clear(bsequence _bs);
+
+// initialize sequence on external array
+void bsequence_init(bsequence _bs,
+                    unsigned char * _v);
+
+// Print sequence to the screen
+void bsequence_print(bsequence _bs);
+
+// Push bit into to back of a binary sequence
+void bsequence_push(bsequence _bs,
+                    unsigned int _bit);
+
+// circular shift (left)
+void bsequence_circshift(bsequence _bs);
+
+// Correlate two binary sequences together
+int bsequence_correlate(bsequence _bs1, bsequence _bs2);
+
+// compute the binary addition of two bit sequences
+void bsequence_add(bsequence _bs1, bsequence _bs2, bsequence _bs3);
+
+// compute the binary multiplication of two bit sequences
+void bsequence_mul(bsequence _bs1, bsequence _bs2, bsequence _bs3);
+
+// accumulate the 1's in a binary sequence
+unsigned int bsequence_accumulate(bsequence _bs);
+
+// accessor functions
+unsigned int bsequence_get_length(bsequence _bs);
+unsigned int bsequence_index(bsequence _bs, unsigned int _i);
+
+// Complementary codes
+
+// intialize two sequences to complementary codes.  sequences must
+// be of length at least 8 and a power of 2 (e.g. 8, 16, 32, 64,...)
+//  _a      :   sequence 'a' (bsequence object)
+//  _b      :   sequence 'b' (bsequence object)
+void bsequence_create_ccodes(bsequence _a,
+                             bsequence _b);
+
+
+// M-Sequence
+
+#define LIQUID_MAX_MSEQUENCE_LENGTH   32767
+
+// default m-sequence generators:       g (hex)     m       n   g (oct)       g (binary)
+#define LIQUID_MSEQUENCE_GENPOLY_M2     0x0007  //  2       3        7               111
+#define LIQUID_MSEQUENCE_GENPOLY_M3     0x000B  //  3       7       13              1011
+#define LIQUID_MSEQUENCE_GENPOLY_M4     0x0013  //  4      15       23             10011
+#define LIQUID_MSEQUENCE_GENPOLY_M5     0x0025  //  5      31       45            100101
+#define LIQUID_MSEQUENCE_GENPOLY_M6     0x0043  //  6      63      103           1000011
+#define LIQUID_MSEQUENCE_GENPOLY_M7     0x0089  //  7     127      211          10001001
+#define LIQUID_MSEQUENCE_GENPOLY_M8     0x011D  //  8     255      435         100101101
+#define LIQUID_MSEQUENCE_GENPOLY_M9     0x0211  //  9     511     1021        1000010001
+#define LIQUID_MSEQUENCE_GENPOLY_M10    0x0409  // 10    1023     2011       10000001001
+#define LIQUID_MSEQUENCE_GENPOLY_M11    0x0805  // 11    2047     4005      100000000101
+#define LIQUID_MSEQUENCE_GENPOLY_M12    0x1053  // 12    4095    10123     1000001010011
+#define LIQUID_MSEQUENCE_GENPOLY_M13    0x201b  // 13    8191    20033    10000000011011
+#define LIQUID_MSEQUENCE_GENPOLY_M14    0x402b  // 14   16383    40053   100000000101011
+#define LIQUID_MSEQUENCE_GENPOLY_M15    0x8003  // 15   32767   100003  1000000000000011
+   
+typedef struct msequence_s * msequence;
+
+// create a maximal-length sequence (m-sequence) object with
+// an internal shift register length of _m bits.
+//  _m      :   generator polynomial length, sequence length is (2^m)-1
+//  _g      :   generator polynomial, starting with most-significant bit
+//  _a      :   initial shift register state, default: 000...001
+msequence msequence_create(unsigned int _m,
+                           unsigned int _g,
+                           unsigned int _a);
+
+// create a maximal-length sequence (m-sequence) object from a generator polynomial
+msequence msequence_create_genpoly(unsigned int _g);
+
+// creates a default maximal-length sequence
+msequence msequence_create_default(unsigned int _m);
+
+// destroy an msequence object, freeing all internal memory
+void msequence_destroy(msequence _m);
+
+// prints the sequence's internal state to the screen
+void msequence_print(msequence _m);
+
+// advance msequence on shift register, returning output bit
+unsigned int msequence_advance(msequence _ms);
+
+// generate pseudo-random symbol from shift register by
+// advancing _bps bits and returning compacted symbol
+//  _ms     :   m-sequence object
+//  _bps    :   bits per symbol of output
+unsigned int msequence_generate_symbol(msequence _ms,
+                                       unsigned int _bps);
+
+// reset msequence shift register to original state, typically '1'
+void msequence_reset(msequence _ms);
+
+// initialize a bsequence object on an msequence object
+//  _bs     :   bsequence object
+//  _ms     :   msequence object
+void bsequence_init_msequence(bsequence _bs,
+                              msequence _ms);
+
+// get the length of the sequence
+unsigned int msequence_get_length(msequence _ms);
+
+// get the internal state of the sequence
+unsigned int msequence_get_state(msequence _ms);
+
+// set the internal state of the sequence
+void msequence_set_state(msequence    _ms,
+                         unsigned int _a);
+
+
+// 
+// MODULE : utility
+//
+
+// pack binary array with symbol(s)
+//  _src        :   source array [size: _n x 1]
+//  _n          :   input source array length
+//  _k          :   bit index to write in _src
+//  _b          :   number of bits in input symbol
+//  _sym_in     :   input symbol
+void liquid_pack_array(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _k,
+                       unsigned int _b,
+                       unsigned char _sym_in);
+
+// unpack symbols from binary array
+//  _src        :   source array [size: _n x 1]
+//  _n          :   input source array length
+//  _k          :   bit index to write in _src
+//  _b          :   number of bits in output symbol
+//  _sym_out    :   output symbol
+void liquid_unpack_array(unsigned char * _src,
+                         unsigned int _n,
+                         unsigned int _k,
+                         unsigned int _b,
+                         unsigned char * _sym_out);
+
+// pack one-bit symbols into bytes (8-bit symbols)
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_pack_bytes(unsigned char * _sym_in,
+                       unsigned int _sym_in_len,
+                       unsigned char * _sym_out,
+                       unsigned int _sym_out_len,
+                       unsigned int * _num_written);
+
+// unpack 8-bit symbols (full bytes) into one-bit symbols
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols array
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_unpack_bytes(unsigned char * _sym_in,
+                         unsigned int _sym_in_len,
+                         unsigned char * _sym_out,
+                         unsigned int _sym_out_len,
+                         unsigned int * _num_written);
+
+// repack bytes with arbitrary symbol sizes
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_bps         :   number of bits per input symbol
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols array
+//  _sym_out_bps        :   number of bits per output symbol
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_repack_bytes(unsigned char * _sym_in,
+                         unsigned int _sym_in_bps,
+                         unsigned int _sym_in_len,
+                         unsigned char * _sym_out,
+                         unsigned int _sym_out_bps,
+                         unsigned int _sym_out_len,
+                         unsigned int * _num_written);
+ 
+// shift array to the left _b bits, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_lbshift(unsigned char * _src,
+                    unsigned int _n,
+                    unsigned int _b);
+ 
+// shift array to the right _b bits, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_rbshift(unsigned char * _src,
+                    unsigned int _n,
+                    unsigned int _b);
+ 
+// circularly shift array to the left _b bits
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_lbcircshift(unsigned char * _src,
+                        unsigned int _n,
+                        unsigned int _b);
+ 
+// circularly shift array to the right _b bits
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_rbcircshift(unsigned char * _src,
+                        unsigned int _n,
+                        unsigned int _b);
+ 
+
+
+
+// shift array to the left _b bytes, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_lshift(unsigned char * _src,
+                   unsigned int _n,
+                   unsigned int _b);
+ 
+// shift array to the right _b bytes, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_rshift(unsigned char * _src,
+                   unsigned int _n,
+                   unsigned int _b);
+ 
+// circular shift array to the left _b bytes
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_lcircshift(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _b);
+ 
+// circular shift array to the right _b bytes
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_rcircshift(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _b);
+ 
+// Count the number of ones in an integer
+unsigned int liquid_count_ones(unsigned int _x); 
+
+// count number of ones in an integer, modulo 2
+unsigned int liquid_count_ones_mod2(unsigned int _x);
+
+// compute bindary dot-product between two integers
+unsigned int liquid_bdotprod(unsigned int _x,
+                             unsigned int _y);
+
+// Count leading zeros in an integer
+unsigned int liquid_count_leading_zeros(unsigned int _x); 
+
+// Most-significant bit index
+unsigned int liquid_msb_index(unsigned int _x);
+
+// Print string of bits to stdout
+void liquid_print_bitstring(unsigned int _x,
+                            unsigned int _n);
+
+// reverse byte, word, etc.
+unsigned char liquid_reverse_byte(  unsigned char _x);
+unsigned int  liquid_reverse_uint16(unsigned int  _x);
+unsigned int  liquid_reverse_uint24(unsigned int  _x);
+unsigned int  liquid_reverse_uint32(unsigned int  _x);
+
+// 
+// MODULE : vector
+//
+
+#define VECTOR_MANGLE_RF(name)  LIQUID_CONCAT(liquid_vectorf, name)
+#define VECTOR_MANGLE_CF(name)  LIQUID_CONCAT(liquid_vectorcf,name)
+
+// large macro
+//   VECTOR     : name-mangling macro
+//   T          : data type
+//   TP         : data type (primitive)
+#define LIQUID_VECTOR_DEFINE_API(VECTOR,T,TP)                   \
+                                                                \
+/* initialize vector with scalar: x[i] = c (scalar)         */  \
+void VECTOR(_init)(T            _c,                             \
+                   T *          _x,                             \
+                   unsigned int _n);                            \
+                                                                \
+/* add each element: z[i] = x[i] + y[i]                     */  \
+void VECTOR(_add)(T *          _x,                              \
+                  T *          _y,                              \
+                  unsigned int _n,                              \
+                  T *          _z);                             \
+/* add scalar to each element: y[i] = x[i] + c              */  \
+void VECTOR(_addscalar)(T *          _x,                        \
+                        unsigned int _n,                        \
+                        T            _c,                        \
+                        T *          _y);                       \
+                                                                \
+/* multiply each element: z[i] = x[i] * y[i]                */  \
+void VECTOR(_mul)(T *          _x,                              \
+                  T *          _y,                              \
+                  unsigned int _n,                              \
+                  T *          _z);                             \
+/* multiply each element with scalar: y[i] = x[i] * c       */  \
+void VECTOR(_mulscalar)(T *          _x,                        \
+                        unsigned int _n,                        \
+                        T            _c,                        \
+                        T *          _y);                       \
+                                                                \
+/* compute complex phase rotation: x[i] = exp{j theta[i]}   */  \
+void VECTOR(_cexpj)(TP *         _theta,                        \
+                    unsigned int _n,                            \
+                    T *          _x);                           \
+/* compute angle of each element: theta[i] = arg{ x[i] }    */  \
+void VECTOR(_carg)(T *          _x,                             \
+                   unsigned int _n,                             \
+                   TP *         _theta);                        \
+/* compute absolute value of each element: y[i] = |x[i]|    */  \
+void VECTOR(_abs)(T *          _x,                              \
+                  unsigned int _n,                              \
+                  TP *         _y);                             \
+                                                                \
+/* compute sum of squares: sum{ |x|^2 }                     */  \
+TP VECTOR(_sumsq)(T *          _x,                              \
+                  unsigned int _n);                             \
+                                                                \
+/* compute l-2 norm: sqrt{ sum{ |x|^2 } }                   */  \
+TP VECTOR(_norm)(T *          _x,                               \
+                 unsigned int _n);                              \
+                                                                \
+/* compute l-p norm: { sum{ |x|^p } }^(1/p)                 */  \
+TP VECTOR(_pnorm)(T *          _x,                              \
+                  unsigned int _n,                              \
+                  TP           _p);                             \
+                                                                \
+/* scale vector elements by l-2 norm: y[i] = x[i]/norm(x)   */  \
+void VECTOR(_normalize)(T *          _x,                        \
+                        unsigned int _n,                        \
+                        T *          _y);                       \
+
+LIQUID_VECTOR_DEFINE_API(VECTOR_MANGLE_RF, float,                float);
+LIQUID_VECTOR_DEFINE_API(VECTOR_MANGLE_CF, liquid_float_complex, float);
+
+// 
+// mixed types
+//
+#if 0
+void liquid_vectorf_add(float *      _a,
+                        float *      _b,
+                        unsigned int _n,
+                        float *      _c);
+#endif
+
+#ifdef __cplusplus
+} //extern "C"
+#endif // __cplusplus
+
+#endif // __LIQUID_H__
+
diff --git a/include/liquid.internal.h b/include/liquid.internal.h
new file mode 100644
index 0000000..f80f336
--- /dev/null
+++ b/include/liquid.internal.h
@@ -0,0 +1,1840 @@
+/*
+ * Copyright (c) 2007 - 2016 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// liquid.internal.h
+//
+// Internal header file for liquid DSP for SDR
+//
+// This file includes function declarations which are intended
+// for internal use
+//
+
+#ifndef __LIQUID_INTERNAL_H__
+#define __LIQUID_INTERNAL_H__
+
+// Configuration file
+#include "config.h"
+
+#include <complex.h>
+#include "liquid.h"
+
+#if defined HAVE_FEC_H && defined HAVE_LIBFEC
+#  define LIBFEC_ENABLED 1
+#endif
+
+
+//
+// Debugging macros
+//
+#define DEBUG_PRINTF_FLOAT(F,STR,I,V)                           \
+    fprintf(F,"%s(%4u) = %12.4e;\n",STR,I+1,V)
+#define DEBUG_PRINTF_CFLOAT(F,STR,I,V)                          \
+    fprintf(F,"%s(%4u) = %12.4e +j*%12.4e;\n",STR,I+1,crealf(V),cimagf(V))
+
+#define PRINTVAL_FLOAT(X,F)     printf(#F,crealf(X));
+#define PRINTVAL_CFLOAT(X,F)    printf(#F "+j*" #F, crealf(X), cimagf(X));
+
+//
+// MODULE : agc
+//
+
+
+//
+// MODULE : audio
+//
+
+
+//
+// MODULE : buffer
+//
+
+
+//
+// MODULE : dotprod
+//
+
+
+//
+// MODULE : fec (forward error-correction)
+//
+
+// checksum / cyclic redundancy check (crc)
+
+#define CRC8_POLY 0x07
+#define CRC16_POLY 0x8005
+#define CRC24_POLY 0x5D6DCB
+#define CRC32_POLY 0x04C11DB7
+
+unsigned int checksum_generate_key(unsigned char * _msg, unsigned int _msg_len);
+unsigned int crc8_generate_key(unsigned char * _msg, unsigned int _msg_len);
+unsigned int crc16_generate_key(unsigned char * _msg, unsigned int _msg_len);
+unsigned int crc24_generate_key(unsigned char * _msg, unsigned int _msg_len);
+unsigned int crc32_generate_key(unsigned char * _msg, unsigned int _msg_len);
+
+
+// fec : basic object
+struct fec_s {
+    // common
+    fec_scheme scheme;
+    //unsigned int dec_msg_len;
+    //unsigned int enc_msg_len;
+    float rate;
+
+    // lengths: convolutional, Reed-Solomon
+    unsigned int num_dec_bytes;
+    unsigned int num_enc_bytes;
+
+    // convolutional : internal memory structure
+    unsigned char * enc_bits;
+    void * vp;      // decoder object
+    int * poly;     // polynomial
+    unsigned int R; // primitive rate, inverted (e.g. R=3 for 1/3)
+    unsigned int K; // constraint length
+    unsigned int P; // puncturing rate (e.g. p=3 for 3/4)
+    int * puncturing_matrix;
+
+    // viterbi decoder function pointers
+    void*(*create_viterbi)(int);
+    //void (*set_viterbi_polynomial)(int*);
+    int  (*init_viterbi)(void*,int);
+    int  (*update_viterbi_blk)(void*,unsigned char*,int);
+    int  (*chainback_viterbi)(void*,unsigned char*,unsigned int,unsigned int);
+    void (*delete_viterbi)(void*);
+
+    // Reed-Solomon
+    int symsize;    // symbol size (bits per symbol)
+    int genpoly;    // generator polynomial
+    int fcs;        //
+    int prim;       //
+    int nroots;     // number of roots in the polynomial
+    //int ntrials;    //
+    unsigned int rspad; // number of implicit padded symbols
+    int nn;         // 2^symsize - 1
+    int kk;         // nn - nroots
+    void * rs;      // Reed-Solomon internal object
+
+    // Reed-Solomon decoder
+    unsigned int num_blocks;    // number of blocks: ceil(dec_msg_len / nn)
+    unsigned int dec_block_len; // number of decoded bytes per block: 
+    unsigned int enc_block_len; // number of encoded bytes per block: 
+    unsigned int res_block_len; // residual bytes in last block
+    unsigned int pad;           // padding for each block
+    unsigned char * tblock;     // decoder input sequence [size: 1 x n]
+    int * errlocs;              // error locations [size: 1 x n]
+    int * derrlocs;             // decoded error locations [size: 1 x n]
+    int erasures;               // number of erasures
+
+    // encode function pointer
+    void (*encode_func)(fec _q,
+                        unsigned int _dec_msg_len,
+                        unsigned char * _msg_dec,
+                        unsigned char * _msg_enc);
+
+    // decode function pointer
+    void (*decode_func)(fec _q,
+                        unsigned int _dec_msg_len,
+                        unsigned char * _msg_enc,
+                        unsigned char * _msg_dec);
+
+    // decode function pointer (soft decision)
+    void (*decode_soft_func)(fec _q,
+                             unsigned int _dec_msg_len,
+                             unsigned char * _msg_enc,
+                             unsigned char * _msg_dec);
+};
+
+// simple type testing
+int fec_scheme_is_convolutional(fec_scheme _scheme);
+int fec_scheme_is_punctured(fec_scheme _scheme);
+int fec_scheme_is_reedsolomon(fec_scheme _scheme);
+int fec_scheme_is_hamming(fec_scheme _scheme);
+int fec_scheme_is_repeat(fec_scheme _scheme);
+
+// Pass
+fec fec_pass_create(void *_opts);
+void fec_pass_destroy(fec _q);
+void fec_pass_print(fec _q);
+void fec_pass_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_dec,
+                     unsigned char * _msg_enc);
+void fec_pass_decode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_enc,
+                     unsigned char * _msg_dec);
+
+// Repeat (3)
+fec fec_rep3_create(void *_opts);
+void fec_rep3_destroy(fec _q);
+void fec_rep3_print(fec _q);
+void fec_rep3_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_dec,
+                     unsigned char * _msg_enc);
+void fec_rep3_decode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_enc,
+                     unsigned char * _msg_dec);
+void fec_rep3_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+
+// Repeat (5)
+fec fec_rep5_create(void *_opts);
+void fec_rep5_destroy(fec _q);
+void fec_rep5_print(fec _q);
+void fec_rep5_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_dec,
+                     unsigned char * _msg_enc);
+void fec_rep5_decode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_enc,
+                     unsigned char * _msg_dec);
+void fec_rep5_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+
+// Hamming(7,4)
+extern unsigned char hamming74_enc_gentab[16];
+extern unsigned char hamming74_dec_gentab[128];
+fec fec_hamming74_create(void *_opts);
+void fec_hamming74_destroy(fec _q);
+void fec_hamming74_print(fec _q);
+void fec_hamming74_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_dec,
+                          unsigned char * _msg_enc);
+void fec_hamming74_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+void fec_hamming74_decode_soft(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char * _msg_enc,
+                               unsigned char * _msg_dec);
+// soft decoding of one symbol
+unsigned char fecsoft_hamming74_decode(unsigned char * _soft_bits);
+
+// Hamming(8,4)
+extern unsigned char hamming84_enc_gentab[16];
+extern unsigned char hamming84_dec_gentab[256];
+fec fec_hamming84_create(void *_opts);
+void fec_hamming84_destroy(fec _q);
+void fec_hamming84_print(fec _q);
+void fec_hamming84_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_dec,
+                          unsigned char * _msg_enc);
+void fec_hamming84_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+void fec_hamming84_decode_soft(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char * _msg_enc,
+                               unsigned char * _msg_dec);
+// soft decoding of one symbol
+unsigned char fecsoft_hamming84_decode(unsigned char * _soft_bits);
+
+// Hamming(12,8)
+
+unsigned int fec_hamming128_encode_symbol(unsigned int _sym_dec);
+unsigned int fec_hamming128_decode_symbol(unsigned int _sym_enc);
+extern unsigned short int hamming128_enc_gentab[256];   // encoding table
+
+fec fec_hamming128_create(void *_opts);
+void fec_hamming128_destroy(fec _q);
+void fec_hamming128_print(fec _q);
+void fec_hamming128_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_dec,
+                           unsigned char * _msg_enc);
+void fec_hamming128_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_enc,
+                           unsigned char * _msg_dec);
+void fec_hamming128_decode_soft(fec _q,
+                                unsigned int _dec_msg_len,
+                                unsigned char * _msg_enc,
+                                unsigned char * _msg_dec);
+// soft decoding of one symbol
+unsigned int fecsoft_hamming128_decode(unsigned char * _soft_bits);
+extern unsigned char fecsoft_hamming128_n3[256][17];
+unsigned int fecsoft_hamming128_decode_n3(unsigned char * _soft_bits);
+
+
+// Hamming(15,11)
+unsigned int fec_hamming1511_encode_symbol(unsigned int _sym_dec);
+unsigned int fec_hamming1511_decode_symbol(unsigned int _sym_enc);
+
+// Hamming(31,26)
+unsigned int fec_hamming3126_encode_symbol(unsigned int _sym_dec);
+unsigned int fec_hamming3126_decode_symbol(unsigned int _sym_enc);
+
+
+// Golay(24,12)
+
+unsigned int fec_golay2412_encode_symbol(unsigned int _sym_dec);
+unsigned int fec_golay2412_decode_symbol(unsigned int _sym_enc);
+extern unsigned int golay2412_P[12];
+extern unsigned int golay2412_Gt[24];
+extern unsigned int golay2412_H[12];
+
+// multiply input vector with matrix
+unsigned int golay2412_matrix_mul(unsigned int   _v,
+                                  unsigned int * _A,
+                                  unsigned int   _n);
+
+// search for p[i] such that w(v+p[i]) <= 2, return -1 on fail
+int golay2412_parity_search(unsigned int _v);
+
+fec fec_golay2412_create(void *_opts);
+void fec_golay2412_destroy(fec _q);
+void fec_golay2412_print(fec _q);
+void fec_golay2412_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_dec,
+                          unsigned char * _msg_enc);
+void fec_golay2412_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+
+// SEC-DED (22,16)
+
+// compute parity on 16-bit input
+unsigned char fec_secded2216_compute_parity(unsigned char * _m);
+
+// compute syndrome on 22-bit input
+unsigned char fec_secded2216_compute_syndrome(unsigned char * _v);
+
+// encode symbol
+//  _sym_dec    :   decoded symbol [size: 2 x 1]
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+void fec_secded2216_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc);
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+//  _sym_dec    :   decoded symbol [size: 2 x 1]
+int  fec_secded2216_decode_symbol(unsigned char * _sym_enc,
+                                  unsigned char * _sym_dec);
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+//  _e_hat      :   estimated error vector [size: 3 x 1]
+int  fec_secded2216_estimate_ehat(unsigned char * _sym_enc,
+                                  unsigned char * _e_hat);
+
+// parity matrix [6 x 16 bits], [6 x 2 bytes]
+extern unsigned char secded2216_P[12];
+
+// syndrome vectors of errors with weight exactly equal to 1
+extern unsigned char secded2216_syndrome_w1[22];
+
+fec fec_secded2216_create(void *_opts);
+void fec_secded2216_destroy(fec _q);
+void fec_secded2216_print(fec _q);
+void fec_secded2216_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_dec,
+                           unsigned char * _msg_enc);
+void fec_secded2216_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_enc,
+                           unsigned char * _msg_dec);
+
+// SEC-DED (39,32)
+
+// compute parity on 32-bit input
+unsigned char fec_secded3932_compute_parity(unsigned char * _m);
+
+// compute syndrome on 39-bit input
+unsigned char fec_secded3932_compute_syndrome(unsigned char * _v);
+
+// encode symbol
+//  _sym_dec    :   decoded symbol [size: 4 x 1]
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 7 bits
+void fec_secded3932_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc);
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 7 bits
+//  _e_hat      :   estimated error vector [size: 5 x 1]
+int  fec_secded3932_estimate_ehat(unsigned char * _sym_enc,
+                                  unsigned char * _e_hat);
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 7 bits
+//  _sym_dec    :   decoded symbol [size: 4 x 1]
+int fec_secded3932_decode_symbol(unsigned char * _sym_enc,
+                                 unsigned char * _sym_dec);
+
+// parity matrix [7 x 32 bits], [7 x 4 bytes]
+extern unsigned char secded3932_P[28];
+
+// syndrome vectors of errors with weight exactly equal to 1
+extern unsigned char secded3932_syndrome_w1[39];
+
+fec fec_secded3932_create(void *_opts);
+void fec_secded3932_destroy(fec _q);
+void fec_secded3932_print(fec _q);
+void fec_secded3932_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_dec,
+                           unsigned char * _msg_enc);
+void fec_secded3932_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_enc,
+                           unsigned char * _msg_dec);
+
+// SEC-DED (72,64)
+
+// compute parity byte on 64-byte input
+unsigned char fec_secded7264_compute_parity(unsigned char * _v);
+
+// compute syndrome on 72-bit input
+unsigned char fec_secded7264_compute_syndrome(unsigned char * _v);
+
+// encode symbol
+//  _sym_dec    :   input symbol [size: 8 x 1]
+//  _sym_enc    :   input symbol [size: 9 x 1]
+void fec_secded7264_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc);
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 9 x 1]
+//  _e_hat      :   estimated error vector [size: 9 x 1]
+int fec_secded7264_estimate_ehat(unsigned char * _sym_enc,
+                                 unsigned char * _e_hat);
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   input symbol [size: 8 x 1]
+//  _sym_dec    :   input symbol [size: 9 x 1]
+int fec_secded7264_decode_symbol(unsigned char * _sym_enc,
+                                 unsigned char * _sym_dec);
+
+extern unsigned char secded7264_P[64];
+extern unsigned char secded7264_syndrome_w1[72];
+
+fec fec_secded7264_create(void *_opts);
+void fec_secded7264_destroy(fec _q);
+void fec_secded7264_print(fec _q);
+void fec_secded7264_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_dec,
+                           unsigned char * _msg_enc);
+void fec_secded7264_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char * _msg_enc,
+                           unsigned char * _msg_dec);
+
+
+// Convolutional: r1/2 K=7
+//                r1/2 K=9
+//                r1/3 K=9
+//                r1/6 K=15
+
+// compute encoded message length for block codes
+//  _dec_msg_len    :   decoded message length (bytes)
+//  _m              :   input block size (bits)
+//  _k              :   output block size (bits)
+unsigned int fec_block_get_enc_msg_len(unsigned int _dec_msg_len,
+                                       unsigned int _m,
+                                       unsigned int _k);
+
+// compute encoded message length for convolutional codes
+//  _dec_msg_len    :   decoded message length
+//  _K              :   constraint length
+//  _p              :   puncturing rate, r = _p / (_p+1)
+unsigned int fec_conv_get_enc_msg_len(unsigned int _dec_msg_len,
+                                      unsigned int _K,
+                                      unsigned int _p);
+
+// convolutional code polynomials
+extern int fec_conv27_poly[2];
+extern int fec_conv29_poly[2];
+extern int fec_conv39_poly[3];
+extern int fec_conv615_poly[6];
+
+// convolutional code puncturing matrices  [R x P]
+extern int fec_conv27p23_matrix[4];     // [2 x 2]
+extern int fec_conv27p34_matrix[6];     // [2 x 3]
+extern int fec_conv27p45_matrix[8];     // [2 x 4]
+extern int fec_conv27p56_matrix[10];    // [2 x 5]
+extern int fec_conv27p67_matrix[12];    // [2 x 6]
+extern int fec_conv27p78_matrix[14];    // [2 x 7]
+
+extern int fec_conv29p23_matrix[4];     // [2 x 2]
+extern int fec_conv29p34_matrix[6];     // [2 x 3]
+extern int fec_conv29p45_matrix[8];     // [2 x 4]
+extern int fec_conv29p56_matrix[10];    // [2 x 5]
+extern int fec_conv29p67_matrix[12];    // [2 x 6]
+extern int fec_conv29p78_matrix[14];    // [2 x 7]
+
+fec fec_conv_create(fec_scheme _fs);
+void fec_conv_destroy(fec _q);
+void fec_conv_print(fec _q);
+void fec_conv_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_dec,
+                     unsigned char * _msg_enc);
+void fec_conv_decode_hard(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+void fec_conv_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec);
+void fec_conv_decode(fec _q,
+                     unsigned char * _msg_dec);
+void fec_conv_setlength(fec _q,
+                        unsigned int _dec_msg_len);
+
+// internal initialization methods (sets r, K, viterbi methods)
+void fec_conv_init_v27(fec _q);
+void fec_conv_init_v29(fec _q);
+void fec_conv_init_v39(fec _q);
+void fec_conv_init_v615(fec _q);
+
+// punctured convolutional codes
+fec fec_conv_punctured_create(fec_scheme _fs);
+void fec_conv_punctured_destroy(fec _q);
+void fec_conv_punctured_print(fec _q);
+void fec_conv_punctured_encode(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char * _msg_dec,
+                               unsigned char * _msg_enc);
+void fec_conv_punctured_decode_hard(fec _q,
+                                    unsigned int _dec_msg_len,
+                                    unsigned char * _msg_enc,
+                                    unsigned char * _msg_dec);
+void fec_conv_punctured_decode_soft(fec _q,
+                                    unsigned int _dec_msg_len,
+                                    unsigned char * _msg_enc,
+                                    unsigned char * _msg_dec);
+void fec_conv_punctured_setlength(fec _q,
+                                  unsigned int _dec_msg_len);
+
+// internal initialization methods (sets r, K, viterbi methods,
+// and puncturing matrix)
+void fec_conv_init_v27p23(fec _q);
+void fec_conv_init_v27p34(fec _q);
+void fec_conv_init_v27p45(fec _q);
+void fec_conv_init_v27p56(fec _q);
+void fec_conv_init_v27p67(fec _q);
+void fec_conv_init_v27p78(fec _q);
+
+void fec_conv_init_v29p23(fec _q);
+void fec_conv_init_v29p34(fec _q);
+void fec_conv_init_v29p45(fec _q);
+void fec_conv_init_v29p56(fec _q);
+void fec_conv_init_v29p67(fec _q);
+void fec_conv_init_v29p78(fec _q);
+
+// Reed-Solomon
+
+// compute encoded message length for Reed-Solomon codes
+//  _dec_msg_len    :   decoded message length
+//  _nroots         :   number of roots in polynomial
+//  _nn             :   
+//  _kk             :   
+unsigned int fec_rs_get_enc_msg_len(unsigned int _dec_msg_len,
+                                    unsigned int _nroots,
+                                    unsigned int _nn,
+                                    unsigned int _kk);
+
+
+fec fec_rs_create(fec_scheme _fs);
+void fec_rs_init_p8(fec _q);
+void fec_rs_setlength(fec _q,
+                      unsigned int _dec_msg_len);
+void fec_rs_encode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char * _msg_dec,
+                   unsigned char * _msg_enc);
+void fec_rs_decode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char * _msg_enc,
+                   unsigned char * _msg_dec);
+
+// phi(x) = -logf( tanhf( x/2 ) )
+float sumproduct_phi(float _x);
+
+// iterate over the sum-product algorithm:
+// returns 1 if parity checks, 0 otherwise
+//  _m          :   rows
+//  _n          :   cols
+//  _H          :   sparse binary parity check matrix [size: _m x _n]
+//  _LLR        :   received signal (soft bits, LLR) [size: _n x 1]
+//  _c_hat      :   estimated transmitted signal [size: _n x 1]
+//  _max_steps  :   maximum number of steps before bailing
+int fec_sumproduct(unsigned int    _m,
+                   unsigned int    _n,
+                   smatrixb        _H,
+                   float *         _LLR,
+                   unsigned char * _c_hat,
+                   unsigned int    _max_steps);
+
+// sum-product algorithm, returns 1 if parity checks, 0 otherwise
+//  _m      :   rows
+//  _n      :   cols
+//  _H      :   sparse binary parity check matrix [size: _m x _n]
+//  _c_hat  :   estimated transmitted signal [size: _n x 1]
+//
+// internal state arrays
+//  _Lq     :   [size: _m x _n]
+//  _Lr     :   [size: _m x _n]
+//  _Lc     :   [size: _n x 1]
+//  _LQ     :   [size: _n x 1]
+//  _parity :   _H * _c_hat [size: _m x 1]
+int fec_sumproduct_step(unsigned int    _m,
+                        unsigned int    _n,
+                        smatrixb        _H,
+                        unsigned char * _c_hat,
+                        float *         _Lq,
+                        float *         _Lr,
+                        float *         _Lc,
+                        float *         _LQ,
+                        unsigned char * _parity);
+
+//
+// packetizer
+//
+
+// fec/interleaver plan
+struct fecintlv_plan {
+    unsigned int dec_msg_len;
+    unsigned int enc_msg_len;
+
+    // fec codec
+    fec_scheme fs;
+    fec f;
+
+    // interleaver
+    interleaver q;
+};
+
+// packetizer object
+struct packetizer_s {
+    unsigned int msg_len;
+    unsigned int packet_len;
+
+    crc_scheme check;
+    unsigned int crc_length;
+
+    struct fecintlv_plan * plan;
+    unsigned int plan_len;
+
+    // buffers (ping-pong)
+    unsigned int buffer_len;
+    unsigned char * buffer_0;
+    unsigned char * buffer_1;
+};
+
+
+//
+// MODULE : fft (fast discrete Fourier transform)
+//
+
+// fast fourier transform method
+typedef enum {
+    LIQUID_FFT_METHOD_UNKNOWN=0,    // unknown method
+    LIQUID_FFT_METHOD_RADIX2,       // Radix-2 (decimation in time)
+    LIQUID_FFT_METHOD_MIXED_RADIX,  // Cooley-Tukey mixed-radix FFT (decimation in time)
+    LIQUID_FFT_METHOD_RADER,        // Rader's method for FFTs of prime length
+    LIQUID_FFT_METHOD_RADER2,       // Rader's method for FFTs of prime length (alternate)
+    LIQUID_FFT_METHOD_DFT,          // regular discrete Fourier transform
+} liquid_fft_method;
+
+// Macro    :   FFT (internal)
+//  FFT     :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_FFT_DEFINE_INTERNAL_API(FFT,T,TC)                \
+                                                                \
+/* print plan recursively */                                    \
+void FFT(_print_plan_recursive)(FFT(plan)    _q,                \
+                                unsigned int _level);           \
+                                                                \
+/* type definitions for create/destroy/execute functions */     \
+typedef FFT(plan)(FFT(_create_t)) (unsigned int _nfft,          \
+                                   TC *         _x,             \
+                                   TC *         _y,             \
+                                   int          _dir,           \
+                                   int          _flags);        \
+typedef void (FFT(_destroy_t))(FFT(plan) _q);                   \
+typedef void (FFT(_execute_t))(FFT(plan) _q);                   \
+                                                                \
+/* FFT create methods */                                        \
+FFT(_create_t) FFT(_create_plan_dft);                           \
+FFT(_create_t) FFT(_create_plan_radix2);                        \
+FFT(_create_t) FFT(_create_plan_mixed_radix);                   \
+FFT(_create_t) FFT(_create_plan_rader);                         \
+FFT(_create_t) FFT(_create_plan_rader2);                        \
+                                                                \
+/* FFT destroy methods */                                       \
+FFT(_destroy_t) FFT(_destroy_plan_dft);                         \
+FFT(_destroy_t) FFT(_destroy_plan_radix2);                      \
+FFT(_destroy_t) FFT(_destroy_plan_mixed_radix);                 \
+FFT(_destroy_t) FFT(_destroy_plan_rader);                       \
+FFT(_destroy_t) FFT(_destroy_plan_rader2);                      \
+                                                                \
+/* FFT execute methods */                                       \
+FFT(_execute_t) FFT(_execute_dft);                              \
+FFT(_execute_t) FFT(_execute_radix2);                           \
+FFT(_execute_t) FFT(_execute_mixed_radix);                      \
+FFT(_execute_t) FFT(_execute_rader);                            \
+FFT(_execute_t) FFT(_execute_rader2);                           \
+                                                                \
+/* specific codelets for small DFTs */                          \
+FFT(_execute_t) FFT(_execute_dft_2);                            \
+FFT(_execute_t) FFT(_execute_dft_3);                            \
+FFT(_execute_t) FFT(_execute_dft_4);                            \
+FFT(_execute_t) FFT(_execute_dft_5);                            \
+FFT(_execute_t) FFT(_execute_dft_6);                            \
+FFT(_execute_t) FFT(_execute_dft_7);                            \
+FFT(_execute_t) FFT(_execute_dft_8);                            \
+FFT(_execute_t) FFT(_execute_dft_16);                           \
+                                                                \
+/* additional methods */                                        \
+unsigned int FFT(_estimate_mixed_radix)(unsigned int _nfft);    \
+                                                                \
+/* discrete cosine transform (DCT) prototypes */                \
+void FFT(_execute_REDFT00)(FFT(plan) _q);   /* DCT-I   */       \
+void FFT(_execute_REDFT10)(FFT(plan) _q);   /* DCT-II  */       \
+void FFT(_execute_REDFT01)(FFT(plan) _q);   /* DCT-III */       \
+void FFT(_execute_REDFT11)(FFT(plan) _q);   /* DCT-IV  */       \
+                                                                \
+/* discrete sine transform (DST) prototypes */                  \
+void FFT(_execute_RODFT00)(FFT(plan) _q);   /* DST-I   */       \
+void FFT(_execute_RODFT10)(FFT(plan) _q);   /* DST-II  */       \
+void FFT(_execute_RODFT01)(FFT(plan) _q);   /* DST-III */       \
+void FFT(_execute_RODFT11)(FFT(plan) _q);   /* DST-IV  */       \
+                                                                \
+/* destroy real-to-real one-dimensional plan */                 \
+void FFT(_destroy_plan_r2r_1d)(FFT(plan) _q);                   \
+                                                                \
+/* print real-to-real one-dimensional plan */                   \
+void FFT(_print_plan_r2r_1d)(FFT(plan) _q);                     \
+
+// determine best FFT method based on size
+liquid_fft_method liquid_fft_estimate_method(unsigned int _nfft);
+
+// is input radix-2?
+int fft_is_radix2(unsigned int _n);
+
+// miscellaneous functions
+unsigned int fft_reverse_index(unsigned int _i, unsigned int _n);
+
+
+LIQUID_FFT_DEFINE_INTERNAL_API(LIQUID_FFT_MANGLE_FLOAT, float, liquid_float_complex)
+
+// Use fftw library if installed (and not overridden with configuration),
+// otherwise use internal (less efficient) fft library.
+#if HAVE_FFTW3_H && !defined LIQUID_FFTOVERRIDE
+#   include <fftw3.h>
+#   define FFT_PLAN             fftwf_plan
+#   define FFT_CREATE_PLAN      fftwf_plan_dft_1d
+#   define FFT_DESTROY_PLAN     fftwf_destroy_plan
+#   define FFT_EXECUTE          fftwf_execute
+#   define FFT_DIR_FORWARD      FFTW_FORWARD
+#   define FFT_DIR_BACKWARD     FFTW_BACKWARD
+#   define FFT_METHOD           FFTW_ESTIMATE
+#else
+#   define FFT_PLAN             fftplan
+#   define FFT_CREATE_PLAN      fft_create_plan
+#   define FFT_DESTROY_PLAN     fft_destroy_plan
+#   define FFT_EXECUTE          fft_execute
+#   define FFT_DIR_FORWARD      LIQUID_FFT_FORWARD
+#   define FFT_DIR_BACKWARD     LIQUID_FFT_BACKWARD
+#   define FFT_METHOD           0
+#endif
+
+
+
+//
+// MODULE : filter
+//
+
+// esimate required filter length given transition bandwidth and
+// stop-band attenuation (algorithm from [Vaidyanathan:1993])
+//  _df     :   transition bandwidth (0 < _df < 0.5)
+//  _As     :   stop-band attenuation [dB] (As > 0)
+float estimate_req_filter_len_Kaiser(float _df,
+                                     float _As);
+
+// esimate required filter length given transition bandwidth and
+// stop-band attenuation (algorithm from [Herrmann:1973])
+//  _df     :   transition bandwidth (0 < _df < 0.5)
+//  _As     :   stop-band attenuation [dB] (As > 0)
+float estimate_req_filter_len_Herrmann(float _df,
+                                       float _As);
+
+
+// fir_farrow
+#define LIQUID_FIRFARROW_DEFINE_INTERNAL_API(FIRFARROW,TO,TC,TI)  \
+void FIRFARROW(_genpoly)(FIRFARROW() _q);
+
+LIQUID_FIRFARROW_DEFINE_INTERNAL_API(FIRFARROW_MANGLE_RRRF,
+                                     float,
+                                     float,
+                                     float)
+
+LIQUID_FIRFARROW_DEFINE_INTERNAL_API(FIRFARROW_MANGLE_CRCF,
+                                     liquid_float_complex,
+                                     float,
+                                     liquid_float_complex)
+
+
+
+// 
+// iirfiltsos : infinite impulse respone filter (second-order sections)
+//
+#define IIRFILTSOS_MANGLE_RRRF(name)  LIQUID_CONCAT(iirfiltsos_rrrf,name)
+#define IIRFILTSOS_MANGLE_CRCF(name)  LIQUID_CONCAT(iirfiltsos_crcf,name)
+#define IIRFILTSOS_MANGLE_CCCF(name)  LIQUID_CONCAT(iirfiltsos_cccf,name)
+
+#define LIQUID_IIRFILTSOS_DEFINE_INTERNAL_API(IIRFILTSOS,TO,TC,TI)  \
+typedef struct IIRFILTSOS(_s) * IIRFILTSOS();                   \
+                                                                \
+/* filter structure */                                          \
+struct IIRFILTSOS(_s) {                                         \
+    TC b[3];    /* feed-forward coefficients                */  \
+    TC a[3];    /* feed-back coefficients                   */  \
+                                                                \
+    /* internal buffering                                   */  \
+    TI x[3];    /* Direct form I  buffer (input)            */  \
+    TO y[3];    /* Direct form I  buffer (output)           */  \
+    TO v[3];    /* Direct form II buffer                    */  \
+};                                                              \
+                                                                \
+/* create 2nd-ordr infinite impulse reponse filter          */  \
+/*  _b      : feed-forward coefficients [size: _3 x 1]      */  \
+/*  _a      : feed-back coefficients    [size: _3 x 1]      */  \
+IIRFILTSOS() IIRFILTSOS(_create)(TC * _b,                       \
+                                 TC * _a);                      \
+                                                                \
+/* explicitly set 2nd-order IIR filter coefficients         */  \
+/*  _q      : iirfiltsos object                             */  \
+/*  _b      : feed-forward coefficients [size: _3 x 1]      */  \
+/*  _a      : feed-back coefficients    [size: _3 x 1]      */  \
+void IIRFILTSOS(_set_coefficients)(IIRFILTSOS() _q,             \
+                                   TC *         _b,             \
+                                   TC *         _a);            \
+                                                                \
+/* destroy iirfiltsos object, freeing all internal memory   */  \
+void IIRFILTSOS(_destroy)(IIRFILTSOS() _q);                     \
+                                                                \
+/* print iirfiltsos object properties to stdout             */  \
+void IIRFILTSOS(_print)(IIRFILTSOS() _q);                       \
+                                                                \
+/* clear/reset iirfiltsos object internals                  */  \
+void IIRFILTSOS(_reset)(IIRFILTSOS() _q);                       \
+                                                                \
+/* compute filter output                                    */  \
+/*  _q      : iirfiltsos object                             */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample pointer                         */  \
+void IIRFILTSOS(_execute)(IIRFILTSOS() _q,                      \
+                          TI           _x,                      \
+                          TO *         _y);                     \
+                                                                \
+/* compute filter output, direct-form I method              */  \
+/*  _q      : iirfiltsos object                             */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample pointer                         */  \
+void IIRFILTSOS(_execute_df1)(IIRFILTSOS() _q,                  \
+                              TI           _x,                  \
+                              TO *         _y);                 \
+                                                                \
+/* compute filter output, direct-form II method             */  \
+/*  _q      : iirfiltsos object                             */  \
+/*  _x      : input sample                                  */  \
+/*  _y      : output sample pointer                         */  \
+void IIRFILTSOS(_execute_df2)(IIRFILTSOS() _q,                  \
+                              TI           _x,                  \
+                              TO *         _y);                 \
+                                                                \
+/* compute and return group delay of filter object          */  \
+/*  _q      : filter object                                 */  \
+/*  _fc     : frequency to evaluate                         */  \
+float IIRFILTSOS(_groupdelay)(IIRFILTSOS() _q,                  \
+                              float        _fc);                \
+
+LIQUID_IIRFILTSOS_DEFINE_INTERNAL_API(IIRFILTSOS_MANGLE_RRRF,
+                                      float,
+                                      float,
+                                      float)
+
+LIQUID_IIRFILTSOS_DEFINE_INTERNAL_API(IIRFILTSOS_MANGLE_CRCF,
+                                      liquid_float_complex,
+                                      float,
+                                      liquid_float_complex)
+
+LIQUID_IIRFILTSOS_DEFINE_INTERNAL_API(IIRFILTSOS_MANGLE_CCCF,
+                                      liquid_float_complex,
+                                      liquid_float_complex,
+                                      liquid_float_complex)
+
+
+// firdes : finite impulse response filter design
+
+// Find approximate bandwidth adjustment factor rho based on
+// filter delay and desired excess bandwdith factor.
+//
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+float rkaiser_approximate_rho(unsigned int _m,
+                              float _beta);
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc using the bisection method
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+void liquid_firdes_rkaiser_bisection(unsigned int _k,
+                                     unsigned int _m,
+                                     float _beta,
+                                     float _dt,
+                                     float * _h,
+                                     float * _rho);
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc using the quadratic method.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+void liquid_firdes_rkaiser_quadratic(unsigned int _k,
+                                     unsigned int _m,
+                                     float _beta,
+                                     float _dt,
+                                     float * _h,
+                                     float * _rho);
+
+// compute filter coefficients and determine resulting ISI
+//  
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+//  _h      :   filter buffer [size: 2*_k*_m+1]
+float liquid_firdes_rkaiser_internal_isi(unsigned int _k,
+                                         unsigned int _m,
+                                         float _beta,
+                                         float _dt,
+                                         float _rho,
+                                         float * _h);
+
+// Design flipped Nyquist/root-Nyquist filters
+void liquid_firdes_fnyquist(liquid_firfilt_type _type,
+                            int                 _root,
+                            unsigned int        _k,
+                            unsigned int        _m,
+                            float               _beta,
+                            float               _dt,
+                            float *             _h);
+
+// flipped exponential frequency response
+void liquid_firdes_fexp_freqresponse(unsigned int _k,
+                                     unsigned int _m,
+                                     float        _beta,
+                                     float *      _H);
+
+// flipped hyperbolic secant frequency response
+void liquid_firdes_fsech_freqresponse(unsigned int _k,
+                                      unsigned int _m,
+                                      float        _beta,
+                                      float *      _H);
+
+// flipped hyperbolic secant frequency response
+void liquid_firdes_farcsech_freqresponse(unsigned int _k,
+                                         unsigned int _m,
+                                         float        _beta,
+                                         float *      _H);
+
+
+
+// initialize the frequency grid on the disjoint bounded set
+void firdespm_init_grid(firdespm _q);
+
+// compute interpolating polynomial
+void firdespm_compute_interp(firdespm _q);
+
+// compute error signal from actual response (interpolator
+// output), desired response, and weights
+void firdespm_compute_error(firdespm _q);
+
+// search error curve for _r+1 extremal indices
+void firdespm_iext_search(firdespm _q);
+
+// evaluates result to determine if Remez exchange algorithm
+// has converged
+int firdespm_is_search_complete(firdespm _q);
+
+// compute filter taps (coefficients) from result
+void firdespm_compute_taps(firdespm _q, float * _h);
+
+// iirdes : infinite impulse response filter design
+
+// Sorts array _z of complex numbers into complex conjugate pairs to
+// within a tolerance. Conjugate pairs are ordered by increasing real
+// component with the negative imaginary element first. All pure-real
+// elements are placed at the end of the array.
+//
+// Example:
+//      v:              liquid_cplxpair(v):
+//      10 + j*3        -3 - j*4
+//       5 + j*0         3 + j*4
+//      -3 + j*4        10 - j*3
+//      10 - j*3        10 + j*3
+//       3 + j*0         3 + j*0
+//      -3 + j*4         5 + j*0
+//
+//  _z      :   complex array (size _n)
+//  _n      :   number of elements in _z
+//  _tol    :   tolerance for finding complex pairs
+//  _p      :   resulting pairs, pure real values of _z at end
+void liquid_cplxpair(float complex * _z,
+                     unsigned int _n,
+                     float _tol,
+                     float complex * _p);
+
+// post-process cleanup used with liquid_cplxpair
+//
+// once pairs have been identified and ordered, this method
+// will clean up the result by ensuring the following:
+//  * pairs are perfect conjugates
+//  * pairs have negative imaginary component first
+//  * pairs are ordered by increasing real component
+//  * pure-real elements are ordered by increasing value
+//
+//  _p          :   pre-processed complex array [size: _n x 1]
+//  _n          :   array length
+//  _num_pairs  :   number of complex conjugate pairs
+void liquid_cplxpair_cleanup(float complex * _p,
+                             unsigned int _n,
+                             unsigned int _num_pairs);
+
+// Jacobian elliptic functions (src/filter/src/ellip.c)
+
+// Landen transformation (_n iterations)
+void landenf(float _k,
+             unsigned int _n,
+             float * _v);
+
+// compute elliptic integral K(k) for _n recursions
+void ellipkf(float _k,
+             unsigned int _n,
+             float * _K,
+             float * _Kp);
+
+// elliptic degree
+float ellipdegf(float _N,
+                float _k1,
+                unsigned int _n);
+
+// elliptic cd() function (_n recursions)
+float complex ellip_cdf(float complex _u,
+                        float _k,
+                        unsigned int _n);
+
+// elliptic inverse cd() function (_n recursions)
+float complex ellip_acdf(float complex _u,
+                         float _k,
+                         unsigned int _n);
+
+// elliptic sn() function (_n recursions)
+float complex ellip_snf(float complex _u,
+                        float _k,
+                        unsigned int _n);
+
+// elliptic inverse sn() function (_n recursions)
+float complex ellip_asnf(float complex _u,
+                         float _k,
+                         unsigned int _n);
+
+//
+// MODULE : framing
+//
+
+//
+// bpacket
+//
+
+#define BPACKET_VERSION 101
+
+// generator
+void bpacketgen_compute_packet_len(bpacketgen _q);
+void bpacketgen_assemble_pnsequence(bpacketgen _q);
+void bpacketgen_assemble_header(bpacketgen _q);
+
+// synchronizer
+void bpacketsync_assemble_pnsequence(bpacketsync _q);
+void bpacketsync_execute_seekpn(bpacketsync _q, unsigned char _bit);
+void bpacketsync_execute_rxheader(bpacketsync _q, unsigned char _bit);
+void bpacketsync_execute_rxpayload(bpacketsync _q, unsigned char _bit);
+void bpacketsync_decode_header(bpacketsync _q);
+void bpacketsync_decode_payload(bpacketsync _q);
+void bpacketsync_reconfig(bpacketsync _q);
+
+
+// 
+// flexframe
+//
+
+// flexframe protocol
+#define FLEXFRAME_PROTOCOL  (101)
+
+// header description
+// NOTE: The flexframe header can be improved with crc24, secded7264, v29
+//       which also generates a 54-byte frame. Improves header decoding
+//       by about 1 dB (99% probability of decoding with SNR = -1 dB);
+//       however this requires that the 'libfec' libraries are installed.
+#define FLEXFRAME_H_USER    (14)                    // user-defined array
+#define FLEXFRAME_H_DEC     (FLEXFRAME_H_USER+6)    // decoded length
+#define FLEXFRAME_H_CRC     (LIQUID_CRC_32)         // header CRC
+#define FLEXFRAME_H_FEC0    (LIQUID_FEC_SECDED7264) // header FEC (inner)
+#define FLEXFRAME_H_FEC1    (LIQUID_FEC_HAMMING84)  // header FEC (outer)
+
+
+// 
+// gmskframe
+//
+
+#define GMSKFRAME_VERSION   (3)
+
+// header description
+#define GMSKFRAME_H_USER    (8)                     // user-defined array
+#define GMSKFRAME_H_DEC     (GMSKFRAME_H_USER+5)    // decoded length
+#define GMSKFRAME_H_CRC     (LIQUID_CRC_32)         // header CRC
+#define GMSKFRAME_H_FEC     (LIQUID_FEC_HAMMING128) // header FEC
+#define GMSKFRAME_H_ENC     (26)                    // encoded length (bytes)
+#define GMSKFRAME_H_SYM     (208)                   // number of encoded bits
+
+
+// 
+// ofdmflexframe
+//
+
+#define OFDMFLEXFRAME_PROTOCOL  (104)
+
+// header description
+#define OFDMFLEXFRAME_H_USER    (8)                         // user-defined array
+#define OFDMFLEXFRAME_H_DEC     (OFDMFLEXFRAME_H_USER+6)    // decoded length
+#define OFDMFLEXFRAME_H_CRC     (LIQUID_CRC_32)             // header CRC
+#define OFDMFLEXFRAME_H_FEC     (LIQUID_FEC_GOLAY2412)      // header FEC
+#define OFDMFLEXFRAME_H_ENC     (36)                        // encoded length
+#define OFDMFLEXFRAME_H_MOD     (LIQUID_MODEM_BPSK)         // modulation scheme
+#define OFDMFLEXFRAME_H_BPS     (1)                         // modulation depth
+#define OFDMFLEXFRAME_H_SYM     (288)                       // number of symbols
+
+//
+// MODULE : math
+//
+
+// 
+// basic trigonometric functions
+//
+float liquid_sinf(float _x);
+float liquid_cosf(float _x);
+float liquid_tanf(float _x);
+void  liquid_sincosf(float _x,
+                     float * _sinf,
+                     float * _cosf);
+float liquid_expf(float _x);
+float liquid_logf(float _x);
+
+// 
+// complex math operations
+//
+
+// complex square root
+float complex liquid_csqrtf(float complex _z);
+
+// complex exponent, logarithm
+float complex liquid_cexpf(float complex _z);
+float complex liquid_clogf(float complex _z);
+
+// complex arcsin, arccos, arctan
+float complex liquid_casinf(float complex _z);
+float complex liquid_cacosf(float complex _z);
+float complex liquid_catanf(float complex _z);
+
+// faster approximation to arg{*}
+float liquid_cargf_approx(float complex _z);
+
+
+// internal trig helper functions
+
+// complex rotation vector: cexpf(_Complex_I*THETA)
+#define liquid_cexpjf(THETA) (cosf(THETA) + _Complex_I*sinf(THETA))
+
+
+//
+// MODULE : matrix
+//
+
+// large macro
+//   MATRIX : name-mangling macro
+//   T      : data type
+#define LIQUID_MATRIX_DEFINE_INTERNAL_API(MATRIX,T)             \
+T    MATRIX(_det2x2)(T * _x,                                    \
+                     unsigned int _rx,                          \
+                     unsigned int _cx);
+
+
+LIQUID_MATRIX_DEFINE_INTERNAL_API(MATRIX_MANGLE_FLOAT,   float)
+LIQUID_MATRIX_DEFINE_INTERNAL_API(MATRIX_MANGLE_DOUBLE,  double)
+
+LIQUID_MATRIX_DEFINE_INTERNAL_API(MATRIX_MANGLE_CFLOAT,  liquid_float_complex)
+LIQUID_MATRIX_DEFINE_INTERNAL_API(MATRIX_MANGLE_CDOUBLE, liquid_double_complex)
+
+
+// sparse 'alist' matrix type (similar to MacKay, Davey Lafferty convention)
+// large macro
+//   SMATRIX    : name-mangling macro
+//   T          : primitive data type
+#define LIQUID_SMATRIX_DEFINE_INTERNAL_API(SMATRIX,T)           \
+                                                                \
+void SMATRIX(_reset_max_mlist)(SMATRIX() _q);                   \
+void SMATRIX(_reset_max_nlist)(SMATRIX() _q);                   \
+
+LIQUID_SMATRIX_DEFINE_INTERNAL_API(SMATRIX_MANGLE_BOOL,  unsigned char)
+LIQUID_SMATRIX_DEFINE_INTERNAL_API(SMATRIX_MANGLE_FLOAT, float)
+LIQUID_SMATRIX_DEFINE_INTERNAL_API(SMATRIX_MANGLE_INT,   short int)
+
+// search for index placement in list
+unsigned short int smatrix_indexsearch(unsigned short int * _list,
+                                       unsigned int         _num_elements,
+                                       unsigned short int   _value);
+
+
+
+
+//
+// MODULE : modem
+//
+
+// 'Square' QAM
+#define QAM4_ALPHA      (1./sqrt(2))
+#define QAM8_ALPHA      (1./sqrt(6))
+#define QAM16_ALPHA     (1./sqrt(10))
+#define QAM32_ALPHA     (1./sqrt(20))
+#define QAM64_ALPHA     (1./sqrt(42))
+#define QAM128_ALPHA    (1./sqrt(82))
+#define QAM256_ALPHA    (1./sqrt(170))
+#define QAM1024_ALPHA   (1./sqrt(682))
+#define QAM4096_ALPHA   (1./sqrt(2730))
+
+// Rectangular QAM
+#define RQAM4_ALPHA     QAM4_ALPHA
+#define RQAM8_ALPHA     QAM8_ALPHA
+#define RQAM16_ALPHA    QAM16_ALPHA
+#define RQAM32_ALPHA    (1./sqrt(26))
+#define RQAM64_ALPHA    QAM64_ALPHA
+#define RQAM128_ALPHA   (1./sqrt(106))
+#define RQAM256_ALPHA   QAM256_ALPHA
+#define RQAM512_ALPHA   (1./sqrt(426))
+#define RQAM1024_ALPHA  QAM1024_ALPHA
+#define RQAM2048_ALPHA  (1./sqrt(1706))
+#define RQAM4096_ALPHA  QAM4096_ALPHA
+
+// ASK
+#define ASK2_ALPHA      (1.)
+#define ASK4_ALPHA      (1./sqrt(5))
+#define ASK8_ALPHA      (1./sqrt(21))
+#define ASK16_ALPHA     (1./sqrt(85))
+#define ASK32_ALPHA     (1./sqrt(341))
+#define ASK64_ALPHA     (1./sqrt(1365))
+#define ASK128_ALPHA    (1./sqrt(5461))
+#define ASK256_ALPHA    (1./sqrt(21845))
+
+// Macro    :   MODEM
+//  MODEM   :   name-mangling macro
+//  T       :   primitive data type
+//  TC      :   primitive data type (complex)
+#define LIQUID_MODEM_DEFINE_INTERNAL_API(MODEM,T,TC)            \
+                                                                \
+/* initialize a generic modem object */                         \
+void MODEM(_init)(MODEM() _q, unsigned int _bits_per_symbol);   \
+                                                                \
+/* initialize symbol map for fast modulation */                 \
+void MODEM(_init_map)(MODEM() _q);                              \
+                                                                \
+/* generic modem create routines */                             \
+MODEM() MODEM(_create_ask)( unsigned int _bits_per_symbol);     \
+MODEM() MODEM(_create_qam)( unsigned int _bits_per_symbol);     \
+MODEM() MODEM(_create_psk)( unsigned int _bits_per_symbol);     \
+MODEM() MODEM(_create_dpsk)(unsigned int _bits_per_symbol);     \
+MODEM() MODEM(_create_apsk)(unsigned int _bits_per_symbol);     \
+MODEM() MODEM(_create_arb)( unsigned int _bits_per_symbol);     \
+                                                                \
+/* Initialize arbitrary modem constellation */                  \
+void MODEM(_arb_init)(MODEM() _q,                               \
+                      TC * _symbol_map,                         \
+                      unsigned int _len);                       \
+                                                                \
+/* Initialize arb modem constellation from external file */     \
+void MODEM(_arb_init_file)(MODEM() _q, char * _filename);       \
+                                                                \
+/* specific modem create routines */                            \
+MODEM() MODEM(_create_bpsk)(void);                              \
+MODEM() MODEM(_create_qpsk)(void);                              \
+MODEM() MODEM(_create_ook)(void);                               \
+MODEM() MODEM(_create_sqam32)(void);                            \
+MODEM() MODEM(_create_sqam128)(void);                           \
+MODEM() MODEM(_create_V29)(void);                               \
+MODEM() MODEM(_create_arb16opt)(void);                          \
+MODEM() MODEM(_create_arb32opt)(void);                          \
+MODEM() MODEM(_create_arb64opt)(void);                          \
+MODEM() MODEM(_create_arb128opt)(void);                         \
+MODEM() MODEM(_create_arb256opt)(void);                         \
+MODEM() MODEM(_create_arb64vt)(void);                           \
+                                                                \
+/* Scale arbitrary modem energy to unity */                     \
+void MODEM(_arb_scale)(MODEM() _q);                             \
+                                                                \
+/* Balance I/Q */                                               \
+void MODEM(_arb_balance_iq)(MODEM() _q);                        \
+                                                                \
+/* modulate using symbol map (look-up table) */                 \
+void MODEM(_modulate_map)(MODEM()      _q,                      \
+                          unsigned int _sym_in,                 \
+                          TC *         _y);                     \
+                                                                \
+/* modem modulate routines */                                   \
+void MODEM(_modulate_ask)      ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_qam)      ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_psk)      ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_dpsk)     ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_arb)      ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_apsk)     ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_bpsk)     ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_qpsk)     ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_ook)      ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_sqam32)   ( MODEM(), unsigned int, TC *);  \
+void MODEM(_modulate_sqam128)  ( MODEM(), unsigned int, TC *);  \
+                                                                \
+/* modem demodulate routines */                                 \
+void MODEM(_demodulate_ask)    ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_qam)    ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_psk)    ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_dpsk)   ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_arb)    ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_apsk)   ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_bpsk)   ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_qpsk)   ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_ook)    ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_sqam32) ( MODEM(), TC, unsigned int *);  \
+void MODEM(_demodulate_sqam128)( MODEM(), TC, unsigned int *);  \
+                                                                \
+/* modem demodulate (soft) routines */                          \
+void MODEM(_demodulate_soft_bpsk)(MODEM()         _q,           \
+                                  TC              _x,           \
+                                  unsigned int *  _sym_out,     \
+                                  unsigned char * _soft_bits);  \
+void MODEM(_demodulate_soft_qpsk)(MODEM()         _q,           \
+                                  TC              _x,           \
+                                  unsigned int *  _sym_out,     \
+                                  unsigned char * _soft_bits);  \
+void MODEM(_demodulate_soft_arb)( MODEM()         _q,           \
+                                  TC              _x,           \
+                                  unsigned int *  _sym_out,     \
+                                  unsigned char * _soft_bits);  \
+                                                                \
+/* generate soft demodulation look-up table */                  \
+void MODEM(_demodsoft_gentab)(MODEM()      _q,                  \
+                              unsigned int _p);                 \
+                                                                \
+/* generic soft demodulation routine using nearest-neighbors */ \
+/* look-up table                                             */ \
+void MODEM(_demodulate_soft_table)(MODEM()         _q,          \
+                                   TC              _x,          \
+                                   unsigned int *  _sym_out,    \
+                                   unsigned char * _soft_bits); \
+                                                                \
+/* Demodulate a linear symbol constellation using dynamic   */  \
+/* threshold calculation                                    */  \
+/*  _v      :   input value             */                      \
+/*  _m      :   bits per symbol         */                      \
+/*  _alpha  :   scaling factor          */                      \
+/*  _s      :   demodulated symbol      */                      \
+/*  _res    :   residual                */                      \
+void MODEM(_demodulate_linear_array)(T              _v,         \
+                                     unsigned int   _m,         \
+                                     T              _alpha,     \
+                                     unsigned int * _s,         \
+                                     T *            _res);      \
+                                                                \
+/* Demodulate a linear symbol constellation using           */  \
+/* refereneced lookup table                                 */  \
+/*  _v      :   input value             */                      \
+/*  _m      :   bits per symbol         */                      \
+/*  _ref    :   array of thresholds     */                      \
+/*  _s      :   demodulated symbol      */                      \
+/*  _res    :   residual                */                      \
+void MODEM(_demodulate_linear_array_ref)(T              _v,     \
+                                         unsigned int   _m,     \
+                                         T *            _ref,   \
+                                         unsigned int * _s,     \
+                                         T *            _res);  \
+
+
+
+// define internal modem APIs
+LIQUID_MODEM_DEFINE_INTERNAL_API(LIQUID_MODEM_MANGLE_FLOAT,float,float complex)
+
+// APSK constants (container for apsk structure definitions)
+struct liquid_apsk_s {
+    modulation_scheme scheme;   // APSK modulation scheme
+    unsigned int    num_levels; // number of rings
+    unsigned int *  p;          // number of symbols per ring
+    float *         r;          // radius of each ring
+    float *         phi;        // phase offset of each ring
+    float *         r_slicer;   // radius slicer
+    unsigned char * map;        // symbol mapping
+};
+
+extern struct liquid_apsk_s liquid_apsk4;
+extern struct liquid_apsk_s liquid_apsk8;
+extern struct liquid_apsk_s liquid_apsk16;
+extern struct liquid_apsk_s liquid_apsk32;
+extern struct liquid_apsk_s liquid_apsk64;
+extern struct liquid_apsk_s liquid_apsk128;
+extern struct liquid_apsk_s liquid_apsk256;
+
+
+// 'square' 32-QAM (first quadrant)
+extern const float complex modem_arb_sqam32[8];
+
+// 'square' 128-QAM (first quadrant)
+extern const float complex modem_arb_sqam128[32];
+
+// V.29 star constellation
+extern const float complex modem_arb_V29[16];
+
+// Virginia Tech logo
+extern const float complex modem_arb_vt64[64];
+
+// optimal QAM constellations
+extern const float complex modem_arb16opt[16];
+extern const float complex modem_arb32opt[32];
+extern const float complex modem_arb64opt[64];
+extern const float complex modem_arb128opt[128];
+extern const float complex modem_arb256opt[256];
+
+
+//
+// MODULE : multichannel
+//
+
+// ofdm frame (common)
+
+// generate short sequence symbols
+//  _p      :   subcarrier allocation array
+//  _M      :   total number of subcarriers
+//  _S0     :   output symbol (freq)
+//  _s0     :   output symbol (time)
+//  _M_S0   :   total number of enabled subcarriers in S0
+void ofdmframe_init_S0(unsigned char * _p,
+                       unsigned int    _M,
+                       float complex * _S0,
+                       float complex * _s0,
+                       unsigned int *  _M_S0);
+
+// generate long sequence symbols
+//  _p      :   subcarrier allocation array
+//  _M      :   total number of subcarriers
+//  _S1     :   output symbol (freq)
+//  _s1     :   output symbol (time)
+//  _M_S1   :   total number of enabled subcarriers in S1
+void ofdmframe_init_S1(unsigned char * _p,
+                       unsigned int    _M,
+                       float complex * _S1,
+                       float complex * _s1,
+                       unsigned int *  _M_S1);
+
+// generate symbol (add cyclic prefix/postfix, overlap)
+void ofdmframegen_gensymbol(ofdmframegen    _q,
+                            float complex * _buffer);
+
+void ofdmframesync_cpcorrelate(ofdmframesync _q);
+void ofdmframesync_findrxypeak(ofdmframesync _q);
+void ofdmframesync_rxpayload(ofdmframesync _q);
+
+void ofdmframesync_execute_seekplcp(ofdmframesync _q);
+void ofdmframesync_execute_S0a(ofdmframesync _q);
+void ofdmframesync_execute_S0b(ofdmframesync _q);
+void ofdmframesync_execute_S1( ofdmframesync _q);
+void ofdmframesync_execute_rxsymbols(ofdmframesync _q);
+
+void ofdmframesync_S0_metrics(ofdmframesync _q,
+                              float complex * _G,
+                              float complex * _s_hat);
+
+// estimate short sequence gain
+//  _q      :   ofdmframesync object
+//  _x      :   input array (time)
+//  _G      :   output gain (freq)
+void ofdmframesync_estimate_gain_S0(ofdmframesync   _q,
+                                    float complex * _x,
+                                    float complex * _G);
+
+// estimate long sequence gain
+//  _q      :   ofdmframesync object
+//  _x      :   input array (time)
+//  _G      :   output gain (freq)
+void ofdmframesync_estimate_gain_S1(ofdmframesync _q,
+                                    float complex * _x,
+                                    float complex * _G);
+
+// estimate complex equalizer gain from G0 and G1
+//  _q      :   ofdmframesync object
+//  _ntaps  :   number of time-domain taps for smoothing
+void ofdmframesync_estimate_eqgain(ofdmframesync _q,
+                                   unsigned int _ntaps);
+
+// estimate complex equalizer gain from G0 and G1 using polynomial fit
+//  _q      :   ofdmframesync object
+//  _order  :   polynomial order
+void ofdmframesync_estimate_eqgain_poly(ofdmframesync _q,
+                                        unsigned int _order);
+
+// recover symbol, correcting for gain, pilot phase, etc.
+void ofdmframesync_rxsymbol(ofdmframesync _q);
+
+// 
+// MODULE : nco (numerically-controlled oscillator)
+//
+
+
+// Numerically-controlled oscillator, floating point phase precision
+#define LIQUID_NCO_DEFINE_INTERNAL_API(NCO,T,TC)                \
+                                                                \
+/* constrain phase/frequency to be in [-pi,pi)          */      \
+void NCO(_constrain_phase)(NCO() _q);                           \
+void NCO(_constrain_frequency)(NCO() _q);                       \
+                                                                \
+/* compute trigonometric functions for nco/vco type     */      \
+void NCO(_compute_sincos_nco)(NCO() _q);                        \
+void NCO(_compute_sincos_vco)(NCO() _q);                        \
+                                                                \
+/* reset internal phase-locked loop filter              */      \
+void NCO(_pll_reset)(NCO() _q);                                 \
+
+// Define nco internal APIs
+LIQUID_NCO_DEFINE_INTERNAL_API(NCO_MANGLE_FLOAT,
+                               float,
+                               float complex)
+
+// 
+// MODULE : optim (non-linear optimization)
+//
+
+// optimization threshold switch
+//  _u0         :   first utility
+//  _u1         :   second utility
+//  _minimize   :   minimize flag
+//
+// returns:
+//  (_u0 > _u1) if (_minimize == 1)
+//  (_u0 < _u1) otherwise
+int optim_threshold_switch(float _u0,
+                           float _u1,
+                           int _minimize);
+
+// compute the gradient of a function at a particular point
+//  _utility    :   user-defined function
+//  _userdata   :   user-defined data object
+//  _x          :   operating point, [size: _n x 1]
+//  _n          :   dimensionality of search
+//  _delta      :   step value for which to compute gradient
+//  _gradient   :   resulting gradient
+void gradsearch_gradient(utility_function _utility,
+                         void  *          _userdata,
+                         float *          _x,
+                         unsigned int     _n,
+                         float            _delta,
+                         float *          _gradient);
+
+// execute line search; loosely solve:
+//
+//    min|max phi(alpha) := f(_x - alpha*_p)
+//
+// and return best guess at alpha that achieves this
+//
+//  _utility    :   user-defined function
+//  _userdata   :   user-defined data object
+//  _direction  :   search direction (e.g. LIQUID_OPTIM_MINIMIZE)
+//  _n          :   dimensionality of search
+//  _x          :   operating point, [size: _n x 1]
+//  _p          :   normalized gradient, [size: _n x 1]
+//  _alpha      :   initial step size
+float gradsearch_linesearch(utility_function _utility,
+                            void  *          _userdata,
+                            int              _direction,
+                            unsigned int     _n,
+                            float *          _x,
+                            float *          _p,
+                            float            _alpha);
+
+// normalize vector, returning its l2-norm
+float gradsearch_norm(float *      _v,
+                      unsigned int _n);
+
+
+// quasi-Newton search object
+struct qnsearch_s {
+    float* v;           // vector to optimize (externally allocated)
+    unsigned int num_parameters;    // number of parameters to optimize [n]
+
+    float gamma;        // nominal stepsize
+    float delta;        // differential used to compute (estimate) derivative
+    float dgamma;       // decremental gamma parameter
+    float gamma_hat;    // step size (decreases each epoch)
+    float* v_prime;     // temporary vector array
+    float* dv;          // parameter step vector
+
+    float * B;          // approximate Hessian matrix inverse [n x n]
+    float * H;          // Hessian matrix
+
+    float* p;           // search direction
+    float* gradient;    // gradient approximation
+    float* gradient0;   // gradient approximation (previous step)
+
+    // External utility function.
+    utility_function get_utility;
+    float utility;      // current utility
+    void * userdata;    // userdata pointer passed to utility callback
+    int minimize;       // minimize/maximimze utility (search direction)
+};
+
+// compute gradient(x_k)
+void qnsearch_compute_gradient(qnsearch _q);
+
+// compute the norm of the gradient(x_k)
+void qnsearch_normalize_gradient(qnsearch _q);
+
+// compute Hessian (estimate)
+void qnsearch_compute_Hessian(qnsearch _q);
+
+// compute the updated inverse hessian matrix using the Broyden, Fletcher,
+// Goldfarb & Shanno method (BFGS)
+void qnsearch_update_hessian_bfgs(qnsearch _q);
+
+
+// Chromosome structure used in genetic algorithm searches
+struct chromosome_s {
+    unsigned int num_traits;            // number of represented traits
+    unsigned int * bits_per_trait;      // bits to represent each trait
+    unsigned long * max_value;          // maximum representable integer value
+    unsigned long * traits;             // chromosome data
+
+    unsigned int num_bits;              // total number of bits
+};
+
+struct gasearch_s {
+    chromosome * population;            // population of chromosomes
+    unsigned int population_size;       // size of the population
+    unsigned int selection_size;        // number of 
+    float mutation_rate;                // rate of mutation
+
+    unsigned int num_parameters;        // number of parameters to optimize
+    unsigned int bits_per_chromosome;   // total number of bits in each chromosome
+
+    float *utility;                     // utility array
+    unsigned int *rank;                 // rank indices of chromosomes (best to worst)
+
+    chromosome c;                       // copy of best chromosome, optimal solution
+    float utility_opt;                  // optimum utility (fitness of best solution)
+
+    // External utility function.
+    //
+    // The success of a GA search algorithm is contingent upon the
+    // design of a good utility function.  It should meet the following
+    // criteria:
+    //   - monotonically increasing (never flat)
+    //   - efficient to compute
+    //   - maps the [0,1] bounded output vector to desired range
+    //   - for multiple objectives, utility should be high \em only when
+    //         all objectives are met (multiplicative, not additive)
+    gasearch_utility get_utility;       // utility function pointer
+    void * userdata;                    // object to optimize
+    int minimize;                       // minimize/maximize utility (search direction)
+};
+
+//
+// gasearch internal methods
+//
+
+// evaluate fitness of entire population
+void gasearch_evaluate(gasearch _q);
+
+// crossover population
+void gasearch_crossover(gasearch _q);
+
+// mutate population
+void gasearch_mutate(gasearch _q);
+
+// rank population by fitness
+void gasearch_rank(gasearch _q);
+
+// sort values by index
+//  _v          :   input values [size: _len x 1]
+//  _rank       :   output rank array (indices) [size: _len x 1]
+//  _len        :   length of input array
+//  _descending :   descending/ascending
+void optim_sort(float *_v,
+                unsigned int * _rank,
+                unsigned int _len,
+                int _descending);
+
+
+//
+// MODULE : random
+//
+
+#define randf_inline() ((float) rand() / (float) RAND_MAX)
+
+float complex icrandnf();
+
+// generate x ~ Gamma(delta,1)
+float randgammaf_delta(float _delta);
+
+// data scrambler masks
+#define LIQUID_SCRAMBLE_MASK0   (0xb4)
+#define LIQUID_SCRAMBLE_MASK1   (0x6a)
+#define LIQUID_SCRAMBLE_MASK2   (0x8b)
+#define LIQUID_SCRAMBLE_MASK3   (0xc5)
+
+//
+// MODULE : sequence
+//
+
+// maximal-length sequence
+struct msequence_s {
+    unsigned int m;     // length generator polynomial, shift register
+    unsigned int g;     // generator polynomial
+    unsigned int a;     // initial shift register state, default: 1
+
+    unsigned int n;     // length of sequence, n = (2^m)-1
+    unsigned int v;     // shift register
+    unsigned int b;     // return bit
+};
+
+// Default msequence generator objects
+extern struct msequence_s msequence_default[16];
+
+
+//
+// MODULE : utility
+//
+
+// number of ones in a byte
+//  0   0000 0000   :   0
+//  1   0000 0001   :   1
+//  2   0000 0010   :   1
+//  3   0000 0011   :   2
+//  4   0000 0100   :   1
+//  ...
+//  126 0111 1110   :   6
+//  127 0111 1111   :   7
+//  128 1000 0000   :   1
+//  129 1000 0001   :   2
+//  ...
+//  253 1111 1101   :   7
+//  254 1111 1110   :   7
+//  255 1111 1111   :   8
+extern const unsigned char liquid_c_ones[256];
+
+// Count the number of ones in an integer, inline insertion
+#define liquid_count_ones_uint16(x) (           \
+    liquid_c_ones[  (x)      & 0xff ] +         \
+    liquid_c_ones[ ((x)>>8)  & 0xff ])
+
+#define liquid_count_ones_uint24(x) (           \
+    liquid_c_ones[  (x)      & 0xff ] +         \
+    liquid_c_ones[ ((x)>> 8) & 0xff ] +         \
+    liquid_c_ones[ ((x)>>16) & 0xff ])
+
+#define liquid_count_ones_uint32(x) (           \
+    liquid_c_ones[  (x)      & 0xff ] +         \
+    liquid_c_ones[ ((x)>> 8) & 0xff ] +         \
+    liquid_c_ones[ ((x)>>16) & 0xff ] +         \
+    liquid_c_ones[ ((x)>>24) & 0xff ])
+
+
+// number of ones in a byte, modulo 2
+//  0   0000 0000   :   0
+//  1   0000 0001   :   1
+//  2   0000 0010   :   1
+//  3   0000 0011   :   0
+//  4   0000 0100   :   1
+//  ...
+//  126 0111 1110   :   0
+//  127 0111 1111   :   1
+//  128 1000 0000   :   1
+//  129 1000 0001   :   0
+//  ...
+//  253 1111 1101   :   1
+//  254 1111 1110   :   1
+//  255 1111 1111   :   0
+extern const unsigned char liquid_c_ones_mod2[256];
+
+// Count the number of ones in an integer modulo 2, inline insertion
+#define liquid_count_ones_mod2_uint16(x) ((         \
+    liquid_c_ones_mod2[  (x)      & 0xff ] +        \
+    liquid_c_ones_mod2[ ((x)>>8)  & 0xff ]) % 2)
+
+#define liquid_count_ones_mod2_uint32(x) ((         \
+    liquid_c_ones_mod2[  (x)       & 0xff ] +       \
+    liquid_c_ones_mod2[ ((x)>> 8)  & 0xff ] +       \
+    liquid_c_ones_mod2[ ((x)>>16)  & 0xff ] +       \
+    liquid_c_ones_mod2[ ((x)>>24)  & 0xff ]) % 2)
+
+// compute binary dot-products (inline pre-processor macros)
+#define liquid_bdotprod_uint8(x,y)  liquid_c_ones_mod2[(x)&(y)]
+#define liquid_bdotprod_uint16(x,y) liquid_count_ones_mod2_uint16((x)&(y))
+#define liquid_bdotprod_uint32(x,y) liquid_count_ones_mod2_uint32((x)&(y))
+
+// number of leading zeros in byte
+extern unsigned int liquid_c_leading_zeros[256];
+
+// byte reversal and manipulation
+extern const unsigned char liquid_reverse_byte_gentab[256];
+#endif // __LIQUID_INTERNAL_H__
+
diff --git a/makefile.in b/makefile.in
new file mode 100644
index 0000000..33a458d
--- /dev/null
+++ b/makefile.in
@@ -0,0 +1,1672 @@
+# Copyright (c) 2007 - 2016 Joseph Gaeddert
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# 
+# Makefile for liquid-dsp libraries
+#
+# Targets:
+#    all                 :   dynamic shared-library object (e.g. libliquid.so)
+#    install             :   install the dynamic shared library object and
+#                            header file(s)
+#    uninstall           :   uninstall the library and header file(s)
+#    clean               :   clean all targets (bench, check, examples, etc)
+#    distclean           :   removes everything except the originally distributed files
+#    check               :   build and run autotest program
+#    bench               :   build and run benchmarking program
+#    examples            :   build all examples
+#    sandbox             :   build all sandbox examples
+#    world               :   build absolutely everything (but don't install)
+#
+#    clean-modules       :   clean all modules
+#    clean-examples      :   clean examples programs
+#    clean-sandbox       :   clean sandbox programs
+#    clean-check         :   clean autotest program
+#    clean-bench         :   clean benchmark program
+#
+
+# autoconf initialization macros
+NAME		:= @PACKAGE_NAME@
+VERSION		:= @PACKAGE_VERSION@
+BUGREPORT	:= @PACKAGE_BUGREPORT@
+
+# paths
+srcdir		:= @srcdir@
+libdir		:= @libdir@
+prefix		:= @prefix@
+exec_prefix	:= @exec_prefix@
+include_dirs	:= . include
+
+# programs
+CC		:= @CC@
+MV		:= mv -f
+RM		:= rm -f
+SED		:= @SED@
+GREP		:= @GREP@
+AR		:= ar
+RANLIB		:= ranlib
+
+# flags
+INCLUDE_CFLAGS	= $(addprefix -I ,$(include_dirs))
+CONFIG_CFLAGS	= @CFLAGS@ @DEBUG_OPTION@ @ARCH_OPTION@
+# -g : debugging info
+CFLAGS		+= $(INCLUDE_CFLAGS) -Wall -fPIC $(CONFIG_CFLAGS)
+LDFLAGS		+= @LDFLAGS@ @LIBS@
+ARFLAGS		= r
+PATHSEP		= /
+
+# 
+# liquid headers
+#
+headers_install	:= liquid.h
+headers		:= $(headers_install) liquid.internal.h
+include_headers	:= $(addprefix include/,$(headers))
+
+
+## 
+## liquid-dsp modules
+##
+
+all:
+
+# additional targets to clean
+extra_clean :=
+
+# additional autotest objects
+autotest_extra_obj :=
+
+# additional benchmark objects
+benchmark_extra_obj :=
+
+#
+# MODULE : agc - automatic gain control
+#
+
+# object files
+agc_objects =							\
+	src/agc/src/agc_crcf.o					\
+	src/agc/src/agc_rrrf.o					\
+
+# explicit targets and dependencies
+src/agc/src/agc_crcf.o : %.o : %.c src/agc/src/agc.c $(include_headers)
+src/agc/src/agc_rrrf.o : %.o : %.c src/agc/src/agc.c $(include_headers)
+
+# autotests
+agc_autotests :=						\
+	src/agc/tests/agc_crcf_autotest.c			\
+
+# benchmarks
+agc_benchmarks :=						\
+	src/agc/bench/agc_crcf_benchmark.c			\
+
+#
+# MODULE : audio
+#
+
+# described below
+audio_objects :=						\
+	src/audio/src/cvsd.o					\
+
+src/cvsd/src/cvsd.o : %.o : %.c $(include_headers)
+
+
+audio_autotests :=						\
+	src/audio/tests/cvsd_autotest.c				\
+
+audio_benchmarks :=						\
+	src/audio/bench/cvsd_benchmark.c			\
+
+
+# 
+# MODULE : buffer
+# 
+
+buffer_objects :=						\
+	src/buffer/src/bufferf.o				\
+	src/buffer/src/buffercf.o				\
+
+buffer_includes :=						\
+	src/buffer/src/cbuffer.c				\
+	src/buffer/src/wdelay.c					\
+	src/buffer/src/window.c					\
+
+src/buffer/src/bufferf.o : %.o : %.c $(include_headers) $(buffer_includes)
+
+src/buffer/src/buffercf.o : %.o : %.c $(include_headers) $(buffer_includes)
+
+
+buffer_autotests :=						\
+	src/buffer/tests/cbuffer_autotest.c			\
+	src/buffer/tests/wdelay_autotest.c			\
+	src/buffer/tests/window_autotest.c			\
+	
+#	src/buffer/tests/sbuffer_autotest.c
+
+buffer_benchmarks :=						\
+	src/buffer/bench/cbuffercf_benchmark.c			\
+	src/buffer/bench/window_push_benchmark.c		\
+	src/buffer/bench/window_read_benchmark.c		\
+
+# 
+# MODULE : channel
+#
+
+channel_objects :=						\
+	src/channel/src/channel_cccf.o				\
+
+channel_includes :=						\
+	src/channel/src/channel.c				\
+	src/channel/src/tvmpch.c				\
+
+src/channel/src/channel_cccf.o : %.o : %.c $(include_headers) $(channel_includes)
+
+channel_autotests :=						\
+	
+#src/channel/tests/channel_cccf_autotest.c
+
+channel_benchmarks :=						\
+
+#src/channel/bench/channel_cccf_benchmark.c
+
+# 
+# MODULE : dotprod
+#
+dotprod_objects :=						\
+	@MLIBS_DOTPROD@						\
+
+src/dotprod/src/dotprod_cccf.o : %.o : %.c $(include_headers) src/dotprod/src/dotprod.c
+src/dotprod/src/dotprod_crcf.o : %.o : %.c $(include_headers) src/dotprod/src/dotprod.c
+src/dotprod/src/dotprod_rrrf.o : %.o : %.c $(include_headers) src/dotprod/src/dotprod.c
+src/dotprod/src/sumsq.o : %.o : %.c $(include_headers)
+
+# specific machine architectures
+
+# AltiVec
+src/dotprod/src/dotprod_rrrf.av.o : %.o : %.c $(include_headers)
+
+# MMX/SSE2
+src/dotprod/src/dotprod_rrrf.mmx.o : %.o : %.c $(include_headers)
+src/dotprod/src/dotprod_crcf.mmx.o : %.o : %.c $(include_headers)
+src/dotprod/src/dotprod_cccf.mmx.o : %.o : %.c $(include_headers)
+
+src/dotprod/src/sumsq.mmx.o : %.o : %.c $(include_headers)
+
+# SSE4.1/2
+src/dotprod/src/dotprod_rrrf.sse4.o : %.o : %.c $(include_headers)
+
+# ARM Neon
+src/dotprod/src/dotprod_rrrf.neon.o : %.o : %.c $(include_headers)
+src/dotprod/src/dotprod_crcf.neon.o : %.o : %.c $(include_headers)
+src/dotprod/src/dotprod_cccf.neon.o : %.o : %.c $(include_headers)
+
+dotprod_autotests :=						\
+	src/dotprod/tests/dotprod_rrrf_autotest.c		\
+	src/dotprod/tests/dotprod_crcf_autotest.c		\
+	src/dotprod/tests/dotprod_cccf_autotest.c		\
+	src/dotprod/tests/sumsqf_autotest.c			\
+	src/dotprod/tests/sumsqcf_autotest.c			\
+
+dotprod_benchmarks :=						\
+	src/dotprod/bench/dotprod_cccf_benchmark.c		\
+	src/dotprod/bench/dotprod_crcf_benchmark.c		\
+	src/dotprod/bench/dotprod_rrrf_benchmark.c		\
+	src/dotprod/bench/sumsqf_benchmark.c			\
+	src/dotprod/bench/sumsqcf_benchmark.c			\
+
+# 
+# MODULE : equalization
+#
+equalization_objects :=						\
+	src/equalization/src/equalizer_cccf.o			\
+	src/equalization/src/equalizer_rrrf.o			\
+
+
+$(equalization_objects) : %.o : %.c $(include_headers) src/equalization/src/eqlms.c src/equalization/src/eqrls.c
+
+
+# autotests
+equalization_autotests :=					\
+	src/equalization/tests/eqlms_cccf_autotest.c		\
+	src/equalization/tests/eqrls_rrrf_autotest.c		\
+
+
+# benchmarks
+equalization_benchmarks :=					\
+	src/equalization/bench/eqlms_cccf_benchmark.c		\
+	src/equalization/bench/eqrls_cccf_benchmark.c		\
+
+# 
+# MODULE : fec - forward error-correction
+#
+fec_objects :=							\
+	src/fec/src/crc.o					\
+	src/fec/src/fec.o					\
+	src/fec/src/fec_conv.o					\
+	src/fec/src/fec_conv_poly.o				\
+	src/fec/src/fec_conv_pmatrix.o				\
+	src/fec/src/fec_conv_punctured.o			\
+	src/fec/src/fec_golay2412.o				\
+	src/fec/src/fec_hamming74.o				\
+	src/fec/src/fec_hamming84.o				\
+	src/fec/src/fec_hamming128.o				\
+	src/fec/src/fec_hamming1511.o				\
+	src/fec/src/fec_hamming3126.o				\
+	src/fec/src/fec_hamming128_gentab.o			\
+	src/fec/src/fec_pass.o					\
+	src/fec/src/fec_rep3.o					\
+	src/fec/src/fec_rep5.o					\
+	src/fec/src/fec_rs.o					\
+	src/fec/src/fec_secded2216.o				\
+	src/fec/src/fec_secded3932.o				\
+	src/fec/src/fec_secded7264.o				\
+	src/fec/src/interleaver.o				\
+	src/fec/src/packetizer.o				\
+	src/fec/src/sumproduct.o				\
+
+
+# list explicit targets and dependencies here
+$(fec_objects) : %.o : %.c $(include_headers)
+
+# autotests
+fec_autotests :=						\
+	src/fec/tests/crc_autotest.c				\
+	src/fec/tests/fec_autotest.c				\
+	src/fec/tests/fec_soft_autotest.c			\
+	src/fec/tests/fec_golay2412_autotest.c			\
+	src/fec/tests/fec_hamming74_autotest.c			\
+	src/fec/tests/fec_hamming84_autotest.c			\
+	src/fec/tests/fec_hamming128_autotest.c			\
+	src/fec/tests/fec_hamming1511_autotest.c		\
+	src/fec/tests/fec_hamming3126_autotest.c		\
+	src/fec/tests/fec_reedsolomon_autotest.c		\
+	src/fec/tests/fec_rep3_autotest.c			\
+	src/fec/tests/fec_rep5_autotest.c			\
+	src/fec/tests/fec_secded2216_autotest.c			\
+	src/fec/tests/fec_secded3932_autotest.c			\
+	src/fec/tests/fec_secded7264_autotest.c			\
+	src/fec/tests/interleaver_autotest.c			\
+	src/fec/tests/packetizer_autotest.c			\
+
+
+# benchmarks
+fec_benchmarks :=						\
+	src/fec/bench/crc_benchmark.c				\
+	src/fec/bench/fec_encode_benchmark.c			\
+	src/fec/bench/fec_decode_benchmark.c			\
+	src/fec/bench/fecsoft_decode_benchmark.c		\
+	src/fec/bench/sumproduct_benchmark.c			\
+	src/fec/bench/interleaver_benchmark.c			\
+	src/fec/bench/packetizer_decode_benchmark.c		\
+
+# 
+# MODULE : fft - fast Fourier transforms, discrete sine/cosine transforms, etc.
+#
+
+fft_objects :=							\
+	src/fft/src/fftf.o					\
+	src/fft/src/spgramcf.o					\
+	src/fft/src/spgramf.o					\
+	src/fft/src/fft_utilities.o				\
+
+# explicit targets and dependencies
+fft_includes :=							\
+	src/fft/src/fft_common.c				\
+	src/fft/src/fft_dft.c					\
+	src/fft/src/fft_radix2.c				\
+	src/fft/src/fft_mixed_radix.c				\
+	src/fft/src/fft_rader.c					\
+	src/fft/src/fft_rader2.c				\
+	src/fft/src/fft_r2r_1d.c				\
+
+src/fft/src/fftf.o          : %.o : %.c $(include_headers) $(fft_includes)
+src/fft/src/asgram.o        : %.o : %.c $(include_headers)
+src/fft/src/dct.o           : %.o : %.c $(include_headers)
+src/fft/src/fftf.o          : %.o : %.c $(include_headers)
+src/fft/src/fft_utilities.o : %.o : %.c $(include_headers)
+src/fft/src/mdct.o          : %.o : %.c $(include_headers)
+src/fft/src/spgramcf.o      : %.o : %.c $(include_headers) src/fft/src/asgram.c src/fft/src/spgram.c
+src/fft/src/spgramf.o       : %.o : %.c $(include_headers) src/fft/src/asgram.c src/fft/src/spgram.c
+
+# fft autotest scripts
+fft_autotests :=						\
+	src/fft/tests/fft_small_autotest.c			\
+	src/fft/tests/fft_radix2_autotest.c			\
+	src/fft/tests/fft_composite_autotest.c			\
+	src/fft/tests/fft_prime_autotest.c			\
+	src/fft/tests/fft_r2r_autotest.c			\
+	src/fft/tests/fft_shift_autotest.c			\
+
+# additional autotest objects
+autotest_extra_obj +=						\
+	src/fft/tests/fft_runtest.o				\
+	src/fft/tests/data/fft_data_2.o				\
+	src/fft/tests/data/fft_data_3.o				\
+	src/fft/tests/data/fft_data_4.o				\
+	src/fft/tests/data/fft_data_5.o				\
+	src/fft/tests/data/fft_data_6.o				\
+	src/fft/tests/data/fft_data_7.o				\
+	src/fft/tests/data/fft_data_8.o				\
+	src/fft/tests/data/fft_data_9.o				\
+	src/fft/tests/data/fft_data_10.o			\
+	src/fft/tests/data/fft_data_16.o			\
+	src/fft/tests/data/fft_data_17.o			\
+	src/fft/tests/data/fft_data_20.o			\
+	src/fft/tests/data/fft_data_21.o			\
+	src/fft/tests/data/fft_data_22.o			\
+	src/fft/tests/data/fft_data_24.o			\
+	src/fft/tests/data/fft_data_26.o			\
+	src/fft/tests/data/fft_data_30.o			\
+	src/fft/tests/data/fft_data_32.o			\
+	src/fft/tests/data/fft_data_35.o			\
+	src/fft/tests/data/fft_data_36.o			\
+	src/fft/tests/data/fft_data_43.o			\
+	src/fft/tests/data/fft_data_48.o			\
+	src/fft/tests/data/fft_data_63.o			\
+	src/fft/tests/data/fft_data_64.o			\
+	src/fft/tests/data/fft_data_79.o			\
+	src/fft/tests/data/fft_data_92.o			\
+	src/fft/tests/data/fft_data_96.o			\
+	src/fft/tests/data/fft_data_120.o			\
+	src/fft/tests/data/fft_data_130.o			\
+	src/fft/tests/data/fft_data_157.o			\
+	src/fft/tests/data/fft_data_192.o			\
+	src/fft/tests/data/fft_data_317.o			\
+	src/fft/tests/data/fft_data_509.o			\
+	src/fft/tests/data/fft_r2rdata_8.o			\
+	src/fft/tests/data/fft_r2rdata_27.o			\
+	src/fft/tests/data/fft_r2rdata_32.o			\
+
+# fft benchmark scripts
+fft_benchmarks :=						\
+	src/fft/bench/fft_composite_benchmark.c			\
+	src/fft/bench/fft_prime_benchmark.c			\
+	src/fft/bench/fft_radix2_benchmark.c			\
+	src/fft/bench/fft_r2r_benchmark.c			\
+
+# additional benchmark objects
+benchmark_extra_obj :=						\
+	src/fft/bench/fft_runbench.o				\
+
+#
+# MODULE : filter
+#
+
+filter_objects :=						\
+	src/filter/src/bessel.o					\
+	src/filter/src/butter.o					\
+	src/filter/src/cheby1.o					\
+	src/filter/src/cheby2.o					\
+	src/filter/src/ellip.o					\
+	src/filter/src/filter_rrrf.o				\
+	src/filter/src/filter_crcf.o				\
+	src/filter/src/filter_cccf.o				\
+	src/filter/src/firdes.o					\
+	src/filter/src/firdespm.o				\
+	src/filter/src/fnyquist.o				\
+	src/filter/src/gmsk.o					\
+	src/filter/src/group_delay.o				\
+	src/filter/src/hM3.o					\
+	src/filter/src/iirdes.pll.o				\
+	src/filter/src/iirdes.o					\
+	src/filter/src/lpc.o					\
+	src/filter/src/rcos.o					\
+	src/filter/src/rkaiser.o				\
+	src/filter/src/rrcos.o					\
+
+
+# list explicit targets and dependencies here
+filter_includes :=						\
+	src/filter/src/fftfilt.c				\
+	src/filter/src/firdecim.c				\
+	src/filter/src/firfarrow.c				\
+	src/filter/src/firfilt.c				\
+	src/filter/src/firhilb.c				\
+	src/filter/src/firinterp.c				\
+	src/filter/src/firpfb.c					\
+	src/filter/src/iirdecim.c				\
+	src/filter/src/iirfilt.c				\
+	src/filter/src/iirfiltsos.c				\
+	src/filter/src/iirinterp.c				\
+	src/filter/src/msresamp.c				\
+	src/filter/src/msresamp2.c				\
+	src/filter/src/resamp.c					\
+	src/filter/src/resamp2.c				\
+	src/filter/src/symsync.c				\
+
+src/filter/src/bessel.o      : %.o : %.c $(include_headers)
+src/filter/src/bessel.o      : %.o : %.c $(include_headers)
+src/filter/src/butter.o      : %.o : %.c $(include_headers)
+src/filter/src/cheby1.o      : %.o : %.c $(include_headers)
+src/filter/src/cheby2.o      : %.o : %.c $(include_headers)
+src/filter/src/ellip.o       : %.o : %.c $(include_headers)
+src/filter/src/filter_rrrf.o : %.o : %.c $(include_headers) $(filter_includes)
+src/filter/src/filter_crcf.o : %.o : %.c $(include_headers) $(filter_includes)
+src/filter/src/filter_cccf.o : %.o : %.c $(include_headers) $(filter_includes)
+src/filter/src/firdes.o      : %.o : %.c $(include_headers)
+src/filter/src/firdespm.o    : %.o : %.c $(include_headers)
+src/filter/src/group_delay.o : %.o : %.c $(include_headers)
+src/filter/src/hM3.o         : %.o : %.c $(include_headers)
+src/filter/src/iirdes.pll.o  : %.o : %.c $(include_headers)
+src/filter/src/iirdes.o      : %.o : %.c $(include_headers)
+src/filter/src/lpc.o         : %.o : %.c $(include_headers)
+src/filter/src/rcos.o        : %.o : %.c $(include_headers)
+src/filter/src/rkaiser.o     : %.o : %.c $(include_headers)
+src/filter/src/rrcos.o       : %.o : %.c $(include_headers)
+
+
+filter_autotests :=						\
+	src/filter/tests/fftfilt_xxxf_autotest.c		\
+	src/filter/tests/filter_crosscorr_autotest.c		\
+	src/filter/tests/firdecim_xxxf_autotest.c		\
+	src/filter/tests/firdes_autotest.c			\
+	src/filter/tests/firdespm_autotest.c			\
+	src/filter/tests/firfilt_xxxf_autotest.c		\
+	src/filter/tests/firhilb_autotest.c			\
+	src/filter/tests/firinterp_autotest.c			\
+	src/filter/tests/firpfb_autotest.c			\
+	src/filter/tests/groupdelay_autotest.c			\
+	src/filter/tests/iirdes_autotest.c			\
+	src/filter/tests/iirfilt_xxxf_autotest.c		\
+	src/filter/tests/iirfiltsos_rrrf_autotest.c		\
+	src/filter/tests/msresamp_crcf_autotest.c		\
+	src/filter/tests/resamp_crcf_autotest.c			\
+	src/filter/tests/resamp2_crcf_autotest.c		\
+	src/filter/tests/symsync_crcf_autotest.c		\
+	src/filter/tests/symsync_rrrf_autotest.c		\
+
+# additional autotest objects
+autotest_extra_obj +=						\
+	src/filter/tests/fftfilt_runtest.o			\
+								\
+	src/filter/tests/data/fftfilt_rrrf_data_h4x256.o	\
+	src/filter/tests/data/fftfilt_crcf_data_h4x256.o	\
+	src/filter/tests/data/fftfilt_cccf_data_h4x256.o	\
+								\
+	src/filter/tests/data/fftfilt_rrrf_data_h7x256.o	\
+	src/filter/tests/data/fftfilt_crcf_data_h7x256.o	\
+	src/filter/tests/data/fftfilt_cccf_data_h7x256.o	\
+								\
+	src/filter/tests/data/fftfilt_rrrf_data_h13x256.o	\
+	src/filter/tests/data/fftfilt_crcf_data_h13x256.o	\
+	src/filter/tests/data/fftfilt_cccf_data_h13x256.o	\
+								\
+	src/filter/tests/data/fftfilt_rrrf_data_h23x256.o	\
+	src/filter/tests/data/fftfilt_crcf_data_h23x256.o	\
+	src/filter/tests/data/fftfilt_cccf_data_h23x256.o	\
+								\
+	src/filter/tests/firdecim_runtest.o			\
+								\
+	src/filter/tests/data/firdecim_rrrf_data_M2h4x20.o	\
+	src/filter/tests/data/firdecim_crcf_data_M2h4x20.o	\
+	src/filter/tests/data/firdecim_cccf_data_M2h4x20.o	\
+								\
+	src/filter/tests/data/firdecim_rrrf_data_M3h7x30.o	\
+	src/filter/tests/data/firdecim_crcf_data_M3h7x30.o	\
+	src/filter/tests/data/firdecim_cccf_data_M3h7x30.o	\
+								\
+	src/filter/tests/data/firdecim_rrrf_data_M4h13x40.o	\
+	src/filter/tests/data/firdecim_crcf_data_M4h13x40.o	\
+	src/filter/tests/data/firdecim_cccf_data_M4h13x40.o	\
+								\
+	src/filter/tests/data/firdecim_rrrf_data_M5h23x50.o	\
+	src/filter/tests/data/firdecim_crcf_data_M5h23x50.o	\
+	src/filter/tests/data/firdecim_cccf_data_M5h23x50.o	\
+								\
+	src/filter/tests/firfilt_runtest.o			\
+								\
+	src/filter/tests/data/firfilt_rrrf_data_h4x8.o		\
+	src/filter/tests/data/firfilt_crcf_data_h4x8.o		\
+	src/filter/tests/data/firfilt_cccf_data_h4x8.o		\
+								\
+	src/filter/tests/data/firfilt_rrrf_data_h7x16.o		\
+	src/filter/tests/data/firfilt_crcf_data_h7x16.o		\
+	src/filter/tests/data/firfilt_cccf_data_h7x16.o		\
+								\
+	src/filter/tests/data/firfilt_rrrf_data_h13x32.o	\
+	src/filter/tests/data/firfilt_crcf_data_h13x32.o	\
+	src/filter/tests/data/firfilt_cccf_data_h13x32.o	\
+								\
+	src/filter/tests/data/firfilt_rrrf_data_h23x64.o	\
+	src/filter/tests/data/firfilt_crcf_data_h23x64.o	\
+	src/filter/tests/data/firfilt_cccf_data_h23x64.o	\
+								\
+	src/filter/tests/iirfilt_runtest.o			\
+								\
+	src/filter/tests/data/iirfilt_rrrf_data_h3x64.o		\
+	src/filter/tests/data/iirfilt_crcf_data_h3x64.o		\
+	src/filter/tests/data/iirfilt_cccf_data_h3x64.o		\
+								\
+	src/filter/tests/data/iirfilt_rrrf_data_h5x64.o		\
+	src/filter/tests/data/iirfilt_crcf_data_h5x64.o		\
+	src/filter/tests/data/iirfilt_cccf_data_h5x64.o		\
+								\
+	src/filter/tests/data/iirfilt_rrrf_data_h7x64.o		\
+	src/filter/tests/data/iirfilt_crcf_data_h7x64.o		\
+	src/filter/tests/data/iirfilt_cccf_data_h7x64.o		\
+
+filter_benchmarks :=						\
+	src/filter/bench/fftfilt_crcf_benchmark.c		\
+	src/filter/bench/firdecim_crcf_benchmark.c		\
+	src/filter/bench/firhilb_benchmark.c			\
+	src/filter/bench/firinterp_crcf_benchmark.c		\
+	src/filter/bench/firfilt_crcf_benchmark.c		\
+	src/filter/bench/iirdecim_crcf_benchmark.c		\
+	src/filter/bench/iirfilt_crcf_benchmark.c		\
+	src/filter/bench/iirinterp_crcf_benchmark.c		\
+	src/filter/bench/resamp_crcf_benchmark.c		\
+	src/filter/bench/resamp2_crcf_benchmark.c		\
+	src/filter/bench/symsync_crcf_benchmark.c		\
+
+# 
+# MODULE : framing
+#
+
+framing_objects :=						\
+	src/framing/src/bpacketgen.o				\
+	src/framing/src/bpacketsync.o				\
+	src/framing/src/bpresync_cccf.o				\
+	src/framing/src/bsync_rrrf.o				\
+	src/framing/src/bsync_crcf.o				\
+	src/framing/src/bsync_cccf.o				\
+	src/framing/src/detector_cccf.o				\
+	src/framing/src/framedatastats.o			\
+	src/framing/src/framesyncstats.o			\
+	src/framing/src/framegen64.o				\
+	src/framing/src/framesync64.o				\
+	src/framing/src/flexframegen.o				\
+	src/framing/src/flexframesync.o				\
+	src/framing/src/gmskframegen.o				\
+	src/framing/src/gmskframesync.o				\
+	src/framing/src/msourcecf.o				\
+	src/framing/src/ofdmflexframegen.o			\
+	src/framing/src/ofdmflexframesync.o			\
+	src/framing/src/presync_cccf.o				\
+	src/framing/src/symstreamcf.o				\
+	src/framing/src/symtrack_cccf.o				\
+	src/framing/src/qdetector_cccf.o			\
+	src/framing/src/qpacketmodem.o				\
+	src/framing/src/qpilotgen.o				\
+	src/framing/src/qpilotsync.o				\
+
+
+# list explicit targets and dependencies here
+
+src/framing/src/bpacketgen.o        : %.o : %.c $(include_headers)
+src/framing/src/bpacketsync.o       : %.o : %.c $(include_headers)
+src/framing/src/bpresync_cccf.o     : %.o : %.c $(include_headers) src/framing/src/bpresync.c
+src/framing/src/bsync_rrrf.o        : %.o : %.c $(include_headers) src/framing/src/bsync.c
+src/framing/src/bsync_crcf.o        : %.o : %.c $(include_headers) src/framing/src/bsync.c
+src/framing/src/bsync_cccf.o        : %.o : %.c $(include_headers) src/framing/src/bsync.c
+src/framing/src/detector_cccf.o     : %.o : %.c $(include_headers)
+src/framing/src/framedatastats.o    : %.o : %.c $(include_headers)
+src/framing/src/framesyncstats.o    : %.o : %.c $(include_headers)
+src/framing/src/framegen64.o        : %.o : %.c $(include_headers)
+src/framing/src/framesync64.o       : %.o : %.c $(include_headers)
+src/framing/src/flexframegen.o      : %.o : %.c $(include_headers)
+src/framing/src/flexframesync.o     : %.o : %.c $(include_headers)
+src/framing/src/msourcecf.o         : %.o : %.c $(include_headers) src/framing/src/msource.c
+src/framing/src/ofdmflexframegen.o  : %.o : %.c $(include_headers)
+src/framing/src/ofdmflexframesync.o : %.o : %.c $(include_headers)
+src/framing/src/presync_cccf.o      : %.o : %.c $(include_headers) src/framing/src/presync.c
+src/framing/src/qpacketmodem.o      : %.o : %.c $(include_headers)
+src/framing/src/symstreamcf.o       : %.o : %.c $(include_headers) src/framing/src/symstream.c
+src/framing/src/symtrack_cccf.o     : %.o : %.c $(include_headers) src/framing/src/symtrack.c
+
+
+framing_autotests :=						\
+	src/framing/tests/bpacketsync_autotest.c		\
+	src/framing/tests/bsync_autotest.c			\
+	src/framing/tests/detector_autotest.c			\
+	src/framing/tests/flexframesync_autotest.c		\
+	src/framing/tests/framesync64_autotest.c		\
+	src/framing/tests/qdetector_cccf_autotest.c		\
+	src/framing/tests/qpacketmodem_autotest.c		\
+	src/framing/tests/qpilotsync_autotest.c			\
+
+
+framing_benchmarks :=						\
+	src/framing/bench/presync_benchmark.c			\
+	src/framing/bench/bpacketsync_benchmark.c		\
+	src/framing/bench/bpresync_benchmark.c			\
+	src/framing/bench/bsync_benchmark.c			\
+	src/framing/bench/detector_benchmark.c			\
+	src/framing/bench/flexframesync_benchmark.c		\
+	src/framing/bench/framesync64_benchmark.c		\
+	src/framing/bench/gmskframesync_benchmark.c		\
+
+
+# 
+# MODULE : math
+#
+
+math_objects :=							\
+	src/math/src/poly.o					\
+	src/math/src/polyc.o					\
+	src/math/src/polyf.o					\
+	src/math/src/polycf.o					\
+	src/math/src/math.o					\
+	src/math/src/math.bessel.o				\
+	src/math/src/math.gamma.o				\
+	src/math/src/math.complex.o				\
+	src/math/src/math.trig.o				\
+	src/math/src/modular_arithmetic.o			\
+	src/math/src/windows.o					\
+
+
+poly_includes :=						\
+	src/math/src/poly.common.c				\
+	src/math/src/poly.expand.c				\
+	src/math/src/poly.findroots.c				\
+	src/math/src/poly.lagrange.c				\
+
+src/math/src/poly.o               : %.o : %.c $(include_headers) $(poly_includes)
+src/math/src/polyc.o              : %.o : %.c $(include_headers) $(poly_includes)
+src/math/src/polyf.o              : %.o : %.c $(include_headers) $(poly_includes)
+src/math/src/polycf.o             : %.o : %.c $(include_headers) $(poly_includes)
+src/math/src/math.o               : %.o : %.c $(include_headers)
+src/math/src/math.bessel.o        : %.o : %.c $(include_headers)
+src/math/src/math.gamma.o         : %.o : %.c $(include_headers)
+src/math/src/math.complex.o       : %.o : %.c $(include_headers)
+src/math/src/math.trig.o          : %.o : %.c $(include_headers)
+src/math/src/modular_arithmetic.o : %.o : %.c $(include_headers)
+src/math/src/windows.o            : %.o : %.c $(include_headers)
+
+
+math_autotests :=						\
+	src/math/tests/kbd_autotest.c				\
+	src/math/tests/math_autotest.c				\
+	src/math/tests/math_bessel_autotest.c			\
+	src/math/tests/math_gamma_autotest.c			\
+	src/math/tests/math_complex_autotest.c			\
+	src/math/tests/polynomial_autotest.c			\
+
+
+math_benchmarks :=						\
+	src/math/bench/polyfit_benchmark.c			\
+
+
+#
+# MODULE : matrix
+#
+
+matrix_objects :=						\
+	src/matrix/src/matrix.o					\
+	src/matrix/src/matrixf.o				\
+	src/matrix/src/matrixc.o				\
+	src/matrix/src/matrixcf.o				\
+	src/matrix/src/smatrix.common.o				\
+	src/matrix/src/smatrixb.o				\
+	src/matrix/src/smatrixf.o				\
+	src/matrix/src/smatrixi.o				\
+
+
+matrix_includes :=						\
+	src/matrix/src/matrix.base.c				\
+	src/matrix/src/matrix.cgsolve.c				\
+	src/matrix/src/matrix.chol.c				\
+	src/matrix/src/matrix.gramschmidt.c			\
+	src/matrix/src/matrix.inv.c				\
+	src/matrix/src/matrix.linsolve.c			\
+	src/matrix/src/matrix.ludecomp.c			\
+	src/matrix/src/matrix.qrdecomp.c			\
+	src/matrix/src/matrix.math.c				\
+
+src/matrix/src/matrix.o   : %.o : %.c $(include_headers) $(matrix_includes)
+src/matrix/src/matrixc.o  : %.o : %.c $(include_headers) $(matrix_includes)
+src/matrix/src/matrixf.o  : %.o : %.c $(include_headers) $(matrix_includes)
+src/matrix/src/matrixcf.o : %.o : %.c $(include_headers) $(matrix_includes)
+src/matrix/src/smatrixb.o : %.o : %.c $(include_headers) src/matrix/src/smatrix.c
+src/matrix/src/smatrixf.o : %.o : %.c $(include_headers) src/matrix/src/smatrix.c
+src/matrix/src/smatrixi.o : %.o : %.c $(include_headers) src/matrix/src/smatrix.c
+
+
+# matrix autotest scripts
+matrix_autotests :=						\
+	src/matrix/tests/matrixcf_autotest.c			\
+	src/matrix/tests/matrixf_autotest.c			\
+	src/matrix/tests/smatrixb_autotest.c			\
+	src/matrix/tests/smatrixf_autotest.c			\
+	src/matrix/tests/smatrixi_autotest.c			\
+
+# additional autotest objects
+autotest_extra_obj +=						\
+	src/matrix/tests/data/matrixf_data_add.o		\
+	src/matrix/tests/data/matrixf_data_aug.o		\
+	src/matrix/tests/data/matrixf_data_cgsolve.o		\
+	src/matrix/tests/data/matrixf_data_chol.o		\
+	src/matrix/tests/data/matrixf_data_gramschmidt.o	\
+	src/matrix/tests/data/matrixf_data_inv.o		\
+	src/matrix/tests/data/matrixf_data_linsolve.o		\
+	src/matrix/tests/data/matrixf_data_ludecomp.o		\
+	src/matrix/tests/data/matrixf_data_mul.o		\
+	src/matrix/tests/data/matrixf_data_qrdecomp.o		\
+	src/matrix/tests/data/matrixf_data_transmul.o		\
+								\
+	src/matrix/tests/data/matrixcf_data_add.o		\
+	src/matrix/tests/data/matrixcf_data_aug.o		\
+	src/matrix/tests/data/matrixcf_data_chol.o		\
+	src/matrix/tests/data/matrixcf_data_inv.o		\
+	src/matrix/tests/data/matrixcf_data_linsolve.o		\
+	src/matrix/tests/data/matrixcf_data_ludecomp.o		\
+	src/matrix/tests/data/matrixcf_data_mul.o		\
+	src/matrix/tests/data/matrixcf_data_qrdecomp.o		\
+	src/matrix/tests/data/matrixcf_data_transmul.o		\
+
+matrix_benchmarks :=						\
+	src/matrix/bench/matrixf_inv_benchmark.c		\
+	src/matrix/bench/matrixf_linsolve_benchmark.c		\
+	src/matrix/bench/matrixf_mul_benchmark.c		\
+	src/matrix/bench/smatrixf_mul_benchmark.c		\
+
+
+# 
+# MODULE : modem
+#
+
+modem_objects :=						\
+	src/modem/src/ampmodem.o				\
+	src/modem/src/cpfskdem.o				\
+	src/modem/src/cpfskmod.o				\
+	src/modem/src/fskdem.o					\
+	src/modem/src/fskmod.o					\
+	src/modem/src/gmskdem.o					\
+	src/modem/src/gmskmod.o					\
+	src/modem/src/modemf.o					\
+	src/modem/src/modem_utilities.o				\
+	src/modem/src/modem_apsk_const.o			\
+	src/modem/src/modem_arb_const.o				\
+
+# explicit targets and dependencies
+modem_includes :=						\
+	src/modem/src/freqdem.c					\
+	src/modem/src/freqmod.c					\
+	src/modem/src/modem_common.c				\
+	src/modem/src/modem_psk.c				\
+	src/modem/src/modem_dpsk.c				\
+	src/modem/src/modem_ask.c				\
+	src/modem/src/modem_qam.c				\
+	src/modem/src/modem_apsk.c				\
+	src/modem/src/modem_bpsk.c				\
+	src/modem/src/modem_qpsk.c				\
+	src/modem/src/modem_ook.c				\
+	src/modem/src/modem_sqam32.c				\
+	src/modem/src/modem_sqam128.c				\
+	src/modem/src/modem_arb.c				\
+	
+#src/modem/src/modem_demod_soft_const.c
+
+src/modem/src/modemf.o           : %.o : %.c $(include_headers) $(modem_includes)
+src/modem/src/gmskmod.o          : %.o : %.c $(include_headers)
+src/modem/src/gmskdem.o          : %.o : %.c $(include_headers)
+src/modem/src/ampmodem.o         : %.o : %.c $(include_headers)
+src/modem/src/freqmodem.o        : %.o : %.c $(include_headers)
+src/modem/src/modem_utilities.o  : %.o : %.c $(include_headers)
+src/modem/src/modem_apsk_const.o : %.o : %.c $(include_headers)
+src/modem/src/modem_arb_const.o  : %.o : %.c $(include_headers)
+
+
+modem_autotests :=						\
+	src/modem/tests/cpfskmodem_autotest.c			\
+	src/modem/tests/freqmodem_autotest.c			\
+	src/modem/tests/fskmodem_autotest.c			\
+	src/modem/tests/modem_autotest.c			\
+	src/modem/tests/modem_demodsoft_autotest.c		\
+	src/modem/tests/modem_demodstats_autotest.c		\
+
+
+modem_benchmarks :=						\
+	src/modem/bench/freqdem_benchmark.c			\
+	src/modem/bench/freqmod_benchmark.c			\
+	src/modem/bench/fskdem_benchmark.c			\
+	src/modem/bench/fskmod_benchmark.c			\
+	src/modem/bench/gmskmodem_benchmark.c			\
+	src/modem/bench/modem_modulate_benchmark.c		\
+	src/modem/bench/modem_demodulate_benchmark.c		\
+	src/modem/bench/modem_demodsoft_benchmark.c		\
+
+# 
+# MODULE : multichannel
+#
+
+multichannel_objects :=						\
+	src/multichannel/src/firpfbch_crcf.o			\
+	src/multichannel/src/firpfbch_cccf.o			\
+	src/multichannel/src/ofdmframe.common.o			\
+	src/multichannel/src/ofdmframegen.o			\
+	src/multichannel/src/ofdmframesync.o			\
+
+$(multichannel_objects) : %.o : %.c $(include_headers)
+
+# list explicit targets and dependencies here
+multichannel_includes :=					\
+	src/multichannel/src/firpfbch.c				\
+	src/multichannel/src/firpfbch2.c			\
+
+src/multichannel/src/firpfbch_crcf.o : %.o : %.c $(include_headers) $(multichannel_includes)
+src/multichannel/src/firpfbch_cccf.o : %.o : %.c $(include_headers) $(multichannel_includes)
+
+# autotests
+multichannel_autotests :=					\
+	src/multichannel/tests/firpfbch2_crcf_autotest.c	\
+	src/multichannel/tests/firpfbch_crcf_synthesizer_autotest.c	\
+	src/multichannel/tests/firpfbch_crcf_analyzer_autotest.c	\
+	src/multichannel/tests/ofdmframesync_autotest.c		\
+
+# benchmarks
+multichannel_benchmarks :=					\
+	src/multichannel/bench/firpfbch_crcf_benchmark.c	\
+	src/multichannel/bench/firpfbch2_crcf_benchmark.c	\
+	src/multichannel/bench/ofdmframesync_acquire_benchmark.c	\
+	src/multichannel/bench/ofdmframesync_rxsymbol_benchmark.c	\
+
+# 
+# MODULE : nco - numerically-controlled oscillator
+#
+
+nco_objects :=							\
+	src/nco/src/nco_crcf.o					\
+	src/nco/src/nco.utilities.o				\
+
+
+src/nco/src/nco_crcf.o      : %.o : %.c $(include_headers) src/nco/src/nco.c
+src/nco/src/nco.utilities.o : %.o : %.c $(include_headers)
+
+
+# autotests
+nco_autotests :=						\
+	src/nco/tests/nco_crcf_frequency_autotest.c		\
+	src/nco/tests/nco_crcf_phase_autotest.c			\
+	src/nco/tests/nco_crcf_pll_autotest.c			\
+	src/nco/tests/unwrap_phase_autotest.c			\
+
+# additional autotest objects
+autotest_extra_obj +=						\
+	src/nco/tests/data/nco_sincos_fsqrt1_2.o		\
+	src/nco/tests/data/nco_sincos_fsqrt1_3.o		\
+	src/nco/tests/data/nco_sincos_fsqrt1_5.o		\
+	src/nco/tests/data/nco_sincos_fsqrt1_7.o		\
+
+# benchmarks
+nco_benchmarks :=						\
+	src/nco/bench/nco_benchmark.c				\
+	src/nco/bench/vco_benchmark.c				\
+
+# 
+# MODULE : optim - optimization
+#
+
+optim_objects :=						\
+	src/optim/src/chromosome.o				\
+	src/optim/src/gasearch.o				\
+	src/optim/src/gradsearch.o				\
+	src/optim/src/optim.common.o				\
+	src/optim/src/qnsearch.o				\
+	src/optim/src/utilities.o				\
+
+$(optim_objects) : %.o : %.c $(include_headers)
+
+# autotests
+optim_autotests :=						\
+	src/optim/tests/gradsearch_autotest.c			\
+
+# benchmarks
+optim_benchmarks :=
+
+
+# 
+# MODULE : quantization
+#
+
+quantization_objects :=						\
+	src/quantization/src/compand.o				\
+	src/quantization/src/quantizercf.o			\
+	src/quantization/src/quantizerf.o			\
+	src/quantization/src/quantizer.inline.o			\
+
+
+src/quantization/src/compand.o          : %.o : %.c $(include_headers)
+src/quantization/src/quantizercf.o      : %.o : %.c $(include_headers) src/quantization/src/quantizer.c
+src/quantization/src/quantizerf.o       : %.o : %.c $(include_headers) src/quantization/src/quantizer.c
+src/quantization/src/quantizer.inline.o : %.o : %.c $(include_headers)
+
+
+# autotests
+quantization_autotests :=					\
+	src/quantization/tests/compand_autotest.c		\
+	src/quantization/tests/quantize_autotest.c		\
+
+
+# benchmarks
+quantization_benchmarks :=					\
+	src/quantization/bench/quantizer_benchmark.c		\
+	src/quantization/bench/compander_benchmark.c		\
+
+# 
+# MODULE : random
+#
+
+random_objects :=						\
+	src/random/src/rand.o					\
+	src/random/src/randn.o					\
+	src/random/src/randexp.o				\
+	src/random/src/randweib.o				\
+	src/random/src/randgamma.o				\
+	src/random/src/randnakm.o				\
+	src/random/src/randricek.o				\
+	src/random/src/scramble.o				\
+
+
+$(random_objects) : %.o : %.c $(include_headers)
+
+# autotests
+random_autotests :=						\
+	src/random/tests/scramble_autotest.c			\
+
+#	src/random/tests/random_autotest.c
+
+
+# benchmarks
+random_benchmarks :=						\
+	src/random/bench/random_benchmark.c			\
+
+
+# 
+# MODULE : sequence
+#
+
+sequence_objects :=						\
+	src/sequence/src/bsequence.o				\
+	src/sequence/src/msequence.o				\
+
+
+$(sequence_objects) : %.o : %.c $(include_headers)
+
+
+# autotests
+sequence_autotests :=						\
+	src/sequence/tests/bsequence_autotest.c			\
+	src/sequence/tests/complementary_codes_autotest.c	\
+	src/sequence/tests/msequence_autotest.c			\
+
+# benchmarks
+sequence_benchmarks :=						\
+	src/sequence/bench/bsequence_benchmark.c		\
+
+# 
+# MODULE : utility
+#
+
+utility_objects :=						\
+	src/utility/src/bshift_array.o				\
+	src/utility/src/byte_utilities.o			\
+	src/utility/src/msb_index.o				\
+	src/utility/src/pack_bytes.o				\
+	src/utility/src/shift_array.o				\
+
+$(utility_objects) : %.o : %.c $(include_headers)
+
+# autotests
+utility_autotests :=						\
+	src/utility/tests/bshift_array_autotest.c		\
+	src/utility/tests/count_bits_autotest.c			\
+	src/utility/tests/pack_bytes_autotest.c			\
+	src/utility/tests/shift_array_autotest.c		\
+
+
+# benchmarks
+utility_benchmarks :=
+
+
+#
+# MODULE : vector
+#
+
+# main objects list
+vector_objects :=						\
+	@MLIBS_VECTOR@						\
+
+# portable builds
+src/vector/src/vectorf_add.port.o   : %.o : %.c $(include_headers) src/vector/src/vector_add.c
+src/vector/src/vectorf_norm.port.o  : %.o : %.c $(include_headers) src/vector/src/vector_norm.c
+src/vector/src/vectorf_mul.port.o   : %.o : %.c $(include_headers) src/vector/src/vector_mul.c
+src/vector/src/vectorf_trig.port.o  : %.o : %.c $(include_headers) src/vector/src/vector_trig.c
+src/vector/src/vectorcf_add.port.o  : %.o : %.c $(include_headers) src/vector/src/vector_add.c
+src/vector/src/vectorcf_norm.port.o : %.o : %.c $(include_headers) src/vector/src/vector_norm.c
+src/vector/src/vectorcf_mul.port.o  : %.o : %.c $(include_headers) src/vector/src/vector_mul.c
+src/vector/src/vectorcf_trig.port.o : %.o : %.c $(include_headers) src/vector/src/vector_trig.c
+
+# builds for specific architectures
+# ...
+
+# vector autotest scripts
+vector_autotests :=
+
+# additional autotest objects
+autotest_extra_obj +=
+
+# vector benchmark scripts
+vector_benchmarks :=
+
+
+
+# Target collection
+#
+# Information about targets for each module is collected
+# in these variables
+objects :=							\
+	src/libliquid.o						\
+	$(agc_objects)						\
+	$(audio_objects)					\
+	$(buffer_objects)					\
+	$(channel_objects)					\
+	$(dotprod_objects)					\
+	$(equalization_objects)					\
+	$(fec_objects)						\
+	$(fft_objects)						\
+	$(filter_objects)					\
+	$(framing_objects)					\
+	$(math_objects)						\
+	$(matrix_objects)					\
+	$(modem_objects)					\
+	$(multichannel_objects)					\
+	$(nco_objects)						\
+	$(optim_objects)					\
+	$(quantization_objects)					\
+	$(random_objects)					\
+	$(sequence_objects)					\
+	$(utility_objects)					\
+	$(vector_objects)					\
+	
+
+autotest_sources :=						\
+	autotest/null_autotest.c				\
+	$(agc_autotests)					\
+	$(audio_autotests)					\
+	$(buffer_autotests)					\
+	$(channel_autotests)					\
+	$(dotprod_autotests)					\
+	$(equalization_autotests)				\
+	$(fec_autotests)					\
+	$(fft_autotests)					\
+	$(filter_autotests)					\
+	$(framing_autotests)					\
+	$(math_autotests)					\
+	$(matrix_autotests)					\
+	$(modem_autotests)					\
+	$(multichannel_autotests)				\
+	$(nco_autotests)					\
+	$(optim_autotests)					\
+	$(quantization_autotests)				\
+	$(random_autotests)					\
+	$(sequence_autotests)					\
+	$(utility_autotests)					\
+	$(vector_autotests)					\
+	
+
+benchmark_sources :=						\
+	bench/null_benchmark.c					\
+	$(agc_benchmarks)					\
+	$(audio_benchmarks)					\
+	$(buffer_benchmarks)					\
+	$(channel_benchmarks)					\
+	$(dotprod_benchmarks)					\
+	$(equalization_benchmarks)				\
+	$(fec_benchmarks)					\
+	$(fft_benchmarks)					\
+	$(filter_benchmarks)					\
+	$(framing_benchmarks)					\
+	$(math_benchmarks)					\
+	$(matrix_benchmarks)					\
+	$(modem_benchmarks)					\
+	$(multichannel_benchmarks)				\
+	$(nco_benchmarks)					\
+	$(optim_benchmarks)					\
+	$(quantization_benchmarks)				\
+	$(random_benchmarks)					\
+	$(sequence_benchmarks)					\
+	$(utility_benchmarks)					\
+	$(vector_benchmarks)					\
+
+
+##
+## TARGET : all       - build shared library (default)
+##
+.PHONY: all
+
+# Shared library
+SHARED_LIB	= @SH_LIB@
+
+# liquid library definition
+libliquid.a: $(objects)
+	$(AR) $(ARFLAGS) $@ $^
+	$(RANLIB) $@
+
+# darwin
+#
+# gcc -dynamiclib -install_name libliquid.dylib -o libliquid.dylib libmodem.a libutility.a 
+libliquid.dylib: $(objects)
+	$(CC) -dynamiclib -install_name $@ -o $@ $^ $(LDFLAGS)
+
+# linux, et al
+libliquid.so: libliquid.a
+	$(CC) -shared -Xlinker -soname=$@ -o $@ -Wl,-whole-archive $^ -Wl,-no-whole-archive $(LDFLAGS)
+
+all: libliquid.a $(SHARED_LIB)
+
+##
+## TARGET : help      - print list of targets
+##
+
+# look for all occurences of '## TARGET : ' and print rest of line to screen
+help:
+	@echo "Targets for liquid-dsp makefile:"
+	@$(GREP) -E "^## TARGET : " [Mm]akefile | $(SED) 's/## TARGET : /  /'
+
+## 
+## TARGET : install   - installs the libraries and header files in the host system
+##
+
+install: all
+	@echo "installing..."
+	@echo ""
+	mkdir -p $(DESTDIR)$(exec_prefix)$(libdir)
+	mkdir -p $(DESTDIR)$(prefix)/include/liquid
+	install -m 644 -p $(SHARED_LIB) libliquid.a $(DESTDIR)$(exec_prefix)$(libdir)
+	install -m 644 -p $(addprefix include/,$(headers_install)) $(DESTDIR)$(prefix)/include/liquid
+	@echo ""
+	@echo "---------------------------------------------------------"
+	@echo "  liquid-dsp was successfully installed.     "
+	@echo ""
+	@echo "  On some machines (e.g. Linux) you should rebind your"
+	@echo "  libraries by running 'ldconfig' to make the shared"
+	@echo "  object available.  You might also need to modify your"
+	@echo "  LD_LIBRARY_PATH environment variable to include the"
+	@echo "  directory $(DESTDIR)$(exec_prefix)"
+	@echo ""
+	@echo "  Please report bugs to $(BUGREPORT)"
+	@echo "---------------------------------------------------------"
+	@echo ""
+
+## 
+## TARGET : uninstall - uninstalls the libraries and header files in the host system
+##
+
+uninstall:
+	@echo "uninstalling..."
+	$(RM) $(addprefix $(DESTDIR)$(prefix)/include/liquid/, $(headers_install))
+	$(RM) $(DESTDIR)$(exec_prefix)$(libdir)/libliquid.a
+	$(RM) $(DESTDIR)$(exec_prefix)$(libdir)/$(SHARED_LIB)
+	@echo "done."
+
+##
+## autoscript
+##
+
+autoscript : scripts/autoscript
+
+scripts/autoscript.o scripts/main.o : %.o : %.c
+	$(CC) $(CFLAGS) -c -o $@ $<
+
+scripts/autoscript : scripts/autoscript.o scripts/main.o
+	$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean-autoscript :
+	$(RM) scripts/autoscript.o scripts/main.o scripts/autoscript
+
+
+##
+## TARGET : check     - build and run autotest scripts
+##
+
+# Autotests are used to check the validity and accuracy of the
+# DSP libraries.
+
+.PHONY: autotest
+autotest_prog	= xautotest
+
+# run the autotest generator script to create autotest_include.h
+autotest_include.h : scripts/autoscript $(autotest_sources) $(include_headers)
+	./scripts/autoscript $(PATHSEP) autotest $(autotest_sources) > $@
+
+# autotest objects
+# NOTE: by default, gcc compiles any file with a '.h' extension as a 'pre-compiled
+#       header' so we need to explicity tell it to compile as a c source file with
+#       the '-x c' flag
+autotest_obj = $(patsubst %.c,%.o,$(autotest_sources))
+$(autotest_obj) : %.o : %.c $(include_headers)
+	$(CC) $(CFLAGS) $< -c -o $@
+
+# additional autotest objects
+$(autotest_extra_obj) : %.o : %.c $(include_headers)
+
+# compile the autotest internal library functions without linking
+autotest/autotestlib.o : autotest/autotestlib.c autotest/autotest.h
+	$(CC) $(CFLAGS) $< -c -o $@
+
+# compile the autotest program without linking
+$(autotest_prog).o : autotest/autotest.c autotest/autotest.h autotest_include.h
+	$(CC) $(CFLAGS) $< -c -o $@
+
+# link the autotest program with the objects
+# NOTE: linked libraries must come _after_ the target program
+$(autotest_prog): $(autotest_prog).o $(autotest_obj) $(autotest_extra_obj) autotest/autotestlib.o libliquid.a
+	$(CC) $^ -o $@ $(LDFLAGS)
+
+# run the autotest program
+check: $(autotest_prog)
+	./$(autotest_prog) -v
+
+# let 'make test' be an alias for 'make check'
+test: check
+
+# clean the generated files
+clean-check:
+	$(RM) autotest_include.h $(autotest_prog).o $(autotest_prog)
+	$(RM) autotest/autotestlib.o
+	$(RM) $(autotest_obj)
+	$(RM) $(autotest_extra_obj)
+
+
+##
+## TARGET : bench     - build and run all benchmarks
+##
+
+# Benchmarks measure the relative speed of the DSP algorithms running
+# on the target platform.
+.PHONY: bench
+bench_prog	= benchmark
+BENCH_CFLAGS	= -Wall $(INCLUDE_CFLAGS) $(CONFIG_CFLAGS)
+BENCH_LDFLAGS	= $(LDFLAGS)
+
+# run the benchmark generator script to create benchmark_include.h
+benchmark_include.h : scripts/autoscript $(benchmark_sources) $(include_headers)
+	./scripts/autoscript $(PATHSEP) benchmark $(benchmark_sources) > $@
+
+# benchmark objects
+# NOTE: by default, gcc compiles any file with a '.h' extension as a 'pre-compiled
+#       header' so we need to explicity tell it to compile as a c source file with
+#       the '-x c' flag
+benchmark_obj = $(patsubst %.c,%.o,$(benchmark_sources))
+$(benchmark_obj) : %.o : %.c $(include_headers)
+	$(CC) $(BENCH_CFLAGS) $< -c -o $@
+
+# additional benchmark objects
+$(benchmark_extra_obj) : %.o : %.c $(include_headers)
+
+# compile the benchmark program without linking
+$(bench_prog).o: bench/bench.c benchmark_include.h bench/bench.c
+	$(CC) $(BENCH_CFLAGS) $< -c -o $(bench_prog).o
+
+# link the benchmark program with the library objects
+# NOTE: linked libraries must come _after_ the target program
+$(bench_prog): $(bench_prog).o $(benchmark_obj) $(benchmark_extra_obj) libliquid.a
+	$(CC) $^ -o $(bench_prog) $(BENCH_LDFLAGS)
+
+# run the benchmark program
+bench: $(bench_prog)
+	./$(bench_prog)
+
+# benchmark compare script
+scripts/benchmark_compare : % : %.c
+	$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+# fftbench program
+bench/fftbench.o : %.o : %.c
+	$(CC) $(BENCH_CFLAGS) $< -c -o $@
+
+bench/fftbench : % : %.o libliquid.a
+	$(CC) $^ -o $@ $(BENCH_LDFLAGS)
+
+# clean up the generated files
+clean-bench:
+	$(RM) benchmark_include.h $(bench_prog).o $(bench_prog)
+	$(RM) scripts/benchmark_compare
+	$(RM) $(benchmark_obj)
+	$(RM) $(benchmark_extra_obj)
+	$(RM) bench/fftbench.o
+	$(RM) bench/fftbench
+
+
+## 
+## TARGET : examples  - build all examples binaries
+##
+.PHONY: examples
+example_programs :=						\
+	examples/agc_crcf_example				\
+	examples/agc_crcf_qpsk_example				\
+	examples/agc_rrrf_example				\
+	examples/ampmodem_example				\
+	examples/asgramcf_example				\
+	examples/asgramf_example				\
+	examples/autocorr_cccf_example				\
+	examples/bpacketsync_example				\
+	examples/bpresync_example				\
+	examples/bsequence_example				\
+	examples/cbufferf_example				\
+	examples/cgsolve_example				\
+	examples/channel_cccf_example				\
+	examples/chromosome_example				\
+	examples/compand_example				\
+	examples/compand_cf_example				\
+	examples/complementary_codes_example			\
+	examples/conversion_example				\
+	examples/crc_example					\
+	examples/cpfskmodem_example				\
+	examples/cpfskmodem_psd_example				\
+	examples/cvsd_example					\
+	examples/detector_cccf_example				\
+	examples/dotprod_rrrf_example				\
+	examples/dotprod_cccf_example				\
+	examples/eqlms_cccf_block_example			\
+	examples/eqlms_cccf_blind_example			\
+	examples/eqlms_cccf_decisiondirected_example		\
+	examples/eqlms_cccf_example				\
+	examples/eqrls_cccf_example				\
+	examples/fec_example					\
+	examples/fec_soft_example				\
+	examples/fft_example					\
+	examples/fftfilt_crcf_example				\
+	examples/firdecim_crcf_example				\
+	examples/firfarrow_rrrf_example				\
+	examples/firfilt_cccf_example				\
+	examples/firfilt_crcf_example				\
+	examples/firfilt_rrrf_example				\
+	examples/firdes_kaiser_example				\
+	examples/firdespm_example				\
+	examples/firhilb_example				\
+	examples/firhilb_decim_example				\
+	examples/firhilb_interp_example				\
+	examples/firpfbch2_crcf_example				\
+	examples/firinterp_crcf_example				\
+	examples/firpfbch_crcf_example				\
+	examples/firpfbch_crcf_analysis_example			\
+	examples/firpfbch_crcf_synthesis_example		\
+	examples/flexframesync_example				\
+	examples/flexframesync_reconfig_example			\
+	examples/framesync64_example				\
+	examples/freqmodem_example				\
+	examples/fskmodem_example				\
+	examples/gasearch_example				\
+	examples/gasearch_knapsack_example			\
+	examples/gmskframesync_example				\
+	examples/gmskmodem_example				\
+	examples/gradsearch_datafit_example			\
+	examples/gradsearch_example				\
+	examples/interleaver_example				\
+	examples/interleaver_soft_example			\
+	examples/interleaver_scatterplot_example		\
+	examples/iirdes_example					\
+	examples/iirdes_analog_example				\
+	examples/iirdes_pll_example				\
+	examples/iirdecim_crcf_example				\
+	examples/iirfilt_cccf_example				\
+	examples/iirfilt_crcf_example				\
+	examples/iirfilt_crcf_dcblocker_example			\
+	examples/iirinterp_crcf_example				\
+	examples/kbd_window_example				\
+	examples/lpc_example					\
+	examples/libliquid_example				\
+	examples/matched_filter_example				\
+	examples/math_lngamma_example				\
+	examples/math_primitive_root_example			\
+	examples/modem_arb_example				\
+	examples/modem_example					\
+	examples/modem_soft_example				\
+	examples/modular_arithmetic_example			\
+	examples/msequence_example				\
+	examples/msourcecf_example				\
+	examples/msresamp_crcf_example				\
+	examples/msresamp2_crcf_example				\
+	examples/nco_example					\
+	examples/nco_pll_example				\
+	examples/nco_pll_modem_example				\
+	examples/nyquist_filter_example				\
+	examples/ofdmflexframesync_example			\
+	examples/ofdmframesync_example				\
+	examples/packetizer_example				\
+	examples/packetizer_soft_example			\
+	examples/pll_example					\
+	examples/polyfit_example				\
+	examples/polyfit_lagrange_example			\
+	examples/poly_findroots_example				\
+	examples/qdetector_cccf_example				\
+	examples/qpacketmodem_performance_example		\
+	examples/qpacketmodem_example				\
+	examples/qpilotsync_example				\
+	examples/qnsearch_example				\
+	examples/quantize_example				\
+	examples/random_histogram_example			\
+	examples/repack_bytes_example				\
+	examples/resamp_crcf_example				\
+	examples/resamp2_cccf_example				\
+	examples/resamp2_crcf_example				\
+	examples/resamp2_crcf_decim_example			\
+	examples/resamp2_crcf_filter_example			\
+	examples/resamp2_crcf_interp_example			\
+	examples/ricek_channel_example				\
+	examples/scramble_example				\
+	examples/smatrix_example				\
+	examples/spgramcf_example				\
+	examples/spgramcf_waterfall_example			\
+	examples/spgramf_example				\
+	examples/symsync_crcf_example				\
+	examples/symsync_crcf_full_example			\
+	examples/symsync_crcf_kaiser_example			\
+	examples/symstreamcf_example				\
+	examples/symtrack_cccf_example				\
+	examples/wdelayf_example				\
+	examples/windowf_example				\
+
+#	examples/metadata_example
+#	examples/ofdmframegen_example
+#	examples/gmskframe_example
+#	examples/fading_generator_example
+
+example_objects	= $(patsubst %,%.o,$(example_programs))
+examples: $(example_programs)
+
+EXAMPLES_LDFLAGS = $(LDFLAGS)
+
+# NOTE: linked libraries must come _after_ the target program
+$(example_objects): %.o : %.c
+
+$(example_programs): % : %.o libliquid.a
+	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
+
+# clean examples
+clean-examples:
+	$(RM) examples/*.o
+	$(RM) $(example_programs)
+
+## 
+## TARGET : sandbox   - build all sandbox binaries
+##
+
+# NOTE: sandbox _requires_ fftw3 to build
+.PHONY: sandbox
+sandbox_programs =						\
+	sandbox/bpresync_test					\
+	sandbox/cpmodem_test					\
+	sandbox/count_ones_gentab				\
+	sandbox/crc_gentab					\
+	sandbox/ellip_func_test					\
+	sandbox/ellip_test					\
+	sandbox/eqlms_cccf_test					\
+	sandbox/fecsoft_ber_test				\
+	sandbox/fec_g2412product_test				\
+	sandbox/fec_golay2412_test				\
+	sandbox/fec_golay_test					\
+	sandbox/fec_hamming3126_example				\
+	sandbox/fec_hamming128_test				\
+	sandbox/fec_hamming128_gentab				\
+	sandbox/fec_hamming128_example				\
+	sandbox/fec_hamming74_gentab				\
+	sandbox/fec_hamming84_gentab				\
+	sandbox/fec_hamming_test				\
+	sandbox/fec_ldpc_test					\
+	sandbox/fec_rep3_test					\
+	sandbox/fec_rep5_test					\
+	sandbox/fec_secded2216_test				\
+	sandbox/fec_secded3932_test				\
+	sandbox/fec_secded7264_test				\
+	sandbox/fec_spc2216_test				\
+	sandbox/fec_secded_punctured_test			\
+	sandbox/fecsoft_conv_test				\
+	sandbox/fecsoft_hamming128_gentab			\
+	sandbox/fecsoft_ldpc_test				\
+	sandbox/fec_sumproduct_test				\
+	sandbox/fskmodem_test					\
+	sandbox/fft_dual_radix_test				\
+	sandbox/fft_mixed_radix_test				\
+	sandbox/fft_recursive_plan_test				\
+	sandbox/fft_recursive_test				\
+	sandbox/fft_rader_prime_test				\
+	sandbox/fft_rader_prime_radix2_test			\
+	sandbox/fft_r2r_test					\
+	sandbox/firdes_energy_test				\
+	sandbox/firdes_fexp_test				\
+	sandbox/firdes_gmskrx_test				\
+	sandbox/firdes_length_test				\
+	sandbox/firfarrow_rrrf_test				\
+	sandbox/firpfbch_analysis_alignment_test		\
+	sandbox/firpfbch2_analysis_equivalence_test		\
+	sandbox/firpfbch2_test					\
+	sandbox/firpfbch_analysis_equivalence_test		\
+	sandbox/firpfbch_synthesis_equivalence_test		\
+	sandbox/gmskmodem_test					\
+	sandbox/gmskmodem_coherent_test				\
+	sandbox/gmskmodem_equalizer_test			\
+	sandbox/householder_test				\
+	sandbox/iirdes_example					\
+	sandbox/iirfilt_intdiff_test				\
+	sandbox/levinson_test					\
+	sandbox/matched_filter_test				\
+	sandbox/matched_filter_cfo_test				\
+	sandbox/math_lngamma_test				\
+	sandbox/math_cacosf_test				\
+	sandbox/math_casinf_test				\
+	sandbox/math_catanf_test				\
+	sandbox/math_cexpf_test					\
+	sandbox/math_clogf_test					\
+	sandbox/math_csqrtf_test				\
+	sandbox/matrix_test					\
+	sandbox/minsearch_test					\
+	sandbox/minsearch2_test					\
+	sandbox/matrix_eig_test					\
+	sandbox/modem_demodulate_arb_gentab			\
+	sandbox/modem_demodulate_soft_test			\
+	sandbox/modem_demodulate_soft_gentab			\
+	sandbox/mskmodem_test					\
+	sandbox/msresamp_crcf_test				\
+	sandbox/ofdmoqam_firpfbch_test				\
+	sandbox/ofdm_ber_test					\
+	sandbox/ofdmframe_papr_test				\
+	sandbox/ofdmframesync_cfo_test				\
+	sandbox/pll_design_test					\
+	sandbox/predemod_sync_test				\
+	sandbox/quasinewton_test				\
+	sandbox/recursive_qpsk_test				\
+	sandbox/resamp2_crcf_filterbank_test			\
+	sandbox/resamp2_crcf_interp_recreate_test		\
+	sandbox/reverse_byte_gentab				\
+	sandbox/rkaiser2_test					\
+	sandbox/shadowing_test					\
+	sandbox/simplex_test					\
+	sandbox/symsync_crcf_test				\
+	sandbox/symsync_eqlms_test				\
+	sandbox/svd_test					\
+	sandbox/thiran_allpass_iir_test				\
+	sandbox/vectorcf_test					\
+
+#	sandbox/packetizer_persistent_ber_test
+#	firpfbch_analysis_test
+#	sandbox/ofdmoqam_firpfbch_cfo_test
+#	sandbox/mdct_test
+#	sandbox/fct_test
+
+
+sandbox_objects	= $(patsubst %,%.o,$(sandbox_programs))
+sandbox: $(sandbox_programs)
+SANDBOX_LDFLAGS = $(LDFLAGS) -lfftw3f
+
+# NOTE: linked libraries must come _after_ the target program
+$(sandbox_objects): %.o : %.c
+
+$(sandbox_programs): % : %.o libliquid.a
+	$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
+
+# clean sandbox
+clean-sandbox:
+	$(RM) sandbox/*.o
+	$(RM) $(sandbox_programs)
+
+
+##
+## TARGET : world     - build absolutely everything
+##
+world : all check bench examples sandbox
+
+##
+## TARGET : clean     - clean build (objects, dependencies, libraries, etc.)
+##
+
+.PHONY: clean
+
+clean-modules:
+	@echo "cleaning modules..."
+	$(RM) src/agc/src/*.o          src/agc/bench/*.o          src/agc/tests/*.o
+	$(RM) src/audio/src/*.o        src/audio/bench/*.o        src/audio/tests/*.o
+	$(RM) src/buffer/src/*.o       src/buffer/bench/*.o       src/buffer/tests/*.o
+	$(RM) src/channel/src/*.o      src/channel/bench/*.o      src/channel/tests/*.o
+	$(RM) src/dotprod/src/*.o      src/dotprod/bench/*.o      src/dotprod/tests/*.o
+	$(RM) src/equalization/src/*.o src/equalization/bench/*.o src/equalization/tests/*.o
+	$(RM) src/fec/src/*.o          src/fec/bench/*.o          src/fec/tests/*.o
+	$(RM) src/fft/src/*.o          src/fft/bench/*.o          src/fft/tests/*.o
+	$(RM) src/filter/src/*.o       src/filter/bench/*.o       src/filter/tests/*.o
+	$(RM) src/framing/src/*.o      src/framing/bench/*.o      src/framing/tests/*.o
+	$(RM) src/math/src/*.o         src/math/bench/*.o         src/math/tests/*.o
+	$(RM) src/matrix/src/*.o       src/matrix/bench/*.o       src/matrix/tests/*.o
+	$(RM) src/modem/src/*.o        src/modem/bench/*.o        src/modem/tests/*.o
+	$(RM) src/multichannel/src/*.o src/multichannel/bench/*.o src/multichannel/tests/*.o
+	$(RM) src/nco/src/*.o          src/nco/bench/*.o          src/nco/tests/*.o
+	$(RM) src/optim/src/*.o        src/optim/bench/*.o        src/optim/tests/*.o
+	$(RM) src/quantization/src/*.o src/quantization/bench/*.o src/quantization/tests/*.o
+	$(RM) src/random/src/*.o       src/random/bench/*.o       src/random/tests/*.o
+	$(RM) src/sequence/src/*.o     src/sequence/bench/*.o     src/sequence/tests/*.o
+	$(RM) src/utility/src/*.o      src/utility/bench/*.o      src/utility/tests/*.o
+	$(RM) src/vector/src/*.o       src/vector/bench/*.o       src/vector/tests/*.o
+	$(RM) src/libliquid.o
+
+clean: clean-modules clean-autoscript clean-check clean-bench clean-examples clean-sandbox
+	$(RM) $(extra_clean)
+	$(RM) libliquid.a
+	$(RM) $(SHARED_LIB)
+
+##
+## TARGET : distclean - removes everything except the originally distributed files
+##
+
+distclean: clean
+	@echo "cleaning distribution..."
+	$(RM) octave-core *.m
+	$(RM) configure config.h config.h.in config.h.in~ config.log config.status
+	$(RM) -r autom4te.cache
+	$(RM) aclocal.m4
+	$(RM) makefile
+
diff --git a/sandbox/README.md b/sandbox/README.md
new file mode 100644
index 0000000..495d0ff
--- /dev/null
+++ b/sandbox/README.md
@@ -0,0 +1,15 @@
+
+liquid-dsp sandbox
+==================
+
+This directory is the sandbox for prototyping DSP processing
+elements in liquid.  It contains a number of scripts which are used
+for testing and prototyping certain signal processing algorithms.
+Its primary function is to validate functional correctness without
+adding unnecessary code to the main project files.
+
+All the programs in this directory can be build by invoking 'make'
+from the root directory, viz.
+
+    $ make sandbox
+
diff --git a/sandbox/bpresync_test.c b/sandbox/bpresync_test.c
new file mode 100644
index 0000000..43b4b3b
--- /dev/null
+++ b/sandbox/bpresync_test.c
@@ -0,0 +1,267 @@
+// 
+// bpresync_test.c
+//
+// This example demonstrates the binary pre-demodulator synchronizer. A random
+// binary sequence is generated, modulated with BPSK, and then interpolated.
+// The resulting sequence is used to generate a bpresync object which in turn
+// is used to detect a signal in the presence of carrier frequency and timing
+// offsets and additive white Gauss noise.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "bpresync_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("bpresync_test -- test binary pre-demodulation synchronization\n");
+    printf("  h     : print usage/help\n");
+    printf("  v     : increase output verbosity\n");
+    printf("  q     : decrease output verbosity\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  n     : number of data symbols, default: 64\n");
+    printf("  F     : carrier frequency offset, default: 0.02\n");
+    printf("  S     : SNR [dB], default: 20\n");
+    printf("  t     : number of trials, default: 1000\n");
+}
+
+void bpresync_test(bpresync_cccf   _q,
+                   float complex * _x,
+                   unsigned int    _n,
+                   float           _SNRdB,
+                   float           _dphi_max,
+                   float *         _rxy_max,
+                   float *         _dphi_err,
+                   float *         _delay_err,
+                   unsigned int    _num_trials,
+                   unsigned int    _verbosity);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int k=2;                   // filter samples/symbol
+    unsigned int num_sync_symbols = 64; // number of synchronization symbols
+    float SNRdB = 20.0f;                // signal-to-noise ratio [dB]
+    float dphi_max = 0.02f;             // maximum carrier frequency offset
+    unsigned int num_trials = 1000;     // number of trials to run
+
+    int verbosity = 1;                  // verbosity level
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hvqk:n:F:S:t:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                          return 0;
+        case 'v': verbosity++;                      break;
+        case 'q': verbosity--;                      break;
+        case 'k': k = atoi(optarg);                 break;
+        case 'n': num_sync_symbols = atoi(optarg);  break;
+        case 'F': dphi_max = atof(optarg);          break;
+        case 'S': SNRdB = atof(optarg);             break;
+        case 't': num_trials = atoi(optarg);        break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // arrays
+    float complex seq[k*num_sync_symbols];  // synchronization pattern (samples)
+    float rxy_max[num_trials];
+    float dphi_err[num_trials];
+    float delay_err[num_trials];
+
+    // generate synchronization pattern (BPSK) and interpolate
+    unsigned int n=0;
+    for (i=0; i<num_sync_symbols; i++) {
+        float sym = rand() % 2 ? -1.0f : 1.0f;
+        
+        unsigned int j;
+        for (j=0; j<k; j++)
+            seq[n++] = sym;
+    }
+
+    // create cross-correlator
+    bpresync_cccf sync = bpresync_cccf_create(seq, k*num_sync_symbols, 0.05f, 11);
+    bpresync_cccf_print(sync);
+
+    // run trials
+    printf("running %u trials...\n", num_trials);
+    bpresync_test(sync, seq, k*num_sync_symbols,
+                  SNRdB, dphi_max,
+                  rxy_max, dphi_err, delay_err,
+                  num_trials,
+                  verbosity);
+
+    // destroy objects
+    bpresync_cccf_destroy(sync);
+
+
+    // print results
+    float rxy_max_avg    = 0.0f;
+    float dphi_err_rmse  = 0.0f;
+    float delay_err_rmse = 0.0f;
+    for (i=0; i<num_trials; i++) {
+        rxy_max_avg    += rxy_max[i];
+        dphi_err_rmse  += dphi_err[i]*dphi_err[i];
+        delay_err_rmse += delay_err[i]*delay_err[i];
+    }
+
+    rxy_max_avg    = rxy_max_avg / (float)num_trials;
+    dphi_err_rmse  = sqrtf( dphi_err_rmse / (float)num_trials );
+    delay_err_rmse = sqrtf( delay_err_rmse / (float)num_trials );
+
+    printf("\n");
+    printf("    rxy_max (average)       :   %12.8f\n", rxy_max_avg);
+    printf("    dphi estimate (RMSE)    :   %12.8f\n", dphi_err_rmse);
+    printf("    delay estimate (RMSE)   :   %12.8f\n", delay_err_rmse);
+    
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"k          = %u;\n", k);
+
+    fprintf(fid,"rxy_max   = zeros(1,num_trials);\n");
+    fprintf(fid,"dphi_err  = zeros(1,num_trials);\n");
+    fprintf(fid,"delay_err = zeros(1,num_trials);\n");
+    for (i=0; i<num_trials; i++) {
+        fprintf(fid,"rxy_max(%4u)   = %12.4e;\n", i+1, rxy_max[i]);
+        fprintf(fid,"dphi_err(%4u)  = %12.4e;\n", i+1, dphi_err[i]);
+        fprintf(fid,"delay_err(%4u) = %12.4e;\n", i+1, delay_err[i]);
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"  hist(rxy_max, 25);\n");
+    fprintf(fid,"  xlabel('|r_{xy}|');\n");
+    fprintf(fid,"  ylabel('histogram');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
+
+void bpresync_test(bpresync_cccf   _q,
+                   float complex * _x,
+                   unsigned int    _n,
+                   float           _SNRdB,
+                   float           _dphi_max,
+                   float *         _rxy_max,
+                   float *         _dphi_err,
+                   float *         _delay_err,
+                   unsigned int    _num_trials,
+                   unsigned int    _verbosity)
+{
+    unsigned int max_delay = 64;
+    float gamma = powf(10.0f, _SNRdB/20.0f);
+    float nstd  = 1.0f;
+
+    // Farrow filter (for facilitating delay)
+    unsigned int h_len = 49;
+    unsigned int order = 4;
+    float        fc    = 0.45f;
+    float        As    = 60.0f;
+    firfarrow_crcf fdelay = firfarrow_crcf_create(h_len, order, fc, As);
+
+    unsigned int num_samples = _n + max_delay + (h_len-1)/2;
+    float complex y[num_samples];
+
+    unsigned int t;
+    for (t=0; t<_num_trials; t++) {
+        unsigned int delay = rand() % max_delay;    // sample delay
+        float        dt    = randf() - 0.5f;        // fractional sample delay
+        float dphi = (2.0f*randf() - 1.0f) * _dphi_max; // carrier frequency offset
+        float phi   = 2*M_PI*randf();                   // carrier phase offset
+
+        // reset binary pre-demod synchronizer
+        bpresync_cccf_reset(_q);
+
+        // reset farrow filter
+        firfarrow_crcf_reset(fdelay);
+        firfarrow_crcf_set_delay(fdelay, dt);
+
+        unsigned int i;
+        unsigned int n=0;
+
+        // generate signal: delay
+        for (i=0; i<delay; i++) {
+            firfarrow_crcf_push(fdelay, 0.0f);
+            firfarrow_crcf_execute(fdelay, &y[n++]);
+        }
+
+        // generate signal: input sequence
+        for (i=0; i<_n; i++) {
+            firfarrow_crcf_push(fdelay, _x[i]);
+            firfarrow_crcf_execute(fdelay, &y[n++]);
+        }
+
+        // generate signal: flush filter
+        while (n < num_samples) {
+            firfarrow_crcf_push(fdelay, 0.0f);
+            firfarrow_crcf_execute(fdelay, &y[n++]);
+        }
+
+        // add channel gain, carrier offset, noise
+        for (i=0; i<num_samples; i++) {
+            y[i] *= gamma;
+            y[i] *= cexpf(_Complex_I*(phi + i*dphi));
+            y[i] += nstd*( randnf() + randnf()*_Complex_I )*M_SQRT1_2;
+        }
+
+        // push through synchronizer
+        _rxy_max[t]   = 0.0f;
+        _dphi_err[t]  = 0.0f;
+        _delay_err[t] = 0.0f;
+        for (i=0; i<num_samples; i++) {
+            // push through correlator
+            float complex rxy;
+            float         dphi_est;
+            bpresync_cccf_push(_q, y[i]);
+            bpresync_cccf_correlate(_q, &rxy, &dphi_est);
+
+            // retain maximum
+            if ( cabsf(rxy) > _rxy_max[t] ) {
+                _rxy_max[t]   = cabsf(rxy);
+                _dphi_err[t]  = dphi_est - dphi;
+                _delay_err[t] = (float)i - (float)(_n + delay + (h_len-1)/2) + 1.0f + dt;
+            }
+        }
+
+        if (_verbosity == 0) {
+            // do nothing
+        } else if (_verbosity == 1) {
+            // print progress bar
+            if ( (t%100)==0 || t==_num_trials-1 ) {
+                float percent = (float)(t+1) / (float)_num_trials;
+                unsigned int bars = (unsigned int) (percent*60);
+                printf("[");
+                for (i=0; i<60; i++)
+                    printf("%c", i < bars ? '#' : ' ');
+                printf("] %5.1f%%\r", percent*100);
+                fflush(stdout);
+            }
+        } else {
+            // print every trial
+            printf("  %6u:  rxy_max=%12.8f, dphi-err=%12.8f, delay-err=%12.8f\n",
+                    t, _rxy_max[t], _dphi_err[t], _delay_err[t]);
+        }
+
+    }
+
+    // destroy Farrow filter
+    firfarrow_crcf_destroy(fdelay);
+}
+
diff --git a/sandbox/count_ones_gentab.c b/sandbox/count_ones_gentab.c
new file mode 100644
index 0000000..101aed1
--- /dev/null
+++ b/sandbox/count_ones_gentab.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// generate tables for counting ones in a byte
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// slow implementation counting ones in a byte
+unsigned int count_ones(unsigned char _x)
+{
+    unsigned int n=0;
+    unsigned int i;
+    for (i=0; i<8; i++)
+        n += (_x >> i) & 0x01;
+    
+    return n;
+}
+
+int main()
+{
+    unsigned int i;
+
+    printf("// auto-generated file (do not edit)\n");
+    printf("\n");
+    printf("// number of ones in a byte\n");
+    printf("unsigned const char liquid_c_ones[256] = {\n    ");
+    for (i=0; i<256; i++) {
+        // 
+        unsigned int n = count_ones((unsigned char)i);
+
+        // print results
+        printf("%1u", n);
+
+        if ( i != 255 ) {
+            printf(",");
+            if ( ((i+1)%16)==0 )
+                printf("\n    ");
+            else
+                printf(" ");
+        }
+
+    }
+    printf("};\n\n");
+
+    // do the same modulo 2
+    printf("// number of ones in a byte modulo 2\n");
+    printf("unsigned const char liquid_c_ones_mod2[256] = {\n    ");
+    for (i=0; i<256; i++) {
+        // 
+        unsigned int n = count_ones((unsigned char)i);
+
+        // print results
+        printf("%1u", n % 2);
+
+        if ( i != 255 ) {
+            printf(",");
+            if ( ((i+1)%16)==0 )
+                printf("\n    ");
+            else
+                printf(" ");
+        }
+
+    }
+    printf("};\n\n");
+
+    
+    return 0;
+}
diff --git a/sandbox/cpmodem_test.c b/sandbox/cpmodem_test.c
new file mode 100644
index 0000000..d796116
--- /dev/null
+++ b/sandbox/cpmodem_test.c
@@ -0,0 +1,153 @@
+//
+// cpmodem_test.c
+//
+// This example demonstrates how the use the nco/pll object
+// (numerically-controlled oscillator with phase-locked loop) interface for
+// tracking to a complex sinusoid.  The loop bandwidth, phase offset, and
+// other parameter can be specified via the command-line interface.
+//
+// SEE ALSO: nco_example.c
+//           nco_pll_modem_example.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "cpmodem_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("cpmodem_test [options]\n");
+    printf("  h     : print usage\n");
+    printf("  m     : bits per symbol, default: 2\n");
+    printf("  n     : number of symbols, default: 20\n");
+    printf("  p     : phase offset (radians), default: pi/4\n");
+    printf("  f     : frequency offset (radians), default: 0.3\n");
+}
+
+int main(int argc, char*argv[]) {
+    srand( time(NULL) );
+    // parameters
+    float phase_offset = M_PI / 4.0f;
+    float frequency_offset = 0.1f;
+    unsigned int m = 2;             // bits per symbol
+    unsigned int num_symbols=20;    // number of iterations
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhm:n:p:f:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();    return 0;
+        case 'm':   m = atoi(optarg);               break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 'p':   phase_offset = atof(optarg);    break;
+        case 'f':   frequency_offset= atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // derived values
+    unsigned int M = 1<<m;  // constellation size
+    unsigned int k = 4*m;   // samples per symbol
+    unsigned int num_samples = num_symbols*k;
+    float nstd = 0.1f;
+
+    // objects
+    nco_crcf nco_tx = nco_crcf_create(LIQUID_VCO);
+
+    // generate input
+    float dphi[num_samples];        // transmitted instantaneous frequency
+    float complex x[num_samples];   // transmitted signal
+    float complex y[num_samples];   // received signal
+    float dphi_hat[num_samples];    // received instantaneous frequency
+
+    unsigned int n=0;               // sample counter
+    unsigned int s;                 // symbol counter
+    unsigned int i;                 // 
+    for (s=0; s<num_symbols; s++) {
+        unsigned int symbol = rand() % M;
+
+        // compute frequency
+        // TODO: determine appropriate scaling
+        float f = (2*(float)symbol - (float)M + 1.0f) * M_PI / (float)M;
+
+        // set frequency
+        nco_crcf_set_frequency(nco_tx, f);
+
+        // synthesize signal
+        for (i=0; i<k; i++) {
+            // generate complex sinusoid
+            nco_crcf_cexpf(nco_tx, &x[n]);
+            nco_crcf_step(nco_tx);
+            
+            // save frequency
+            dphi[n] = f;
+            n++;
+        }
+    }
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i]  = x[i] * cexpf(_Complex_I*(frequency_offset*i + phase_offset));
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // run loop
+    float complex yn = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        float complex r = y[i] * conjf(yn);
+
+        dphi_hat[i] = cargf(r);
+
+        yn = y[i];
+    }
+    nco_crcf_destroy(nco_tx);
+
+    // write output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"m           = %u;\n", m);
+    fprintf(fid,"M           = %u;\n", M);
+    fprintf(fid,"k           = %u;\n", k);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    fprintf(fid,"dphi     = zeros(1,num_samples);\n");
+    fprintf(fid,"dphi_hat = zeros(1,num_samples);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"dphi(%4u)     = %12.4e;\n", i+1, dphi[i]);
+        fprintf(fid,"dphi_hat(%4u) = %12.4e;\n", i+1, dphi_hat[i]);
+    }
+    fprintf(fid,"t=0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),t,imag(x));\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('x(t)');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(t,real(y),t,imag(y));\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('y(t)');\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,dphi/(2*pi), t, dphi_hat/(2*pi));\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('instantaneous frequency');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/crc_gentab.c b/sandbox/crc_gentab.c
new file mode 100644
index 0000000..3d84796
--- /dev/null
+++ b/sandbox/crc_gentab.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// CRC table generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// generator polynomial
+#define CRC32_POLY 0xEDB88320 // 0x04C11DB7
+
+int main() {
+    unsigned int crc;
+    unsigned int mask;
+    unsigned int crcpoly = CRC32_POLY;
+
+    unsigned int crc_gentab[256];
+
+    // generate table
+    unsigned int i, j;
+    for (i=0; i<256; i++) {
+        crc = i;
+        for (j=0; j<8; j++) {
+            mask = -(crc & 0x01);
+            crc = (crc >> 1) ^ (crcpoly & mask);
+        }
+        crc_gentab[i] = crc;
+    }
+
+    printf("\n");
+    printf("unsigned char crc_gentab[%u] = {\n    ", 256);
+    for (i=0; i<256; i++) {
+        printf("0x%.2x", crc_gentab[i]);
+        if (i==255)
+            printf("};\n");
+        else if (((i+1)%8)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+
+    // test it...
+
+    return 0;
+}
+
diff --git a/sandbox/ellip_func_test.c b/sandbox/ellip_func_test.c
new file mode 100644
index 0000000..c3a8a84
--- /dev/null
+++ b/sandbox/ellip_func_test.c
@@ -0,0 +1,35 @@
+//
+// ellip_func_test.c
+//
+// gcc -I. -I./include -lm libliquid.a ellip_func_test.c -o ellip_func_test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "include/liquid.h"
+#include "src/filter/src/ellip.c"
+
+int main() {
+
+    float complex u = 0.7f;
+    float k = 0.8f;
+    unsigned int n=7;
+
+    float complex cd = ellip_cdf(u,k,n);
+    float complex sn = ellip_snf(u,k,n);
+    printf("u   : %12.8f + j*%12.8f\n", crealf(u), cimagf(u));
+    printf("k   : %12.8f\n", k);
+    printf("cd  : %12.8f + j*%12.8f\n", crealf(cd), cimagf(cd));
+    printf("sn  : %12.8f + j*%12.8f\n", crealf(sn), cimagf(sn));
+
+    printf("\n");
+    float complex acd = ellip_acdf(cd,k,n);
+    float complex asn = ellip_asnf(sn,k,n);
+    printf("acd : %12.8f + j*%12.8f\n", crealf(acd), cimagf(acd));
+    printf("asn : %12.8f + j*%12.8f\n", crealf(asn), cimagf(asn));
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/ellip_test.c b/sandbox/ellip_test.c
new file mode 100644
index 0000000..f84d368
--- /dev/null
+++ b/sandbox/ellip_test.c
@@ -0,0 +1,88 @@
+//
+// elliptest.c
+//
+// gcc -I. -I./include -lm libliquid.a elliptest.c -o elliptest
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "include/liquid.h"
+#include "src/filter/src/ellip.c"
+
+int main() {
+    // filter specifications
+    float fp = 4.0f;
+    float fs = 4.5f;
+    float Gp = 0.95f;
+    float Gs = 0.05f;
+
+    unsigned int n=7;   // number of iterations
+
+    float Wp = 2*M_PI*fp;
+    float Ws = 2*M_PI*fs;
+
+    // ripples passband, stopband
+    float ep = sqrtf(1.0f/(Gp*Gp) - 1.0f);
+    float es = sqrtf(1.0f/(Gs*Gs) - 1.0f);
+    printf("ep, es      : %12.8f, %12.8f\n", ep, es);
+
+    float k  = Wp/Ws;           // 0.8889f;
+    float k1 = ep/es;           // 0.0165f;
+
+    float K,  Kp;
+    float K1, K1p;
+    ellipkf(k, n, &K,  &Kp);    // K  = 2.23533416, Kp  = 1.66463780
+    ellipkf(k1,n, &K1, &K1p);   // K1 = 1.57090271, K1p = 5.49361753
+    printf("K,  Kp      : %12.8f, %12.8f\n", K,  Kp);
+    printf("K1, K1p     : %12.8f, %12.8f\n", K1, K1p);
+
+    float Nexact = (K1p/K1)/(Kp/K); // 4.69604063
+    float N = ceilf(Nexact);        // 5
+    printf("N (exact)   : %12.8f\n", Nexact);
+    printf("N           : %12.8f\n", N);
+
+    k = ellipdegf(N,k1,n);      // 0.91427171
+    printf("k           : %12.8f\n", k);
+
+    float fs_new = fp/k;        // 4.37506723
+    printf("fs_new      : %12.8f\n", fs_new);
+
+    unsigned int L = (unsigned int)(floorf(N/2.0f)); // 2
+    //unsigned int r = ((unsigned int)N) % 2;
+    float u[L];
+    unsigned int i;
+    for (i=0; i<L; i++) {
+        float t = (float)i + 1.0f;
+        u[i] = (2.0f*t - 1.0f)/N;
+        printf("u[%3u]      : %12.8f\n", i, u[i]);
+    }
+    float complex zeta[L];
+    for (i=0; i<L; i++) {
+        zeta[i] = ellip_cdf(u[i],k,n);
+        printf("zeta[%3u]   : %12.8f + j*%12.8f\n", i, crealf(zeta[i]), cimagf(zeta[i]));
+    }
+
+    // compute filter zeros
+    float complex za[L];
+    for (i=0; i<L; i++) {
+        za[i] = _Complex_I * Wp / (k*zeta[i]);
+        printf("za[%3u]     : %12.8f + j*%12.8f\n", i, crealf(za[i]), cimagf(za[i]));
+    }
+
+    float complex v0 = -_Complex_I*ellip_asnf(_Complex_I/ep, k1, n)/N;
+    printf("v0          : %12.8f + j*%12.8f\n", crealf(v0), cimagf(v0));
+
+    float complex pa[L];
+    for (i=0; i<L; i++) {
+        pa[i] = Wp*_Complex_I*ellip_cdf(u[i]-_Complex_I*v0, k, n);
+        printf("pa[%3u]     : %12.8f + j*%12.8f\n", i, crealf(pa[i]), cimagf(pa[i]));
+    }
+    float complex pa0 = Wp * _Complex_I*ellip_snf(_Complex_I*v0, k, n);
+    printf("pa0         : %12.8f + j*%12.8f\n", crealf(pa0), cimagf(pa0));
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/eqlms_cccf_test.c b/sandbox/eqlms_cccf_test.c
new file mode 100644
index 0000000..0f5cad9
--- /dev/null
+++ b/sandbox/eqlms_cccf_test.c
@@ -0,0 +1,208 @@
+// 
+// eqlms_cccf_test.c
+//
+// Tests least mean-squares (LMS) equalizer (EQ).
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "eqlms_cccf_test.m"
+
+int main() {
+    // options
+    unsigned int k=2;       // samples/symbol
+    unsigned int m=3;
+    float beta = 0.3f;
+    //float dt = 0.4f;        // timing offset
+    unsigned int num_symbols = 256;
+    unsigned int hc_len=1;  // channel impulse response length
+    unsigned int p=13;      // equalizer filter length
+    float mu = 0.05f;
+
+    // TODO : validate input
+
+    // derived values
+    unsigned int num_samples = k*num_symbols;
+
+    // data arrays
+    float complex s[num_symbols];   // original QPSK symbols
+    float complex x[num_samples];   // interpolated samples
+    float complex y[num_samples];   // received samples
+    float complex z[num_samples];   // recovered samples
+
+    // generate data sequence
+    unsigned int i;
+    for (i=0; i<num_symbols; i++) {
+        s[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f) * _Complex_I;
+    }
+
+    // interpolate 
+    unsigned int h_len = 2*k*m+1;
+    float hm[h_len];
+#if 1
+    // design GMSK
+    float c0 = 1.0f / sqrtf(logf(2.0f));
+    for (i=0; i<h_len; i++) {
+        float t = (float)(i) / (float)(k) - (float)(m);
+        //float sig = 0.7f;
+        //hm[i] = expf(-t*t / (2*sig*sig) );
+
+        hm[i] = liquid_Qf(2*M_PI*beta*(t-0.5f)*c0) -
+                liquid_Qf(2*M_PI*beta*(t+0.5f)*c0);
+        printf("h(%3u) = %12.8f\n", i+1, hm[i]);
+    }
+#else
+    design_rrc_filter(k,m,beta,dt,hm);
+#endif
+    firinterp_crcf interp = firinterp_crcf_create(k,hm,h_len);
+    for (i=0; i<num_symbols; i++)
+        firinterp_crcf_execute(interp, s[i], &x[i*k]);
+    firinterp_crcf_destroy(interp);
+
+    // generate channel filter
+    float complex hc[hc_len];
+    for (i=0; i<hc_len; i++)
+        hc[i] = (i==0) ? 1.0f : 0.1f*randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+    // push signal through channel...
+    firfilt_cccf fchannel = firfilt_cccf_create(hc,hc_len);
+    for (i=0; i<num_samples; i++) {
+        firfilt_cccf_push(fchannel, x[i]);
+        firfilt_cccf_execute(fchannel, &y[i]);
+    }
+    firfilt_cccf_destroy(fchannel);
+    
+    // initialize equalizer
+#if 1
+    // initialize with delta at center
+    float complex h[p]; // coefficients
+    unsigned int p0 = (p-1)/2;  // center index
+    for (i=0; i<p; i++)
+        h[i] = (i==p0) ? 1.0f : 0.0f;
+#else
+    // initialize with square-root raised cosine
+    p = 2*k*m+1;
+    float h_tmp[p];
+    float complex h[p];
+    design_rrc_filter(k,m,beta,0.0f,h_tmp);
+    for (i=0; i<p; i++)
+        h[i] = h_tmp[i] / k;
+#endif
+    
+    // run equalizer
+    float complex w0[p];
+    float complex w1[p];
+    memmove(w0, h, p*sizeof(float complex));
+    windowcf buffer = windowcf_create(p);
+    for (i=0; i<num_samples; i++) {
+        // push value into buffer
+        windowcf_push(buffer, y[i]);
+
+        // compute d_hat
+        float complex d_hat = 0.0f;
+        float complex * r;
+        windowcf_read(buffer, &r);
+        unsigned int j;
+        for (j=0; j<p; j++)
+            d_hat += conjf(w0[j]) * r[j];
+
+        // store in output
+        z[i] = d_hat;
+
+        // check to see if buffer is full, return if not
+        if (i <= p) continue;
+
+        // decimate by k
+        if ( (i%k) != 0 ) continue;
+
+        // estimate transmitted QPSK symbol
+        float complex d_prime = (crealf(d_hat) > 0.0f ? 1.0f : -1.0f) +
+                                (cimagf(d_hat) > 0.0f ? 1.0f : -1.0f) * _Complex_I;
+
+        // compute error
+        float complex alpha = d_prime - d_hat;
+
+        // compute signal energy
+        float ex2 = 1.414f;
+
+        // update weighting vector
+        for (j=0; j<p; j++)
+            w1[j] = w0[j] + mu*conjf(alpha)*r[j]/ex2;
+
+        // copy new filter values
+        memmove(w0, w1, p*sizeof(complex float));
+    }
+
+    // destroy additional objects
+    windowcf_destroy(buffer);
+
+    // print results to file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_symbols*k;\n");
+
+    // save samples
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    fprintf(fid,"z = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(z[i]), cimagf(z[i]));
+    }
+    fprintf(fid,"t = 1:num_samples;\n");
+    fprintf(fid,"tsym = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1), plot(t, real(z), tsym, real(z(tsym)),'x');\n");
+    fprintf(fid,"subplot(2,1,2), plot(t, imag(z), tsym, imag(z(tsym)),'x');\n");
+
+    // plot symbols
+    fprintf(fid,"tsym0 = tsym([1:length(tsym)/2]);\n");
+    fprintf(fid,"tsym1 = tsym((length(tsym)/2):end);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(z(tsym0)),imag(z(tsym0)),'x','Color',[0.7 0.7 0.7],...\n");
+    fprintf(fid,"     real(z(tsym1)),imag(z(tsym1)),'x','Color',[0.0 0.0 0.0]);\n");
+    fprintf(fid,"xlabel('In-phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    // save filter coefficients
+    for (i=0; i<h_len; i++) fprintf(fid,"hm(%3u) = %12.4e;\n", i+1, hm[i]);
+    for (i=0; i<p; i++)     fprintf(fid,"hp(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(w0[i]), cimagf(w0[i]));
+    for (i=0; i<hc_len; i++)fprintf(fid,"hc(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(hc[i]), cimagf(hc[i]));
+
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Hm = 20*log10(abs(fftshift(fft(hm/k,nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc,nfft))));\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp,nfft))));\n");
+    fprintf(fid,"G = Hm + Hp + Hc;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Hm, f,Hc, f,Hp, f,G,'-k','LineWidth',2);\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density');\n");
+    fprintf(fid,"legend('transmit','channel','equalizer','total');\n");
+    fprintf(fid,"axis([-0.5 0.5 -10 10]);\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/fct_test.c b/sandbox/fct_test.c
new file mode 100644
index 0000000..b17d2c7
--- /dev/null
+++ b/sandbox/fct_test.c
@@ -0,0 +1,75 @@
+//
+// (Fast) discrete cosine transforms and equivalent FFTs
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.h"
+
+int main() {
+    unsigned int n=16;
+    float x[n]; // time-domain 'signal'
+    float y[n]; // fft(x)
+    float z[n]; // ifft(y)
+
+    unsigned int i;
+    for (i=0; i<n; i++)
+        x[i] = (float)i;
+
+    // execute dcts
+    dct(x,y,n);
+    idct(y,z,n);
+
+    // run equivalent ffts
+    float complex x0[2*n];
+    float complex y0[2*n];
+    float complex y1[2*n];
+    float complex z0[2*n];
+
+    // create the plan
+    int method = 0;
+    for (i=0; i<n; i++) {
+        x0[i] = x0[2*n-i-1] = x[i];
+    }
+    fftplan pf = fft_create_plan(2*n, x0, y0, LIQUID_FFT_FORWARD,  method);
+    fftplan pr = fft_create_plan(2*n, y1, z0, LIQUID_FFT_BACKWARD, method);
+
+    // execute forward plan and scale appropriately
+    fft_execute(pf);
+    for (i=0; i<2*n; i++)
+        y0[i] *= 0.5f*cexpf(-_Complex_I*M_PI*i/(float)(2*n));
+
+    // scale reverse plan appropriately and execute
+    for (i=0; i<2*n; i++)
+        y1[i] = y0[i]*cexpf(_Complex_I*M_PI*i/(float)(2*n)) / n;
+    fft_execute(pr);
+    
+    // destroy fft plans
+    fft_destroy_plan(pf);
+    fft_destroy_plan(pr);
+
+    // normalize inverse
+    for (i=0; i<n; i++)
+        z[i] *= 2.0f / (float) n;
+
+    // print results
+    printf("----------------\n");
+    printf("original signal, x[n]:\n");
+    for (i=0; i<n; i++)
+        printf("  x[%3u] = %8.4f\n", i, x[i]);
+
+    printf("----------------\n");
+    printf("  y[n]   = dct(x)[n]   : fft( [x fliplr(x)] ).*(1/2)*exp(-j*pi*[0:n-1]/n)\n");
+    for (i=0; i<n; i++)
+        printf("  y[%3u] = %8.4f    : %12.8f + j*%12.8f\n", i, y[i], crealf(y0[i]), cimagf(y0[i]));
+
+    printf("----------------\n");
+    printf("  z[n]   = idct(x)[n]  : ifft( [y fliplr(y)] ).*(1/n)*exp(j*pi*[0:n-1]/n)\n");
+    for (i=0; i<n; i++)
+        printf("  z[%3u] = %8.4f    : %12.8f + j*%12.8f\n", i, z[i], crealf(z0[i]), cimagf(z0[i]));
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/fec_g2412product_test.c b/sandbox/fec_g2412product_test.c
new file mode 100644
index 0000000..9807022
--- /dev/null
+++ b/sandbox/fec_g2412product_test.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Golay(24,12) product code test
+//
+// rate 1/4 product code:
+// input    : 12 x 12 bits = 144 bits = 18 bytes
+// output   : 24 x 24 bits = 576 bits = 72 bytes
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG (0)
+
+typedef struct g2412p_s * g2412p;
+g2412p g2412p_create  ();
+void   g2412p_destroy (g2412p _q);
+void   g2412p_print   (g2412p _q);
+void   g2412p_encode  (g2412p _q, unsigned char * _msg_org, unsigned char * _msg_enc);
+void   g2412p_decode  (g2412p _q, unsigned char * _msg_rec, unsigned char * _msg_dec);
+
+int    g2412p_iterate (g2412p _q);
+int    g2412p_step    (g2412p _q);
+
+void   g2412p_load_row(g2412p _q, unsigned int _row);
+void   g2412p_save_row(g2412p _q, unsigned int _row);
+void   g2412p_load_col(g2412p _q, unsigned int _col);
+void   g2412p_save_col(g2412p _q, unsigned int _col);
+
+// pack 24-value soft-bit message into integer
+unsigned int g2412p_pack_symbol(unsigned char * _msg_soft);
+
+// unpack 24-bit integer into soft array
+void g2412p_unpack_symbol(unsigned int    _msg_hard,
+                          unsigned char * _msg_soft);
+
+unsigned int liquid_hard_decision(unsigned char _soft_bit);
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    printf("    ");
+    for (i=0; i<_n; i++)
+        printf("%1u ", (_x >> (_n-i-1)) & 1);
+    printf("\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int i;
+    unsigned int j;
+
+    unsigned char msg_org[144];
+    unsigned char msg_enc[576];
+    unsigned char msg_rec[576];
+    unsigned char msg_dec[144];
+
+    // create object to handle encoding/decoding
+    g2412p q = g2412p_create();
+
+    // initialize input array
+    unsigned char p = 0x01;
+    for (i=0; i<144; i++) {
+        //msg_org[i] = i % 2;
+
+        msg_org[i] = p & 1;
+        p = ((p << 1) & 0xfe) | liquid_bdotprod(p,0xad);
+        
+        msg_org[i] = 0;
+    }
+
+#if DEBUG
+    // print original message
+    printf("msg_org:\n");
+    for (i=0; i<12; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<12; j++)
+            printf("%2u", msg_org[12*i+j]);
+        printf("\n");
+    }
+#endif
+
+    // encode message
+    g2412p_encode(q, msg_org, msg_enc);
+
+#if DEBUG
+    // print encoded message
+    printf("msg_enc:\n");
+    for (i=0; i<24; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<24; j++)
+            printf("%2u", msg_enc[24*i+j]);
+        printf("\n");
+    }
+#endif
+
+#if 0
+    // corrupt message
+    for (i=0; i<576; i++)
+        msg_rec[i] = msg_enc[i] ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+    msg_rec[  0] = 255 - msg_rec[  0];
+    msg_rec[ 24] = 255 - msg_rec[ 24];
+    msg_rec[ 48] = 255 - msg_rec[ 48];
+    msg_rec[ 72] = 255 - msg_rec[ 72];
+    msg_rec[ 96] = 255 - msg_rec[ 96];
+    msg_rec[120] = 255 - msg_rec[120];
+#else
+    // modulate, add noise, demodulate soft
+    float SNRdB = -4.0f;
+    float nstd  = powf(10.0f, -SNRdB/20.0f);
+    modem mod = modem_create(LIQUID_MODEM_BPSK);
+    for (i=0; i<576; i++) {
+        float complex v;
+        unsigned int  s;
+        modem_modulate(mod, msg_enc[i], &v);
+        v += nstd*(randnf() + randnf()*_Complex_I) * M_SQRT1_2;
+        modem_demodulate_soft(mod, v, &s, &msg_rec[i]);
+    }
+    modem_destroy(mod);
+#endif
+
+    // decode message
+    g2412p_decode(q, msg_rec, msg_dec);
+
+    // print decoded message
+    printf("msg_org:\n");
+    for (i=0; i<12; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<12; j++)
+            printf("%2u", msg_dec[12*i+j]);
+        printf("\n");
+    }
+
+    // count errors
+    unsigned int num_errors = 0;
+    for (i=0; i<144; i++)
+        num_errors += (msg_dec[i] & 1) == (msg_org[i] & 1) ? 0 : 1;
+    printf("bit errors: %u / %u\n", num_errors, 144);
+    
+    // clean up allocated objects
+    g2412p_destroy(q);
+
+    printf("done\n");
+    return 0;
+}
+
+struct g2412p_s {
+    unsigned char msg_buf[576]; // 24 x 24 message array
+    unsigned char reg0[24];     // 24 value register
+    unsigned int  r;
+    unsigned int  d;
+};
+
+g2412p g2412p_create()
+{
+    g2412p q = (g2412p) malloc( sizeof(struct g2412p_s) );
+
+    return q;
+}
+
+void g2412p_destroy(g2412p _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+void g2412p_print(g2412p _q)
+{
+    unsigned int i;
+#if 1
+    unsigned int j;
+    printf("msg_buf:\n");
+    for (i=0; i<24; i++) {
+        if (i==12)
+            printf("------\n");
+        printf("%2u:", i);
+        for (j=0; j<24; j++)
+            printf(" %s%3u", j==12 ? ":" : "", _q->msg_buf[24*i+j]);
+        printf("\n");
+    }
+#else
+    for (i=0; i<576; i++)
+        printf(" %3u%s", _q->msg_buf[i], ((i+1)%24)==0 ? "\n" : "");
+#endif
+}
+
+void g2412p_encode(g2412p          _q,
+                   unsigned char * _msg_org,
+                   unsigned char * _msg_enc)
+{
+    unsigned int r;
+    unsigned int c;
+
+    // encode rows
+#if DEBUG
+    printf("rows:\n");
+#endif
+    for (r=0; r<12; r++) {
+        unsigned int sym_org = 0;
+        for (c=0; c<12; c++) {
+            sym_org <<= 1;
+            sym_org |= _msg_org[12*r+c] ? 1 : 0;
+        }
+        unsigned int sym_enc = fec_golay2412_encode_symbol(sym_org);
+        // make systematic (swap upper and lower 12 bit chunks)
+        sym_enc = ((sym_enc >> 12) & 0xfff) | ((sym_enc << 12) & 0xfff000);
+#if DEBUG
+        printf(" %2u: 0x%.3x > 0x%.6x\n", r, sym_org, sym_enc);
+#endif
+        
+        //
+        for (c=0; c<24; c++) {
+            unsigned int bit = (sym_enc >> (24-c-1)) & 1;
+            _msg_enc[24*r+c] = bit;
+        }
+    }
+
+    // encode columns
+#if DEBUG
+    printf("cols:\n");
+#endif
+    for (c=0; c<24; c++) {
+        unsigned int sym_org = 0;
+        for (r=0; r<12; r++) {
+            sym_org <<= 1;
+            sym_org |= _msg_enc[c+24*r] ? 1 : 0;
+        }
+        unsigned int sym_enc = fec_golay2412_encode_symbol(sym_org);
+        // make systematic (swap upper and lower 12 bit chunks)
+        sym_enc = ((sym_enc >> 12) & 0xfff) | ((sym_enc << 12) & 0xfff000);
+#if DEBUG
+        printf(" %2u: 0x%.3x > 0x%.6x\n", c, sym_org, sym_enc);
+#endif
+        
+        // append only parity bits (no need to overwrite original bits)
+        for (r=12; r<24; r++) {
+            unsigned int bit = (sym_enc >> (24-r-1)) & 1;
+            _msg_enc[c+24*r] = bit;
+        }
+    }
+
+}
+
+void g2412p_decode(g2412p          _q,
+                   unsigned char * _msg_rec,
+                   unsigned char * _msg_dec)
+{
+    unsigned int i;
+
+    // copy received message to internal buffer
+    memmove(_q->msg_buf, _msg_rec, 576*sizeof(unsigned char));
+
+    // print
+#if DEBUG
+    g2412p_print(_q);
+#endif
+
+#if 1
+    // iterate
+    // while...
+    for (i=0; i<30; i++) {
+        int rc = g2412p_iterate(_q);
+
+        if (rc == 0)
+            break;
+    }
+    printf("steps: %u\n", i);
+#endif
+    
+    // print
+#if DEBUG
+    g2412p_print(_q);
+#endif
+
+    // copy resulting output
+#if 0
+    for (i=0; i<12; i++)
+        memmove(&_msg_dec[24*i], &_q->msg_buf[12*i], 12*sizeof(unsigned char));
+#else
+    unsigned int r;
+    unsigned int c;
+    unsigned int n = 0;
+    for (r=0; r<12; r++) {
+        for (c=0; c<12; c++) {
+            _msg_dec[n++] = _q->msg_buf[24*r+c] > LIQUID_SOFTBIT_ERASURE ? 1 : 0;
+        }
+    }
+#endif
+}
+
+// run single iteration
+int g2412p_iterate(g2412p _q)
+{
+    unsigned int i;
+
+    int rc = 0;
+
+    // decode columns
+    for (i=0; i<24; i++) {
+        g2412p_load_col     (_q, i);
+        rc += g2412p_step   (_q   );
+        g2412p_save_col     (_q, i);
+    }
+
+    // decode rows
+    for (i=0; i<12; i++) {
+        g2412p_load_row     (_q, i);
+        rc += g2412p_step   (_q   );
+        g2412p_save_row     (_q, i);
+    }
+
+    return rc;
+}
+
+// 
+int g2412p_step(g2412p _q)
+{
+    // TODO: determine if parity check passed
+
+    // run hard-decision decoding
+    unsigned int sym_rec = 0;
+    unsigned int i;
+    for (i=0; i<24; i++) {
+        sym_rec <<= 1;
+        sym_rec |= _q->reg0[i] > 127 ? 1 : 0;
+    }
+
+    // flip...
+    //printf("  0x%.6x > ", sym_rec);
+    sym_rec = ((sym_rec >> 12) & 0xfff) | ((sym_rec << 12) & 0xfff000);
+
+    // decode to 12-bit symbol
+    unsigned int sym_dec = fec_golay2412_decode_symbol(sym_rec);
+
+    // re-encode 24-bit symbol
+    unsigned int sym_enc = fec_golay2412_encode_symbol(sym_dec);
+
+    int rc = (sym_rec == sym_enc) ? 0 : 1;
+
+    // flip...
+    sym_enc = ((sym_enc >> 12) & 0xfff) | ((sym_enc << 12) & 0xfff000);
+    //printf("0x%.6x\n", sym_enc);
+
+    // update register
+    // TODO: this is the crux of the algorithm and should be adjusted to
+    //       produce maximum performance.
+    float alpha = 0.9f;         // percentage of old value to retain
+    float beta  = 1.0f - alpha; // percentage of new value to retain
+    for (i=0; i<24; i++) {
+        //int v = (int)(_q->reg0[i]) + ((sym_enc >> i) & 1 ? 16 : -16);
+        int p = (sym_enc >> (24-i-1)) & 1 ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+        int v = ((int)(alpha*_q->reg0[i]) + beta*p);
+
+        if      (v <   0) _q->reg0[i] = 0;
+        else if (v > 255) _q->reg0[i] = 255;
+        else              _q->reg0[i] = (unsigned char)v;
+    }
+
+    return rc;
+}
+
+// load 24-bit row into register
+void g2412p_load_row(g2412p       _q,
+                     unsigned int _row)
+{
+    //memmove(_q->reg0, &_q->msg_buf[_row*24], 24*sizeof(unsigned char));
+    unsigned int c;
+    for (c=0; c<24; c++)
+        _q->reg0[c] = _q->msg_buf[24*_row + c];
+}
+
+void g2412p_save_row(g2412p       _q,
+                     unsigned int _row)
+{
+    //memmove(&_q->msg_buf[_row*24], _q->reg0, 24*sizeof(unsigned char));
+    unsigned int c;
+    for (c=0; c<24; c++)
+        _q->msg_buf[24*_row + c] = _q->reg0[c];
+}
+
+void g2412p_load_col(g2412p       _q,
+                     unsigned int _col)
+{
+    unsigned int r;
+    for (r=0; r<24; r++)
+        _q->reg0[r] = _q->msg_buf[24*r + _col];
+}
+
+void g2412p_save_col(g2412p       _q,
+                     unsigned int _col)
+{
+    unsigned int r;
+    for (r=0; r<24; r++)
+        _q->msg_buf[24*r + _col] = _q->reg0[r];
+}
+
+// pack 24-value soft-bit message into integer
+unsigned int g2412p_pack_symbol(unsigned char * _msg_soft)
+{
+    unsigned int i;
+    unsigned int sym = 0;
+    for (i=0; i<24; i++) {
+        sym <<= 1;
+        sym |= liquid_hard_decision(_msg_soft[i]);
+    }
+
+    return sym;
+}
+
+// unpack 24-bit integer into soft array
+void g2412p_unpack_symbol(unsigned int    _msg_hard,
+                          unsigned char * _msg_soft)
+{
+    unsigned int i;
+    for (i=0; i<24; i++) {
+        _msg_soft[i] = _msg_hard & 1 ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+        _msg_hard >>= 1;
+    }
+}
+
+unsigned int liquid_hard_decision(unsigned char _soft_bit)
+{
+    return _soft_bit > LIQUID_SOFTBIT_ERASURE ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+}
diff --git a/sandbox/fec_golay2412_test.c b/sandbox/fec_golay2412_test.c
new file mode 100644
index 0000000..be89a98
--- /dev/null
+++ b/sandbox/fec_golay2412_test.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//  Golay(24,12) code test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_GOLAY 1
+
+// P matrix [12 x 12]
+unsigned char P[144] = {
+    1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0,  1,
+    0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1,  1,
+    0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0,  1,
+    0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0,  1,
+    1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0,  1,
+    1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1,  1,
+    1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1,  1,
+    0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1,  1,
+    1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0,  1,
+    1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1,  1,
+    0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1,  1,
+
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  0};
+
+// generator matrix [12 x 24]
+unsigned char G[288];
+void golay_init_G();
+
+// parity check matrix [12 x 24]
+unsigned char H[288];
+void golay_init_H();
+
+unsigned int fec_golay2412_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<11)) {
+        fprintf(stderr,"error, fec_golay2412_encode(), input symbol too large\n");
+        exit(1);
+    }
+    return 0;
+}
+
+unsigned int fec_golay2412_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<15)) {
+        fprintf(stderr,"error, fec_golay2412_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    return 0;
+}
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+void matrix2_print(unsigned char * _x,
+                   unsigned int _m,
+                   unsigned int _n)
+{
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_m; i++) {
+        printf("    ");
+        for (j=0; j<_n; j++)
+            printf("%c ", _x[i*_n+j] ? '1' : '0');
+        printf("\n");
+    }
+}
+
+// multiply two matrices modulo 2
+void matrix2_mul(unsigned char * _x, unsigned int _mx, unsigned int _nx,
+                 unsigned char * _y, unsigned int _my, unsigned int _ny,
+                 unsigned char * _z, unsigned int _mz, unsigned int _nz)
+{
+    // ensure lengths are valid
+    if (_mz != _mx || _nz != _ny || _nx != _my ) {
+        fprintf(stderr,"error: matrix2_mul(), invalid dimensions\n");
+        exit(1);
+    }
+
+    unsigned int r, c, i;
+    for (r=0; r<_mz; r++) {
+        for (c=0; c<_nz; c++) {
+            unsigned int sum = 0;
+            for (i=0; i<_nx; i++) {
+                sum += matrix_access(_x,_mx,_nx,r,i) *
+                       matrix_access(_y,_my,_ny,i,c);
+            }
+            matrix_access(_z,_mz,_nz,r,c) = sum % 2;
+        }
+    }
+}
+
+unsigned int vector_weight(unsigned char * _x,
+                           unsigned int _n)
+{
+    unsigned int w = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        w += _x[i] ? 1 : 0;
+    return w;
+}
+
+int main(int argc, char*argv[])
+{
+    // initialize matrices
+    golay_init_G();
+    golay_init_H();
+
+    printf("P:\n");
+    matrix2_print(P,12,12);
+
+    printf("G:\n");
+    matrix2_print(G,12,24);
+
+    printf("H:\n");
+    matrix2_print(H,12,24);
+
+    unsigned int i;
+    
+    // error vector
+    unsigned char err[24] = {
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0};
+
+    // arrays
+    unsigned char m[12];    // original message
+    unsigned char v[24];    // encoded/transmitted message
+    unsigned char e[24];    // error vector
+    unsigned char r[24];    // received vector
+    unsigned char s[12];    // syndrome vector
+    unsigned char e_hat[24];// estimated error vector
+    unsigned char v_hat[24];// estimated transmitted message
+    unsigned char m_hat[24];// estimated original message
+
+    // compute (random) original message
+    for (i=0; i<12; i++)
+        m[i] = rand() % 2;
+    printf("m (original message):\n");
+    matrix2_print(m,1,12);
+
+    // compute encoded/transmitted message: v = m*G
+    matrix2_mul(m,1,12, G,12,24, v,1,24);
+    printf("v (encoded/transmitted message):\n");
+    matrix2_print(v,1,24);
+
+#if 0
+    // generate random error vector
+    unsigned int e0 = rand() % 24;
+    for (i=0; i<24; i++) e[i] = (i==e0) ? 1 : 0;
+#else
+    // use pre-determined error vector
+    for (i=0; i<24; i++) e[i] = err[i];
+#endif
+    printf("e (error vector):\n");
+    matrix2_print(e,1,24);
+
+#if 0
+    // from Lin & Costello, p. 128,
+    // error locations at indices: 3, 10, 23
+    r[ 0] = 1;    r[ 1] = 0;    r[ 2] = 0;    r[ 3] = 0;
+    r[ 4] = 0;    r[ 5] = 0;    r[ 6] = 1;    r[ 7] = 1;
+    r[ 8] = 0;    r[ 9] = 1;    r[10] = 0;    r[11] = 0;
+    r[12] = 1;    r[13] = 1;    r[14] = 0;    r[15] = 0;
+    r[16] = 0;    r[17] = 0;    r[18] = 0;    r[19] = 0;
+    r[20] = 0;    r[21] = 0;    r[22] = 0;    r[23] = 1;
+#else
+    // compute received vector: r = v + e
+    for (i=0; i<24; i++) r[i] = (v[i] + e[i]) % 2;
+    printf("r (received vector):\n");
+    matrix2_print(r,1,24);
+#endif 
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    matrix2_mul(H,12,24, r,24,1, s,12,1);
+    printf("s (syndrome vector):\n");
+    matrix2_print(s,1,12);
+
+    // compute weight of s
+    unsigned int ws = vector_weight(s,12);
+    printf("w(s) = %u\n", ws);
+
+    // step 2:
+    if (ws <= 3) {
+        printf("    w(s) <= 3: estimating error vector as [s, 0(12)]\n");
+        // set e_hat = [s 0(12)]
+        for (i=0; i<24; i++)
+            e_hat[i] = i < 12 ? s[i] : 0;
+    } else {
+        // step 3: search for p[i] s.t. w(s+p[i]) <= 2
+        printf("    searching for w(s + p_i) <= 2...\n");
+        unsigned int j;
+        unsigned char spj[12];
+        int flag3 = 0;
+        unsigned int p3_index = 0;
+        for (j=0; j<12; j++) {
+            for (i=0; i<12; i++)
+                spj[i] = (s[i] + P[12*j+i]) % 2;
+            unsigned int wj = vector_weight(spj,12);
+            printf("    w(s + p[%2u]) = %2u%s\n", j, wj, wj <= 2 ? " *" : "");
+            if (wj <= 2) {
+                flag3 = 1;
+                p3_index = j;
+                break;
+            }
+        }
+
+        if (flag3) {
+            // vector found!
+            printf("    w(s + p[%2u]) <= 2: estimating error vector as [s+p[%2u],u[%2u]]\n", p3_index, p3_index, p3_index);
+            for (i=0; i<12; i++)
+                e_hat[i] = (s[i] + P[12*p3_index + i]) % 2;
+            for (i=0; i<12; i++)
+                e_hat[i+12] = (i == p3_index) ? 1 : 0;
+
+        } else {
+            // step 4: compute s*P
+            unsigned char sP[12];
+            matrix2_mul(s,1,12, P,12,12, sP,1,12);
+            printf("s*P:\n");
+            matrix2_print(sP,1,12);
+
+            unsigned int wsP = vector_weight(sP,12);
+            printf("w(s*P) = %u\n", wsP);
+
+            if (wsP == 2 || wsP == 3) {
+                // step 5: set e = [0, s*P]
+                printf("    w(s*P) in [2,3]: estimating error vector as [0(12), s*P]\n");
+                for (i=0; i<24; i++)
+                    e_hat[i] = i < 12 ? 0 : sP[i-12];
+            } else {
+                // step 6: search for p[i] s.t. w(s*P + p[i]) == 2...
+
+                printf("    searching for w(s*P + p_i) == 2...\n");
+                unsigned int j;
+                unsigned char sPpj[12];
+                int flag6 = 0;
+                unsigned int p6_index = 0;
+                for (j=0; j<12; j++) {
+                    for (i=0; i<12; i++)
+                        sPpj[i] = (sP[i] + P[12*j + i]) % 2;
+                    unsigned int wj = vector_weight(sPpj,12);
+                    printf("    w(s*P + p[%2u]) = %2u%s\n", j, wj, wj == 2 ? " *" : "");
+                    if (wj == 2) {
+                        flag6 = 1;
+                        p6_index = j;
+                        break;
+                    }
+                }
+
+                if (flag6) {
+                    // vector found!
+                    printf("    w(s*P + p[%2u]) == 2: estimating error vector as [u[%2u],s*P+p[%2u]]\n", p6_index, p6_index, p6_index);
+                    for (i=0; i<12; i++)
+                        e_hat[i] = (i == p6_index) ? 1 : 0;
+                    for (i=0; i<12; i++)
+                        e_hat[i+12] = (sP[i] + P[12*p6_index+i]) % 2;
+                } else {
+                    // step 7: decoding error
+                    printf("  **** decoding error\n");
+                }
+            }
+        }
+    }
+
+    // step 8: compute estimated transmitted message: v_hat = r + e_hat
+    printf("e-hat (estimated error vector):\n");
+    matrix2_print(e_hat,1,24);
+    for (i=0; i<24; i++) v_hat[i] = (r[i] + e_hat[i]) % 2;
+    printf("v-hat (estimated transmitted vector):\n");
+    matrix2_print(v_hat,1,24);
+    matrix2_print(v,    1,24);
+
+    // compute errors between v, v_hat
+    unsigned int num_errors_v = 0;
+    for (i=0; i<24; i++)
+        num_errors_v += v[i] == v_hat[i] ? 0 : 1;
+    printf("\n");
+    printf("decoding errors (encoded)  : %2u / 24\n", num_errors_v);
+
+    // compute estimated original message: (last 12 bits of encoded message)
+    for (i=0; i<12; i++)
+        m_hat[i] = v_hat[i+12];
+
+    // compute errors between m, m_hat
+    unsigned int num_errors_m = 0;
+    for (i=0; i<12; i++)
+        num_errors_m += m[i] == m_hat[i] ? 0 : 1;
+    printf("decoding errors (original) : %2u / 12\n", num_errors_m);
+
+    return 0;
+}
+
+// initialize generator matrix as G = [P I_12]
+void golay_init_G()
+{
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<12; i++) {
+        for (j=0; j<12; j++)
+            G[i*24 + j] = P[i*12 + j];
+
+        for (j=0; j<12; j++)
+            G[i*24 + j + 12] = (i==j) ? 1 : 0;
+    }
+}
+
+// initialize generator matrix as H = [I_12 P]
+void golay_init_H()
+{
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<12; i++) {
+        for (j=0; j<12; j++)
+            H[i*24+j] = (i==j) ? 1 : 0;
+
+        for (j=0; j<12; j++)
+            H[i*24 + j + 12] = P[i*12 + j];
+    }
+}
+
diff --git a/sandbox/fec_golay_test.c b/sandbox/fec_golay_test.c
new file mode 100644
index 0000000..bf0db08
--- /dev/null
+++ b/sandbox/fec_golay_test.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//  Golay(24,12) code test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_GOLAY 1
+
+// P matrix [12 x 12]
+unsigned int P[12] = {
+    0x08ed, 0x01db, 0x03b5, 0x0769,
+    0x0ed1, 0x0da3, 0x0b47, 0x068f,
+    0x0d1d, 0x0a3b, 0x0477, 0x0ffe};
+
+#if 0
+// generator matrix [12 x 24]
+unsigned int G[12] = {
+    0x008ed800, 0x001db400, 0x003b5200, 0x00769100,
+    0x00ed1080, 0x00da3040, 0x00b47020, 0x0068f010,
+    0x00d1d008, 0x00a3b004, 0x00477002, 0x00ffe001};
+#endif
+
+// generator matrix transposed [24 x 12]
+unsigned int Gt[24] = {
+    0x08ed, 0x01db, 0x03b5, 0x0769, 0x0ed1, 0x0da3, 0x0b47, 0x068f, 
+    0x0d1d, 0x0a3b, 0x0477, 0x0ffe, 0x0800, 0x0400, 0x0200, 0x0100, 
+    0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001};
+
+// parity check matrix [12 x 24]
+unsigned int H[12] = {
+    0x008008ed, 0x004001db, 0x002003b5, 0x00100769,
+    0x00080ed1, 0x00040da3, 0x00020b47, 0x0001068f,
+    0x00008d1d, 0x00004a3b, 0x00002477, 0x00001ffe};
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    printf("    ");
+    for (i=0; i<_n; i++)
+        printf("%1u ", (_x >> (_n-i-1)) & 1);
+    printf("\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int i;
+    
+    // error vector
+    //  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    //  1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0
+    unsigned int err = 0x00000d00;
+
+    // original message
+    // 1 0 1 1 1 1 0 0 1 1 0 1
+    unsigned int m  = 0x0bcd;
+
+    // derived values
+    unsigned int v;     // encoded/transmitted message
+    unsigned int e;     // error vector
+    unsigned int r;     // received vector
+    unsigned int s;     // syndrome vector
+    unsigned int e_hat; // estimated error vector
+    unsigned int v_hat; // estimated transmitted message
+    unsigned int m_hat; // estimated original message
+
+    // original message
+    printf("m (original message):\n");
+    print_bitstring(m,12);
+
+    // compute encoded/transmitted message: v = m*G
+    v = 0;
+    for (i=0; i<24; i++) {
+        v <<= 1;
+        v |= liquid_count_ones_mod2(Gt[i] & m);
+    }
+    printf("v (encoded/transmitted message):\n");
+    print_bitstring(v,24);
+
+    // use pre-determined error vector
+    e = err;
+    printf("e (error vector):\n");
+    print_bitstring(e,24);
+
+    // compute received vector: r = v + e
+    r = v ^ e;
+    printf("r (received vector):\n");
+    print_bitstring(r,24);
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    s = 0;
+    for (i=0; i<12; i++) {
+        s <<= 1;
+        s |= liquid_count_ones_mod2(H[i] & r);
+    }
+    printf("s (syndrome vector):\n");
+    print_bitstring(s,12);
+
+    // compute weight of s
+    unsigned int ws = liquid_count_ones(s);
+    printf("w(s) = %u\n", ws);
+
+    // step 2:
+    e_hat = 0;
+    if (ws <= 3) {
+        printf("    w(s) <= 3: estimating error vector as [s, 0(12)]\n");
+        // set e_hat = [s 0(12)]
+        e_hat = s << 12;
+    } else {
+        // step 3: search for p[i] s.t. w(s+p[i]) <= 2
+        printf("    searching for w(s + p_i) <= 2...\n");
+        unsigned int j;
+        unsigned int spj;
+        int flag3 = 0;
+        unsigned int p3_index = 0;
+        for (j=0; j<12; j++) {
+            spj = s ^ P[j];
+            unsigned int wj = liquid_count_ones(spj);
+            printf("    w(s + p[%2u]) = %2u%s\n", j, wj, wj <= 2 ? " *" : "");
+            if (wj <= 2) {
+                flag3 = 1;
+                p3_index = j;
+                break;
+            }
+        }
+
+        if (flag3) {
+            // vector found!
+            printf("    w(s + p[%2u]) <= 2: estimating error vector as [s+p[%2u],u[%2u]]\n", p3_index, p3_index, p3_index);
+            // NOTE : uj = 1 << (12-j-1)
+            e_hat = ((s ^ P[p3_index]) << 12) | (1 << (11-p3_index));
+        } else {
+            // step 4: compute s*P
+            unsigned int sP = 0;
+            for (i=0; i<12; i++) {
+                sP <<= 1;
+                sP |= liquid_count_ones_mod2(s & P[i]);
+            }
+            printf("s*P:\n");
+            print_bitstring(sP,12);
+
+            unsigned int wsP = liquid_count_ones(sP);
+            printf("w(s*P) = %u\n", wsP);
+
+            if (wsP == 2 || wsP == 3) {
+                // step 5: set e = [0, s*P]
+                printf("    w(s*P) in [2,3]: estimating error vector as [0(12), s*P]\n");
+                e_hat = sP;
+            } else {
+                // step 6: search for p[i] s.t. w(s*P + p[i]) == 2...
+
+                printf("    searching for w(s*P + p_i) == 2...\n");
+                unsigned int j;
+                unsigned int sPpj;
+                int flag6 = 0;
+                unsigned int p6_index = 0;
+                for (j=0; j<12; j++) {
+                    sPpj = sP ^ P[j];
+                    unsigned int wj = liquid_count_ones(sPpj);
+                    printf("    w(s*P + p[%2u]) = %2u%s\n", j, wj, wj == 2 ? " *" : "");
+                    if (wj == 2) {
+                        flag6 = 1;
+                        p6_index = j;
+                        break;
+                    }
+                }
+
+                if (flag6) {
+                    // vector found!
+                    printf("    w(s*P + p[%2u]) == 2: estimating error vector as [u[%2u],s*P+p[%2u]]\n", p6_index, p6_index, p6_index);
+                    // NOTE : uj = 1 << (12-j-1)
+                    //      [      uj << 1 2    ] [    sP + p[j]    ]
+                    e_hat = (1 << (23-p6_index)) | (sP ^ P[p6_index]);
+                } else {
+                    // step 7: decoding error
+                    printf("  **** decoding error\n");
+                }
+            }
+        }
+    }
+
+    // step 8: compute estimated transmitted message: v_hat = r + e_hat
+    printf("e-hat (estimated error vector):\n");
+    print_bitstring(e_hat,24);
+    v_hat = r ^ e_hat;
+
+    printf("v-hat (estimated transmitted vector):\n");
+    print_bitstring(v_hat,24);
+    print_bitstring(v,    24);
+
+    // compute errors between v, v_hat
+    printf("decoding errors (encoded)  : %2u / 24\n", count_bit_errors(v, v_hat));
+
+    // compute estimated original message: (last 12 bits of encoded message)
+    m_hat = v_hat & ((1<<12)-1);
+
+    // compute errors between m, m_hat
+    printf("decoding errors (original) : %2u / 12\n", count_bit_errors(m, m_hat));
+
+    return 0;
+}
+
diff --git a/sandbox/fec_hamming128_example.c b/sandbox/fec_hamming128_example.c
new file mode 100644
index 0000000..21e7a52
--- /dev/null
+++ b/sandbox/fec_hamming128_example.c
@@ -0,0 +1,44 @@
+//
+// Hamming(12,8) example
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+int main(int argc, char*argv[])
+{
+    // original symbol
+    unsigned int sym_org = 139;
+
+    // encoded symbol
+    unsigned int sym_enc = fec_hamming128_encode_symbol(sym_org);
+
+    // received symbol
+    unsigned int n=7;  // index of bit to corrupt
+    unsigned int sym_rec = sym_enc ^ (1<<(12-n));
+
+    // decoded symbol
+    unsigned int sym_dec = fec_hamming128_decode_symbol(sym_rec);
+
+    // print results
+    printf("    sym org     :       "); print_bitstring(sym_org,  8); printf("\n");
+    printf("    sym enc     :   ");     print_bitstring(sym_enc, 12); printf("\n");
+    printf("    sym rec     :   ");     print_bitstring(sym_rec, 12); printf("\n");
+    printf("    sym dec     :       "); print_bitstring(sym_dec,  8); printf("\n");
+
+    // print number of bit errors
+    printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+
+    return 0;
+}
+
diff --git a/sandbox/fec_hamming128_gentab.c b/sandbox/fec_hamming128_gentab.c
new file mode 100644
index 0000000..949ae12
--- /dev/null
+++ b/sandbox/fec_hamming128_gentab.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 2/3-rate (12,8) Hamming code encoding table generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+int main()
+{
+    unsigned int i;
+    unsigned int c;
+
+    printf("unsigned short int hamming128_enc_gentab[256] = {\n    ");
+
+    for (i=0; i<256; i++) {
+        // encode symbol
+        c = fec_hamming128_encode_symbol(i);
+
+        // print result
+        printf("0x%.4x", c);
+        if (i != 255)
+            printf(", ");
+        else
+            printf("};");
+
+        if ( ((i+1)%8) == 0)
+            printf("\n    ");
+    }
+    printf("\n");
+
+    return 0;
+}
+
diff --git a/sandbox/fec_hamming128_test.c b/sandbox/fec_hamming128_test.c
new file mode 100644
index 0000000..3edfe64
--- /dev/null
+++ b/sandbox/fec_hamming128_test.c
@@ -0,0 +1,127 @@
+//
+// Hamming(12,8) code test
+//
+// Tests syndrome decoding
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+unsigned char hamming128_P[4] = {
+        0xda,   // 1101 1010
+        0xb6,   // 1011 0110
+        0x71,   // 0111 0001
+        0x0f};  // 0000 1111
+
+int main(int argc, char*argv[])
+{
+    // ...
+    unsigned int sym_org = 0x21;    // original symbol
+    unsigned int sym_enc;           // encoded symbol
+    unsigned int e = 0x001;         // error vector
+    unsigned int sym_rec;           // received symbol
+    unsigned int sym_dec;           // decoded symbol
+    
+    unsigned int i;
+
+    // encode symbol
+    unsigned int p0 = liquid_c_ones[sym_org & hamming128_P[0]] & 0x01;
+    unsigned int p1 = liquid_c_ones[sym_org & hamming128_P[1]] & 0x01;
+    unsigned int p2 = liquid_c_ones[sym_org & hamming128_P[2]] & 0x01;
+    unsigned int p3 = liquid_c_ones[sym_org & hamming128_P[3]] & 0x01;
+    unsigned int parity = (p0 << 3) | (p1 << 2) | (p2 << 1) | (p3 << 0);
+    sym_enc = (parity << 8) | sym_org;
+
+    // received symbol (add error)
+    sym_rec = sym_enc ^ e;
+
+    // 
+    // decode symbol
+    //
+    sym_dec = sym_rec & 0xff;
+    unsigned int e_hat = 0x000;
+    // compute syndrome vector; s = r*H^T = ( H*r^T )^T
+    unsigned int s = 0x0;   // 4 bits resolution
+    for (i=0; i<4; i++) {
+        s <<= 1;
+
+        unsigned int p =
+            ( (sym_rec & (1<<(12-i-1))) ? 1 : 0 ) +
+            liquid_c_ones[sym_rec & hamming128_P[i]];
+
+        s |= p & 0x01;
+    }
+    // compute weight of s
+    unsigned int ws = liquid_count_ones(s);
+    printf("    syndrome    :           "); print_bitstring(s,  4); printf(" (%u)\n", ws);
+
+    if (ws == 0) {
+        printf("no errors detected\n");
+    } else {
+        // estimate error location
+        unsigned int e_test = 0x001;
+        int syndrome_match = 0;
+
+        // TODO : these can be pre-computed
+        unsigned int n;
+        for (n=0; n<12; n++) {
+            // compute syndrome
+            unsigned int s_hat = 0;
+
+            for (i=0; i<4; i++) {
+                s_hat <<= 1;
+
+                unsigned int p =
+                    ( (e_test & (1<<(12-i-1))) ? 1 : 0 ) +
+                    liquid_c_ones[e_test & hamming128_P[i]];
+
+                s_hat |= p & 0x01;
+            }
+
+            // print results
+            //printf("e_test:"); print_bitstring(e_test, 72);
+            printf("    %2u e = ", n);
+            print_bitstring(e_test,12);
+            printf(" s = ");
+            print_bitstring(s_hat,4);
+            if (s == s_hat) printf(" *");
+            printf("\n");
+
+            if (s == s_hat) {
+                e_hat = e_test;
+                syndrome_match = 1;
+            }
+
+            // shift e_test
+            e_test <<= 1;
+        }
+
+        // apply error correction and return
+        if (syndrome_match)
+            sym_dec = (sym_rec ^ e_hat) & 0xff;
+    }
+
+
+    // print results
+    printf("    sym org     :       "); print_bitstring(sym_org,  8); printf("\n");
+    printf("    sym enc     :   ");     print_bitstring(sym_enc, 12); printf("\n");
+    printf("    sym rec     :   ");     print_bitstring(sym_rec, 12); printf("\n");
+    printf("    sym dec     :       "); print_bitstring(sym_dec,  8); printf("\n");
+
+    // print number of bit errors
+    printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+
+    return 0;
+}
+
+
diff --git a/sandbox/fec_hamming3126_example.c b/sandbox/fec_hamming3126_example.c
new file mode 100644
index 0000000..1e1b750
--- /dev/null
+++ b/sandbox/fec_hamming3126_example.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Hamming code parity check matrix
+//
+//  bit position    1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38
+//  encoded bits    P1  P2  1   P4  2   3   4   P8  5   6   7   8   9   10  11  P16 12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  P32 27  28  29  30  31  32
+//
+//  parity bit  P1  x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .
+//  coverage    P2  .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .
+//              P4  .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .
+//              P8  .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .
+//              P16 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   .
+//              P32 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_HAMMING 1
+
+#if 0
+
+//
+// (12,8) Hamming code
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:8)
+//
+// bit position     3   5   6   7   9   10  11  12
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   =   1101 1010
+//  coverage    P2  x   .   x   x   .   x   x   .   =   1011 0110
+//              P4  .   x   x   x   .   .   .   x   =   0111 0001
+//              P8  .   .   .   .   x   x   x   x   =   0000 1111
+#define HAMMING_M1   0xda    // 1101 1010
+#define HAMMING_M2   0xb6    // 1011 0110
+#define HAMMING_M4   0x71    // 0111 0001
+#define HAMMING_M8   0x0f    // 0000 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING_S1   0x0aaa  // .... 1010 1010 1010
+#define HAMMING_S2   0x0666  // .... 0110 0110 0110
+#define HAMMING_S4   0x01e1  // .... 0001 1110 0001
+#define HAMMING_S8   0x001f  // .... 0000 0001 1111
+
+#endif
+
+
+#if 0
+//
+// (15,11) Hamming code
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:11)
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   x   .   x   = .110 1101 0101
+//  coverage    P2  x   .   x   x   .   x   x   .   .   x   x   = .101 1011 0011 
+//              P4  .   x   x   x   .   .   .   x   x   x   x   = .011 1000 1111
+//              P8  .   .   .   .   x   x   x   x   x   x   x   = .000 0111 1111
+
+#define HAMMING_M1   0x0d65     // .110 1101 0101
+#define HAMMING_M2   0x05b3     // .101 1011 0011
+#define HAMMING_M4   0x038f     // .011 1000 1111
+#define HAMMING_M8   0x007f     // .000 0111 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING_S1   0x5555  // .101 0101 0101 0101
+#define HAMMING_S2   0x3333  // .011 0011 0011 0011
+#define HAMMING_S4   0x0f0f  // .000 1111 0000 1111
+#define HAMMING_S8   0x00ff  // .000 0000 1111 1111
+#endif
+
+//
+// (31,26) Hamming code
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8, P16 and only including
+// data bits 1:26)
+//
+//  bit position    3   5   6   7   8   9   10  11  12  13  14  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30
+//                          *               *               *               *               *               *
+//  parity bit  P1  x   x   .   x   x   .   x   .   x   .   x   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   = ..11 0110 1010 1101 0101 0101 0101
+//  coverage    P2  x   .   x   x   .   x   x   .   .   x   x   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   = ..10 1101 1001 1011 0011 0011 0011
+//              P4  .   x   x   x   .   .   .   x   x   x   x   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   = ..01 1100 0111 1000 1111 0000 1111
+//              P8  .   .   .   .   x   x   x   x   x   x   x   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   = ..00 0011 1111 1000 0000 1111 1111
+//              P16 .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   = ..00 0000 0000 0111 1111 1111 1111
+
+#define HAMMING_M1  0x036AD555  //  ..11 0110 1010 1101 0101 0101 0101
+#define HAMMING_M2  0x02D9B333  //  ..10 1101 1001 1011 0011 0011 0011
+#define HAMMING_M4  0x01C78F0F  //  ..01 1100 0111 1000 1111 0000 1111
+#define HAMMING_M8  0x003F80FF  //  ..00 0011 1111 1000 0000 1111 1111
+#define HAMMING_M16 0x00007FFF  //  ..00 0000 0000 0111 1111 1111 1111
+
+//  bit position    1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31
+//                              *               *               *               *               *               *               *
+//  parity bit  P1  x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   = .101 0101 0101 0101 0101 0101 0101 0101
+//  coverage    P2  .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   = .011 0011 0011 0011 0011 0011 0011 0011
+//              P4  .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   = .000 1111 0000 1111 0000 1111 0000 1111
+//              P8  .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   = .000 0000 1111 1111 0000 0000 1111 1111
+//              P16 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   = .000 0000 0000 0000 1111 1111 1111 1111
+//
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see figure, above).
+#define HAMMING_S1  0x55555555  //  .101 0101 0101 0101 0101 0101 0101 0101
+#define HAMMING_S2  0x33333333  //  .011 0011 0011 0011 0011 0011 0011 0011
+#define HAMMING_S4  0x0f0f0f0f  //  .000 1111 0000 1111 0000 1111 0000 1111
+#define HAMMING_S8  0x00ff00ff  //  .000 0000 1111 1111 0000 0000 1111 1111
+#define HAMMING_S16 0x0000ffff  //  .000 0000 0000 0000 1111 1111 1111 1111
+
+
+
+unsigned int fec_hamming_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<26)) {
+        fprintf(stderr,"error, fec_hamming_encode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute parity bits
+    unsigned int p1  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M1);
+    unsigned int p2  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M2);
+    unsigned int p4  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M4);
+    unsigned int p8  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M8);
+    unsigned int p16 = liquid_bdotprod_uint32(_sym_dec, HAMMING_M16);
+
+#if DEBUG_FEC_HAMMING
+    printf("parity bits (p1,p2,p4,p8,p16) : (%1u,%1u,%1u,%1u,%1u)\n", p1, p2, p4, p8, p16);
+#endif
+
+    // encode symbol by inserting parity bits with data bits to
+    // make a 31-bit symbol
+    unsigned int sym_enc = ((_sym_dec & 0x00007fff) << 0) | //  ..00 0000 0000 0111 1111 1111 1111
+                           ((_sym_dec & 0x003F8000) << 1) | //  ..00 0011 1111 1000 0000 0000 0000
+                           ((_sym_dec & 0x01C00000) << 2) | //  ..01 1100 0000 0000 0000 0000 0000
+                           ((_sym_dec & 0x02000000) << 3) | //  ..10 0000 0000 0000 0000 0000 0000
+                           ( p1  << 30 ) |  // 30 = 31 - 1  (position of P1)
+                           ( p2  << 29 ) |  // 29 = 31 - 2  (position of P2)
+                           ( p4  << 27 ) |  // 27 = 31 - 4  (position of P4)
+                           ( p8  << 23 ) |  // 23 = 31 - 8  (position of P8)
+                           ( p16 << 15 );   // 15 = 31 - 16 (position of P16)
+
+    return sym_enc;
+}
+
+unsigned int fec_hamming_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<31)) {
+        fprintf(stderr,"error, fec_hamming_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute syndrome bits
+    unsigned int s1  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S1);
+    unsigned int s2  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S2);
+    unsigned int s4  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S4);
+    unsigned int s8  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S8);
+    unsigned int s16 = liquid_bdotprod_uint32(_sym_enc, HAMMING_S16);
+
+    // index
+    unsigned int z = (s16<<4) | (s8<<3) | (s4<<2) | (s2<<1) | s1;
+
+#if DEBUG_FEC_HAMMING
+    printf("syndrome bits (s1,s2,s4,s8,16) : (%1u,%1u,%1u,%1u,%1u)\n", s1, s2, s4, s8, s16);
+    printf("syndrome z : %u\n", z);
+#endif
+
+    // flip bit at this position
+    if (z) {
+        if (z > 31) {
+            // if this happens there are likely too many errors
+            // to correct; just pass without trying to do anything
+            fprintf(stderr,"warning, fec_hamming_decode_symbol(), syndrome index exceeds maximum\n");
+        } else {
+            //printf("error detected!\n");
+            _sym_enc ^= 1 << (31-z);
+        }
+    }
+
+    // strip data bits from encoded symbol with parity bits
+    unsigned int sym_dec = ((_sym_enc & 0x00007fff)     )   |   //  .000 0000 0000 0000 0111 1111 1111 1111
+                           ((_sym_enc & 0x007f0000) >> 1)   |   //  .000 0000 0111 1111 0000 0000 0000 0000
+                           ((_sym_enc & 0x07000000) >> 2)   |   //  .000 0111 0000 0000 0000 0000 0000 0000
+                           ((_sym_enc & 0x10000000) >> 3);      //  .001 0000 0000 0000 0000 0000 0000 0000
+    return sym_dec;
+}
+
+//
+// Hamming(31,26) example
+//
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+int main(int argc, char*argv[])
+{
+    // original symbol
+    unsigned int sym_org = 28236851;    // ..01 1010 1110 1101 1100 0011 0011
+
+    // encoded symbol
+    unsigned int sym_enc = fec_hamming_encode_symbol(sym_org);
+
+    // received symbol
+    unsigned int n=7;  // index of bit to corrupt
+    unsigned int sym_rec = sym_enc ^ (1<<(31-n));
+
+    // decoded symbol
+    unsigned int sym_dec = fec_hamming_decode_symbol(sym_rec);
+
+    // print results
+    printf("    sym org     :        "); print_bitstring(sym_org, 26); printf("\n");
+    printf("    sym enc     :   ");      print_bitstring(sym_enc, 31); printf("\n");
+    printf("    sym rec     :   ");      print_bitstring(sym_rec, 31); printf("\n");
+    printf("    sym dec     :        "); print_bitstring(sym_dec, 26); printf("\n");
+
+    // print number of bit errors
+    printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+
+    return 0;
+}
+
diff --git a/sandbox/fec_hamming74_gentab.c b/sandbox/fec_hamming74_gentab.c
new file mode 100644
index 0000000..49229cd
--- /dev/null
+++ b/sandbox/fec_hamming74_gentab.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 1/2-rate (7,4) Hamming code table generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// generator matrices
+
+// encoding matrix
+#define HAMMING74_G0    0x0d    // 1101
+#define HAMMING74_G1    0x0b    // 1011
+#define HAMMING74_G2    0x08    // 1000
+#define HAMMING74_G3    0x07    // 0111
+#define HAMMING74_G4    0x04    // 0100
+#define HAMMING74_G5    0x02    // 0010
+#define HAMMING74_G6    0x01    // 0001
+
+// syndrome matrix
+#define HAMMING74_H0    0x55    // .101 0101
+#define HAMMING74_H1    0x33    // .011 0011
+#define HAMMING74_H2    0x0f    // .000 1111
+
+// decoding matrix
+#define HAMMING74_R0    0x10    // .001 0000
+#define HAMMING74_R1    0x04    // .000 0100
+#define HAMMING74_R2    0x02    // .000 0010
+#define HAMMING74_R3    0x01    // .000 0001
+
+unsigned int count_ones(unsigned int _x);
+unsigned int hamming74_enc_symbol(unsigned int _p);
+unsigned int hamming74_dec_symbol(unsigned int _x);
+
+int main() {
+    unsigned int n = 16;
+    unsigned int k = 128;
+
+    // generate encoding table
+    unsigned int enc_gentab[n];
+    unsigned int p;
+    for (p=0; p<n; p++)
+        enc_gentab[p] = hamming74_enc_symbol(p);
+
+    // generate decoding table
+    unsigned int dec_gentab[k];
+    unsigned int x;
+    for (x=0; x<k; x++)
+        dec_gentab[x] = hamming74_dec_symbol(x);
+
+    printf("// Hamming(7,4) table generator\n");
+    printf("\n");
+    printf("unsigned char enc_gentab[%u] = {\n    ", n);
+    for (p=0; p<n; p++) {
+        printf("0x%.2x", enc_gentab[p]);
+        if (p==n-1)
+            printf("};\n");
+        else if (((p+1)%8)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+
+    printf("\n");
+    printf("unsigned char dec_gentab[%u] = {\n    ", k);
+    for (x=0; x<k; x++) {
+        printf("0x%.2x", dec_gentab[x]);
+        if (x==k-1)
+            printf("};\n");
+        else if (((x+1)%8)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+
+    // test codec
+    unsigned int r;     // received symbol
+    unsigned int p_hat; // decoded symbol
+    unsigned int i;
+    unsigned int num_errors=0;
+    for (p=0; p<n; p++) {
+        // encode symbol
+        x = enc_gentab[p];
+
+        // test decoding with no errors
+        p_hat = dec_gentab[x];
+        num_errors += (p_hat != p);
+
+        // add single bit error and decode
+        for (i=0; i<7; i++) {
+            // flip bit at index i
+            r = x ^ (1 << i);
+
+            // decode symbol
+            p_hat = dec_gentab[r];
+
+            // check for errors
+            num_errors += (p_hat != p);
+        }
+    }
+    printf("\n");
+    printf("// number of decoding errors: %u\n", num_errors);
+
+    return 0;
+}
+
+// count number of ones in an integer
+unsigned int count_ones(unsigned int _x)
+{
+    unsigned int num_ones=0;
+    unsigned int i;
+    for (i=0; i<64; i++) {
+        num_ones += _x & 0x01;
+        _x >>= 1;
+    }
+    return num_ones;
+}
+
+unsigned int hamming74_enc_symbol(unsigned int _p)
+{
+    unsigned int x0 = count_ones(_p & HAMMING74_G0) % 2;
+    unsigned int x1 = count_ones(_p & HAMMING74_G1) % 2;
+    unsigned int x2 = count_ones(_p & HAMMING74_G2) % 2;
+    unsigned int x3 = count_ones(_p & HAMMING74_G3) % 2;
+    unsigned int x4 = count_ones(_p & HAMMING74_G4) % 2;
+    unsigned int x5 = count_ones(_p & HAMMING74_G5) % 2;
+    unsigned int x6 = count_ones(_p & HAMMING74_G6) % 2;
+
+    unsigned int x = (x0 << 6) | (x1 << 5) | (x2 << 4) |
+                     (x3 << 3) | (x4 << 2) | (x5 << 1) |
+                     (x6     );
+
+    return x;
+}
+
+unsigned int hamming74_dec_symbol(unsigned int _r)
+{
+    // compute syndromes
+    unsigned int z0 = count_ones(_r & HAMMING74_H0) % 2;
+    unsigned int z1 = count_ones(_r & HAMMING74_H1) % 2;
+    unsigned int z2 = count_ones(_r & HAMMING74_H2) % 2;
+    unsigned int z = (z0 << 0) | (z1 << 1) | (z2 << 2);
+
+    unsigned int r_hat = _r;
+    if (z) {
+        // error detected: flip appropriate bit of
+        // received symbol
+        r_hat ^= 1 << (7-z);
+    }
+
+    // decode
+    unsigned int p0 = count_ones(r_hat & HAMMING74_R0) % 2;
+    unsigned int p1 = count_ones(r_hat & HAMMING74_R1) % 2;
+    unsigned int p2 = count_ones(r_hat & HAMMING74_R2) % 2;
+    unsigned int p3 = count_ones(r_hat & HAMMING74_R3) % 2;
+
+    unsigned int p = (p0 << 3) | (p1 << 2) | (p2 << 1) | (p3 << 0);
+
+    return p;
+}
+
diff --git a/sandbox/fec_hamming84_gentab.c b/sandbox/fec_hamming84_gentab.c
new file mode 100644
index 0000000..911bc81
--- /dev/null
+++ b/sandbox/fec_hamming84_gentab.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 1/2-rate (8,4) Hamming code table generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// generator matrices
+
+// encoding matrix
+#define HAMMING84_G0    0x0d    // 1101
+#define HAMMING84_G1    0x0b    // 1011
+#define HAMMING84_G2    0x08    // 1000
+#define HAMMING84_G3    0x07    // 0111
+#define HAMMING84_G4    0x04    // 0100
+#define HAMMING84_G5    0x02    // 0010
+#define HAMMING84_G6    0x01    // 0001
+#define HAMMING84_G7    0x0e    // 1110
+
+// syndrome matrix
+#define HAMMING84_H0    0xaa    // 1010 1010
+#define HAMMING84_H1    0x66    // 0110 0110
+#define HAMMING84_H2    0x1e    // 0001 1110
+#define HAMMING84_H3    0xff    // 1111 1111
+
+// decoding matrix
+#define HAMMING84_R0    0x20    // 0010 0000
+#define HAMMING84_R1    0x08    // 0000 1000
+#define HAMMING84_R2    0x04    // 0000 0100
+#define HAMMING84_R3    0x02    // 0000 0010
+
+unsigned int count_ones(unsigned int _x);
+unsigned int hamming84_enc_symbol(unsigned int _p);
+unsigned int hamming84_dec_symbol(unsigned int _x);
+
+int main() {
+    unsigned int n = 16;
+    unsigned int k = 256;
+
+    // generate encoding table
+    unsigned int enc_gentab[n];
+    unsigned int p;
+    for (p=0; p<n; p++)
+        enc_gentab[p] = hamming84_enc_symbol(p);
+
+    // generate decoding table
+    unsigned int dec_gentab[k];
+    unsigned int x;
+    for (x=0; x<k; x++)
+        dec_gentab[x] = hamming84_dec_symbol(x);
+
+    printf("// Hamming(8,4) table generator\n");
+    printf("\n");
+    printf("unsigned char enc_gentab[%u] = {\n    ", n);
+    for (p=0; p<n; p++) {
+        printf("0x%.2x", enc_gentab[p]);
+        if (p==n-1)
+            printf("};\n");
+        else if (((p+1)%8)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+
+    printf("\n");
+    printf("unsigned char dec_gentab[%u] = {\n    ", k);
+    for (x=0; x<k; x++) {
+        printf("0x%.2x", dec_gentab[x]);
+        if (x==k-1)
+            printf("};\n");
+        else if (((x+1)%8)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+
+    // test codec
+    unsigned int r;     // received symbol
+    unsigned int p_hat; // decoded symbol
+    unsigned int i;
+    unsigned int num_errors=0;
+    for (p=0; p<n; p++) {
+        // encode symbol
+        x = enc_gentab[p];
+
+        // test decoding with no errors
+        p_hat = dec_gentab[x];
+        num_errors += (p_hat != p);
+
+        // add single bit error and decode
+        for (i=0; i<8; i++) {
+            // flip bit at index i
+            r = x ^ (1 << i);
+
+            // decode symbol
+            p_hat = dec_gentab[r];
+
+            // check for errors
+            num_errors += (p_hat != p);
+        }
+    }
+    printf("\n");
+    printf("// number of decoding errors: %u\n", num_errors);
+
+    return 0;
+}
+
+// count number of ones in an integer
+unsigned int count_ones(unsigned int _x)
+{
+    unsigned int num_ones=0;
+    unsigned int i;
+    for (i=0; i<64; i++) {
+        num_ones += _x & 0x01;
+        _x >>= 1;
+    }
+    return num_ones;
+}
+
+unsigned int hamming84_enc_symbol(unsigned int _p)
+{
+    unsigned int x0 = count_ones(_p & HAMMING84_G0) % 2;
+    unsigned int x1 = count_ones(_p & HAMMING84_G1) % 2;
+    unsigned int x2 = count_ones(_p & HAMMING84_G2) % 2;
+    unsigned int x3 = count_ones(_p & HAMMING84_G3) % 2;
+    unsigned int x4 = count_ones(_p & HAMMING84_G4) % 2;
+    unsigned int x5 = count_ones(_p & HAMMING84_G5) % 2;
+    unsigned int x6 = count_ones(_p & HAMMING84_G6) % 2;
+    unsigned int x7 = count_ones(_p & HAMMING84_G7) % 2;
+
+    unsigned int x = (x0 << 7) | (x1 << 6) | (x2 << 5) |
+                     (x3 << 4) | (x4 << 3) | (x5 << 2) |
+                     (x6 << 1) | (x7     );
+
+    return x;
+}
+
+unsigned int hamming84_dec_symbol(unsigned int _r)
+{
+    // compute syndromes
+    unsigned int z0 = count_ones(_r & HAMMING84_H0) % 2;
+    unsigned int z1 = count_ones(_r & HAMMING84_H1) % 2;
+    unsigned int z2 = count_ones(_r & HAMMING84_H2) % 2;
+    unsigned int z = (z0 << 0) | (z1 << 1) | (z2 << 2);
+
+    unsigned int r_hat = _r;
+    if (z) {
+        // error detected: flip appropriate bit of
+        // received symbol
+        r_hat ^= 1 << (8-z);
+    }
+
+#if 0
+    // check extra parity bit
+    unsigned int z3 = count_ones(r_hat & HAMMING84_H3) % 2;
+    if (z3) printf("decoding error!\n");
+#endif
+
+    // decode
+    unsigned int p0 = count_ones(r_hat & HAMMING84_R0) % 2;
+    unsigned int p1 = count_ones(r_hat & HAMMING84_R1) % 2;
+    unsigned int p2 = count_ones(r_hat & HAMMING84_R2) % 2;
+    unsigned int p3 = count_ones(r_hat & HAMMING84_R3) % 2;
+
+    unsigned int p = (p0 << 3) | (p1 << 2) | (p2 << 1) | (p3 << 0);
+
+    return p;
+}
+
diff --git a/sandbox/fec_hamming_test.c b/sandbox/fec_hamming_test.c
new file mode 100644
index 0000000..f9d4a4c
--- /dev/null
+++ b/sandbox/fec_hamming_test.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//  Hamming code parity check matrix
+//
+//  bit position    1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38
+//  encoded bits    P1  P2  1   P4  2   3   4   P8  5   6   7   8   9   10  11  P16 12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  P32 27  28  29  30  31  32
+//
+//  parity bit  P1  x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .
+//  coverage    P2  .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .
+//              P4  .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .
+//              P8  .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .
+//              P16 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   .
+//              P32 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_HAMMING 1
+
+#if 0
+
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:8)
+//
+// bit position     3   5   6   7   9   10  11  12
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   =   1101 1010
+//  coverage    P2  x   .   x   x   .   x   x   .   =   1011 0110
+//              P4  .   x   x   x   .   .   .   x   =   0111 0001
+//              P8  .   .   .   .   x   x   x   x   =   0000 1111
+#define HAMMING_M1   0xda    // 1101 1010
+#define HAMMING_M2   0xb6    // 1011 0110
+#define HAMMING_M4   0x71    // 0111 0001
+#define HAMMING_M8   0x0f    // 0000 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING_S1   0x0aaa  // .... 1010 1010 1010
+#define HAMMING_S2   0x0666  // .... 0110 0110 0110
+#define HAMMING_S4   0x01e1  // .... 0001 1110 0001
+#define HAMMING_S8   0x001f  // .... 0000 0001 1111
+
+#endif
+
+
+//
+// Hamming(15,11)
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:11)
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   x   .   x   = .110 1101 0101
+//  coverage    P2  x   .   x   x   .   x   x   .   .   x   x   = .101 1011 0011 
+//              P4  .   x   x   x   .   .   .   x   x   x   x   = .011 1000 1111
+//              P8  .   .   .   .   x   x   x   x   x   x   x   = .000 0111 1111
+
+#define HAMMING_M1   0x06d5     // .110 1101 0101
+#define HAMMING_M2   0x05b3     // .101 1011 0011
+#define HAMMING_M4   0x038f     // .011 1000 1111
+#define HAMMING_M8   0x007f     // .000 0111 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING_S1   0x5555  // .101 0101 0101 0101
+#define HAMMING_S2   0x3333  // .011 0011 0011 0011
+#define HAMMING_S4   0x0f0f  // .000 1111 0000 1111
+#define HAMMING_S8   0x00ff  // .000 0000 1111 1111
+
+unsigned int fec_hamming_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<11)) {
+        fprintf(stderr,"error, fec_hamming_encode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute parity bits
+    unsigned int p1 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M1);
+    unsigned int p2 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M2);
+    unsigned int p4 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M4);
+    unsigned int p8 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M8);
+
+#if DEBUG_FEC_HAMMING
+    printf("parity bits (p1,p2,p4,p8) : (%1u,%1u,%1u,%1u)\n", p1, p2, p4, p8);
+#endif
+
+    // encode symbol by inserting parity bits with data bits to
+    // make a 15-bit symbol
+    unsigned int sym_enc = ((_sym_dec & 0x007f) << 0) |
+                           ((_sym_dec & 0x0380) << 1) |
+                           ((_sym_dec & 0x0400) << 2) |
+                           ( p1 << 14 ) |
+                           ( p2 << 13 ) |
+                           ( p4 << 11 ) |
+                           ( p8 << 7  );
+
+    return sym_enc;
+}
+
+unsigned int fec_hamming_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<15)) {
+        fprintf(stderr,"error, fec_hamming_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute syndrome bits
+    unsigned int s1 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S1);
+    unsigned int s2 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S2);
+    unsigned int s4 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S4);
+    unsigned int s8 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S8);
+
+    // index
+    unsigned int z = (s8<<3) | (s4<<2) | (s2<<1) | s1;
+
+#if DEBUG_FEC_HAMMING
+    printf("syndrome bits (s1,s2,s4,s8) : (%1u,%1u,%1u,%1u)\n", s1, s2, s4, s8);
+    printf("syndrome z : %u\n", z);
+#endif
+
+    // flip bit at this position
+    if (z) {
+        if (z > 15) {
+            // if this happens there are likely too many errors
+            // to correct; just pass without trying to do anything
+            fprintf(stderr,"warning, fec_hamming_decode_symbol(), syndrome index exceeds maximum\n");
+        } else {
+            //printf("error detected!\n");
+            _sym_enc ^= 1 << (15-z);
+        }
+    }
+
+    // strip data bits (x) from encoded symbol with parity bits (.)
+    //      symbol:  [..x. xxx. xxxx]
+    //                0000 0000 1111     >  0x000f
+    //                0000 1110 0000     >  0x00e0
+    //                0010 0000 0000     >  0x0200
+    //
+    //      symbol: [ -..x .xxx .xxx xxxx]
+    //                -000 0000 0xxx xxxx   > 0x007f
+    //                -000 0111 0000 0000   > 0x0700
+    //                -001 0000 0000 0000   > 0x1000
+    unsigned int sym_dec = ((_sym_enc & 0x007f)     )   |
+                           ((_sym_enc & 0x0700) >> 1)   |
+                           ((_sym_enc & 0x1000) >> 2);
+
+
+    return sym_dec;
+}
+
+//
+// Hamming(12,8) example
+//
+
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+int main(int argc, char*argv[])
+{
+    // original symbol
+    unsigned int sym_org = 1377;    // 10101100001
+
+    // encoded symbol
+    unsigned int sym_enc = fec_hamming_encode_symbol(sym_org);
+
+    // received symbol
+    unsigned int n=7;  // index of bit to corrupt
+    unsigned int sym_rec = sym_enc ^ (1<<(15-n));
+
+    // decoded symbol
+    unsigned int sym_dec = fec_hamming_decode_symbol(sym_rec);
+
+    // print results
+    printf("    sym org     :       "); print_bitstring(sym_org, 11); printf("\n");
+    printf("    sym enc     :   ");     print_bitstring(sym_enc, 15); printf("\n");
+    printf("    sym rec     :   ");     print_bitstring(sym_rec, 15); printf("\n");
+    printf("    sym dec     :       "); print_bitstring(sym_dec, 11); printf("\n");
+
+    // print number of bit errors
+    printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+
+    return 0;
+}
+
diff --git a/sandbox/fec_ldpc_test.c b/sandbox/fec_ldpc_test.c
new file mode 100644
index 0000000..4ef8887
--- /dev/null
+++ b/sandbox/fec_ldpc_test.c
@@ -0,0 +1,204 @@
+//
+// fec_ldpc_test.c
+//
+// Test soft demodulation of LDPC code
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#define DEBUG_LDPC_TEST (0)
+
+#if DEBUG_LDPC_TEST
+#include <liquid.h>
+#endif
+
+//
+float phi(float _x) { return -logf(tanhf(_x/2.0f + 1e-12)); }
+
+int main(int argc, char*argv[])
+{
+    // parity check matrix
+    unsigned char H[32] = {
+        1, 1, 1, 0, 0, 0, 0, 0,
+        0, 0, 0, 1, 1, 1, 0, 0,
+        1, 0, 0, 1, 0, 0, 1, 0,
+        0, 1, 0, 0, 1, 0, 0, 1};
+
+    // transmitted codeword
+    unsigned char c[8] = {
+        1, 0, 1, 0, 1, 1, 1, 1};
+
+    // received message
+    float y[8] = {
+        0.2, 0.2, -0.9, 0.6, 0.5, -1.1, -0.4, -1.2};
+
+    // noise standard deviation
+    float sigma = sqrtf(0.5f);
+
+    unsigned int max_iterations = 10;
+    unsigned int n = 8;
+    unsigned int m = 4;
+
+    // internal variables
+    unsigned int num_iterations = 0;
+    float Lq[m*n];
+    float Lr[m*n];
+    float Lc[n];
+    float LQ[n];
+    unsigned char c_hat[n];
+    unsigned char parity[m];
+    unsigned int i;
+    unsigned int j;
+    unsigned int ip;
+    unsigned int jp;
+    float alpha_prod;
+    float phi_sum;
+    int parity_pass;
+    int continue_running = 1;
+
+    // initialize Lq with log-likelihood values
+    for (i=0; i<n; i++)
+        Lc[i] = 2.0f * y[i] / (sigma*sigma);
+
+    for (j=0; j<m; j++) {
+        for (i=0; i<n; i++) {
+            Lq[j*n+i] = H[j*n+i] ? Lc[i] : 0.0f;
+        }
+    }
+#if DEBUG_LDPC_TEST
+    // print Lc
+    matrixf_print(Lc,1,n);
+#endif
+
+    // TODO : run multiple iterations
+    while (continue_running) {
+#if DEBUG_LDPC_TEST
+        //
+        printf("\n");
+        printf("************* iteration %u ****************\n", num_iterations);
+#endif
+
+        // compute Lr
+        for (i=0; i<n; i++) {
+            for (j=0; j<m; j++) {
+                alpha_prod = 1.0f;
+                phi_sum    = 0.0f;
+                for (ip=0; ip<n; ip++) {
+                    if (H[j*n+ip]==1 && i != ip) {
+                        float alpha = Lq[j*n+ip] > 0.0f ? 1.0f : -1.0f;
+                        float beta  = fabsf(Lq[j*n+ip]);
+                        phi_sum += phi(beta);
+                        alpha_prod *= alpha;
+                    }
+                }
+                Lr[j*n+i] = alpha_prod * phi(phi_sum);
+            }
+        }
+
+#if DEBUG_LDPC_TEST
+        // print Lq
+        matrixf_print(Lq,m,n);
+
+        // print Lr
+        matrixf_print(Lr,m,n);
+#endif
+
+        // compute next iteration of Lq
+        for (i=0; i<n; i++) {
+            for (j=0; j<m; j++) {
+                // initialize with LLR
+                Lq[j*n+i] = Lc[i];
+
+                for (jp=0; jp<m; jp++) {
+                    if (H[jp*n+i]==1 && j != jp)
+                        Lq[j*n+i] += Lr[jp*n+i];
+                }
+            }
+        }
+
+#if DEBUG_LDPC_TEST
+        // print Lq
+        matrixf_print(Lq,m,n);
+#endif
+
+        // compute LQ
+        for (i=0; i<n; i++) {
+            LQ[i] = Lc[i];  // initialize with LLR value
+
+            for (j=0; j<m; j++) {
+                if (H[j*n+i]==1)
+                    LQ[i] += Lr[j*n+i];
+            }
+        }
+
+#if DEBUG_LDPC_TEST
+        // print LQ
+        matrixf_print(LQ,1,n);
+#endif
+
+        // compute hard-decoded value
+        for (i=0; i<n; i++)
+            c_hat[i] = LQ[i] < 0.0f ? 1 : 0;
+
+        // compute parity check: p = H*c_hat
+        for (j=0; j<m; j++) {
+            parity[j] = 0;
+
+            // 
+            for (i=0; i<n; i++)
+                parity[j] += H[j*n+i] * c_hat[i];
+
+            // math is modulo 2
+            parity[j] %= 2;
+        }
+
+        // check parity
+        parity_pass = 1;
+        for (j=0; j<m; j++) {
+            if (parity[j]) parity_pass = 0;
+        }
+
+        // print hard-decision output
+        printf("%3u : c hat = [", num_iterations);
+        for (i=0; i<n; i++)
+            printf(" %1u", c_hat[i]);
+        printf(" ],  ");
+
+        // print parity
+        printf("parity = [");
+        for (j=0; j<m; j++)
+            printf(" %1u", parity[j]);
+        printf(" ],  ");
+
+        printf(" (%s)\n", parity_pass ? "pass" : "FAIL");
+
+        // update...
+        num_iterations++;
+        if (parity_pass || num_iterations == max_iterations)
+            continue_running = 0;
+    }
+
+    // compute errors and print results
+    unsigned int num_errors = 0;
+    for (i=0; i<n; i++)
+        num_errors += (c[i] == c_hat[i]) ? 0 : 1;
+
+    printf("\nresults:\n");
+
+    printf("c    :");
+    for (i=0; i<n; i++)
+        printf(" %1u", c[i]);
+    printf("\n");
+
+    printf("c_hat:");
+    for (i=0; i<n; i++)
+        printf(" %1u", c_hat[i]);
+    printf("\n");
+
+    printf("num errors: %u / %u\n", num_errors, n);
+
+    return 0;
+}
+
diff --git a/sandbox/fec_rep3_test.c b/sandbox/fec_rep3_test.c
new file mode 100644
index 0000000..16ec497
--- /dev/null
+++ b/sandbox/fec_rep3_test.c
@@ -0,0 +1,31 @@
+//
+//
+//
+
+#include <stdio.h>
+#include "liquid.h"
+
+int main() {
+    unsigned char s0, s1, s2, r, r_hat;
+    unsigned int errors = 0;
+
+    for (s0=0; s0<2; s0++) {
+        for (s1=0; s1<2; s1++) {
+            for (s2=0; s2<2; s2++) {
+                r = (s0 + s1 + s2) < 2 ? 0 : 1;
+
+                //r_hat = (!s0 & s1 & s2) | (s0 & !(!s1 & !s2));
+                r_hat = (s1 & s2) | (s0 & s1) | (s0 & s2);
+
+                int error_found = (s0 ^ s1) | (s0 ^ s2) | (s1 ^ s2);
+
+                errors += r != r_hat;
+
+                printf("%2u %2u %2u    %2u (%2u) %2u\n", s0, s1, s2, r, r_hat, error_found);
+            }
+        }
+    }
+    printf("errors : %u\n", errors);
+
+    return 0;
+}
diff --git a/sandbox/fec_rep5_test.c b/sandbox/fec_rep5_test.c
new file mode 100644
index 0000000..2bc47b6
--- /dev/null
+++ b/sandbox/fec_rep5_test.c
@@ -0,0 +1,49 @@
+//
+//
+//
+
+#include <stdio.h>
+#include "liquid.h"
+
+int main() {
+    unsigned char s0, s1, s2, s3, s4, r, r_hat;
+    unsigned int errors = 0;
+
+    for (s0=0; s0<2; s0++) {
+    for (s1=0; s1<2; s1++) {
+    for (s2=0; s2<2; s2++) {
+    for (s3=0; s3<2; s3++) {
+    for (s4=0; s4<2; s4++) {
+        r = (s0 + s1 + s2 + s3 + s4) < 3 ? 0 : 1;
+
+        r_hat = (s0 & s1) | (s0 & s2) | (s0 & s3) | (s0 & s4) |
+                            (s1 & s2) | (s1 & s3) | (s1 & s4) |
+                                        (s2 & s3) | (s2 & s4) |
+                                                    (s3 & s4);
+
+        r_hat = (s0 & s1 & s2) |
+                (s0 & s1 & s3) |
+                (s0 & s1 & s4) |
+                (s0 & s2 & s3) |
+                (s0 & s2 & s4) |
+                (s0 & s3 & s4) |
+                (s1 & s2 & s3) |
+                (s1 & s2 & s4) |
+                (s1 & s3 & s4) |
+                (s2 & s3 & s4);
+
+        int error_found = 0;
+
+        errors += r != r_hat;
+
+        printf("%2u %2u %2u %2u %2u    %2u (%2u) %c %2u\n",
+                s0, s1, s2, s3, s4,    r,  r_hat, r==r_hat ? ' ' : '*', error_found);
+    }
+    }
+    }
+    }
+    }
+    printf("errors : %u\n", errors);
+
+    return 0;
+}
diff --git a/sandbox/fec_secded2216_test.c b/sandbox/fec_secded2216_test.c
new file mode 100644
index 0000000..f277580
--- /dev/null
+++ b/sandbox/fec_secded2216_test.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// (22,16) code test (SEC-DED)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED 1
+
+// P matrix [6 x 16 bits], [6 x 2 bytes]
+//  1001 1001 0011 1100 :
+//  0011 1110 1000 1010 :
+//  1110 1110 0110 0000 :
+//  1110 0001 1101 0001 :
+//  0001 0011 1100 0111 :
+//  0100 0100 0011 1111 :
+unsigned char P[12] = {
+    0x99, 0x3c,
+    0x3e, 0x8a,
+    0xee, 0x60,
+    0xe1, 0xd1,
+    0x13, 0xc7,
+    0x44, 0x3f};
+
+void print_bitstring_short(unsigned char _x,
+                           unsigned char _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+void print_bitstring(unsigned char * _x,
+                     unsigned char   _n)
+{
+    unsigned int i;
+    // compute number of elements in _x
+    div_t d = div(_n, 8);
+    unsigned int N = d.quot + (d.rem ? 1 : 0);
+
+    // print leader
+    printf("    ");
+    if (d.rem == 0) printf(" ");
+    for (i=0; i<8-d.rem-1; i++)
+        printf(" ");
+
+    // print bitstring
+    for (i=0; i<N; i++) {
+        if (i==0 && d.rem)
+            print_bitstring_short(_x[i], d.rem);
+        else
+            print_bitstring_short(_x[i], 8);
+
+        printf(" ");
+
+    }
+    printf("\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int i;
+    
+    // error vector [22 x 1]
+    unsigned char err[3] = {0x00, 0x0000, 0x0001};
+
+    // original message [16 x 1]
+    unsigned char m[2] = {0x0000, 0x0001};
+    m[0] = rand() & 0xffff;
+    m[1] = rand() & 0xffff;
+
+    // derived values
+    unsigned char v[3];     // encoded/transmitted message
+    unsigned char e[3];     // error vector
+    unsigned char r[3];     // received vector
+    unsigned char s;        // syndrome vector
+    unsigned char e_hat[3] = {0,0,0};  // estimated error vector
+    unsigned char v_hat[3]; // estimated transmitted message
+    unsigned char m_hat[2]; // estimated original message
+
+#if 0
+    // print P matrix
+    printf("P : \n");
+    print_bitstring(&P[ 0],16);
+    print_bitstring(&P[ 2],16);
+    print_bitstring(&P[ 4],16);
+    print_bitstring(&P[ 6],16);
+    print_bitstring(&P[ 8],16);
+    print_bitstring(&P[10],16);
+#endif
+
+    // original message
+    printf("m (original message):   ");
+    print_bitstring(m,16);
+
+    // compute encoded/transmitted message: v = m*G
+    v[0] = 0;
+    for (i=0; i<6; i++) {
+        v[0] <<= 1;
+
+        unsigned int p = liquid_c_ones[P[2*i+0] & m[0]] +
+                         liquid_c_ones[P[2*i+1] & m[1]];
+        printf("p = %u\n", p);
+        v[0] |= p & 0x01;
+    }
+    v[1] = m[0];
+    v[2] = m[1];
+    printf("v (encoded message):    ");
+    print_bitstring(v,22);
+
+    // use pre-determined error vector
+    e[0] = err[0];
+    e[1] = err[1];
+    e[2] = err[2];
+    printf("e (error vector):       ");
+    print_bitstring(e,22);
+
+    // compute received vector: r = v + e
+    r[0] = v[0] ^ e[0];
+    r[1] = v[1] ^ e[1];
+    r[2] = v[2] ^ e[2];
+    printf("r (received vector):    ");
+    print_bitstring(r,22);
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    s = 0;
+    for (i=0; i<6; i++) {
+        s <<= 1;
+
+        unsigned int p =
+            ( (r[0] & (1<<(6-i-1))) ? 1 : 0 )+
+            liquid_count_ones(P[2*i+0] & r[1]) +
+            liquid_count_ones(P[2*i+1] & r[2]);
+        //printf("p = %u\n", p);
+
+        s |= p & 0x01;
+    }
+    printf("s (syndrome vector):    ");
+    print_bitstring(&s,6);
+
+    // compute weight of s
+    unsigned int ws = liquid_count_ones(s);
+    printf("weight(s) = %u\n", ws);
+
+    if (ws == 0) {
+        printf("no errors detected\n");
+    } else {
+        // estimate error location
+        unsigned char e_test[3]  = {0x00, 0x0000, 0x0001};
+        int syndrome_match = 0;
+
+        // TODO : these can be pre-computed
+        unsigned int n;
+        for (n=0; n<22; n++) {
+            // compute syndrome
+            unsigned int s_test = 0;
+
+            for (i=0; i<6; i++) {
+                s_test <<= 1;
+                unsigned int p =
+                    ( (e_test[0] & (1<<(6-i-1))) ? 1 : 0 )+
+                    liquid_count_ones(P[2*i+0] & e_test[1]) +
+                    liquid_count_ones(P[2*i+1] & e_test[2]);
+
+                s_test |= p & 0x01;
+            }
+
+#if 1
+            // print results
+            //printf("e_test:"); print_bitstring(e_test, 72);
+            printf("%3u : e = ", n);
+            print_bitstring_short(e_test[0],6); printf(" ");
+            print_bitstring_short(e_test[1],8); printf(" ");
+            print_bitstring_short(e_test[2],8); printf(" ");
+            printf(", s = ");
+            print_bitstring_short(s_test,6);
+            if (s == s_test) printf(" *");
+            printf("\n");
+#else
+            // print output array (secded2216_syndrome_w1[])
+            printf("0x%.2x\n", s_test);
+#endif
+
+            if (s == s_test) {
+                memmove(e_hat, e_test, sizeof(e_test));
+                syndrome_match = 1;
+            }
+
+            // shift e_test
+            e_test[0] = (e_test[0] << 1) | ((e_test[1] & 0x80) ? 1 : 0);
+            e_test[1] = (e_test[1] << 1) | ((e_test[2] & 0x80) ? 1 : 0);
+            e_test[2] <<= 1;
+
+        }
+
+        if (syndrome_match) {
+            printf("syndrome match!\n");
+        } else {
+            printf("no syndrome match; expected multiple errors\n");
+        }
+    }
+
+    // compute estimated transmitted message: v_hat = r + e_hat
+    printf("e-hat (estimated error):");
+    print_bitstring(e_hat,22);
+
+    printf("v-hat (estimated tx):   ");
+    v_hat[0] = r[0] ^ e_hat[0];
+    v_hat[1] = r[1] ^ e_hat[1];
+    v_hat[2] = r[2] ^ e_hat[2];
+    print_bitstring(v_hat,22);
+
+    // compute estimated original message: (last 16 bits of encoded message)
+    m_hat[0] = v_hat[1];
+    m_hat[1] = v_hat[2];
+    printf("m-hat (estimated orig.):");
+    print_bitstring(m_hat,16);
+
+    // compute errors between v, v_hat
+    unsigned int num_errors_encoded = count_bit_errors(v[0], v_hat[0]) +
+                                      count_bit_errors(v[1], v_hat[1]) +
+                                      count_bit_errors(v[2], v_hat[2]);
+    printf("decoding errors (encoded)  : %2u / 22\n", num_errors_encoded);
+
+    // compute errors between m, m_hat
+    unsigned int num_errors_decoded = count_bit_errors(m[0], m_hat[0]) +
+                                      count_bit_errors(m[1], m_hat[1]);
+    printf("decoding errors (original) : %2u / 16\n", num_errors_decoded);
+
+    return 0;
+}
+
diff --git a/sandbox/fec_secded3932_test.c b/sandbox/fec_secded3932_test.c
new file mode 100644
index 0000000..0ccc9a2
--- /dev/null
+++ b/sandbox/fec_secded3932_test.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// (39,32) code test (SEC-DED)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED 1
+
+// P matrix [7 x 32 bits], [7 x 4 bytes]
+//  1000 1010 1000 0010 0000 1111 0001 1011
+//  0001 0000 0001 1111 0111 0001 0110 0001
+//  0001 0110 1111 0000 1001 0010 1010 0110
+//  1111 1111 0000 0001 1010 0100 0100 0100
+//  0110 1100 1111 1111 0000 1000 0000 1000
+//  0010 0001 0010 0100 1111 1111 1001 0000
+//  1100 0001 0100 1000 0100 0000 1111 1111
+unsigned char P[28] = {
+    0x8a, 0x82, 0x0f, 0x1b,
+    0x10, 0x1f, 0x71, 0x61,
+    0x16, 0xf0, 0x92, 0xa6,
+    0xff, 0x01, 0xa4, 0x44,
+    0x6c, 0xff, 0x08, 0x08,
+    0x21, 0x24, 0xff, 0x90,
+    0xc1, 0x48, 0x40, 0xff};
+
+
+void print_bitstring_short(unsigned char _x,
+                           unsigned char _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+void print_bitstring(unsigned char * _x,
+                     unsigned char   _n)
+{
+    unsigned int i;
+    // compute number of elements in _x
+    div_t d = div(_n, 8);
+    unsigned int N = d.quot + (d.rem ? 1 : 0);
+
+    // print leader
+    printf("    ");
+    if (d.rem == 0) printf(" ");
+    for (i=0; i<8-d.rem-1; i++)
+        printf(" ");
+
+    // print bitstring
+    for (i=0; i<N; i++) {
+        if (i==0 && d.rem)
+            print_bitstring_short(_x[i], d.rem);
+        else
+            print_bitstring_short(_x[i], 8);
+
+        printf(" ");
+
+    }
+    printf("\n");
+}
+
+int main(int argc, char*argv[])
+{
+    unsigned int i;
+    
+    // error vector [22 x 1]
+    unsigned char err[5] = {0,0,0,0,2};
+
+    // original message [16 x 1]
+    unsigned char m[4] = {0,0,0,1};
+    //m[0] = rand() & 0xffff;
+    //m[1] = rand() & 0xffff;
+
+    // derived values
+    unsigned char v[5];     // encoded/transmitted message
+    unsigned char e[5];     // error vector
+    unsigned char r[5];     // received vector
+    unsigned char s;        // syndrome vector
+    unsigned char e_hat[5] = {0,0,0,0,0};  // estimated error vector
+    unsigned char v_hat[5]; // estimated transmitted message
+    unsigned char m_hat[4]; // estimated original message
+
+#if 0
+    // print P matrix
+    printf("P : \n");
+    print_bitstring(&P[ 0],32);
+    print_bitstring(&P[ 4],32);
+    print_bitstring(&P[ 8],32);
+    print_bitstring(&P[12],32);
+    print_bitstring(&P[16],32);
+    print_bitstring(&P[20],32);
+    print_bitstring(&P[24],32);
+#endif
+
+    // original message
+    printf("m (original message):   ");
+    print_bitstring(m,32);
+
+    // compute encoded/transmitted message: v = m*G
+    v[0] = 0;
+    for (i=0; i<7; i++) {
+        v[0] <<= 1;
+
+        unsigned int p = liquid_c_ones[P[4*i+0] & m[0]] +
+                         liquid_c_ones[P[4*i+1] & m[1]] +
+                         liquid_c_ones[P[4*i+2] & m[2]] +
+                         liquid_c_ones[P[4*i+3] & m[3]];
+        //printf("p = %u\n", p);
+        v[0] |= p & 0x01;
+    }
+    v[1] = m[0];
+    v[2] = m[1];
+    v[3] = m[2];
+    v[4] = m[3];
+    printf("v (encoded message):    ");
+    print_bitstring(v,39);
+
+    // use pre-determined error vector
+    e[0] = err[0];
+    e[1] = err[1];
+    e[2] = err[2];
+    e[3] = err[3];
+    e[4] = err[4];
+    printf("e (error vector):       ");
+    print_bitstring(e,39);
+
+    // compute received vector: r = v + e
+    r[0] = v[0] ^ e[0];
+    r[1] = v[1] ^ e[1];
+    r[2] = v[2] ^ e[2];
+    r[3] = v[3] ^ e[3];
+    r[4] = v[4] ^ e[4];
+    printf("r (received vector):    ");
+    print_bitstring(r,39);
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    s = 0;
+    for (i=0; i<7; i++) {
+        s <<= 1;
+
+        unsigned int p =
+            ( (r[0] & (1<<(7-i-1))) ? 1 : 0 )+
+            liquid_count_ones(P[4*i+0] & r[1]) +
+            liquid_count_ones(P[4*i+1] & r[2]) +
+            liquid_count_ones(P[4*i+2] & r[3]) +
+            liquid_count_ones(P[4*i+3] & r[4]);
+        //printf("p = %u\n", p);
+
+        s |= p & 0x01;
+    }
+    printf("s (syndrome vector):    ");
+    print_bitstring(&s,7);
+
+    // compute weight of s
+    unsigned int ws = liquid_count_ones(s);
+    printf("weight(s) = %u\n", ws);
+
+    if (ws == 0) {
+        printf("no errors detected\n");
+    } else {
+        // estimate error location
+        int syndrome_match = 0;
+
+        // TODO : these can be pre-computed
+        unsigned int n;
+        for (n=0; n<39; n++) {
+            // compute syndrome
+            unsigned int s_test = 0;
+            unsigned char e_test[5] = {0,0,0,0,0};
+
+            div_t d = div(n,8);
+            e_test[5-d.quot-1] = 1 << d.rem;
+
+            for (i=0; i<7; i++) {
+                s_test <<= 1;
+                unsigned int p =
+                    ( (e_test[0] & (1<<(7-i-1))) ? 1 : 0 )+
+                    liquid_count_ones(P[4*i+0] & e_test[1]) +
+                    liquid_count_ones(P[4*i+1] & e_test[2]) +
+                    liquid_count_ones(P[4*i+2] & e_test[3]) +
+                    liquid_count_ones(P[4*i+3] & e_test[4]);
+
+                s_test |= p & 0x01;
+            }
+
+#if 1
+            // print results
+            //printf("e_test:"); print_bitstring(e_test, 72);
+            printf("%3u : e = ", n);
+            print_bitstring_short(e_test[0],7); printf(" ");
+            print_bitstring_short(e_test[1],8); printf(" ");
+            print_bitstring_short(e_test[2],8); printf(" ");
+            print_bitstring_short(e_test[3],8); printf(" ");
+            print_bitstring_short(e_test[4],8); printf(" ");
+            printf(", s = ");
+            print_bitstring_short(s_test,7);
+            if (s == s_test) printf(" *");
+            printf("\n");
+#else
+            // print output array (secded2216_syndrome_w1[])
+            printf("0x%.2x\n", s_test);
+#endif
+
+            if (s == s_test) {
+                memmove(e_hat, e_test, sizeof(e_test));
+                syndrome_match = 1;
+            }
+        }
+
+        if (syndrome_match) {
+            printf("syndrome match!\n");
+        } else {
+            printf("no syndrome match; expected multiple errors\n");
+        }
+    }
+
+    // compute estimated transmitted message: v_hat = r + e_hat
+    printf("e-hat (estimated error):");
+    print_bitstring(e_hat,39);
+
+    printf("v-hat (estimated tx):   ");
+    v_hat[0] = r[0] ^ e_hat[0];
+    v_hat[1] = r[1] ^ e_hat[1];
+    v_hat[2] = r[2] ^ e_hat[2];
+    v_hat[3] = r[3] ^ e_hat[3];
+    v_hat[4] = r[4] ^ e_hat[4];
+    print_bitstring(v_hat,39);
+
+    // compute estimated original message: (last 16 bits of encoded message)
+    m_hat[0] = v_hat[1];
+    m_hat[1] = v_hat[2];
+    m_hat[2] = v_hat[3];
+    m_hat[3] = v_hat[4];
+    printf("m-hat (estimated orig.):");
+    print_bitstring(m_hat,32);
+
+    // compute errors between v, v_hat
+    unsigned int num_errors_encoded = count_bit_errors_array(v, v_hat, 5);
+    printf("decoding errors (encoded)  : %2u / 39\n", num_errors_encoded);
+
+    // compute errors between m, m_hat
+    unsigned int num_errors_decoded = count_bit_errors_array(m, m_hat, 4);
+    printf("decoding errors (original) : %2u / 32\n", num_errors_decoded);
+
+    return 0;
+}
+
diff --git a/sandbox/fec_secded7264_test.c b/sandbox/fec_secded7264_test.c
new file mode 100644
index 0000000..fa03c00
--- /dev/null
+++ b/sandbox/fec_secded7264_test.c
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// (72,64) code test (SEC-DED)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED 1
+
+// P matrix [8 x 64]
+//  11111111 00001111 00001111 00001100 01101000 10001000 10001000 10000000 : 
+//  11110000 11111111 00000000 11110011 01100100 01000100 01000100 01000000 : 
+//  00110000 11110000 11111111 00001111 00000010 00100010 00100010 00100110 : 
+//  11001111 00000000 11110000 11111111 00000001 00010001 00010001 00010110 : 
+//  01101000 10001000 10001000 10000000 11111111 00001111 00000000 11110011 : 
+//  01100100 01000100 01000100 01000000 11110000 11111111 00001111 00001100 : 
+//  00000010 00100010 00100010 00100110 11001111 00000000 11111111 00001111 : 
+//  00000001 00010001 00010001 00010110 00110000 11110000 11110000 11111111 : 
+unsigned char P[64] = {
+    0xFF, 0x0F, 0x0F, 0x0C, 0x68, 0x88, 0x88, 0x80,
+    0xF0, 0xFF, 0x00, 0xF3, 0x64, 0x44, 0x44, 0x40,
+    0x30, 0xF0, 0xFF, 0x0F, 0x02, 0x22, 0x22, 0x26,
+    0xCF, 0x00, 0xF0, 0xFF, 0x01, 0x11, 0x11, 0x16,
+    0x68, 0x88, 0x88, 0x80, 0xFF, 0x0F, 0x00, 0xF3,
+    0x64, 0x44, 0x44, 0x40, 0xF0, 0xFF, 0x0F, 0x0C,
+    0x02, 0x22, 0x22, 0x26, 0xCF, 0x00, 0xFF, 0x0F,
+    0x01, 0x11, 0x11, 0x16, 0x30, 0xF0, 0xF0, 0xFF};
+
+void print_bitstring_short(unsigned char _x,
+                           unsigned char _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+void print_bitstring(unsigned char * _x,
+                     unsigned char   _n)
+{
+    unsigned int i;
+    // compute number of elements in _x
+    div_t d = div(_n, 8);
+    unsigned int N = d.quot + (d.rem ? 1 : 0);
+
+    // print leader
+    printf("    ");
+    if (d.rem == 0) printf(" ");
+    for (i=0; i<8-d.rem-1; i++)
+        printf(" ");
+
+    // print bitstring
+    for (i=0; i<N; i++) {
+        if (i==0 && d.rem)
+            print_bitstring_short(_x[i], d.rem);
+        else
+            print_bitstring_short(_x[i], 8);
+
+        printf(" ");
+
+    }
+    printf("\n");
+}
+
+
+int main(int argc, char*argv[])
+{
+    unsigned int i;
+    
+    // error vector [72 x 1]
+    unsigned char e[9] = {0,0,0,0,0,0,0,0,2};
+
+    // original message [64 x 1]
+    unsigned char m[8] = {0,0,0,0,0,0,0,1};
+
+    // derived values
+    unsigned char v[9];     // encoded/transmitted message
+    unsigned char r[9];     // received vector
+    unsigned char s;        // syndrome vector
+    unsigned char v_hat[9]; // estimated transmitted message
+    unsigned char m_hat[8]; // estimated original message
+
+    // original message
+    printf("m (original message):\n         ");
+    print_bitstring(m,64);
+
+    // compute encoded/transmitted message: v = m*G
+    v[0] = 0;
+    for (i=0; i<8; i++) {
+        v[0] <<= 1;
+
+        unsigned int p = liquid_c_ones[ P[8*i+0] & m[0] ] +
+                         liquid_c_ones[ P[8*i+1] & m[1] ] +
+                         liquid_c_ones[ P[8*i+2] & m[2] ] +
+                         liquid_c_ones[ P[8*i+3] & m[3] ] +
+                         liquid_c_ones[ P[8*i+4] & m[4] ] +
+                         liquid_c_ones[ P[8*i+5] & m[5] ] +
+                         liquid_c_ones[ P[8*i+6] & m[6] ] +
+                         liquid_c_ones[ P[8*i+7] & m[7] ];
+        //printf("p = %u\n", p);
+        v[0] |= p & 0x01;
+    }
+    for (i=0; i<8; i++)
+        v[i+1] = m[i];
+    printf("v (encoded/transmitted message):\n");
+    print_bitstring(v,72);
+
+    // use pre-determined error vector
+    printf("e (error vector):\n");
+    print_bitstring(e,72);
+
+    // compute received vector: r = v + e
+    for (i=0; i<9; i++)
+        r[i] = v[i] ^ e[i];
+    printf("r (received vector):\n");
+    print_bitstring(r,72);
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    s = 0;
+    for (i=0; i<8; i++) {
+        s <<= 1;
+        unsigned int p =
+            ( (r[0] & (1<<(8-i-1))) ? 1 : 0 )+
+            liquid_c_ones[ P[8*i+0] & r[1] ] +
+            liquid_c_ones[ P[8*i+1] & r[2] ] +
+            liquid_c_ones[ P[8*i+2] & r[3] ] +
+            liquid_c_ones[ P[8*i+3] & r[4] ] +
+            liquid_c_ones[ P[8*i+4] & r[5] ] +
+            liquid_c_ones[ P[8*i+5] & r[6] ] +
+            liquid_c_ones[ P[8*i+6] & r[7] ] +
+            liquid_c_ones[ P[8*i+7] & r[8] ];
+
+        printf("p = %u\n", p);
+
+        s |= p & 0x01;
+    }
+    printf("s (syndrome vector):\n");
+    print_bitstring(&s,8);
+
+    // compute weight of s
+    unsigned int ws = liquid_count_ones(s);
+    printf("w(s) = %u\n", ws);
+
+    // estimated error vector
+    unsigned char e_hat[9] = {0,0,0,0,0,0,0,0,0}; 
+
+    if (ws == 0) {
+        printf("no errors detected\n");
+    } else {
+        // estimate error location
+        int syndrome_match = 0;
+
+        // TODO : these can be pre-computed
+        unsigned int n;
+        for (n=0; n<72; n++) {
+            // compute syndrome
+            unsigned char e_test[9]  = {0,0,0,0,0,0,0,0,0};
+            unsigned char s_hat = 0;
+
+            div_t d = div(n,8);
+            e_test[9-d.quot-1] = 1 << d.rem;
+
+            for (i=0; i<8; i++) {
+                s_hat <<= 1;
+                unsigned int p =
+                    ( (e_test[0] & (1<<(8-i-1))) ? 1 : 0 )+
+                    liquid_c_ones[ P[8*i+0] & e_test[1] ] +
+                    liquid_c_ones[ P[8*i+1] & e_test[2] ] +
+                    liquid_c_ones[ P[8*i+2] & e_test[3] ] +
+                    liquid_c_ones[ P[8*i+3] & e_test[4] ] +
+                    liquid_c_ones[ P[8*i+4] & e_test[5] ] +
+                    liquid_c_ones[ P[8*i+5] & e_test[6] ] +
+                    liquid_c_ones[ P[8*i+6] & e_test[7] ] +
+                    liquid_c_ones[ P[8*i+7] & e_test[8] ];
+
+                s_hat |= p & 0x01;
+            }
+
+            // print results
+            //printf("e_test:"); print_bitstring(e_test, 72);
+            printf("%2u e=", n);
+            for (i=0; i<9; i++) {
+                print_bitstring_short(e_test[i],8);
+                printf(" ");
+            }
+            printf("s=");
+            print_bitstring_short(s_hat,8);
+            if (s == s_hat) printf("*");
+            printf("\n");
+
+            if (s == s_hat) {
+                memmove(e_hat, e_test, 9*sizeof(unsigned char));
+                syndrome_match = 1;
+            }
+        }
+
+        if (syndrome_match) {
+            printf("syndrome match!\n");
+        } else {
+            printf("no syndrome match; expected multiple errors\n");
+        }
+    }
+
+    // compute estimated transmitted message: v_hat = r + e_hat
+    printf("e-hat (estimated error vector):\n");
+    print_bitstring(e_hat,72);
+
+    printf("v-hat (estimated transmitted vector):\n");
+    for (i=0; i<9; i++)
+        v_hat[i] = r[i] ^ e_hat[i];
+    print_bitstring(v_hat,72);
+    //print_bitstring(v,    72);
+
+    // compute errors between v, v_hat
+    unsigned int num_errors_encoded = count_bit_errors_array(v, v_hat, 9);
+    printf("decoding errors (encoded)  : %2u / 72\n", num_errors_encoded);
+
+    // compute estimated original message: (last 64 bits of encoded message)
+    for (i=0; i<9; i++)
+        m_hat[i] = v_hat[i+1];
+    printf("m-hat (estimated original vector):\n         ");
+    print_bitstring(m_hat,64);
+    //print_bitstring(m,    64);
+
+    // compute errors between m, m_hat
+    unsigned int num_errors_decoded = count_bit_errors_array(m, m_hat, 8);
+    printf("decoding errors (original) : %2u / 64\n", num_errors_decoded);
+
+    return 0;
+}
+
diff --git a/sandbox/fec_secded_punctured_test.c b/sandbox/fec_secded_punctured_test.c
new file mode 100644
index 0000000..2bc8b5b
--- /dev/null
+++ b/sandbox/fec_secded_punctured_test.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// punctured SEC-DED test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.internal.h"
+
+#define OUTPUT_FILENAME "fec_secded_punctured_test.m"
+
+void usage()
+{
+    printf("Usage: fec_secded_punctured_test\n");
+    printf("  h     : print usage/help\n");
+    printf("  p     : input rate, [1:8], default: 4\n");
+    printf("  s     : SNR start [dB], default: -5\n");
+    printf("  x     : SNR max [dB], default: 5\n");
+    printf("  n     : number of SNR steps, default: 21\n");
+    printf("  t     : number of trials, default: 1000\n");
+}
+
+// encode symbol
+void secded7264_encode_symbol_punctured(unsigned char * _sym_dec,
+                                        unsigned char * _sym_enc,
+                                        unsigned int    _p);
+
+// decode symbol
+void secded7264_decode_symbol_punctured(unsigned char * _sym_enc,
+                                        unsigned char * _sym_dec,
+                                        unsigned int    _p);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int p = 4;             // input size: p in [1,8]
+    float SNRdB_min =  0.0f;        // minimum SNR
+    float SNRdB_max = 10.0f;        // maximum SNR
+    unsigned int num_steps = 21;    // number of steps
+    unsigned int num_trials = 80000;
+
+    // get command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hp:s:x:n:t:")) != EOF){
+        switch (dopt) {
+        case 'h': usage(); return 0;
+        case 'p': p          = atoi(optarg);    break;
+        case 's': SNRdB_min  = atof(optarg);    break;
+        case 'x': SNRdB_max  = atof(optarg);    break;
+        case 'n': num_steps  = atoi(optarg);    break;
+        case 't': num_trials = atoi(optarg);    break;
+        default:
+            printf("error: %s, unknown option\n", argv[0]);
+            exit(-1);
+        }
+    }
+
+    // validate input
+    if (p < 1 || p >8) {
+        fprintf(stderr,"error: %s, p must be in [1,8]\n", argv[0]);
+        exit(1);
+    } else if (SNRdB_max <= SNRdB_min) {
+        fprintf(stderr,"error: %s, invalid SNR range\n", argv[0]);
+        exit(1);
+    } else if (num_steps < 1) {
+        fprintf(stderr,"error: %s, must have at least 1 SNR step\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int n = p*8;           // decoded message length (bits)
+    unsigned int k = (p+1)*8;       // encoded message length (bits)
+    float rate = (float)n / (float)k;
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_steps-1);
+
+    unsigned int i;
+    
+    // arrays
+    unsigned char m[p];     // original message [64 x 1]
+    unsigned char v[p+1];   // encoded/transmitted message
+    float complex y[k];     // received message with noise
+    unsigned char r[p+1];   // received vector (hard decision)
+    unsigned char m_hat[p]; // estimated original message
+
+#if 0
+    // test it out
+    for (i=0; i<p; i++)
+        m[i] = rand() & 0xff;
+
+    secded7264_encode_symbol_punctured(m,v,p);
+
+    // add error
+    v[0] ^= 0x01;
+
+    // decode
+    secded7264_decode_symbol_punctured(v,m_hat,p);
+
+    // print
+    printf("m:      ");
+    for (i=0; i<p; i++) { liquid_print_bitstring(m[i], 8); printf(" "); }
+    printf("\n");
+
+    printf("m-hat:  ");
+    for (i=0; i<p; i++) { liquid_print_bitstring(m_hat[i], 8); printf(" "); }
+    printf("\n");
+
+    // count errors
+    printf("decoded errors : %3u / %3u\n", count_bit_errors_array(m,m_hat,p), p*8);
+#else
+    // data arrays
+    unsigned int num_bit_errors[num_steps];
+
+    unsigned int t;
+    unsigned int s;
+    printf("  %8s %8s [%8s] %8s %12s %12s\n", "SNR [dB]", "Eb/N0", "trials", "# errs", "(BER)", "uncoded");
+    for (s=0; s<num_steps; s++) {
+        // compute SNR
+        float SNRdB = SNRdB_min + s*SNRdB_step;
+
+        // noise standard deviation
+        float sigma = powf(10.0f, -SNRdB/20.0f);
+
+        // reset error counter
+        num_bit_errors[s] = 0;
+
+        for (t=0; t<num_trials; t++) {
+            // generate original message signal
+            for (i=0; i<p; i++)
+                m[i] = rand() & 0xff;
+
+            // compute encoded message
+            secded7264_encode_symbol_punctured(m,v,p);
+
+            // expand to bits
+            for (i=0; i<p+1; i++) {
+                y[8*i+0] = (v[i] & 0x80) ? 1.0f : -1.0f;
+                y[8*i+1] = (v[i] & 0x40) ? 1.0f : -1.0f;
+                y[8*i+2] = (v[i] & 0x20) ? 1.0f : -1.0f;
+                y[8*i+3] = (v[i] & 0x10) ? 1.0f : -1.0f;
+                y[8*i+4] = (v[i] & 0x08) ? 1.0f : -1.0f;
+                y[8*i+5] = (v[i] & 0x04) ? 1.0f : -1.0f;
+                y[8*i+6] = (v[i] & 0x02) ? 1.0f : -1.0f;
+                y[8*i+7] = (v[i] & 0x01) ? 1.0f : -1.0f;
+            }
+
+            // add noise
+            for (i=0; i<k; i++)
+                y[i] += sigma*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+            // demodulate (hard decision)
+            for (i=0; i<p+1; i++) {
+                r[i] = 0x00;
+
+                r[i] |= ( crealf(y[8*i+0]) > 0 ) ? 0x80 : 0x00;
+                r[i] |= ( crealf(y[8*i+1]) > 0 ) ? 0x40 : 0x00;
+                r[i] |= ( crealf(y[8*i+2]) > 0 ) ? 0x20 : 0x00;
+                r[i] |= ( crealf(y[8*i+3]) > 0 ) ? 0x10 : 0x00;
+                r[i] |= ( crealf(y[8*i+4]) > 0 ) ? 0x08 : 0x00;
+                r[i] |= ( crealf(y[8*i+5]) > 0 ) ? 0x04 : 0x00;
+                r[i] |= ( crealf(y[8*i+6]) > 0 ) ? 0x02 : 0x00;
+                r[i] |= ( crealf(y[8*i+7]) > 0 ) ? 0x01 : 0x00;
+            }
+
+            // decode
+            secded7264_decode_symbol_punctured(r,m_hat,p);
+
+            // compute errors in decoded message signal
+            num_bit_errors[s] += count_bit_errors_array(m,m_hat,p);
+        }
+
+        // print results for this SNR step
+        printf("  %8.3f %8.3f [%8u] %8u %12.4e %12.4e\n",
+                SNRdB,
+                SNRdB - 10*log10f(rate),
+                n*num_trials,
+                num_bit_errors[s], (float)(num_bit_errors[s]) / (float)(num_trials*n),
+                0.5f*erfcf(1.0f/sigma));
+    }
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"r = n / k;\n");
+    fprintf(fid,"num_steps = %u;\n", num_steps);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"num_bit_trials = num_trials*n;\n");
+    for (i=0; i<num_steps; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"num_bit_errors(%6u) = %u;\n", i+1, num_bit_errors[i]);
+    }
+    fprintf(fid,"EbN0dB = SNRdB - 10*log10(r);\n");
+    fprintf(fid,"EbN0dB_bpsk = -15:0.5:40;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(EbN0dB_bpsk, 0.5*erfc(sqrt(10.^[EbN0dB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         EbN0dB,      num_bit_errors / num_bit_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f (%f-10*log10(r)) 1e-6 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('uncoded','coded',1);\n");
+    fprintf(fid,"xlabel('E_b/N_0 [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('BER vs. E_b/N_0 for (71,64,%u) r=%8.4f code');\n", 8*p, rate);
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+#endif
+
+    return 0;
+}
+
+
+// encode symbol
+void secded7264_encode_symbol_punctured(unsigned char * _sym_dec,
+                                        unsigned char * _sym_enc,
+                                        unsigned int    _p)
+{
+    unsigned int i;
+    
+    // compute parity
+    unsigned char m[8];
+    for (i=0; i<8; i++)
+        m[i] = (i < _p) ? _sym_dec[i] : 0x00;
+    unsigned char parity = fec_secded7264_compute_parity(m);
+
+    // copy input message
+    _sym_enc[0] = parity;
+    for (i=0; i<_p; i++)
+        _sym_enc[i+1] = _sym_dec[i];
+}
+
+// decode symbol
+void secded7264_decode_symbol_punctured(unsigned char * _sym_enc,
+                                        unsigned char * _sym_dec,
+                                        unsigned int    _p)
+{
+    unsigned int i;
+
+    // strip input, padding with zeros
+    unsigned char v[9];
+    for (i=0; i<9; i++)
+        v[i] = (i <= _p) ? _sym_enc[i] : 0x00;
+
+    // decode
+    unsigned char m_hat[8];
+    fec_secded7264_decode_symbol(v, m_hat);
+
+    // copy to output
+    for (i=0; i<_p; i++)
+        _sym_dec[i] = m_hat[i];
+}
+
diff --git a/sandbox/fec_spc2216_test.c b/sandbox/fec_spc2216_test.c
new file mode 100644
index 0000000..bd52600
--- /dev/null
+++ b/sandbox/fec_spc2216_test.c
@@ -0,0 +1,1007 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// SEC-DED(22,16) product code test (soft decoding)
+//  ________________ 
+// [            |   ]
+// [            |   ]
+// [   16 x 16  | 6 ]
+// [            |   ]
+// [____________|___]
+// [        6       ]
+// [________________]
+//
+// input:   16 x 16 bits = 256 bits = 32 bytes
+// output:  22 x 22 bits = 484 bits = 60 bytes + 4 bits (61 bytes)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_SPC2216 0
+
+#define OUTPUT_FILENAME "spc2216_ber_test.m"
+
+// encode message
+//  _msg_org    :   original message [size: 32 bytes]
+//  _msg_enc    :   encoded message  [size: 61 bytes]
+void spc2216_encode(unsigned char * _msg_org,
+                    unsigned char * _msg_enc);
+
+// decode message
+//  _msg_rec    :   received message [size: 61 bytes]
+//  _msg_dec    :   decoded message  [size: 32 bytes]
+void spc2216_decode(unsigned char * _msg_rec,
+                    unsigned char * _msg_dec);
+
+// pack message
+//  _m  :   input message   [size: 32 bytes]
+//  _pr :   row parities    [size: 16 bytes, @ 6 bits]
+//  _pc :   col parities    [size: 22 bytes, @ 6 bits]
+//  _v  :   output message  [size: 61 bytes]
+void spc2216_pack(unsigned char * _m,
+                  unsigned char * _pr,
+                  unsigned char * _pc,
+                  unsigned char * _v);
+
+// unpack message
+//  _v  :   input message   [size: 61 bytes]
+//  _pr :   row parities    [size: 16 bytes, @ 6 bits]
+//  _pc :   col parities    [size: 22 bytes, @ 6 bits]
+//  _m  :   output message  [size: 32 bytes]
+void spc2216_unpack(unsigned char * _v,
+                    unsigned char * _pr,
+                    unsigned char * _pc,
+                    unsigned char * _m);
+
+// transpose square message block (generic)
+//  _m  :   input message block [size: _n x _n bits, _n*_n/8 bytes]
+//  _n  :   matrix dimension (must be divisble by 8)
+void spc2216_transpose_block(unsigned char * _m,
+                             unsigned int    _n,
+                             unsigned char * _mT);
+
+// transpose message block and row parities
+//  _m  :   message block       [size: 16 x 16 bits, 32 bytes]
+//  _pr :   row parities        [size: 16 x  6 bits, 16 bytes]
+//  _w  :   transposed array    [size: 22 x 16 bits, 44 bytes]
+void spc2216_transpose_row(unsigned char * _m,
+                           unsigned char * _pr,
+                           unsigned char * _w);
+
+// transpose message block and row parities (reverse)
+//  _w  :   transposed array    [size: 22 x 16 bits, 44 bytes]
+//  _m  :   message block       [size: 16 x 16 bits, 32 bytes]
+//  _pr :   row parities        [size: 16 x  6 bits, 16 bytes]
+void spc2216_transpose_col(unsigned char * _w,
+                           unsigned char * _m,
+                           unsigned char * _pr);
+
+// print decoded block
+void spc2216_print_decoded(unsigned char * _m);
+
+// print encoded block
+void spc2216_print_encoded(unsigned char * _v);
+
+// print unpacked block
+void spc2216_print_unpacked(unsigned char * _m,
+                            unsigned char * _pr,
+                            unsigned char * _pc);
+
+// decode message (soft bits)
+//  _msg_rec        :   received soft message [size: 488 soft bits]
+//  _msg_dec_hard   :   decoded hard message  [size: 32 bytes]
+void spc2216_decode_soft(unsigned char * _msg_rec,
+                         unsigned char * _msg_dec_hard);
+
+// decode soft symbol
+//  _msg_rec        :   received soft bits [size: 22 soft bits]
+//  _msg_dec        :   decoded soft bits [size: 22 soft bits]
+//  _msg_dec_hard   :   decoded hard bits [size: 32 bytes]
+int spc2216_decode_sym_soft(unsigned char * _msg_rec,
+                            unsigned char * _msg_dec);
+
+void print_bitstring(unsigned char _x,
+                     unsigned char _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%2s", ((_x >> (_n-i-1)) & 1) ? "1" : ".");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+    unsigned int i;
+    
+    // error vector
+    unsigned char e[61];
+#if 0
+    for (i=0; i<61; i++)
+        e[i] = (rand() % 8) == 0 ? 1 << rand() % 7 : 0;
+#else
+    memset(e, 0x00, 61);
+    e[3] = 0x08;
+#endif
+
+    // original message [16 x 16 bits], 32 bytes
+    unsigned char m[32];
+
+    // derived values
+    unsigned char v[61];    // encoded/transmitted message
+    unsigned char r[61];    // received vector
+    unsigned char m_hat[32];// estimated original message
+
+    // generate random transmitted message
+#if 0
+    for (i=0; i<32; i++)
+        m[i] = rand() & 0xff;
+#else
+    for (i=0; i<32; i++) {
+        if (i < 16 && (i%2)==0)
+            m[i] = 0xff >> (i/2); //1 << 7-(i/2);
+        else if (i >= 16 && (i%2)==1)
+            m[i] = 1 << (7-((i-16-1)/2));
+        else
+            m[i] = 0x00;
+    }
+#endif
+    printf("m (original message):\n");
+    spc2216_print_decoded(m);
+
+    // encode
+    spc2216_encode(m, v);
+    printf("v (encoded message):\n");
+    spc2216_print_encoded(v);
+
+    printf("e (error message):\n");
+    spc2216_print_encoded(e);
+
+    // add errors
+    for (i=0; i<61; i++)
+        r[i] = v[i] ^ e[i];
+    printf("r (received message):\n");
+    spc2216_print_encoded(r);
+
+    // decode
+    spc2216_decode(r, m_hat);
+
+    // compute errors between m, m_hat
+    unsigned int num_errors_decoded = count_bit_errors_array(m, m_hat, 32);
+    printf("decoding errors (original) : %2u / 256\n", num_errors_decoded);
+
+    //
+    // test soft decoding
+    //
+#if 1
+    unsigned char msg_rec_soft[8*61];
+    unsigned char soft0 =  64;
+    unsigned char soft1 = 192;
+
+    // modulate with BPSK
+    for (i=0; i<61; i++) {
+        msg_rec_soft[8*i+0] = (r[i] & 0x80) ? soft1 : soft0;
+        msg_rec_soft[8*i+1] = (r[i] & 0x40) ? soft1 : soft0;
+        msg_rec_soft[8*i+2] = (r[i] & 0x20) ? soft1 : soft0;
+        msg_rec_soft[8*i+3] = (r[i] & 0x10) ? soft1 : soft0;
+        msg_rec_soft[8*i+4] = (r[i] & 0x08) ? soft1 : soft0;
+        msg_rec_soft[8*i+5] = (r[i] & 0x04) ? soft1 : soft0;
+        msg_rec_soft[8*i+6] = (r[i] & 0x02) ? soft1 : soft0;
+        msg_rec_soft[8*i+7] = (r[i] & 0x01) ? soft1 : soft0;
+    }
+
+    // add 'noise'...
+    for (i=0; i<6*61; i++) {
+        int LLR = msg_rec_soft[i] + 30*randnf();
+        if (LLR <   0) LLR =   0;
+        if (LLR > 255) LLR = 255;
+        msg_rec_soft[i] = LLR;
+    }
+
+    // test soft-decision decoding
+    memset(m_hat, 0x00, 32);
+    spc2216_decode_soft(msg_rec_soft, m_hat);
+
+    // compute errors between m, m_hat
+    num_errors_decoded = count_bit_errors_array(m, m_hat, 32);
+    printf("soft decoding errors (original) : %2u / 256\n", num_errors_decoded);
+#else
+    // 1 1 . . 1 1 |. . 1 1 1 1 1 1 . . . . . . . .
+    unsigned char msg_rec_soft[22] = {
+        255, 255, 0,   0,   255, 255,
+        0,   0,   255, 255, 255, 255, 255, 255,
+        0,   0,   0,   0,   0,   0,   0,   0,
+    };
+
+    // add noise
+    for (i=0; i<22; i++) {
+        int bit = msg_rec_soft[i] > 128 ? 192 : 64;
+        bit += 30*randnf();
+        if (bit < 0)   bit = 0;
+        if (bit > 255) bit = 255;
+        msg_rec_soft[i] = (unsigned char)bit;
+    }
+    int rc = spc2216_decode_sym_soft(msg_rec_soft, m_hat);
+#endif
+
+    // 
+    // run SNR trials
+    //
+    float SNRdB_min = -1.0f;                // signal-to-noise ratio (minimum)
+    float SNRdB_max =  7.0f;                // signal-to-noise ratio (maximum)
+    unsigned int num_snr = 33;              // number of SNR steps
+    unsigned int num_trials=10000;          // number of trials
+
+    // arrays
+    float complex sym_rec[8*61];            // received BPSK symbols
+    unsigned int bit_errors_hard[num_snr];
+    unsigned int bit_errors_soft[num_snr];
+
+    unsigned int n_enc = 61;
+    unsigned char msg_org[32];
+    unsigned char msg_enc[61];
+    unsigned char msg_cor[61];      // corrupted message (hard bits)
+    unsigned char msg_LLR[8*61];    // corrupted message (soft-bit log-likelihood ratio)
+    unsigned char msg_dec_hard[32];
+    unsigned char msg_dec_soft[32];
+
+    // set up parameters
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1);
+
+    printf("  %8s %8s [%8s] %8s %12s %8s %12s %12s\n",
+            "SNR [dB]", "Eb/N0", "trials", "hard", "(hard BER)", "soft", "(soft BER)", "uncoded");
+    unsigned int s;
+    for (s=0; s<num_snr; s++) {
+        // compute SNR for this level
+        float SNRdB = SNRdB_min + s*SNRdB_step; // SNR in dB for this round
+        float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
+
+        // reset results
+        bit_errors_hard[s] = 0;
+        bit_errors_soft[s] = 0;
+
+        unsigned int t;
+        for (t=0; t<num_trials; t++) {
+            // generate data
+            for (i=0; i<32; i++)
+                msg_org[i] = rand() & 0xff;
+
+            // encode message
+            spc2216_encode(msg_org, msg_enc);
+
+            // modulate with BPSK
+            for (i=0; i<n_enc; i++) {
+                sym_rec[8*i+0] = (msg_enc[i] & 0x80) ? 1.0f : -1.0f;
+                sym_rec[8*i+1] = (msg_enc[i] & 0x40) ? 1.0f : -1.0f;
+                sym_rec[8*i+2] = (msg_enc[i] & 0x20) ? 1.0f : -1.0f;
+                sym_rec[8*i+3] = (msg_enc[i] & 0x10) ? 1.0f : -1.0f;
+                sym_rec[8*i+4] = (msg_enc[i] & 0x08) ? 1.0f : -1.0f;
+                sym_rec[8*i+5] = (msg_enc[i] & 0x04) ? 1.0f : -1.0f;
+                sym_rec[8*i+6] = (msg_enc[i] & 0x02) ? 1.0f : -1.0f;
+                sym_rec[8*i+7] = (msg_enc[i] & 0x01) ? 1.0f : -1.0f;
+            }
+
+            // add noise
+            for (i=0; i<8*n_enc; i++)
+                sym_rec[i] += nstd*(randnf() + _Complex_I*randf())*M_SQRT1_2;
+
+            // convert to hard bits (hard decoding)
+            for (i=0; i<n_enc; i++) {
+                msg_cor[i] = 0x00;
+
+                msg_cor[i] |= crealf(sym_rec[8*i+0]) > 0.0f ? 0x80 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+1]) > 0.0f ? 0x40 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+2]) > 0.0f ? 0x20 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+3]) > 0.0f ? 0x10 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+4]) > 0.0f ? 0x08 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+5]) > 0.0f ? 0x04 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+6]) > 0.0f ? 0x02 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+7]) > 0.0f ? 0x01 : 0x00;
+            }
+            spc2216_decode(msg_cor, msg_dec_hard);
+
+            // convert to approximate LLR (soft decoding)
+            for (i=0; i<8*n_enc; i++) {
+                int LLR = (int)( 256*(crealf(sym_rec[i])*0.5f + 0.5f) );
+                if (LLR < 0)   LLR = 0;
+                if (LLR > 255) LLR = 255;
+                msg_LLR[i] = (unsigned char) LLR;
+            }
+            spc2216_decode_soft(msg_LLR, msg_dec_soft);
+            
+            // tabulate results
+            bit_errors_hard[s] += count_bit_errors_array(msg_org, msg_dec_hard, 32);
+            bit_errors_soft[s] += count_bit_errors_array(msg_org, msg_dec_soft, 32);
+        }
+
+        // print results for this SNR step
+        float rate = 32. / 61.; // true rate
+        printf("  %8.3f %8.3f [%8u] %8u %12.4e %8u %12.4e %12.4e\n",
+                SNRdB,
+                SNRdB - 10*log10f(rate),
+                8*32*num_trials,
+                bit_errors_hard[s], (float)(bit_errors_hard[s]) / (float)(num_trials*32*8),
+                bit_errors_soft[s], (float)(bit_errors_soft[s]) / (float)(num_trials*32*8),
+                0.5f*erfcf(1.0f/nstd));
+    }
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;    %% frame size [bytes]\n", 32);
+    fprintf(fid,"k = %u;    %% encoded frame size [bytes]\n", 61);
+    fprintf(fid,"r = n / k; %% true rate\n");
+    fprintf(fid,"num_snr = %u;\n", num_snr);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"num_bit_trials = num_trials*n*8;\n");
+    fprintf(fid,"bit_errors_hard = zeros(1,num_snr);\n");
+    fprintf(fid,"bit_errors_soft = zeros(1,num_snr);\n");
+    for (i=0; i<num_snr; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"bit_errors_hard(%6u) = %u;\n", i+1, bit_errors_hard[i]);
+        fprintf(fid,"bit_errors_soft(%6u) = %u;\n", i+1, bit_errors_soft[i]);
+    }
+    fprintf(fid,"EbN0dB = SNRdB - 10*log10(r);\n");
+    fprintf(fid,"EbN0dB_bpsk = -15:0.5:40;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(EbN0dB_bpsk, 0.5*erfc(sqrt(10.^[EbN0dB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         EbN0dB,      bit_errors_hard / num_bit_trials + 1e-12,  '-x',\n");
+    fprintf(fid,"         EbN0dB,      bit_errors_soft / num_bit_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f (%f-10*log10(r)) 1e-6 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('uncoded','hard','soft',1);\n");
+    fprintf(fid,"xlabel('E_b/N_0 [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('BER vs. E_b/N_0 for SPC(22,16)');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+void spc2216_encode(unsigned char * _msg_org,
+                    unsigned char * _msg_enc)
+{
+    unsigned int i;
+
+    unsigned char parity_row[16];
+    unsigned char parity_col[22];
+
+    // compute row parities
+    for (i=0; i<16; i++)
+        parity_row[i] = fec_secded2216_compute_parity(&_msg_org[2*i]);
+
+    // compute column parities
+    unsigned char w[44];
+    spc2216_transpose_row(_msg_org, parity_row, w);
+    for (i=0; i<22; i++)
+        parity_col[i] = fec_secded2216_compute_parity(&w[2*i]);
+
+    // pack encoded message
+    spc2216_pack(_msg_org, parity_row, parity_col, _msg_enc);
+}
+
+void spc2216_decode(unsigned char * _msg_rec,
+                    unsigned char * _msg_dec)
+{
+    unsigned char parity_row[16];
+    unsigned char parity_col[22];
+    unsigned char m_hat[32];
+    unsigned char w[44];
+
+    // unpack encoded message
+    spc2216_unpack(_msg_rec, parity_row, parity_col, m_hat);
+
+    // decode
+    unsigned int i;
+    unsigned char sym_enc[3];   // encoded 22-bit message
+    unsigned char e_hat[3];     // estimated error vector
+    
+    // transpose
+    spc2216_transpose_row(m_hat, parity_row, w);
+
+    // compute syndromes on columns and decode
+    for (i=0; i<22; i++) {
+        sym_enc[0] = parity_col[i];
+        sym_enc[1] = w[2*i+0];
+        sym_enc[2] = w[2*i+1];
+
+#if DEBUG_SPC2216
+        int syndrome_flag = fec_secded2216_estimate_ehat(sym_enc, e_hat);
+#else
+        // same as above, but do not declare 'syndrome_flag' variable
+        // (rid compiler of unused variable warning)
+        fec_secded2216_estimate_ehat(sym_enc, e_hat);
+#endif
+
+        // apply error vector estimate to appropriate arrays
+        parity_col[i] ^= e_hat[0];
+        w[2*i+0]      ^= e_hat[1];
+        w[2*i+1]      ^= e_hat[2];
+
+#if DEBUG_SPC2216
+        // print encoded symbol
+        printf("%3u:", i);
+        print_bitstring(w[2*i+0], 8);
+        print_bitstring(w[2*i+1], 8);
+        printf(" |");
+        print_bitstring(parity_col[i], 6);
+        if (syndrome_flag == 0) {
+            printf(" (no errors detected)\n");
+        } else if (syndrome_flag == 1) {
+            printf(" (one error detected and corrected!)\n");
+        } else {
+            printf(" (multiple errors detected)\n");
+        }
+#endif
+    }
+#if DEBUG_SPC2216
+    printf("******** transposed: **********\n");
+    for (i=0; i<22; i++) {
+        printf("    ");
+        print_bitstring(w[2*i+0], 8);
+        print_bitstring(w[2*i+1], 8);
+        printf("\n");
+        if (i==15)
+            printf("    ---------------------------------\n");
+    }
+#endif
+
+    // transpose back
+    spc2216_transpose_col(w, m_hat, parity_row);
+
+    // compute syndromes on rows and decode
+    unsigned int num_uncorrected_errors = 0;
+    for (i=0; i<16; i++) {
+        sym_enc[0] = parity_row[i];
+        sym_enc[1] = m_hat[2*i+0];
+        sym_enc[2] = m_hat[2*i+1];
+        int syndrome_flag = fec_secded2216_estimate_ehat(sym_enc, e_hat);
+
+#if DEBUG_SPC2216
+        if (syndrome_flag == 0) {
+            printf("%3u : no errors detected\n", i);
+        } else if (syndrome_flag == 1) {
+            printf("%3u : one error detected and corrected!\n", i);
+        } else {
+            printf("%3u : multiple errors detected\n", i);
+        }
+#endif
+
+        if (syndrome_flag == 2)
+            num_uncorrected_errors++;
+
+        // apply error vector estimate to appropriate arrays
+        parity_col[i] ^= e_hat[0];
+        m_hat[2*i+0]  ^= e_hat[1];
+        m_hat[2*i+1]  ^= e_hat[2];
+    }
+
+    //printf("number of uncorrected errors: %u\n", num_uncorrected_errors);
+    
+    // copy decoded message to output
+    memmove(_msg_dec, m_hat, 32*sizeof(unsigned char));
+}
+
+// pack message
+//  _m  :   input message   [size: 32 bytes]
+//  _pr :   row parities    [size: 16 bytes, @ 6 bits]
+//  _pc :   col parities    [size: 22 bytes, @ 6 bits]
+//  _v  :   output message  [size: 61 bytes]
+void spc2216_pack(unsigned char * _m,
+                  unsigned char * _pr,
+                  unsigned char * _pc,
+                  unsigned char * _v)
+{
+    // copy input message to begining of encoded vector
+    memmove(_v, _m, 32*sizeof(unsigned char));
+
+    // pack row parities
+    unsigned int i;
+    unsigned int k=0;
+    for (i=0; i<16; i++) {
+        liquid_pack_array(&_v[32], 29, k, 6, _pr[i]);
+        k += 6;
+    }
+
+    // pack column parities
+    for (i=0; i<22; i++) {
+        liquid_pack_array(&_v[32], 29, k, 6, _pc[i]);
+        k += 6;
+    }
+}
+
+// unpack message
+//  _v  :   input message   [size: 61 bytes]
+//  _pr :   row parities    [size: 16 bytes, @ 6 bits]
+//  _pc :   col parities    [size: 22 bytes, @ 6 bits]
+//  _m  :   output message  [size: 32 bytes]
+void spc2216_unpack(unsigned char * _v,
+                    unsigned char * _pr,
+                    unsigned char * _pc,
+                    unsigned char * _m)
+{
+    // copy input message to begining of encoded vector
+    memmove(_m, _v, 32*sizeof(unsigned char));
+
+    // unpack row parities
+    unsigned int i;
+    unsigned int k=0;
+    for (i=0; i<16; i++) {
+        //liquid_unpack_array(&_pr[k], &_v[32*8+k], 
+        liquid_unpack_array(&_v[32], 29, k, 6, &_pr[i]);
+        k += 6;
+    }
+
+    // unpack column parities
+    for (i=0; i<22; i++) {
+        //liquid_unpack_array(&_pr[k], &_v[32*8+k], 
+        liquid_unpack_array(&_v[32], 29, k, 6, &_pc[i]);
+        k += 6;
+    }
+}
+
+// transpose message block and row parities
+//  _m  :   message block       [size: 16 x 16 bits, 32 bytes]
+//  _pr :   row parities        [size: 16 x  6 bits, 16 bytes]
+//  _w  :   transposed array    [size: 22 x 16 bits, 44 bytes]
+void spc2216_transpose_row(unsigned char * _m,
+                           unsigned char * _pr,
+                           unsigned char * _w)
+{
+    // transpose main input message, store in first 32
+    // bytes of _w array
+    spc2216_transpose_block(_m, 16, _w);
+
+    // transpose row parities
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<6; i++) {
+        unsigned char mask = 1 << (6-i-1);
+        unsigned char w0 = 0;
+        unsigned char w1 = 0;
+        for (j=0; j<8; j++) {
+            w0 |= (_pr[j  ] & mask) ? 1 << (8-j-1) : 0;
+            w1 |= (_pr[j+8] & mask) ? 1 << (8-j-1) : 0;
+        }
+
+        _w[32 + 2*i + 0] = w0;
+        _w[32 + 2*i + 1] = w1;
+    }
+
+#if 0
+    printf("transposed:\n");
+    for (i=0; i<22; i++) {
+        printf("    ");
+        print_bitstring(_w[2*i+0], 8);
+        print_bitstring(_w[2*i+1], 8);
+        printf("\n");
+        if (i==15)
+            printf("    ---------------------------------\n");
+    }
+#endif
+}
+
+// transpose message block and row parities (reverse)
+//  _w  :   transposed array    [size: 22 x 16 bits, 44 bytes]
+//  _m  :   message block       [size: 16 x 16 bits, 32 bytes]
+//  _pr :   row parities        [size: 16 x  6 bits, 16 bytes]
+void spc2216_transpose_col(unsigned char * _w,
+                           unsigned char * _m,
+                           unsigned char * _pr)
+{
+    // transpose main message block from first 32
+    // bytes of _w array
+    spc2216_transpose_block(_w, 16, _m);
+
+    // transpose row parities
+    unsigned int i; // input row
+    unsigned int j; // input column (rem 8)
+
+    // initialize to zero
+    for (i=0; i<16; i++)
+        _pr[i] = 0;
+
+    for (i=0; i<6; i++) {
+        unsigned char mask = 1 << (6-i-1);
+        for (j=0; j<8; j++) {
+            _pr[j  ] |= (_w[32 + 2*i + 0] & (1 << (8-j-1)) ) ? mask : 0;
+            _pr[j+8] |= (_w[32 + 2*i + 1] & (1 << (8-j-1)) ) ? mask : 0;
+        }
+    }
+
+#if 0
+    printf("transposed (reverse):\n");
+    for (i=0; i<16; i++) {
+        printf("    ");
+        print_bitstring(_m[2*i+0], 8);
+        print_bitstring(_m[2*i+1], 8);
+        printf(" ");
+        print_bitstring(_pr[i], 6);
+        printf("\n");
+    }
+#endif
+}
+
+
+// transpose square message block (generic)
+//  _m  :   input message block [size: _n x _n bits, _n*_n/8 bytes]
+//  _n  :   matrix dimension (must be divisble by 8)
+void spc2216_transpose_block(unsigned char * _m,
+                             unsigned int    _n,
+                             unsigned char * _mT)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    // ensure that _n is divisible by 8
+    if (_n % 8) {
+        fprintf(stderr,"error: spc2216_transpose_block(), number of rows must be divisible by 8\n");
+        exit(1);
+    }
+    unsigned int c = _n / 8;    // number of byte columns
+    
+    unsigned char w0;
+    unsigned char w1;
+    for (k=0; k<c; k++) {
+        for (i=0; i<8; i++) {
+            unsigned char mask = 1 << (8-i-1);
+            w0 = 0;
+            w1 = 0;
+            for (j=0; j<8; j++) {
+                w0 |= (_m[2*j + c*8*k + 0] & mask) ? 1 << (8-j-1) : 0;
+                w1 |= (_m[2*j + c*8*k + 1] & mask) ? 1 << (8-j-1) : 0;
+            }
+
+            _mT[2*i       + k] = w0;
+            _mT[2*i + c*8 + k] = w1;
+        }
+    }
+}
+
+// print decoded block
+void spc2216_print_decoded(unsigned char * _m)
+{
+    unsigned int i;
+    
+    for (i=0; i<16; i++) {
+        printf("    ");
+        print_bitstring(_m[2*i+0],8);
+        print_bitstring(_m[2*i+1],8);
+        printf("\n");
+    }
+}
+
+// print encoded block
+void spc2216_print_encoded(unsigned char * _v)
+{
+    unsigned char parity_row[16];
+    unsigned char parity_col[22];
+    unsigned char m[32];
+
+    // unpack encoded message
+    spc2216_unpack(_v, parity_row, parity_col, m);
+
+    // print unpacked version
+#if 0
+    spc2216_print_decoded(m);
+    
+    unsigned int i;
+    printf("rows:\n");
+    for (i=0; i<16; i++) {
+        printf("  %3u : ", i);
+        print_bitstring(parity_row[i], 6);
+        printf("\n");
+    }
+    printf("cols:\n");
+    for (i=0; i<22; i++) {
+        printf("  %3u : ", i);
+        print_bitstring(parity_col[i], 6);
+        printf("\n");
+    }
+#else
+    spc2216_print_unpacked(m, parity_row, parity_col);
+#endif
+}
+
+// print unpacked block
+void spc2216_print_unpacked(unsigned char * _m,
+                            unsigned char * _pr,
+                            unsigned char * _pc)
+{
+    unsigned int i;
+    
+    for (i=0; i<16; i++) {
+        printf("    ");
+        print_bitstring(_m[2*i+0],8);
+        print_bitstring(_m[2*i+1],8);
+        printf(" |");
+        print_bitstring(_pr[i], 6);
+        printf("\n");
+    }
+    printf("    ---------------------------------+-------------\n");
+
+    // print column parities
+    unsigned int j;
+    for (j=0; j<6; j++) {
+        printf("    ");
+        for (i=0; i<22; i++) {
+            printf("%2s", ((_pc[i] >> (6-j-1)) & 0x01) ? "1" : ".");
+
+            if (i==15) printf("  ");
+        }
+        printf("\n");
+    }
+}
+
+// decode message (soft bits)
+//  _msg_rec        :   received soft message [size: 488 soft bits]
+//  _msg_dec_hard   :   decoded hard message  [size: 32 bytes]
+void spc2216_decode_soft(unsigned char * _msg_rec,
+                         unsigned char * _msg_dec_hard)
+{
+    unsigned char w[256];           // 256 = 16 x 16 bits
+    unsigned char parity_row[96];   //  96 = 16 rows @ 6 bits
+    unsigned char parity_col[132];  // 132 = 22 cols @ 6 bits
+    
+    // unpack encoded soft bits:
+    memmove(w,           _msg_rec,         256);
+    memmove(parity_row, &_msg_rec[256],     96);
+    memmove(parity_col, &_msg_rec[256+96], 132);
+
+    unsigned int i;
+    unsigned int j;
+#if 0
+    // print...
+    for (i=0; i<16; i++) {
+        printf("    ");
+        for (j=0; j<16; j++)
+            printf("%2s", w[16*i+j] > 128 ? "1" : ".");
+        printf(" |");
+        for (j=0; j<6; j++)
+            printf("%2s", parity_row[6*i+j] > 128 ? "1" : ".");
+        printf("\n");
+    }
+    printf("    ---------------------------------+-------------\n");
+    for (i=0; i<6; i++) {
+        printf("    ");
+        for (j=0; j<22; j++)
+            printf("%2s", parity_col[6*j + i] > 128 ? "1" : ".");
+        printf("\n");
+    }
+#endif
+
+    // 
+    unsigned char sym_rec[22];  // encoded 22-bit message
+    unsigned char sym_dec[22];  // decoded 22-bit message
+    int syndrome_flag;
+
+    // TODO : run multiple iterations...
+    unsigned int n;
+    for (n=0; n<6; n++) {
+#if DEBUG_SPC2216
+        printf("\n");
+        // print soft values
+        for (i=0; i<16; i++) {
+            for (j=0; j<16; j++)
+                printf("%3u ", w[16*i+j]);
+            printf("\n");
+        }
+#endif
+        // compute syndromes on columns and run soft decoder
+        //printf("columns (w):\n");
+        for (i=0; i<16; i++) {
+            // extract encoded symbol
+            for (j=0; j<6; j++)  sym_rec[j  ] = parity_col[j + 6*i];
+            for (j=0; j<16; j++) sym_rec[j+6] = w[16*j + i];
+
+            // run soft decoder
+            syndrome_flag = spc2216_decode_sym_soft(sym_rec, sym_dec);
+
+            // replace decoded symbol
+            for (j=0; j<6; j++)  parity_col[j + 6*i] = sym_dec[j];
+            for (j=0; j<16; j++) w[16*j + i]         = sym_dec[j+6];
+        }
+
+        // compute syndromes on row parities and run soft decoder
+        //printf("row parities:\n");
+        for (i=0; i<6; i++) {
+            // extract encoded symbol
+            for (j=0; j<6; j++)  sym_rec[j  ] = parity_col[j + 6*(i+16)];
+            for (j=0; j<16; j++) sym_rec[j+6] = parity_row[6*j + i];
+
+            // run soft decoder
+            syndrome_flag = spc2216_decode_sym_soft(sym_rec, sym_dec);
+
+            // replace decoded symbol
+            for (j=0; j<6; j++)  parity_col[j + 6*(i+16)] = sym_rec[j];
+            for (j=0; j<16; j++) parity_row[6*j + i]      = sym_rec[j+6];
+        }
+
+#if DEBUG_SPC2216
+        printf("...\n");
+        // print soft values
+        for (i=0; i<16; i++) {
+            for (j=0; j<16; j++)
+                printf("%3u ", w[16*i+j]);
+            printf("\n");
+        }
+#endif
+        // compute syndromes on rows and run soft decoder
+        //printf("rows:\n");
+        unsigned int num_errors = 0;
+        for (i=0; i<16; i++) {
+            // extract encoded symbol
+            for (j=0; j<6; j++)  sym_rec[j  ] = parity_row[6*i + j];
+            for (j=0; j<16; j++) sym_rec[j+6] = w[16*i + j];
+
+            // run soft decoder
+            syndrome_flag = spc2216_decode_sym_soft(sym_rec, sym_dec);
+            num_errors += syndrome_flag == 0 ? 0 : 1;
+
+            // replace decoded symbol
+            for (j=0; j<6; j++)  parity_row[6*i + j] = sym_rec[j];
+            for (j=0; j<16; j++) w[16*i + j]         = sym_rec[j+6];
+        }
+
+        //printf("%3u, detected %u soft decoding errors\n", n, num_errors);
+        if (num_errors == 0)
+            break;
+    }
+
+    // hard-decision decoding
+    for (i=0; i<16; i++) {
+        _msg_dec_hard[2*i+0] = 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 0] > 128 ? 0x80 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 1] > 128 ? 0x40 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 2] > 128 ? 0x20 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 3] > 128 ? 0x10 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 4] > 128 ? 0x08 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 5] > 128 ? 0x04 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 6] > 128 ? 0x02 : 0;
+        _msg_dec_hard[2*i+0] |= w[16*i+ 7] > 128 ? 0x01 : 0;
+
+        _msg_dec_hard[2*i+1] = 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+ 8] > 128 ? 0x80 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+ 9] > 128 ? 0x40 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+10] > 128 ? 0x20 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+11] > 128 ? 0x10 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+12] > 128 ? 0x08 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+13] > 128 ? 0x04 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+14] > 128 ? 0x02 : 0;
+        _msg_dec_hard[2*i+1] |= w[16*i+15] > 128 ? 0x01 : 0;
+    }
+
+    // print decoded block
+    //spc2216_print_decoded(_msg_dec_hard);
+}
+
+// decode soft symbol
+//  _msg_rec    :   received soft bits [size: 22 soft bits]
+//  _msg_dec    :   decoded soft bits [size: 22 soft bits]
+//  return value:   syndrome flag
+int spc2216_decode_sym_soft(unsigned char * _msg_rec,
+                            unsigned char * _msg_dec)
+{
+    // pack...
+    unsigned char sym_enc[3] = {0, 0, 0};
+    unsigned char e_hat[3];
+    unsigned int i;
+
+#if DEBUG_SPC2216
+    printf(" msg_rec:");
+    for (i=0; i<22; i++)
+        printf("%3u,", _msg_rec[i]);
+    printf("\n");
+#endif
+
+    for (i=0; i<6; i++) {
+        sym_enc[0] <<= 1;
+        sym_enc[0] |= _msg_rec[i] > 128 ? 1 : 0;
+    }
+
+    for (i=0; i<8; i++) {
+        sym_enc[1] <<= 1;
+        sym_enc[2] <<= 1;
+        sym_enc[1] |= _msg_rec[ 6 + i] > 128 ? 1 : 0;
+        sym_enc[2] |= _msg_rec[14 + i] > 128 ? 1 : 0;
+    }
+        
+    int syndrome_flag = fec_secded2216_estimate_ehat(sym_enc, e_hat);
+
+#if DEBUG_SPC2216
+    if (syndrome_flag == 0) {
+        printf(" (no errors detected)\n");
+    } else if (syndrome_flag == 1) {
+        printf(" (one error detected and corrected!)\n");
+    } else {
+        printf(" (multiple errors detected)\n");
+    }
+
+    printf(" input  : ");
+    print_bitstring(sym_enc[0], 6);
+    printf("|");
+    print_bitstring(sym_enc[1], 8);
+    print_bitstring(sym_enc[2], 8);
+    printf("\n");
+#endif
+
+    // apply error vector estimate to appropriate arrays
+    sym_enc[0] ^= e_hat[0];
+    sym_enc[1] ^= e_hat[1];
+    sym_enc[2] ^= e_hat[2];
+    
+#if DEBUG_SPC2216
+    printf(" output : ");
+    print_bitstring(sym_enc[0], 6);
+    printf("|");
+    print_bitstring(sym_enc[1], 8);
+    print_bitstring(sym_enc[2], 8);
+    printf("\n");
+#endif
+
+    // unpack...
+    unsigned char sym_dec_soft[22];
+    for (i=0; i<6; i++) {
+        sym_dec_soft[i] = sym_enc[0] & (1 << (6-i-1)) ? 255 : 0;
+    }
+    for (i=0; i<8; i++) {
+        sym_dec_soft[ 6 + i] = sym_enc[1] & (1 << (8-i-1)) ? 255 : 0;
+        sym_dec_soft[14 + i] = sym_enc[2] & (1 << (8-i-1)) ? 255 : 0;
+    }
+
+    // combine...
+    for (i=0; i<22; i++) {
+        int delta = (int)sym_dec_soft[i] - 128;
+        //_msg_dec[i] = 0.5*_msg_rec[i] + 0.5*sym_dec_soft[i];
+
+        int bit = _msg_rec[i] + 0.2*delta;
+        if (bit <   0) bit =   0;
+        if (bit > 255) bit = 255;
+        _msg_dec[i] = (unsigned char) bit;
+    }
+
+#if DEBUG_SPC2216
+    printf(" msg_dec:");
+    for (i=0; i<22; i++)
+        printf("%3u,", _msg_dec[i]);
+    printf("\n");
+#endif
+    
+    return syndrome_flag;
+}
+
diff --git a/sandbox/fec_sumproduct_test.c b/sandbox/fec_sumproduct_test.c
new file mode 100644
index 0000000..7f526e4
--- /dev/null
+++ b/sandbox/fec_sumproduct_test.c
@@ -0,0 +1,73 @@
+//
+// fec_sumproduct_test.c
+//
+// Test soft decoding of LDPC codes using internal sum-product
+// algorithm.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+int main(int argc, char*argv[])
+{
+    // parity check matrix
+    unsigned char Hb[32] = {
+        1, 1, 1, 0, 0, 0, 0, 0,
+        0, 0, 0, 1, 1, 1, 0, 0,
+        1, 0, 0, 1, 0, 0, 1, 0,
+        0, 1, 0, 0, 1, 0, 0, 1};
+
+    // convert H to sparse matrix
+    smatrixb H = smatrixb_create_array(Hb, 4, 8);
+
+    // transmitted codeword
+    unsigned char c[8] = {
+        1, 0, 1, 0, 1, 1, 1, 1};
+
+    // received message
+    float y[8] = {0.2, 0.2, -0.9, 0.6, 0.5, -1.1, -0.4, -1.2};
+    unsigned int i;
+
+    // noise standard deviation
+    float sigma = sqrtf(0.5f);
+
+    unsigned int m = 4;
+    unsigned int n = 8;
+
+    // convert received signal to LLR
+    float LLR[n];
+    for (i=0; i<n; i++)
+        LLR[i] = 2.0f * y[i] / (sigma*sigma);
+
+    unsigned int max_iterations = 10;
+    unsigned char c_hat[n];
+
+    // run internal sum-product algorithm
+    int parity_pass = fec_sumproduct(m, n, H, LLR, c_hat, max_iterations);
+
+    // compute errors and print results
+    unsigned int num_errors = 0;
+    for (i=0; i<n; i++)
+        num_errors += (c[i] == c_hat[i]) ? 0 : 1;
+
+    printf("\nresults:\n");
+
+    printf("c    :");
+    for (i=0; i<n; i++)
+        printf(" %1u", c[i]);
+    printf("\n");
+
+    printf("c_hat:");
+    for (i=0; i<n; i++)
+        printf(" %1u", c_hat[i]);
+    printf("\n");
+
+    printf("parity : %s\n", parity_pass ? "pass" : "FAIL");
+    printf("num errors: %u / %u\n", num_errors, n);
+
+    return 0;
+}
+
diff --git a/sandbox/fecsoft_ber_test.c b/sandbox/fecsoft_ber_test.c
new file mode 100644
index 0000000..3f66082
--- /dev/null
+++ b/sandbox/fecsoft_ber_test.c
@@ -0,0 +1,232 @@
+// 
+// fecsoft_ber_test.c
+//
+// Simulate error rate using soft vs. hard decoding
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <complex.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "fecsoft_ber_test.m"
+
+void usage()
+{
+    printf("fec_ber\n");
+    printf("  Simulates soft decoding\n");
+    printf("options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  s     : SNR start [dB], default: -2\n");
+    printf("  x     : SNR max [dB], default: 13\n");
+    printf("  n     : number of SNR steps, default: 16\n");
+    printf("  t     : number of trials, default: 800\n");
+    printf("  f     : frame size, default: 64\n");
+    printf("  c     : coding scheme, (h74 default):\n");
+    liquid_print_fec_schemes();
+}
+
+int main(int argc, char *argv[]) {
+    // set initial seed to random
+    srand(time(NULL));
+
+    // options
+    unsigned int n = 64;                    // frame size (bytes)
+    float SNRdB_min = -2.0f;                // signal-to-noise ratio (minimum)
+    float SNRdB_max = 13.0f;                // signal-to-noise ratio (maximum)
+    unsigned int num_snr = 16;              // number of SNR steps
+    unsigned int num_trials=800;            // number of trials
+    fec_scheme fs = LIQUID_FEC_HAMMING74;   // error-correcting scheme
+
+    // get command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhs:x:n:t:f:c:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 's': SNRdB_min = atof(optarg);     break;
+        case 'x': SNRdB_max = atof(optarg);     break;
+        case 'n': num_snr = atoi(optarg);       break;
+        case 't': num_trials = atoi(optarg);    break;
+        case 'f': n = atoi(optarg);             break;
+        case 'c':
+            fs = liquid_getopt_str2fec(optarg);
+            if (fs == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported fec scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    printf("dec msg len : %u\n", n);
+    printf("enc msg len : %u\n", n_enc);
+    float rate = (float)n / (float)n_enc;
+    unsigned char msg_org[n];            // original data message
+    unsigned char msg_enc[n_enc];        // encoded data message
+    float complex sym_rec[8*n_enc];      // received BPSK symbols
+    unsigned char msg_cor_soft[8*n_enc]; // corrupted data message (soft bits)
+    unsigned char msg_cor_hard[n_enc];   // corrupted data message (hard bits)
+    unsigned char msg_dec_soft[n];       // decoded data message (soft bits)
+    unsigned char msg_dec_hard[n];       // decoded data message (soft bits)
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    fec_print(q);
+
+    unsigned int bit_errors_soft[num_snr];
+    unsigned int bit_errors_hard[num_snr];
+
+    //
+    // set up parameters
+    //
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1);
+
+    // 
+    // start trials
+    //
+    
+    printf("  %8s %8s [%8s] %8s %12s %8s %12s %12s\n",
+            "SNR [dB]", "Eb/N0", "trials", "soft", "(BER)", "hard", "(BER)", "uncoded");
+    unsigned int s;
+    for (s=0; s<num_snr; s++) {
+        // compute SNR for this level
+        float SNRdB = SNRdB_min + s*SNRdB_step; // SNR in dB for this round
+        float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
+
+        // reset results
+        bit_errors_soft[s] = 0;
+        bit_errors_hard[s] = 0;
+
+        unsigned int t;
+        for (t=0; t<num_trials; t++) {
+            // generate data
+            for (i=0; i<n; i++)
+                msg_org[i] = rand() & 0xff;
+
+            // encode message
+            fec_encode(q, n, msg_org, msg_enc);
+
+            // modulate with BPSK
+            for (i=0; i<n_enc; i++) {
+                sym_rec[8*i+0] = (msg_enc[i] & 0x80) ? 1.0f : -1.0f;
+                sym_rec[8*i+1] = (msg_enc[i] & 0x40) ? 1.0f : -1.0f;
+                sym_rec[8*i+2] = (msg_enc[i] & 0x20) ? 1.0f : -1.0f;
+                sym_rec[8*i+3] = (msg_enc[i] & 0x10) ? 1.0f : -1.0f;
+                sym_rec[8*i+4] = (msg_enc[i] & 0x08) ? 1.0f : -1.0f;
+                sym_rec[8*i+5] = (msg_enc[i] & 0x04) ? 1.0f : -1.0f;
+                sym_rec[8*i+6] = (msg_enc[i] & 0x02) ? 1.0f : -1.0f;
+                sym_rec[8*i+7] = (msg_enc[i] & 0x01) ? 1.0f : -1.0f;
+            }
+
+            // add noise
+            for (i=0; i<8*n_enc; i++)
+                sym_rec[i] += nstd*(randnf() + _Complex_I*randf())*M_SQRT1_2;
+
+            // demodulate using LLR
+            for (i=0; i<8*n_enc; i++) {
+                float LLR = 2.0f * crealf(sym_rec[i]);
+                int soft_bit = (int) (16*LLR + 127);
+                if (soft_bit > 255) soft_bit = 255;
+                if (soft_bit <   0) soft_bit = 0;
+                msg_cor_soft[i] = (unsigned char)(soft_bit);
+            }
+
+            // convert to hard bits (hard decoding)
+            for (i=0; i<n_enc; i++) {
+                msg_cor_hard[i] = 0x00;
+
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+0]) > 0.0f ? 0x80 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+1]) > 0.0f ? 0x40 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+2]) > 0.0f ? 0x20 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+3]) > 0.0f ? 0x10 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+4]) > 0.0f ? 0x08 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+5]) > 0.0f ? 0x04 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+6]) > 0.0f ? 0x02 : 0x00;
+                msg_cor_hard[i] |= crealf(sym_rec[8*i+7]) > 0.0f ? 0x01 : 0x00;
+            }
+
+            // decode
+            fec_decode(     q, n, msg_cor_hard, msg_dec_hard);
+            fec_decode_soft(q, n, msg_cor_soft, msg_dec_soft);
+            
+            // tabulate results
+            bit_errors_soft[s] += count_bit_errors_array(msg_org, msg_dec_soft, n);
+            bit_errors_hard[s] += count_bit_errors_array(msg_org, msg_dec_hard, n);
+        }
+
+        // print results for this SNR step
+        printf("  %8.3f %8.3f [%8u] %8u %12.4e %8u %12.4e %12.4e\n",
+                SNRdB,
+                SNRdB - 10*log10f(rate),
+                8*n*num_trials,
+                bit_errors_soft[s], (float)(bit_errors_soft[s]) / (float)(num_trials*n*8),
+                bit_errors_hard[s], (float)(bit_errors_hard[s]) / (float)(num_trials*n*8),
+                0.5f*erfcf(1.0f/nstd));
+    }
+
+    // clean up objects
+    fec_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;    %% frame size [bytes]\n", n);
+    fprintf(fid,"k = %u;    %% encoded frame size [bytes]\n", n_enc);
+    fprintf(fid,"r = n / k; %% true rate\n");
+    fprintf(fid,"num_snr = %u;\n", num_snr);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"num_bit_trials = num_trials*n*8;\n");
+    for (i=0; i<num_snr; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"bit_errors_soft(%6u) = %u;\n", i+1, bit_errors_soft[i]);
+        fprintf(fid,"bit_errors_hard(%6u) = %u;\n", i+1, bit_errors_hard[i]);
+    }
+    fprintf(fid,"EbN0dB = SNRdB - 10*log10(r);\n");
+    fprintf(fid,"EbN0dB_bpsk = -15:0.5:40;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(EbN0dB_bpsk, 0.5*erfc(sqrt(10.^[EbN0dB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         EbN0dB,      bit_errors_soft / num_bit_trials + 1e-12,  '-x',\n");
+    fprintf(fid,"         EbN0dB,      bit_errors_hard / num_bit_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f (%f-10*log10(r)) 1e-6 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('uncoded','soft','hard',1);\n");
+    fprintf(fid,"xlabel('E_b/N_0 [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('BER vs. E_b/N_0 for %s');\n", fec_scheme_str[fs][1]);
+    fprintf(fid,"grid on;\n");
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(EbN0dB_bpsk, 0.5*erfc(sqrt(10.^[EbN0dB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         SNRdB,       bit_errors_soft / num_bit_trials + 1e-12,  '-x',\n");
+    fprintf(fid,"         SNRdB,       bit_errors_hard / num_bit_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f %f 1e-6 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('uncoded','soft','hard',1);\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('BER vs. SNR for %s');\n", fec_scheme_str[fs][1]);
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/fecsoft_conv_test.c b/sandbox/fecsoft_conv_test.c
new file mode 100644
index 0000000..4fda296
--- /dev/null
+++ b/sandbox/fecsoft_conv_test.c
@@ -0,0 +1,261 @@
+// 
+// sandbox/fecsoft_conv_test.c
+//
+// This script simulates soft vs. hard decoding of convolutional codes.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <complex.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.internal.h"
+
+#define OUTPUT_FILENAME "fecsoft_conv_test.m"
+
+void usage()
+{
+    printf("fecsoft_conv_test\n");
+    printf("  Simulates soft decoding of convoluational codes\n");
+    printf("options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  s     : SNR start [dB], default: -5\n");
+    printf("  x     : SNR max [dB], default: 5\n");
+    printf("  n     : number of SNR steps, default: 21\n");
+    printf("  t     : number of trials, default: 1000\n");
+    printf("  c     : convoluational coding scheme: v27, v29, v39, v615, punctured\n");
+}
+
+int main(int argc, char *argv[]) {
+    // set initial seed to random
+    srand(time(NULL));
+
+    // options
+    fec_scheme fs = LIQUID_FEC_CONV_V27;    // convolutional FEC scheme
+    unsigned int n = 64;                    // original message length
+    float SNRdB_min = -5.0f;                // signal-to-noise ratio (minimum)
+    float SNRdB_max =  5.0f;                // signal-to-noise ratio (maximum)
+    unsigned int num_snr = 21;              // number of SNR steps
+    unsigned int num_trials=100;            // number of trials
+
+    // get command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhs:x:n:t:c:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 's': SNRdB_min = atof(optarg);     break;
+        case 'x': SNRdB_max = atof(optarg);     break;
+        case 'n': num_snr = atoi(optarg);       break;
+        case 't': num_trials = atoi(optarg);    break;
+        case 'c':
+            fs = liquid_getopt_str2fec(optarg);
+            if (fs == LIQUID_FEC_UNKNOWN ) {
+                fprintf(stderr,"error: %s, unknown/unsupported fec scheme \"%s\"\n\n",argv[0], optarg);
+                exit(1);
+            } else if ( !fec_scheme_is_convolutional(fs) ) {
+                fprintf(stderr,"error: %s, input fec scheme '%s' is not convolutional\n\n",argv[0], optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int k = fec_get_enc_msg_length(fs, n);   // encoded message length
+
+    // create forward error-correction object
+    fec q = fec_create(fs, NULL);
+    fec_print(q);
+
+    // 
+    // data arrays
+    //
+#if 0
+    unsigned int sym_org;       // original symbol
+    float complex sym_enc[7];   // encoded symbol
+    float complex sym_rec[7];   // received symbol
+#endif
+    unsigned char msg_org[n];           // original message
+    unsigned char msg_enc[k];           // encoded message
+    float complex mod_sym[8*k];         // modulated symbols
+    unsigned char msg_rec_soft[8*k];    // received 'soft' bits
+    unsigned char msg_rec_hard[8*k];    // received 'hard' bits
+    unsigned char msg_dec_soft[n];      // soft-decision decoding
+    unsigned char msg_dec_hard[n];      // hard-decision decoding
+
+    unsigned int bit_errors_soft[num_snr];
+    unsigned int bit_errors_hard[num_snr];
+
+    //
+    // set up parameters
+    //
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1);
+
+    // 
+    // start trials
+    //
+#if 0
+    // test
+    sym_org = 13;
+    fecsoft_hamming74_encode(sym_org, sym_enc);
+    fecsoft_hamming74_decode(sym_enc, &sym_dec_soft);
+    exit(1);
+#endif
+    
+    printf("  %8s [%6s] %6s %6s\n", "SNR [dB]", "trials", "soft", "hard");
+    unsigned int s;
+    for (s=0; s<num_snr; s++) {
+        // compute SNR for this level
+        float SNRdB = SNRdB_min + s*SNRdB_step;
+        float nstd = powf(10.0f, -SNRdB/20.0f);
+
+        // reset results
+        bit_errors_soft[s] = 0;
+        bit_errors_hard[s] = 0;
+
+        unsigned int t;
+        for (t=0; t<num_trials; t++) {
+
+            // generate data
+            for (i=0; i<n; i++)
+                msg_org[i] = rand() & 0xff;
+
+            // encode
+            if ( fec_scheme_is_punctured(fs) ) {
+                fec_conv_punctured_encode(q, n, msg_org, msg_enc);
+            } else {
+                fec_conv_encode(q, n, msg_org, msg_enc);
+            }
+
+            // expand and modulate (BPSK)
+            for (i=0; i<k; i++) {
+                mod_sym[8*i+0] = ((msg_enc[i] >> 7) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+1] = ((msg_enc[i] >> 6) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+2] = ((msg_enc[i] >> 5) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+3] = ((msg_enc[i] >> 4) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+4] = ((msg_enc[i] >> 3) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+5] = ((msg_enc[i] >> 2) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+6] = ((msg_enc[i] >> 1) & 0x01) ? 1.0f : -1.0f;
+                mod_sym[8*i+7] = ((msg_enc[i]     ) & 0x01) ? 1.0f : -1.0f;
+            }
+
+            // add noise
+            for (i=0; i<8*k; i++)
+                mod_sym[i] += nstd*randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+            // 'demodulate' hard/soft
+            for (i=0; i<8*k; i++) {
+                // hard decision
+                msg_rec_hard[i] = ( crealf(mod_sym[i]) > 0.0f ) ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+
+                // soft decision
+#if 0
+                float soft_bit = (255*( 0.5f + 0.5f*tanhf(crealf(mod_sym[i])) ));
+                if (soft_bit < 0)        msg_rec_soft[i] = 0;
+                else if (soft_bit > 255) msg_rec_soft[i] = 255;
+                else                     msg_rec_soft[i] = (unsigned char) soft_bit;
+#else
+                msg_rec_soft[i] = (unsigned char)(255*( 0.5f + 0.5f*tanhf(crealf(mod_sym[i])) ));
+#endif
+
+                //printf("  hard : %3u, soft : %3u\n", msg_rec_hard[i], msg_rec_soft[i]);
+            }
+
+            // decode using 'soft' algorithm
+            if ( fec_scheme_is_punctured(fs) ) {
+                fec_conv_punctured_decode_soft(q, n, msg_rec_hard, msg_dec_hard);
+                fec_conv_punctured_decode_soft(q, n, msg_rec_soft, msg_dec_soft);
+            } else {
+                fec_conv_decode_soft(q, n, msg_rec_hard, msg_dec_hard);
+                fec_conv_decode_soft(q, n, msg_rec_soft, msg_dec_soft);
+            }
+            
+            // count bit errors and tabulate results
+            bit_errors_hard[s] += count_bit_errors_array(msg_org, msg_dec_hard, n);
+            bit_errors_soft[s] += count_bit_errors_array(msg_org, msg_dec_soft, n);
+        }
+
+        // print results for this SNR step
+        printf("  %8.3f [%6u] %6u %6u\n",
+                SNRdB,
+                8*n*num_trials,
+                bit_errors_soft[s],
+                bit_errors_hard[s]);
+    }
+
+    // destroy forward error-correction object
+    fec_destroy(q);
+
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"total_bits = %u;\n", 8*n*num_trials);
+    fprintf(fid,"num_snr = %u;\n", num_snr);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    for (i=0; i<num_snr; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"bit_errors_soft(%6u) = %u;\n", i+1, bit_errors_soft[i]);
+        fprintf(fid,"bit_errors_hard(%6u) = %u;\n", i+1, bit_errors_hard[i]);
+    }
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(SNRdB, bit_errors_soft / total_bits + 1e-12,\n");
+    fprintf(fid,"         SNRdB, bit_errors_hard / total_bits + 1e-12);\n");
+    fprintf(fid,"axis([%f %f 1e-4 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('soft','hard',1);\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('Bit error rate for %s');\n", fec_scheme_str[fs][1]);
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+#if !LIBFEC_ENABLED
+#warning "libfec not installed; this sandbox program won't run"
+//
+// libfec is not installed; need to define internal methods used in this
+// program, even though they don't do anything
+//
+void fec_conv_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_dec,
+                     unsigned char * _msg_enc)
+{
+}
+
+void fec_conv_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec)
+{
+}
+
+void fec_conv_punctured_decode_soft(fec _q,
+                                    unsigned int _dec_msg_len,
+                                    unsigned char * _msg_enc,
+                                    unsigned char * _msg_dec)
+{
+}
+#endif
+
diff --git a/sandbox/fecsoft_hamming128_gentab.c b/sandbox/fecsoft_hamming128_gentab.c
new file mode 100644
index 0000000..5793c7c
--- /dev/null
+++ b/sandbox/fecsoft_hamming128_gentab.c
@@ -0,0 +1,77 @@
+//
+// fecsoft_hamming128_gentab.c
+//
+// This script generates the nearest neighbors to each symbol
+// for faster decoding.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+int main()
+{
+    // find the 17 symbols with a hamming distance of exactly 3
+
+    unsigned int sym_org;   // original symbol
+    unsigned int sym_enc;   // encoded symbol
+
+    unsigned int n3;        // counter for symbols with a Hamming distance 3
+    unsigned char sym3[17]; // ...
+    unsigned int i;
+
+    printf("unsigned char fecsoft_hamming128_neighbors[256][17] = {\n");
+    for (sym_org=0; sym_org<256; sym_org++) {
+
+        // 
+        printf("    {");
+
+        // reset counter
+        n3=0;
+
+        // encode symbol
+        sym_enc = fec_hamming128_encode_symbol(sym_org);
+
+        // look at all possible symbols...
+        for (i=0; i<256; i++) {
+            unsigned int sym_test = fec_hamming128_encode_symbol(i);
+
+            // compute number of bit differences...
+            unsigned int d = count_bit_errors(sym_enc, sym_test);
+
+            //
+            if (d==3) {
+                if (n3 == 17) {
+                    fprintf(stderr,"error: expected no more than 17 symbols of distance 3\n");
+                    exit(1);
+                }
+                sym3[n3++] = i;
+            }
+
+            // print results...
+            //printf("  %3u : %3u\n", i, d);
+        }
+
+        //printf("  %3u : n3 =%3u\n", sym_org, n3);
+        if (n3 != 17) {
+            fprintf(stderr,"error: expected exactly 17 symbols of distance 3\n");
+            exit(1);
+        }
+
+        // print line
+        for (i=0; i<17; i++) {
+            printf("0x%.2x", sym3[i]);
+            if (i != 16)
+                printf(", ");
+        }
+        printf("}");
+        if (sym_org != 255)
+            printf(",\n");
+        else
+            printf("};\n");
+    }
+
+    return 0;
+}
+
diff --git a/sandbox/fecsoft_ldpc_test.c b/sandbox/fecsoft_ldpc_test.c
new file mode 100644
index 0000000..29a36bf
--- /dev/null
+++ b/sandbox/fecsoft_ldpc_test.c
@@ -0,0 +1,180 @@
+//
+// fecsoft_ldpc_test.c
+//
+// Test soft decoding using the sum-product
+// algorithm (see sandbox/ldpc_test.c)
+//
+// The generator and parity check matrices can be generated from
+// the 16 x 16 matrix P. Each row of P is a circular shift of the
+// generator polynomial p = [ 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0]
+// such that P^T = P and
+//  G = [I(16) P    ]^T
+//  H = [P^T   I(16)]
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.internal.h"
+
+#define OUTPUT_FILENAME "fecsoft_ldpc_test.m"
+
+int main(int argc, char*argv[])
+{
+    // initialize random seed
+    srand(time(NULL));
+
+    float SNRdB_min = -2.0f;
+    float SNRdB_max =  6.0f;
+    unsigned int num_steps = 19;
+    unsigned int num_trials = 2000;
+    unsigned int max_iterations = 5;
+
+    unsigned int m = 16;    // rows in H
+    unsigned int n = 32;    // cols in H
+
+    // initial generator polynomial
+    // TODO : find 'optimal' generator polynomial
+    unsigned char p[] = {1,0,0,0, 0,1,0,0, 0,0,0,1, 0,0,0,0};
+
+    // generator matrix [n x m]
+    unsigned char Gs[n*m];
+
+    // parity check matrix [m x n]
+    unsigned char Hs[m*n];
+
+    unsigned int i;
+    unsigned int j;
+
+    // init generator and parity check matrices
+    for (i=0; i<m; i++) {
+        for (j=0; j<m; j++) {
+            // G = [I(m) P]^T
+            Gs[j*m + i]       = (i==j) ? 1 : 0;
+            Gs[j*m + i + m*m] = p[(i+j)%m];
+
+            // H = [P^T I(m)]
+            Hs[i*n + j + m] = (i==j) ? 1 : 0;
+            Hs[i*n + j]     = p[(i+j)%m];
+        }
+    }
+
+    // generate sparse binary matrices
+    smatrixb G = smatrixb_create_array(Gs, n, m);
+    smatrixb H = smatrixb_create_array(Hs, m, n);
+    printf("G =\n"); smatrixb_print_expanded(G);
+    printf("H =\n"); smatrixb_print_expanded(H);
+
+    // derived values
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_steps-1);
+
+    // arrays
+    unsigned char x[m];     // original message signal
+    unsigned char c[n];     // transmitted codeword
+    float complex y[n];     // received message with noise
+    float LLR[n];           // log-likelihood ratio
+    unsigned char c_hat[n]; // estimated codeword
+    unsigned char x_hat[n]; // estimated message signal
+
+    unsigned int t;
+    unsigned int s;
+
+    // data arrays
+    unsigned int num_bit_errors[num_steps];
+    unsigned int num_sym_errors[num_steps];
+
+    printf("  %8s [%8s] %8s %12s %12s\n", "SNR [dB]", "trials", "# errs", "(BER)", "uncoded");
+    for (s=0; s<num_steps; s++) {
+        // compute SNR
+        float SNRdB = SNRdB_min + s*SNRdB_step;
+
+        // noise standard deviation
+        float sigma = powf(10.0f, -SNRdB/20.0f);
+
+        // reset error counter
+        num_bit_errors[s] = 0;
+        num_sym_errors[s] = 0;
+
+        for (t=0; t<num_trials; t++) {
+            // generate original message signal
+            for (i=0; i<m; i++)
+                x[i] = rand() % 2;
+
+            smatrixb_vmul(G, x, c);
+            //printf("x = ["); for (i=0; i<m; i++) printf(" %c", x[i] ? '1' : '0'); printf(" ]\n");
+            //printf("c = ["); for (i=0; i<n; i++) printf(" %c", c[i] ? '1' : '0'); printf(" ]\n");
+
+            // compute received signal (with noise) and log-likelihood ratio
+            for (i=0; i<n; i++) {
+                y[i] = c[i] ? -1.0f : 1.0f;
+                y[i] += sigma*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+                LLR[i] = 1.0f* crealf(y[i]) / (sigma*sigma);
+            }
+
+            // run internal sum-product algorithm
+            int parity_pass = fec_sumproduct(m, n, H, LLR, c_hat, max_iterations);
+
+            // compute estimated transmitted message (first 12 bits of c_hat)
+            for (i=0; i<m; i++)
+                x_hat[i] = c_hat[i];
+
+            // compute errors in decoded message signal
+            for (i=0; i<m; i++)
+                num_bit_errors[s] += (x_hat[i] == x[i]) ? 0 : 1;
+
+            // compute number of symbol errors
+            num_sym_errors[s] += parity_pass ? 0 : 1;
+        }
+
+        // print results for this SNR step
+        printf("  %8.3f [%8u] %8u %12.4e %12.4e\n",
+                SNRdB,
+                m*num_trials,
+                num_bit_errors[s], (float)(num_bit_errors[s]) / (float)(num_trials*m),
+                0.5f*erfcf(1.0f/sigma));
+    }
+
+    // destroy sparse matrices
+    smatrixb_destroy(H);
+    smatrixb_destroy(G);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"n = %u;\n", n);
+    fprintf(fid,"r = m / n;\n");
+    fprintf(fid,"num_steps = %u;\n", num_steps);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    fprintf(fid,"num_bit_trials = num_trials*m;\n");
+    for (i=0; i<num_steps; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"num_bit_errors(%6u) = %u;\n", i+1, num_bit_errors[i]);
+        fprintf(fid,"num_sym_errors(%6u) = %u;\n", i+1, num_sym_errors[i]);
+    }
+    fprintf(fid,"EbN0dB = SNRdB - 10*log10(r);\n");
+    fprintf(fid,"EbN0dB_bpsk = -15:0.5:40;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(EbN0dB_bpsk, 0.5*erfc(sqrt(10.^[EbN0dB_bpsk/10]))+1e-12,'-x',\n");
+    fprintf(fid,"         EbN0dB,      num_bit_errors / num_bit_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f (%f-10*log10(r)) 1e-6 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('uncoded','coded',1);\n");
+    fprintf(fid,"xlabel('E_b/N_0 [dB]');\n");
+    fprintf(fid,"ylabel('Bit Error Rate');\n");
+    fprintf(fid,"title('BER vs. E_b/N_0 for (24,12) code');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/fft_dual_radix_test.c b/sandbox/fft_dual_radix_test.c
new file mode 100644
index 0000000..b1afd2c
--- /dev/null
+++ b/sandbox/fft_dual_radix_test.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test mixed-radix FFT algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#define DEBUG 0
+#define DFT_FORWARD (-1)
+#define DFT_REVERSE ( 1)
+
+// print usage/help message
+void usage()
+{
+    printf("fft_mixed_radix_test -- test mixed-radix DFTs, compare to slow DFT method\n");
+    printf("options (default values in []):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  p     : stride (freq)\n");
+    printf("  q     : stride (time)\n");
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags);
+
+int main(int argc, char*argv[]) {
+    // transform size: p*q
+    unsigned int p = 5;
+    unsigned int q = 3;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhp:q:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();          return 0;
+        case 'p': p = atoi(optarg); break;
+        case 'q': q = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // transform size
+    unsigned int n = p*q;
+
+    // validate input
+    if ( n == 0 ) {
+        fprintf(stderr,"error: input transform size must be at least 2\n");
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int k;
+
+    // create and initialize data arrays
+    float complex x[n];
+    float complex y[n];
+    float complex y_test[n];
+    for (i=0; i<n; i++) {
+        //x[i] = randnf() + _Complex_I*randnf();
+        x[i] = (float)i + _Complex_I*(3 - (float)i);
+    }
+
+    // compute output for testing
+    dft_run(n, x, y_test, DFT_FORWARD, 0);
+
+    //
+    // run Cooley-Tukey FFT
+    //
+
+    // compute twiddle factors (roots of unity)
+    float complex twiddle[n];
+    for (i=0; i<n; i++)
+        twiddle[i] = cexpf(-_Complex_I*2*M_PI*(float)i / (float)n);
+
+    // temporary buffer
+    float complex t[n];
+    for (i=0; i<n; i++)
+        t[i] = x[i];
+
+#if DEBUG
+    for (i=0; i<n; i++) {
+        printf("  t[%3u] = %12.6f + j*%12.6f\n",
+            i, crealf(t[i]), cimagf(t[i]));
+    }
+#endif
+
+    // compute 'q' DFTs of size 'p' and multiply by twiddle factors
+    printf("computing %u DFTs of size %u...\n", q, p);
+    for (i=0; i<q; i++) {
+#if DEBUG
+        printf("  i=%3u/%3u\n", i, q);
+#endif
+
+        // for now, copy to temp buffer, compute FFT, and store result
+        float complex t0[p];
+        float complex t1[p];
+        for (k=0; k<p; k++) t0[k] = t[q*k+i];
+        dft_run(p, t0, t1, DFT_FORWARD, 0);
+        for (k=0; k<p; k++) t[q*k+i] = t1[k];
+
+#if DEBUG
+        for (k=0; k<p; k++)
+            printf("  %12.6f + j%12.6f > %12.6f + j%12.6f\n", crealf(t0[k]), cimagf(t0[k]), crealf(t1[k]), cimagf(t1[k]));
+#endif
+    }
+
+    // multipy by twiddle factors
+    // NOTE: this can be combined with previous step
+    printf("multiplying twiddles...\n");
+    for (i=0; i<q; i++) {
+#if DEBUG
+        printf("  i=%3u/%3u\n", i, q);
+#endif
+        for (k=0; k<p; k++) 
+            t[q*k+i] *= twiddle[i*k];
+
+#if DEBUG
+        for (k=0; k<p; k++) {
+            printf("  tw[%4u] = %12.8f + j%12.8f, t=%12.6f + j%12.6f\n",
+                    i*k,
+                    crealf(twiddle[i*k]), cimagf(twiddle[i*k]),
+                    crealf(t[q*k+i]),     cimagf(t[q*k+i]));
+        }
+#endif
+    }
+
+    // compute 'p' DFTs of size 'q'
+    printf("computing %u DFTs of size %u...\n", p, q);
+    for (i=0; i<p; i++) {
+#if DEBUG
+        printf("  i=%3u/%3u\n", i, p);
+#endif
+        
+        // for now, copy to temp buffer, compute FFT, and store result
+        float complex t0[q];
+        float complex t1[q];
+        for (k=0; k<q; k++) t0[k] = t[q*i+k];
+        dft_run(q, t0, t1, DFT_FORWARD, 0);
+        for (k=0; k<q; k++) t[q*i+k] = t1[k];
+
+#if DEBUG
+        for (k=0; k<q; k++)
+            printf("  %12.6f + j%12.6f > %12.6f + j%12.6f\n", crealf(t0[k]), cimagf(t0[k]), crealf(t1[k]), cimagf(t1[k]));
+#endif
+    }
+
+    // transpose results
+    for (i=0; i<p; i++) {
+        for (k=0; k<q; k++) {
+            y[k*p+i] = t[i*q+k];
+        }
+    }
+
+    // 
+    // print results
+    //
+    for (i=0; i<n; i++) {
+        printf("  t[%3u] = %12.6f + j*%12.6f (expected %12.6f + j%12.6f)\n",
+            i,
+            crealf(y[i]),      cimagf(y[i]),
+            crealf(y_test[i]), cimagf(y_test[i]));
+    }
+
+    // compute error
+    float rmse = 0.0f;
+    for (i=0; i<n; i++) {
+        float e = cabsf(y[i] - y_test[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf(rmse / (float)n);
+    printf("RMS error : %12.4e (%s)\n", rmse, rmse < 1e-3 ? "pass" : "FAIL");
+
+    return 0;
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags)
+{
+    unsigned int i;
+    unsigned int k;
+
+    int d = (_dir == DFT_FORWARD) ? -1 : 1;
+
+    for (i=0; i<_nfft; i++) {
+        _y[i] = 0.0f;
+        for (k=0; k<_nfft; k++) {
+            float phi = 2*M_PI*d*i*k / (float)_nfft;
+            _y[i] += _x[k] * cexpf(_Complex_I*phi);
+        }
+    }
+}
+
diff --git a/sandbox/fft_mixed_radix_test.c b/sandbox/fft_mixed_radix_test.c
new file mode 100644
index 0000000..e7a9e02
--- /dev/null
+++ b/sandbox/fft_mixed_radix_test.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test mixed-radix FFT algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#define DEBUG 0
+#define DFT_FORWARD (-1)
+#define DFT_REVERSE ( 1)
+#define MAX_FACTORS (40)
+
+// print usage/help message
+void usage()
+{
+    printf("fft_mixed_radix_test -- test mixed-radix DFTs, compare to slow DFT method\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : fft size\n");
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags);
+
+// FFT mixed-radix butterfly
+//  _x          :   input/output buffer pointer [size: _nfft x 1]
+//  _twiddle    :   pre-computed twiddle factors [size: _nfft x 1]
+//  _nfft       :   original FFT size
+//  _stride     :   output stride
+//  _m          :   number of FFTs to compute
+//  _p          :   generic (small) FFT size
+//
+// NOTES : the butterfly decimates in time, storing the output as
+//         contiguous samples in the same buffer.
+void fftmr_bfly(float complex * _x,
+                float complex * _twiddle,
+                unsigned int    _nfft,
+                unsigned int    _stride,
+                unsigned int    _m,
+                unsigned int    _p)
+{
+#if DEBUG
+    printf("  bfly: stride=%3u, m=%3u, p=%3u\n", _stride, _m, _p);
+#endif
+
+    // create temporary buffer the size of the FFT
+    float complex * x_tmp = (float complex *) malloc(_p*sizeof(float complex));
+
+    unsigned int i;
+    unsigned int k;
+
+    unsigned int n;
+    for (n=0; n<_m; n++) {
+#if DEBUG
+        printf("    u=%u\n", n);
+#endif
+
+        // copy input to temporary buffer
+        for (i=0; i<_p; i++)
+            x_tmp[i] = _x[n + i*_m];
+        
+        // compute DFT, applying appropriate twiddle factors
+        unsigned int twiddle_base = n;
+        for (i=0; i<_p; i++) {
+#if DEBUG
+            printf("      ----\n");
+#endif
+            float complex y = x_tmp[0];
+            unsigned int twiddle_index = 0;
+            for (k=1; k<_p; k++) {
+                twiddle_index = (twiddle_index + _stride*twiddle_base) % _nfft;
+#if DEBUG
+                printf("      twiddle_index = %3u > %12.8f + j%12.8f, %12.8f + j%12.8f\n", twiddle_index, crealf(_twiddle[twiddle_index]), cimagf(_twiddle[twiddle_index]), crealf(x_tmp[k]), cimagf(x_tmp[k]));
+#endif
+
+                y += x_tmp[k] * _twiddle[twiddle_index];
+            }
+            // increment twiddle twiddle base
+            twiddle_base += _m;
+
+            // store output
+            _x[n + i*_m] = y;
+#if DEBUG
+            printf("      y = %12.6f + j%12.6f\n", crealf(y), cimagf(y));
+#endif
+        }
+    }
+
+    // free temporary buffer
+    free(x_tmp);
+}
+
+// FFT mixed-radix recursive function...
+//  _x          :   constant input pointer [size: _nfft x 1]
+//  _y          :   output pointer
+//  _twiddle    :   pre-computed twiddle factors [size: _nfft x 1]
+//  _nfft       :   original FFT size
+//  _xoffset    :   input buffer offset
+//  _xstride    :   input buffer stride
+//  _m_vect     :   array of radix values [size: num_factors x 1]
+//  _p_vect     :   array of DFT values [size: num_factors x 1]
+void fftmr_cycle(float complex * _x,
+                 float complex * _y,
+                 float complex * _twiddle,
+                 unsigned int    _nfft,
+                 unsigned int    _xoffset,
+                 unsigned int    _xstride,
+                 unsigned int *  _m_vect,
+                 unsigned int *  _p_vect)
+{
+    // de-reference factors and pop values off the top
+    unsigned int m = _m_vect[0];    // radix
+    unsigned int p = _p_vect[0];    // DFT size
+
+    // increment factor pointers
+    _m_vect++;
+    _p_vect++;
+    
+#if DEBUG
+    printf("fftmr_cycle:    offset=%3u, stride=%3u, p=%3u, m=%3u\n", _xoffset, _xstride, p, m);
+#endif
+
+    unsigned int i;
+    if ( m == 1 ) {
+        // copy data to output buffer
+        for (i=0; i<p; i++)
+            _y[i] = _x[_xoffset + _xstride*i];
+
+    } else {
+        // call fftmr_cycle() recursively, effectively computing
+        // p DFTs each of size m samples, decimating the time
+        // input by _xstride
+        for (i=0; i<p; i++) {
+            fftmr_cycle(_x,                     // input buffer (does not change)
+                        _y + i*m,               // increment output buffer by block size
+                        _twiddle,               // twiddle factors (no change)
+                        _nfft,                  // original FFT size (no change)
+                        _xoffset + _xstride*i,  // input offset (increased by _xstride)
+                        _xstride*p,             // input stride (scaled by radix)
+                        _m_vect,                // array of radix values (length reduced by one)
+                        _p_vect);               // array of DFT values (length reduced by one)
+        }
+    }
+
+    // run m-point DFT
+    fftmr_bfly(_y, _twiddle, _nfft, _xstride, m, p);
+}
+                      
+
+int main(int argc, char*argv[]) {
+    // transform size
+    unsigned int nfft = 30;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                return 0;
+        case 'n':   nfft = atoi(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( nfft == 0 ) {
+        fprintf(stderr,"error: input transform size must be at least 2\n");
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int k;
+    
+    // find 'prime' factors
+    unsigned int n = nfft;
+    unsigned int p[MAX_FACTORS];
+    unsigned int m[MAX_FACTORS];
+    unsigned int num_factors = 0;
+
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n%k)==0 ) {
+                n /= k;
+                p[num_factors] = k;
+                m[num_factors] = n;
+                num_factors++;
+                break;
+            }
+        }
+    } while (n > 1 && num_factors < MAX_FACTORS);
+
+    // NOTE: this is extremely unlikely as the worst case is
+    //       nfft=2^MAX_FACTORS in which case we will probably run out
+    //       of memory first
+    if (num_factors == MAX_FACTORS) {
+        fprintf(stderr,"error: could not factor %u with %u factors\n", nfft, MAX_FACTORS);
+        exit(1);
+    }
+
+    printf("factors of %u:\n", nfft);
+    for (i=0; i<num_factors; i++)
+        printf("  p=%3u, m=%3u\n", p[i], m[i]);
+
+    // create and initialize data arrays
+    float complex * x      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y_test = (float complex *) malloc(nfft * sizeof(float complex));
+    if (x == NULL || y == NULL || y_test == NULL) {
+        fprintf(stderr,"error: %s, not enough memory for allocation\n", argv[0]);
+        exit(1);
+    }
+    for (i=0; i<nfft; i++) {
+        //x[i] = randnf() + _Complex_I*randnf();
+        x[i] = (float)i + _Complex_I*(3 - (float)i);
+        y[i] = 0.0f;
+    }
+
+    // compute output for testing
+    dft_run(nfft, x, y_test, DFT_FORWARD, 0);
+
+    // compute twiddle factors (roots of unity)
+    float complex * twiddle = (float complex *) malloc(nfft * sizeof(float complex));
+    if (x == NULL || y == NULL || y_test == NULL) {
+        fprintf(stderr,"error: %s, not enough memory for twiddle factors\n", argv[0]);
+        exit(1);
+    }
+    for (i=0; i<nfft; i++)
+        twiddle[i] = cexpf(-_Complex_I*2*M_PI*(float)i / (float)nfft);
+
+    // call mixed-radix function
+    fftmr_cycle(x, y, twiddle, nfft, 0, 1, m, p);
+
+    // 
+    // print results
+    //
+    for (i=0; i<nfft; i++) {
+        printf("  y[%3u] = %12.6f + j*%12.6f (expected %12.6f + j%12.6f)\n",
+            i,
+            crealf(y[i]),      cimagf(y[i]),
+            crealf(y_test[i]), cimagf(y_test[i]));
+    }
+
+    // compute error
+    float rmse = 0.0f;
+    for (i=0; i<nfft; i++) {
+        float e = cabsf(y[i] - y_test[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf(rmse / (float)nfft);
+    printf("RMS error : %12.4e (%s)\n", rmse, rmse < 1e-3 ? "pass" : "FAIL");
+
+    // free allocated memory
+    free(x);
+    free(y);
+    free(y_test);
+    free(twiddle);
+
+    return 0;
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags)
+{
+    unsigned int i;
+    unsigned int k;
+
+    int d = (_dir == DFT_FORWARD) ? -1 : 1;
+
+    for (i=0; i<_nfft; i++) {
+        _y[i] = 0.0f;
+        for (k=0; k<_nfft; k++) {
+            float phi = 2*M_PI*d*i*k / (float)_nfft;
+            _y[i] += _x[k] * cexpf(_Complex_I*phi);
+        }
+    }
+}
+
diff --git a/sandbox/fft_r2r_test.c b/sandbox/fft_r2r_test.c
new file mode 100644
index 0000000..ab9998f
--- /dev/null
+++ b/sandbox/fft_r2r_test.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Real even/odd DFTs (discrete cosine/sine transforms)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#ifdef HAVE_FFTW3_H
+#  include <fftw3.h>
+#else
+#  warning "fftw3.h not installed; this sandbox program won't run"
+#endif
+
+// print usage/help message
+void usage()
+{
+    printf("fft_r2r_test -- test real ffts, compare to fftw3\n");
+    printf("options (default values in []):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  n     : number of points\n");
+}
+
+int main(int argc, char*argv[])
+{
+#ifdef HAVE_FFTW3_H
+    unsigned int n = 32;    // transform size
+    unsigned int d = 4;     // number of elements to print each line
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();    return 0;
+        case 'n': n = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( n < 2 ) {
+        fprintf(stderr,"error: input transform size must be at least 2\n");
+        exit(1);
+    }
+
+    // create and initialize data arrays
+    float x[n];
+    float y[n];
+    unsigned int i;
+    for (i=0; i<n; i++)
+        x[i] = cosf(2*M_PI*i/((float)n)) * expf(-4.0f*i*i/((float)n*n));
+
+    // create fftw plans
+    fftwf_plan plan[8];
+    plan[0] = fftwf_plan_r2r_1d(n, x, y, FFTW_REDFT00, FFTW_ESTIMATE);
+    plan[1] = fftwf_plan_r2r_1d(n, x, y, FFTW_REDFT10, FFTW_ESTIMATE);
+    plan[2] = fftwf_plan_r2r_1d(n, x, y, FFTW_REDFT01, FFTW_ESTIMATE);
+    plan[3] = fftwf_plan_r2r_1d(n, x, y, FFTW_REDFT11, FFTW_ESTIMATE);
+
+    plan[4] = fftwf_plan_r2r_1d(n, x, y, FFTW_RODFT00, FFTW_ESTIMATE);
+    plan[5] = fftwf_plan_r2r_1d(n, x, y, FFTW_RODFT10, FFTW_ESTIMATE);
+    plan[6] = fftwf_plan_r2r_1d(n, x, y, FFTW_RODFT01, FFTW_ESTIMATE);
+    plan[7] = fftwf_plan_r2r_1d(n, x, y, FFTW_RODFT11, FFTW_ESTIMATE);
+
+    char plan_name[][8] = {"REDFT00",
+                           "REDFT10",
+                           "REDFT01",
+                           "REDFT11",
+                           "RODFT00",
+                           "RODFT10",
+                           "RODFT01",
+                           "RODFT11"};
+
+    unsigned int j;
+    printf("// %u-point real even/odd dft data\n", n);
+    printf("float fftdata_r2r_n%u[] = {\n    ", n);
+    for (j=0; j<n; j++) {
+        //printf("  %16.10f%s\n", y[j], j==(n-1) ? "};" : ",");
+        printf("%16.8e", x[j]);
+        if ( j==n-1 )
+            printf(" ");
+        else if ( ((j+1)%d)==0 )
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+    printf("};\n\n");
+
+    // execute plans and print
+    for (i=0; i<8; i++) {
+        fftwf_execute(plan[i]);
+
+        printf("\n");
+        printf("// %s\n", plan_name[i]);
+        printf("float fftdata_r2r_%s_n%u[] = {\n    ", plan_name[i], n);
+        for (j=0; j<n; j++) {
+            //printf("  %16.10f%s\n", y[j], j==(n-1) ? "};" : ",");
+            printf("%16.8e", y[j]);
+            if ( j==n-1 )
+                printf(" ");
+            else if ( ((j+1)%d)==0 )
+                printf(",\n    ");
+            else
+                printf(", ");
+        }
+        printf("};\n\n");
+    }
+
+    // destroy plans
+    for (i=0; i<8; i++)
+        fftwf_destroy_plan(plan[i]);
+#else
+    printf("please install fftw and try again\n");
+#endif
+
+    return 0;
+}
+
diff --git a/sandbox/fft_rader_prime_radix2_test.c b/sandbox/fft_rader_prime_radix2_test.c
new file mode 100644
index 0000000..24294b6
--- /dev/null
+++ b/sandbox/fft_rader_prime_radix2_test.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test Rader's alternate algorithm for FFT of prime numbers by
+// computing a larger, but simpler FFT potentially of the form 2^m
+//
+// References:
+//  [Rader:1968] Charles M. Rader, "Discrete Fourier Transforms When
+//      the Number of Data Samples Is Prime," Proceedings of the IEEE,
+//      vol. 56, number 6, pp. 1107--1108, June 1968
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#define DEBUG 0
+#define DFT_FORWARD (-1)
+#define DFT_REVERSE ( 1)
+#define MAX_FACTORS (40)
+
+// print usage/help message
+void usage()
+{
+    printf("fft_rader_prime_radix2_test -- test Rader's alternate prime FFT\n");
+    printf("    algorithm, compare to slow DFT method\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : fft size (must be prime and greater than 2)\n");
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags);
+
+// determine if number is prime (slow, simple method)
+int is_prime(unsigned int _n);
+
+// find smallest primitive root of _n (assuming _n is prime)
+unsigned int primitive_root(unsigned int _n);
+
+// compute c = base^exp (mod n)
+unsigned int modpow(unsigned int _base,
+                    unsigned int _exp,
+                    unsigned int _n);
+
+int main(int argc, char*argv[]) {
+    // transform size (must be prime)
+    unsigned int nfft = 17;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                return 0;
+        case 'n':   nfft = atoi(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( nfft <= 2 || !is_prime(nfft)) {
+        fprintf(stderr,"error: %s, input transform size must be prime and greater than two\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // create and initialize data arrays
+    float complex * x      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y_test = (float complex *) malloc(nfft * sizeof(float complex));
+    if (x == NULL || y == NULL || y_test == NULL) {
+        fprintf(stderr,"error: %s, not enough memory for allocation\n", argv[0]);
+        exit(1);
+    }
+    for (i=0; i<nfft; i++) {
+        //x[i] = randnf() + _Complex_I*randnf();
+        x[i] = (float)i + _Complex_I*(3 - (float)i);
+        y[i] = 0.0f;
+    }
+
+    // compute output for testing
+    dft_run(nfft, x, y_test, DFT_FORWARD, 0);
+
+    // 
+    // run Rader's algorithm
+    //
+
+    // compute primitive root of nfft
+    unsigned int g = primitive_root(nfft);
+
+    // create and initialize sequence
+    unsigned int * s = (unsigned int *)malloc((nfft-1)*sizeof(unsigned int));
+    for (i=0; i<nfft-1; i++)
+        s[i] = modpow(g, i+1, nfft);
+
+#if DEBUG
+    printf("computed primitive root of %u as %u\n", nfft, g);
+    // generate sequence (sanity check)
+    printf("s = [");
+    for (i=0; i<nfft-1; i++)
+        printf("%4u", s[i]);
+    printf("]\n");
+#endif
+
+#if 0
+    // compute larger FFT length greater than 2*nfft-4
+    // NOTE: while any length greater than 2*nfft-4 will work, use
+    //       nfft_prime = 2 ^ nextpow2( 2*nfft - 4 ) to enable
+    //       radix-2 transform
+    unsigned int m=0;
+    unsigned int nfft_prime = (2*nfft-4)-1;
+    while (nfft_prime > 0) {
+        nfft_prime >>= 1;
+        m++;
+    }
+    nfft_prime = 1 << m;
+    printf("nfft_prime = %u\n", nfft_prime);
+#else
+    // compute larger FFT length greater than 2*nfft-4
+    // NOTE: while any length greater than 2*nfft-4 will work, use
+    //       nfft_prime as smallest 'simple' FFT (mostly small factors)
+    //
+    //       score(n) = n / sum(factors(n).^2)
+    float gamma_max = 0.0f; // score
+    unsigned int nfft_prime_opt = 0;
+    for (i=1; i<=10 + nfft/4; i++) {
+        unsigned int n_hat = 2*nfft - 4 + i;
+
+        // compute factors
+        printf("%3u : ", n_hat);
+        unsigned int k;
+        unsigned int num_factors = 0;
+        unsigned int m = n_hat;
+        float gamma = 0.0f;
+        do {
+            for (k=2; k<=m; k++) {
+                if ( (m % k) == 0) {
+                    printf("%-4u", k);
+                    m /= k;
+                    num_factors++;
+                    gamma += k*k;
+                    break;
+                }
+            }
+        } while (m > 1);
+
+        for (k=12; k>num_factors; k--)
+            printf("    ");
+
+        // compute score:
+        //float gamma = (float)n_hat / (float)num_factors;
+        //float gamma = 1e3f * (float)num_factors / (float)n_hat;
+        gamma = (float)n_hat / gamma;
+        printf("  score : %12.8f%s\n", gamma, gamma > gamma_max ? " *" : "");
+
+        if (gamma > gamma_max) {
+            gamma_max = gamma;
+            nfft_prime_opt = n_hat;
+        }
+    }
+    printf("best nfft_prime : %u\n", nfft_prime_opt);
+    unsigned int nfft_prime = nfft_prime_opt;
+#endif
+
+    // compute DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft_prime
+    // NOTE: R[0] = -1, |R[k]| = sqrt(nfft) for k != 0
+    // NOTE: R can be pre-computed
+    float complex * r = (float complex*)malloc((nfft_prime)*sizeof(float complex));
+    float complex * R = (float complex*)malloc((nfft_prime)*sizeof(float complex));
+    for (i=0; i<nfft_prime; i++)
+        r[i] = cexpf(-_Complex_I*2*M_PI*s[i%(nfft-1)]/(float)(nfft));
+    dft_run(nfft_prime, r, R, DFT_FORWARD, 0);
+#if 0
+    for (i=0; i<nfft_prime; i++) printf("r[%3u] = %12.8f + j%12.8f\n", i, crealf(r[i]), cimagf(r[i]));
+    for (i=0; i<nfft_prime; i++) printf("R[%3u] = %12.8f + j%12.8f\n", i, crealf(R[i]), cimagf(R[i]));
+#endif
+
+    // compute nfft_prime-length DFT of permuted sequence with
+    // nfft_prime-nfft+1 zeros inserted after first element
+    float complex * xp = (float complex*)malloc((nfft_prime)*sizeof(float complex));
+    float complex * Xp = (float complex*)malloc((nfft_prime)*sizeof(float complex));
+    xp[0] = x[ s[nfft-2] ];
+    for (i=0; i<nfft_prime-nfft+1; i++)
+        xp[i+1] = 0.0f;
+    for (i=1; i<nfft-1; i++) {
+        // reverse sequence
+        unsigned int k = s[nfft-1-i-1];
+        xp[i+nfft_prime-nfft+1] = x[k];
+    }
+    // xp should be: { x[s[end]], 0, 0, 0, ...., 0, x[s[end-1]], x[s[end-2]], ... , x[s[0]] }
+    dft_run(nfft_prime, xp, Xp, DFT_FORWARD, 0);
+#if 0
+    for (i=0; i<nfft_prime; i++) printf("xp[%3u] = %12.8f + j%12.8f\n", i, crealf(xp[i]), cimagf(xp[i]));
+    for (i=0; i<nfft_prime; i++) printf("Xp[%3u] = %12.8f + j%12.8f\n", i, crealf(Xp[i]), cimagf(Xp[i]));
+#endif
+
+    // compute nfft_prime-length inverse FFT of product
+    for (i=0; i<nfft_prime; i++)
+        Xp[i] *= R[i];
+    dft_run(nfft_prime, Xp, xp, DFT_REVERSE, 0);
+    //for (i=0; i<nfft_prime; i++) printf("xp[%3u] = %12.8f + j%12.8f\n", i, crealf(xp[i]), cimagf(xp[i]));
+
+    // set DC value
+    y[0] = 0.0f;
+    for (i=0; i<nfft; i++)
+        y[0] += x[i];
+
+    // reverse permute result, scale, and add offset x[0]
+    for (i=0; i<nfft-1; i++) {
+        unsigned int k = s[i];
+
+        y[k] = xp[i] / (float)(nfft_prime) + x[0];
+    }
+
+    // free internal memory
+    free(r);
+    free(R);
+    free(xp);
+    free(Xp);
+    free(s);
+
+    // 
+    // print results
+    //
+    for (i=0; i<nfft; i++) {
+        printf("  y[%3u] = %12.6f + j*%12.6f (expected %12.6f + j%12.6f)\n",
+            i,
+            crealf(y[i]),      cimagf(y[i]),
+            crealf(y_test[i]), cimagf(y_test[i]));
+    }
+
+    // compute error
+    float rmse = 0.0f;
+    for (i=0; i<nfft; i++) {
+        float e = cabsf(y[i] - y_test[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf(rmse / (float)nfft);
+    printf("RMS error : %12.4e (%s)\n", rmse, rmse < 1e-3 ? "pass" : "FAIL");
+
+    // free allocated memory
+    free(x);
+    free(y);
+    free(y_test);
+
+    return 0;
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags)
+{
+    unsigned int i;
+    unsigned int k;
+
+    int d = (_dir == DFT_FORWARD) ? -1 : 1;
+
+    for (i=0; i<_nfft; i++) {
+        _y[i] = 0.0f;
+        for (k=0; k<_nfft; k++) {
+            float phi = 2*M_PI*d*i*k / (float)_nfft;
+            _y[i] += _x[k] * cexpf(_Complex_I*phi);
+        }
+    }
+}
+
+// determine if number is prime (slow, simple method)
+int is_prime(unsigned int _n)
+{
+    if (_n < 4) return 1;
+
+    unsigned int i;
+    for (i=2; i<_n; i++) {
+        if ( (_n % i) == 0)
+            return 0;
+    }
+
+    return 1;
+}
+
+// find smallest primitive root of _n (assuming _n is prime)
+unsigned int primitive_root(unsigned int _n)
+{
+    // find unique factors of _n-1
+    unsigned int unique_factors[MAX_FACTORS];
+    unsigned int num_unique_factors = 0;
+    unsigned int n = _n-1;
+    unsigned int k;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n%k)==0 ) {
+                // k is a factor of (_n-1)
+                n /= k;
+
+                // add element to end of table
+                unique_factors[num_unique_factors] = k;
+
+                // increment counter only if element is unique
+                if (num_unique_factors == 0)
+                    num_unique_factors++;
+                else if (unique_factors[num_unique_factors-1] != k)
+                    num_unique_factors++;
+                break;
+            }
+        }
+    } while (n > 1 && num_unique_factors < MAX_FACTORS);
+
+#if 0
+    // print unique factors
+    printf("found %u unique factors of n-1 = %u\n", num_unique_factors, _n-1);
+    for (k=0; k<num_unique_factors; k++)
+        printf("  %3u\n", unique_factors[k]);
+#endif
+
+    // search for minimum integer for which
+    //   g^( (_n-1)/m ) != 1 (mod _n)
+    // for all unique roots 'm'
+    unsigned int g;
+    int root_found = 0;
+    for (g=2; g<_n; g++) {
+        int is_root = 1;
+        for (k=0; k<num_unique_factors; k++) {
+            unsigned int e = (_n-1) / unique_factors[k];
+            if ( modpow(g,e,_n) == 1 ) {
+                // not a root
+                is_root = 0;
+                break;
+            }
+        }
+
+        if (is_root) {
+            //printf("  %u is a primitive root!\n", g);
+            root_found = 1;
+            break;
+        }
+    }
+
+    if (!root_found)
+        printf("warning: primitive_root(), could not find primitive root of %u\n", _n);
+    return g;
+}
+
+// compute c = base^exp (mod n)
+unsigned int modpow(unsigned int _base,
+                    unsigned int _exp,
+                    unsigned int _n)
+{
+    unsigned int c = 1;
+    unsigned int i;
+    for (i=0; i<_exp; i++)
+        c = (c * _base) % _n;
+
+    return c;
+}
+
diff --git a/sandbox/fft_rader_prime_test.c b/sandbox/fft_rader_prime_test.c
new file mode 100644
index 0000000..b200bea
--- /dev/null
+++ b/sandbox/fft_rader_prime_test.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test Rader's algorithm for FFT of prime number
+//
+// References:
+//  [Rader:1968] Charles M. Rader, "Discrete Fourier Transforms When
+//      the Number of Data Samples Is Prime," Proceedings of the IEEE,
+//      vol. 56, number 6, pp. 1107--1108, June 1968
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#define DEBUG 0
+#define DFT_FORWARD (-1)
+#define DFT_REVERSE ( 1)
+#define MAX_FACTORS (40)
+
+// print usage/help message
+void usage()
+{
+    printf("fft_rader_prime_test -- test Rader's prime FFT algorithm, compare to slow DFT method\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print usage/help\n");
+    printf("  n     : fft size (must be prime and greater than 2)\n");
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags);
+
+// determine if number is prime (slow, simple method)
+int is_prime(unsigned int _n);
+
+// find smallest primitive root of _n (assuming _n is prime)
+unsigned int primitive_root(unsigned int _n);
+
+// compute c = base^exp (mod n)
+unsigned int modpow(unsigned int _base,
+                    unsigned int _exp,
+                    unsigned int _n);
+
+int main(int argc, char*argv[]) {
+    // transform size (must be prime)
+    unsigned int nfft = 17;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhn:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                return 0;
+        case 'n':   nfft = atoi(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( nfft <= 2 || !is_prime(nfft)) {
+        fprintf(stderr,"error: %s, input transform size must be prime and greater than two\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // create and initialize data arrays
+    float complex * x      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y      = (float complex *) malloc(nfft * sizeof(float complex));
+    float complex * y_test = (float complex *) malloc(nfft * sizeof(float complex));
+    if (x == NULL || y == NULL || y_test == NULL) {
+        fprintf(stderr,"error: %s, not enough memory for allocation\n", argv[0]);
+        exit(1);
+    }
+    for (i=0; i<nfft; i++) {
+        //x[i] = randnf() + _Complex_I*randnf();
+        x[i] = (float)i + _Complex_I*(3 - (float)i);
+        y[i] = 0.0f;
+    }
+
+    // compute output for testing
+    dft_run(nfft, x, y_test, DFT_FORWARD, 0);
+
+    // 
+    // run Rader's algorithm
+    //
+
+    // compute primitive root of nfft
+    unsigned int g = primitive_root(nfft);
+
+    // create and initialize sequence
+    unsigned int * s = (unsigned int *)malloc((nfft-1)*sizeof(unsigned int));
+    for (i=0; i<nfft-1; i++)
+        s[i] = modpow(g, i+1, nfft);
+
+#if DEBUG
+    printf("computed primitive root of %u as %u\n", nfft, g);
+    // generate sequence (sanity check)
+    printf("s = [");
+    for (i=0; i<nfft-1; i++)
+        printf("%4u", s[i]);
+    printf("]\n");
+#endif
+
+    // compute DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft-1
+    // NOTE: R[0] = -1, |R[k]| = sqrt(nfft) for k != 0
+    // NOTE: R can be pre-computed
+    float complex * r = (float complex*)malloc((nfft-1)*sizeof(float complex));
+    float complex * R = (float complex*)malloc((nfft-1)*sizeof(float complex));
+    for (i=0; i<nfft-1; i++)
+        r[i] = cexpf(-_Complex_I*2*M_PI*s[i]/(float)(nfft));
+    dft_run(nfft-1, r, R, DFT_FORWARD, 0);
+
+    // compute DFT of permuted sequence, size: nfft-1
+    float complex * xp = (float complex*)malloc((nfft-1)*sizeof(float complex));
+    float complex * Xp = (float complex*)malloc((nfft-1)*sizeof(float complex));
+    for (i=0; i<nfft-1; i++) {
+        // reverse sequence
+        unsigned int k = s[nfft-1-i-1];
+        xp[i] = x[k];
+    }
+    dft_run(nfft-1, xp, Xp, DFT_FORWARD, 0);
+
+    // compute inverse FFT of product
+    for (i=0; i<nfft-1; i++)
+        Xp[i] *= R[i];
+    dft_run(nfft-1, Xp, xp, DFT_REVERSE, 0);
+
+    // set DC value
+    y[0] = 0.0f;
+    for (i=0; i<nfft; i++)
+        y[0] += x[i];
+
+    // reverse permute result, scale, and add offset x[0]
+    for (i=0; i<nfft-1; i++) {
+        unsigned int k = s[i];
+
+        y[k] = xp[i] / (float)(nfft-1) + x[0];
+    }
+
+    // free internal memory
+    free(r);
+    free(R);
+    free(xp);
+    free(Xp);
+    free(s);
+
+    // 
+    // print results
+    //
+    for (i=0; i<nfft; i++) {
+        printf("  y[%3u] = %12.6f + j*%12.6f (expected %12.6f + j%12.6f)\n",
+            i,
+            crealf(y[i]),      cimagf(y[i]),
+            crealf(y_test[i]), cimagf(y_test[i]));
+    }
+
+    // compute error
+    float rmse = 0.0f;
+    for (i=0; i<nfft; i++) {
+        float e = cabsf(y[i] - y_test[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf(rmse / (float)nfft);
+    printf("RMS error : %12.4e (%s)\n", rmse, rmse < 1e-3 ? "pass" : "FAIL");
+
+    // free allocated memory
+    free(x);
+    free(y);
+    free(y_test);
+
+    return 0;
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir,
+             int             _flags)
+{
+    unsigned int i;
+    unsigned int k;
+
+    int d = (_dir == DFT_FORWARD) ? -1 : 1;
+
+    for (i=0; i<_nfft; i++) {
+        _y[i] = 0.0f;
+        for (k=0; k<_nfft; k++) {
+            float phi = 2*M_PI*d*i*k / (float)_nfft;
+            _y[i] += _x[k] * cexpf(_Complex_I*phi);
+        }
+    }
+}
+
+// determine if number is prime (slow, simple method)
+int is_prime(unsigned int _n)
+{
+    if (_n < 4) return 1;
+
+    unsigned int i;
+    for (i=2; i<_n; i++) {
+        if ( (_n % i) == 0)
+            return 0;
+    }
+
+    return 1;
+}
+
+// find smallest primitive root of _n (assuming _n is prime)
+unsigned int primitive_root(unsigned int _n)
+{
+    // find unique factors of _n-1
+    unsigned int unique_factors[MAX_FACTORS];
+    unsigned int num_unique_factors = 0;
+    unsigned int n = _n-1;
+    unsigned int k;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n%k)==0 ) {
+                // k is a factor of (_n-1)
+                n /= k;
+
+                // add element to end of table
+                unique_factors[num_unique_factors] = k;
+
+                // increment counter only if element is unique
+                if (num_unique_factors == 0)
+                    num_unique_factors++;
+                else if (unique_factors[num_unique_factors-1] != k)
+                    num_unique_factors++;
+                break;
+            }
+        }
+    } while (n > 1 && num_unique_factors < MAX_FACTORS);
+
+#if 0
+    // print unique factors
+    printf("found %u unique factors of n-1 = %u\n", num_unique_factors, _n-1);
+    for (k=0; k<num_unique_factors; k++)
+        printf("  %3u\n", unique_factors[k]);
+#endif
+
+    // search for minimum integer for which
+    //   g^( (_n-1)/m ) != 1 (mod _n)
+    // for all unique roots 'm'
+    unsigned int g;
+    int root_found = 0;
+    for (g=2; g<_n; g++) {
+        int is_root = 1;
+        for (k=0; k<num_unique_factors; k++) {
+            unsigned int e = (_n-1) / unique_factors[k];
+            if ( modpow(g,e,_n) == 1 ) {
+                // not a root
+                is_root = 0;
+                break;
+            }
+        }
+
+        if (is_root) {
+            //printf("  %u is a primitive root!\n", g);
+            root_found = 1;
+            break;
+        }
+    }
+
+    if (!root_found)
+        printf("warning: primitive_root(), could not find primitive root of %u\n", _n);
+    return g;
+}
+
+// compute c = base^exp (mod n)
+unsigned int modpow(unsigned int _base,
+                    unsigned int _exp,
+                    unsigned int _n)
+{
+    unsigned int c = 1;
+    unsigned int i;
+    for (i=0; i<_exp; i++)
+        c = (c * _base) % _n;
+
+    return c;
+}
+
diff --git a/sandbox/fft_recursive_plan_test.c b/sandbox/fft_recursive_plan_test.c
new file mode 100644
index 0000000..5e8141d
--- /dev/null
+++ b/sandbox/fft_recursive_plan_test.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test recursive FFT plan
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+// print usage/help message
+void usage()
+{
+    printf("fft_recursive_plan_test -- test planning FFTs\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print help\n");
+    printf("  n     : input fft size\n");
+}
+
+// print fft plan
+//  _nfft   :   input fft size
+//  _level  :   level
+void liquid_fft_plan(unsigned int _nfft,
+                     unsigned int _level);
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int nfft = 124;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'n': nfft = atoi(optarg);  break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( nfft == 0 ) {
+        fprintf(stderr,"error: input transform size must be at least 2\n");
+        exit(1);
+    }
+
+    // print the FFT plan
+    liquid_fft_plan(nfft, 0);
+
+    printf("done.\n");
+    return 0;
+}
+
+// print fft plan
+//  _nfft   :   input fft size
+//  _level  :   level
+void liquid_fft_plan(unsigned int _nfft,
+                     unsigned int _level)
+{
+    if (_nfft == 0) {
+        // invalid length
+        fprintf(stderr,"error: liquid_fft_estimate_method(), fft size must be > 0\n");
+        exit(1);
+    }
+
+    // print appropriate spacing
+    unsigned int i;
+    for (i=0; i<_level; i++)
+        printf("    ");
+
+    // print fft size
+    printf("%u, ", _nfft);
+
+    if (_nfft < 8) {
+        // use simple DFT codelet
+        printf("codelet\n");
+        return;
+
+    } else if (fft_is_radix2(_nfft)) {
+        // transform is of the form 2^m
+        printf("radix-2\n");
+        return;
+
+    } else if (liquid_is_prime(_nfft)) {
+        // compute prime factors of _nfft-1
+        unsigned int factors[LIQUID_MAX_FACTORS];
+        unsigned int num_factors=0;
+        liquid_factor(_nfft-1,factors,&num_factors);
+        
+        if (num_factors > 2) {
+            // use Rader's algorithm (type I) for size _nfft-1
+            printf("Rader's algorithm, Type I > %u\n", _nfft-1);
+            liquid_fft_plan(_nfft-1, _level+1);
+            return;
+
+        } else {
+            // use Rader's algorithm (type II) for radix-2
+            // nfft_prime = 2 ^ nextpow2(2*nfft - 4)
+            unsigned int nfft_prime = 1 << liquid_nextpow2(2*_nfft-4);
+            printf("Rader's algorithm, Type II > %u\n", nfft_prime);
+            liquid_fft_plan(nfft_prime, _level+1);
+            return;
+        }
+    }
+
+    // use Cooley-Tukey algorithm
+    printf("Cooley-Tukey\n");
+
+    // compute prime factors of _nfft
+    unsigned int factors[LIQUID_MAX_FACTORS];
+    unsigned int num_factors=0;
+    liquid_factor(_nfft,factors,&num_factors);
+
+    for (i=0; i<num_factors; i++)
+        liquid_fft_plan(factors[i], _level+1);
+}
+
diff --git a/sandbox/fft_recursive_test.c b/sandbox/fft_recursive_test.c
new file mode 100644
index 0000000..1c07bb3
--- /dev/null
+++ b/sandbox/fft_recursive_test.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Test recursive mixed-radix FFT algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <getopt.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define DEBUG 0
+#define DFT_FORWARD (-1)
+#define DFT_REVERSE ( 1)
+
+// print usage/help message
+void usage()
+{
+    printf("fft_recursive_test -- test recursive mixed-radix DFTs, compare to slow DFT method\n");
+    printf("options (default values in []):\n");
+    printf("  h     : print help\n");
+    printf("  n     : fft size, default: 30\n");
+}
+
+// recursive FFT algorithm
+void fft_recursion(unsigned int    _nfft,
+                   float complex * _x,
+                   float complex * _y,
+                   int             _dir);
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir);
+
+int main(int argc, char*argv[])
+{
+    // transform size
+    unsigned int nfft = 30;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'n': nfft = atoi(optarg);  break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if ( nfft < 2 ) {
+        fprintf(stderr,"error: %s, input transform size must be at least 2\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // create and initialize data arrays
+    float complex x[nfft];
+    float complex y[nfft];
+    float complex y_test[nfft];
+    for (i=0; i<nfft; i++) {
+        //x[i] = randnf() + _Complex_I*randnf();
+        x[i] = (float)i + _Complex_I*(3 - (float)i);
+    }
+
+    // compute output for testing
+    printf("running dft...\n");
+    dft_run(nfft, x, y_test, DFT_FORWARD);
+
+    //
+    // run Cooley-Tukey FFT
+    //
+
+    for (i=0; i<nfft; i++)
+        y[i] = 0.0f;
+
+    // run recursion
+    // NOTE: this will destroy input array x
+    printf("running recursion...\n");
+    fft_recursion(nfft, x, y, DFT_FORWARD);
+
+    // 
+    // print results
+    //
+    for (i=0; i<nfft; i++) {
+        printf("  y[%3u] = %12.6f + j*%12.6f (expected %12.6f + j%12.6f)\n",
+            i,
+            crealf(y[i]),      cimagf(y[i]),
+            crealf(y_test[i]), cimagf(y_test[i]));
+    }
+
+    // compute error
+    float rmse = 0.0f;
+    for (i=0; i<nfft; i++) {
+        float e = cabsf(y[i] - y_test[i]);
+        rmse += e*e;
+    }
+    rmse = sqrtf(rmse / (float)nfft);
+    printf("RMS error : %12.4e (%s)\n", rmse, rmse < 1e-3 ? "pass" : "FAIL");
+
+    return 0;
+}
+
+// recursive...
+void fft_recursion(unsigned int    _nfft,
+                   float complex * _x,
+                   float complex * _y,
+                   int             _dir)
+{
+    // determine if _nfft is divisible by ...
+    unsigned int i;
+    unsigned int q = 0;
+    for (i=2; i<_nfft; i++) {
+        if ( (_nfft % i) == 0 ) {
+            q = i;
+            break;
+        }
+    }
+
+    if ( q == 0 ) {
+        // run slow DFT method and return
+#if DEBUG
+        printf("computing DFT of size %u\n", _nfft);
+#endif
+        dft_run(_nfft, _x, _y, _dir);
+        return;
+    }
+
+    // run dual-radix method
+    unsigned int p = _nfft / q;
+    unsigned int k;
+
+    // initialize twiddle factors
+    float complex twiddle[_nfft];
+    float d = _dir == DFT_FORWARD ? -1.0f : 1.0f;
+    for (i=0; i<_nfft; i++)
+        twiddle[i] = cexpf(d*_Complex_I*2*M_PI*(float)i/(float)_nfft);
+
+    // compute 'q' DFTs of size 'p'
+#if DEBUG
+    printf("computing %u DFTs of size %u\n", q, p);
+#endif
+    for (i=0; i<q; i++) {
+        // copy to temp buffer, compute FFT, return result
+        float complex t0[p];
+        float complex t1[p];
+
+        // copy to temporary buffer
+        for (k=0; k<p; k++)
+            t0[k] = _x[q*k+i];
+
+        // run DFT
+        fft_recursion(p, t0, t1, _dir);
+
+        // copy back to input, applying twiddle factors
+        for (k=0; k<p; k++)
+            _x[q*k+i] = t1[k] * twiddle[i*k];
+
+#if DEBUG
+        printf("i=%3u/%3u\n", i, q);
+        for (k=0; k<p; k++)
+            printf("  %12.6f %12.6f\n", crealf(_x[q*k+i]), cimagf(_x[q*k+i]));
+#endif
+    }
+
+    // compute 'p' DFTs of size 'q' and transpose
+#if DEBUG
+    printf("computing %u DFTs of size %u\n", p, q);
+#endif
+    for (i=0; i<p; i++) {
+        // copy to temp buffer...
+        float complex t0[q];
+        float complex t1[q];
+
+        for (k=0; k<q; k++)
+            t0[k] = _x[q*i+k];
+
+        fft_recursion(q, t0, t1, _dir);
+
+        // copy and transpose
+        for (k=0; k<q; k++)
+            _y[k*p+i] = t1[k];
+            //_x[q*i+k] = t1[k];
+        
+#if DEBUG
+        printf("i=%3u/%3u\n", i, p);
+        for (k=0; k<q; k++)
+            printf("  %12.6f %12.6f\n", crealf(_x[q*i+k]), cimagf(_x[q*i+k]));
+#endif
+    }
+}
+
+// super slow DFT, but functionally correct
+void dft_run(unsigned int    _nfft,
+             float complex * _x,
+             float complex * _y,
+             int             _dir)
+{
+    unsigned int i;
+    unsigned int k;
+
+    int d = (_dir == DFT_FORWARD) ? -1 : 1;
+
+    for (i=0; i<_nfft; i++) {
+        _y[i] = 0.0f;
+        for (k=0; k<_nfft; k++) {
+            float phi = 2*M_PI*d*i*k / (float)_nfft;
+            _y[i] += _x[k] * cexpf(_Complex_I*phi);
+        }
+    }
+}
+
diff --git a/sandbox/firdes_energy_test.c b/sandbox/firdes_energy_test.c
new file mode 100644
index 0000000..cd93747
--- /dev/null
+++ b/sandbox/firdes_energy_test.c
@@ -0,0 +1,80 @@
+//
+// firdes_energy_test.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+// print usage/help message
+void usage()
+{
+    printf("firdes_energy_test:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  f     : filter cutoff frequency,           0 < f < 0.5, default: 0.2\n");
+    printf("  t     : filter transition bandwidth,       0 < t < 0.5, default: 0.1\n");
+    printf("  s     : filter stop-band attenuation [dB], 0 < s,       default: 60\n");
+    printf("  m     : fractional sample delay,        -0.5 < m < 0.5, default: 0\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    float fc=0.2f;          // filter cutoff frequency
+    float ft=0.1f;          // filter transition
+    float As=60.0f;         // stop-band attenuation [dB]
+    float mu=0.0f;          // fractional timing offset
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhf:t:s:m:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                      return 0;
+        case 'f': fc = atof(optarg);            break;
+        case 't': ft = atof(optarg);            break;
+        case 's': As = atof(optarg);            break;
+        case 'm': mu = atof(optarg);            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // derived values
+    unsigned int h_len = estimate_req_filter_len(ft,As);
+    printf("h_len : %u\n", h_len);
+
+    printf("filter design parameters\n");
+    printf("    cutoff frequency            :   %8.4f\n", fc);
+    printf("    transition bandwidth        :   %8.4f\n", ft);
+    printf("    stop-band attenuation [dB]  :   %8.4f\n", As);
+    printf("    fractional sample offset    :   %8.4f\n", mu);
+    printf("    filter length               :   %u\n", h_len);
+
+    // generate the filter
+    unsigned int i;
+    float h[h_len];
+    liquid_firdes_kaiser(h_len,fc,As,mu,h);
+
+    // print coefficients
+    for (i=0; i<h_len; i++)
+        printf("h(%4u) = %16.12f;\n", i+1, h[i]);
+
+    // compute energy test
+    unsigned int nfft = 1024;
+#if 0
+    float fs = fc + 0.5f*ft;    // filter stop-band
+    float r = liquid_filter_energy(h,h_len,fs,nfft);
+    printf("r = %f\n", 10*log10f(r));
+#else
+    // in-band energy at filter cut-off
+    float fs = fc;    // filter stop-band
+    float r = liquid_filter_energy(h,h_len,fs,nfft);
+    printf(" in-band energy : %8.3f %%\n", (1-r)*100.0f);
+#endif
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/firdes_fexp_test.c b/sandbox/firdes_fexp_test.c
new file mode 100644
index 0000000..5c435b8
--- /dev/null
+++ b/sandbox/firdes_fexp_test.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdes_fexp_test.c : test synthesis of fexp receive filter
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firdes_fexp_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: sandbox/firdes_fexp_test [OPTION]\n");
+    printf("Run example fexp receive filter design\n");
+    printf("\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 4\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 3\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.3\n");
+}
+
+float firdes_freqresponse_fexp(float _f, unsigned int _k, float _beta);
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;       // filter samples/symbol
+    unsigned int m=3;       // filter delay [symbols]
+    float beta = 0.3f;      // filter excess bandwidth factor
+
+    // read properties from command line
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'k': k     = atoi(optarg); break;
+        case 'm': m     = atoi(optarg); break;
+        case 'b': beta  = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, beta must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+
+    unsigned int i;
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;   // filter length
+
+    // arrays
+    float ht[h_len];         // transmit filter coefficients
+    float hr[h_len];         // recieve filter coefficients
+
+    //
+    // start of filter design procedure
+    //
+
+    float H_prime[h_len];           // frequency response of Nyquist filter
+    float complex h_tx[h_len];      // impulse response of square-root Nyquist filter
+    float complex H_tx[h_len];      // frequency response of square-root Nyquist filter
+
+    // compute frequency response of Nyquist filter
+    for (i=0; i<h_len; i++) {
+        float f = (float)i / (float)h_len;
+        if (f > 0.5f) f = f - 1.0f;
+
+        H_prime[i] = firdes_freqresponse_fexp(f,k,beta);
+    }
+
+    // compute square-root response, copy to fft input
+    for (i=0; i<h_len; i++)
+        H_tx[i] = sqrtf(H_prime[i]);
+
+    // compute ifft and copy response
+    fft_run(h_len, H_tx, h_tx, LIQUID_FFT_BACKWARD, 0);
+    for (i=0; i<h_len; i++)
+        ht[i] = crealf( h_tx[(i+k*m+1)%h_len] ) / (float)(h_len);
+
+    // copy receive...
+    for (i=0; i<h_len; i++)
+        hr[i] = ht[i];
+
+#if 0
+    // print results
+    for (i=0; i<h_len; i++)
+        printf("H_prime(%3u) = %12.8f;\n", i+1, H_prime[i]);
+#endif
+
+    //
+    // end of filter design procedure
+    //
+
+    // print results to screen
+    printf("fexp receive filter:\n");
+    for (i=0; i<h_len; i++)
+        printf("  hr(%3u) = %12.8f;\n", i+1, hr[i]);
+
+    // compute isi
+    float rxy0 = liquid_filter_crosscorr(ht,h_len, hr,h_len, 0);
+    float isi_rms = 0.0f;
+    for (i=1; i<2*m; i++) {
+        float e = liquid_filter_crosscorr(ht,h_len, hr,h_len, i*k) / rxy0;
+        isi_rms += e*e;
+    }
+    isi_rms = sqrtf(isi_rms / (float)(2*m-1));
+    printf("\n");
+    printf("ISI (RMS) = %12.8f dB\n", 20*log10f(isi_rms));
+    
+
+    // 
+    // export output file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %f;\n", beta);
+    fprintf(fid,"h_len = 2*k*m+1;\n");
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"ht = zeros(1,h_len);\n");
+    fprintf(fid,"hp = zeros(1,h_len);\n");
+    fprintf(fid,"hr = zeros(1,h_len);\n");
+
+    // print results
+    for (i=0; i<h_len; i++)   fprintf(fid,"ht(%3u) = %12.4e;\n", i+1, ht[i]);
+    for (i=0; i<h_len; i++)   fprintf(fid,"hr(%3u) = %12.4e;\n", i+1, hr[i]);
+    
+    fprintf(fid,"hc = k*conv(ht,hr);\n");
+
+    // plot results
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Ht = 20*log10(abs(fftshift(fft(ht,  nfft))));\n");
+    fprintf(fid,"Hr = 20*log10(abs(fftshift(fft(hr,  nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc/k,nfft))));\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Ht,'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    fprintf(fid,"     f,Hr,'LineWidth',1,'Color',[0.00 0.50 0.25],...\n");
+    fprintf(fid,"     f,Hc,'LineWidth',2,'Color',[0.50 0.00 0.00],...\n");
+    fprintf(fid,"     [-0.5/k 0.5/k], [1 1]*20*log10(0.5),'or');\n");
+    fprintf(fid,"legend('transmit','receive','composite','alias points',1);\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('PSD');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 20]);\n");
+
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"tr = [  -k*m:k*m]/k;\n");
+    fprintf(fid,"tc = [-2*k*m:2*k*m]/k;\n");
+    fprintf(fid,"ic = [0:k:(4*k*m)]+1;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tr,ht,'-x', tr,hr,'-x');\n");
+    fprintf(fid,"  legend('transmit','receive',1);\n");
+    fprintf(fid,"  xlabel('Time');\n");
+    fprintf(fid,"  ylabel('fexp Tx/Rx Filters');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-2*m 2*m floor(5*min([hr ht]))/5 ceil(5*max([hr ht]))/5]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tc,hc,'-x', tc(ic),hc(ic),'or');\n");
+    fprintf(fid,"  xlabel('Time');\n");
+    fprintf(fid,"  ylabel('fexp Composite Response');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-2*m 2*m -0.2 1.2]);\n");
+    fprintf(fid,"  axis([-2*m 2*m floor(5*min(hc))/5 ceil(5*max(hc))/5]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    return 0;
+}
+
+float firdes_freqresponse_fexp(float _f,
+                               unsigned int _k,
+                               float _beta)
+{
+    // TODO : validate input
+
+    // enforce even symmetry
+    float f = fabsf(_f);
+
+    float f0 = 0.5f*(1.0f - _beta) / (float)_k;
+    float f1 = 0.5f*(1.0f        ) / (float)_k;
+    float f2 = 0.5f*(1.0f + _beta) / (float)_k;
+
+    if ( f < f0 ) {
+        return 1.0f;
+    } else if (f > f0 && f < f2) {
+        // transition band
+        float B     = 0.5f/(float)_k;
+        float gamma = logf(2.0f)/(_beta*B);
+        if ( f < f1) {
+            return expf(gamma*(B*(1-_beta) - f));
+        } else {
+            return 1.0f - expf(gamma*(f - (1+_beta)*B));
+        }
+    } else {
+        return 0.0f;
+    }
+
+    return 0.0f;
+}
+
diff --git a/sandbox/firdes_gmskrx_test.c b/sandbox/firdes_gmskrx_test.c
new file mode 100644
index 0000000..4df49ea
--- /dev/null
+++ b/sandbox/firdes_gmskrx_test.c
@@ -0,0 +1,243 @@
+//
+// firdes_gmskrx_test.c : test synthesis of GMSK receive filter
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firdes_gmskrx_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: sandbox/firdes_gmskrx_test [OPTION]\n");
+    printf("Run example GMSK receive filter design\n");
+    printf("\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 4\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 3\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.3\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;       // samples/symbol
+    unsigned int m=3;       // filter delay [symbols]
+    float BT = 0.3f;        // bandwidth-time product
+
+    // read properties from command line
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();            return 0;
+        case 'k':   k  = atoi(optarg);  break;
+        case 'm':   m  = atoi(optarg);  break;
+        case 'b':   BT = atof(optarg);  break;
+        default:
+            exit(1);
+        }
+    }
+
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (BT <= 0.0f || BT >= 1.0f) {
+        fprintf(stderr,"error: %s, BT must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+
+    unsigned int i;
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;   // filter length
+
+    // arrays
+    float ht[h_len];         // transmit filter coefficients
+    float hr[h_len];         // recieve filter coefficients
+
+    // design transmit filter
+    liquid_firdes_gmsktx(k,m,BT,0.0f,ht);
+
+    // print results to screen
+    printf("gmsk transmit filter:\n");
+    for (i=0; i<h_len; i++)
+        printf("  ht(%3u) = %12.8f;\n", i+1, ht[i]);
+
+    //
+    // start of filter design procedure
+    //
+
+    float beta = BT;        // prototype filter cut-off
+    float delta = 1e-2f;    // filter design correction factor
+
+    // temporary arrays
+    float h_primef[h_len];          // temporary buffer for real coefficients
+    float g_primef[h_len];          // 
+
+    float complex h_tx[h_len];      // impulse response of transmit filter
+    float complex h_prime[h_len];   // impulse response of 'prototype' filter
+    float complex g_prime[h_len];   // impulse response of 'gain' filter
+    float complex h_hat[h_len];     // impulse response of receive filter
+    
+    float complex H_tx[h_len];      // frequency response of transmit filter
+    float complex H_prime[h_len];   // frequency response of 'prototype' filter
+    float complex G_prime[h_len];   // frequency response of 'gain' filter
+    float complex H_hat[h_len];     // frequency response of receive filter
+
+    // create 'prototype' matched filter
+    // for now use raised-cosine
+    liquid_firdes_prototype(LIQUID_FIRFILT_RCOS,k,m,beta,0.0f,h_primef);
+
+    // create 'gain' filter to improve stop-band rejection
+    float fc = (0.7f + 0.1*beta) / (float)k;
+    float As = 60.0f;
+    liquid_firdes_kaiser(h_len, fc, As, 0.0f, g_primef);
+
+    // copy to fft input buffer, shifting appropriately
+    for (i=0; i<h_len; i++) {
+        h_prime[i] = h_primef[ (i+k*m)%h_len ];
+        g_prime[i] = g_primef[ (i+k*m)%h_len ];
+        h_tx[i]    = ht[       (i+k*m)%h_len ];
+    }
+
+    // run ffts
+    fft_run(h_len, h_prime, H_prime, LIQUID_FFT_FORWARD, 0);
+    fft_run(h_len, g_prime, G_prime, LIQUID_FFT_FORWARD, 0);
+    fft_run(h_len, h_tx,    H_tx,    LIQUID_FFT_FORWARD, 0);
+
+#if 0
+    // print results
+    for (i=0; i<h_len; i++)
+        printf("Ht(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(H_tx[i]), cimagf(H_tx[i]));
+    for (i=0; i<h_len; i++)
+        printf("H_prime(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(H_prime[i]), cimagf(H_prime[i]));
+    for (i=0; i<h_len; i++)
+        printf("G_prime(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(G_prime[i]), cimagf(G_prime[i]));
+#endif
+
+    // find minimum of reponses
+    float H_tx_min = 0.0f;
+    float H_prime_min = 0.0f;
+    float G_prime_min = 0.0f;
+    for (i=0; i<h_len; i++) {
+        if (i==0 || crealf(H_tx[i])    < H_tx_min)    H_tx_min    = crealf(H_tx[i]);
+        if (i==0 || crealf(H_prime[i]) < H_prime_min) H_prime_min = crealf(H_prime[i]);
+        if (i==0 || crealf(G_prime[i]) < G_prime_min) G_prime_min = crealf(G_prime[i]);
+    }
+
+    // compute 'prototype' response, removing minima, and add correction factor
+    for (i=0; i<h_len; i++) {
+        // compute response necessary to yeild prototype response (not exact, but close)
+        H_hat[i] = crealf(H_prime[i] - H_prime_min + delta) / crealf(H_tx[i] - H_tx_min + delta);
+
+        // include additional term to add stop-band suppression
+        H_hat[i] *= crealf(G_prime[i] - G_prime_min) / crealf(G_prime[0]);
+    }
+
+    // compute ifft and copy response
+    fft_run(h_len, H_hat, h_hat, LIQUID_FFT_BACKWARD, 0);
+    for (i=0; i<h_len; i++)
+        hr[i] = crealf( h_hat[(i+k*m+1)%h_len] ) / (float)(k*h_len);
+
+    //
+    // end of filter design procedure
+    //
+
+    // print results to screen
+    printf("gmsk receive filter:\n");
+    for (i=0; i<h_len; i++)
+        printf("  hr(%3u) = %12.8f;\n", i+1, hr[i]);
+
+    // compute isi
+    float rxy0 = liquid_filter_crosscorr(ht,h_len, hr,h_len, 0);
+    float isi_rms = 0.0f;
+    for (i=1; i<2*m; i++) {
+        float e = liquid_filter_crosscorr(ht,h_len, hr,h_len, i*k) / rxy0;
+        isi_rms += e*e;
+    }
+    isi_rms = sqrtf(isi_rms / (float)(2*m-1));
+    printf("\n");
+    printf("ISI (RMS) = %12.8f dB\n", 20*log10f(isi_rms));
+    
+
+    // 
+    // export output file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %f;\n", BT);
+    fprintf(fid,"h_len = 2*k*m+1;\n");
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"ht = zeros(1,h_len);\n");
+    fprintf(fid,"hp = zeros(1,h_len);\n");
+    fprintf(fid,"hr = zeros(1,h_len);\n");
+
+    // print results
+    for (i=0; i<h_len; i++)   fprintf(fid,"ht(%3u) = %12.4e;\n", i+1, ht[i] / k);
+    for (i=0; i<h_len; i++)   fprintf(fid,"hr(%3u) = %12.4e;\n", i+1, hr[i] * k);
+    for (i=0; i<h_len; i++)   fprintf(fid,"hp(%3u) = %12.4e;\n", i+1, h_primef[i]);
+    
+    fprintf(fid,"hc = k*conv(ht,hr);\n");
+
+    // plot results
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Ht = 20*log10(abs(fftshift(fft(ht,  nfft))));\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp/k,nfft))));\n");
+    fprintf(fid,"Hr = 20*log10(abs(fftshift(fft(hr,  nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc/k,nfft))));\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Ht,'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    fprintf(fid,"     f,Hp,'LineWidth',1,'Color',[0.80 0.80 0.80],...\n");
+    fprintf(fid,"     f,Hr,'LineWidth',1,'Color',[0.00 0.50 0.25],...\n");
+    fprintf(fid,"     f,Hc,'LineWidth',2,'Color',[0.50 0.00 0.00],...\n");
+    fprintf(fid,"     [-0.5/k 0.5/k], [1 1]*20*log10(0.5),'or');\n");
+    fprintf(fid,"legend('transmit','prototype','receive','composite','alias points',1);\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('PSD');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 20]);\n");
+
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"tr = [  -k*m:k*m]/k;\n");
+    fprintf(fid,"tc = [-2*k*m:2*k*m]/k;\n");
+    fprintf(fid,"ic = [0:k:(4*k*m)]+1;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tr,ht,'-x', tr,hr,'-x');\n");
+    fprintf(fid,"  legend('transmit','receive',1);\n");
+    fprintf(fid,"  xlabel('Time');\n");
+    fprintf(fid,"  ylabel('GMSK Tx/Rx Filters');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-2*m 2*m floor(5*min([hr ht]))/5 ceil(5*max([hr ht]))/5]);\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tc,hc,'-x', tc(ic),hc(ic),'or');\n");
+    fprintf(fid,"  xlabel('Time');\n");
+    fprintf(fid,"  ylabel('GMSK Composite Response');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([-2*m 2*m -0.2 1.2]);\n");
+    fprintf(fid,"  axis([-2*m 2*m floor(5*min(hc))/5 ceil(5*max(hc))/5]);\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    return 0;
+}
+
diff --git a/sandbox/firdes_length_test.c b/sandbox/firdes_length_test.c
new file mode 100644
index 0000000..18a0e60
--- /dev/null
+++ b/sandbox/firdes_length_test.c
@@ -0,0 +1,97 @@
+//
+// firdes_length_test.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "liquid.internal.h"
+
+#define OUTPUT_FILENAME "firdes_length_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("firdes_length_test:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter transition bandwidth,  0 < t < 0.5, default: 0.1\n");
+    printf("  a     : filter attenuation (minimum) [dB], default: 20\n");
+    printf("  A     : filter attenuation (maximum) [dB], default: 100\n");
+    printf("  n     : number of steps, default: 41\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    float ft=0.1f;          // filter transition
+    float As_min = 20.0f;
+    float As_max = 100.0f;
+    unsigned int num_As = 41;   // number of steps
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:a:A:n:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();                  return 0;
+        case 't': ft = atof(optarg);        break;
+        case 'a': As_min = atof(optarg);    break;
+        case 'A': As_max = atof(optarg);    break;
+        case 'n': num_As = atoi(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (As_min <= 0.0f || As_max <= 0.0f) {
+        fprintf(stderr,"error: %s, attenuation must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (As_max <= As_min) {
+        fprintf(stderr,"error: %s, minimum attenuation cannot exceed maximum\n", argv[0]);
+        exit(1);
+    } else if (num_As < 2) {
+        fprintf(stderr,"error: %s, must have at least 2 steps", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    float As_step = (As_max - As_min) / (float)(num_As - 1);
+
+    // output to file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"num_steps = %u;\n", num_As);
+    fprintf(fid,"As=zeros(1,num_steps);\n");
+    fprintf(fid,"n_Kaiser=zeros(1,num_steps);\n");
+    fprintf(fid,"n_Herrmann=zeros(1,num_steps);\n");
+
+    unsigned int i;
+    for (i=0; i<num_As; i++) {
+        float As = As_min + i*As_step;
+        float n_Kaiser = estimate_req_filter_len_Kaiser(ft,As);
+        float n_Herrmann = estimate_req_filter_len_Herrmann(ft,As);//+8);
+        printf("As = %8.2f, n_Kaiser=%8.2f, n_Herrmann=%8.2f\n",
+                As, n_Kaiser, n_Herrmann);
+
+        fprintf(fid,"As(%4u) = %12.8f; n_Kaiser(%4u)=%12.8f; n_Herrmann(%4u)=%12.8f;\n",
+                i+1, As,
+                i+1, n_Kaiser,
+                i+1, n_Herrmann);
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(As,n_Kaiser, As,n_Herrmann);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('Stop-band Attenuation [dB]');\n");
+    fprintf(fid,"ylabel('Filter Length');\n");
+    fprintf(fid,"legend('Kaiser','Herrmann',0);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/firfarrow_rrrf_test.c b/sandbox/firfarrow_rrrf_test.c
new file mode 100644
index 0000000..861bbd6
--- /dev/null
+++ b/sandbox/firfarrow_rrrf_test.c
@@ -0,0 +1,102 @@
+//
+// firfarrow_rrrf_test.c
+//
+// Demonstrates the functionality of the finite impulse response Farrow
+// filter for arbitrary fractional sample group delay.
+//
+
+#include <stdio.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firfarrow_rrrf_test.m"
+
+int main() {
+    // options
+    unsigned int h_len=19;  // filter length
+    unsigned int p=5;       // polynomial order
+    float fc=0.45f;         // filter cutoff
+    float As=60.0f;         // stop-band attenuation [dB]
+    unsigned int m=9;       // number of delays to evaluate
+
+    // coefficients array
+    float h[h_len];
+    float tao = ((float)h_len-1)/2.0f;  // nominal filter delay
+
+    firfarrow_rrrf f = firfarrow_rrrf_create(h_len, p, fc, As);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"h_len = %u;\n", h_len);
+    fprintf(fid,"h = zeros(%u,%u);\n", m, h_len);
+    fprintf(fid,"mu = zeros(1,%u);\n", m);
+    fprintf(fid,"tao = %12.8f;\n", tao);
+
+    unsigned int i, j;
+    float mu_vect[m];
+    for (i=0; i<m; i++) {
+        mu_vect[i] = ((float)i)/((float)m-1) - 0.5f;
+        //printf("mu[%3u] = %12.8f\n", i, mu_vect[i]);
+        fprintf(fid,"mu(%3u) = %12.8f;\n", i+1, mu_vect[i]);
+    }
+
+    firfarrow_rrrf_print(f);
+    printf("Farrow filter group delay error for certain frequencies:\n");
+    printf("%8s %8s  : %8.4f   %8.4f   %8.4f\n", "mu", "delay", 0.0f, 0.2f, 0.4f);
+    for (i=0; i<m; i++) {
+        firfarrow_rrrf_set_delay(f,mu_vect[i]);
+
+        firfarrow_rrrf_get_coefficients(f,h);
+        for (j=0; j<h_len; j++)
+            fprintf(fid,"  h(%3u,%3u) = %12.4e;\n", i+1, j+1, h[j]);
+
+        //printf("group delay (mu = %12.8f) : %12.8f\n", mu_vect[i], fir_group_delay(h,h_len,0.0f));
+        printf("%8.4f ", mu_vect[i]);
+        printf("%8.4f  : ", tao + mu_vect[i]);
+        printf("%8.1e   ", tao + mu_vect[i] - firfarrow_rrrf_groupdelay(f,0.0f));
+        printf("%8.1e   ", tao + mu_vect[i] - firfarrow_rrrf_groupdelay(f,0.2f));
+        printf("%8.1e   ", tao + mu_vect[i] - firfarrow_rrrf_groupdelay(f,0.4f));
+        printf("\n");
+    }
+
+   
+    // compute delay, print results
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/(2*nfft);\n");
+    fprintf(fid,"g = 0:(h_len-1);\n");
+    fprintf(fid,"D = zeros(m,nfft);\n");
+    fprintf(fid,"D_ideal = zeros(m,2);\n");
+    fprintf(fid,"H = zeros(m,nfft);\n");
+    fprintf(fid,"for i=1:m,\n");
+    fprintf(fid,"    d = real( fft(h(i,:).*g,2*nfft) ./ fft(h(i,:),2*nfft) );\n");
+    fprintf(fid,"    D(i,:) = d(1:nfft);\n");
+    fprintf(fid,"    D_ideal(i,:) = tao + mu(i);\n");
+    fprintf(fid,"    H_prime = 20*log10(abs(fftshift(fft(h(i,:),2*nfft))));\n");
+    fprintf(fid,"    H(i,:) = H_prime([nfft+1]:[2*nfft]);\n");
+    fprintf(fid,"end;\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-');\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,D,'-k','LineWidth',2,[0 0.5],D_ideal,'-k');\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Group Delay [samples]');\n");
+    fprintf(fid,"axis([0 0.5 (tao-1) (tao+1)]);\n");
+    fprintf(fid,"for i=1:m,\n");
+    fprintf(fid,"    text(0.3,tao+mu(i)+0.025,['\\mu =' num2str(mu(i))]);\n");
+    fprintf(fid,"end;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    firfarrow_rrrf_destroy(f);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/firpfbch2_analysis_equivalence_test.c b/sandbox/firpfbch2_analysis_equivalence_test.c
new file mode 100644
index 0000000..fcac0fe
--- /dev/null
+++ b/sandbox/firpfbch2_analysis_equivalence_test.c
@@ -0,0 +1,267 @@
+//
+// firpfbch2_analysis_equivalence_test.c
+//
+// This script tests the equivalence of a finite impulse response (fir)
+// polyphase filter bank (pfb) channelizer with two outputs per branch
+// per input symbol.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG 1
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int num_channels=6;    // number of channels (must be even)
+    unsigned int m=4;               // filter delay
+    unsigned int num_symbols=4*m;   // number of symbols
+
+    // validate input
+    if (num_channels%2) {
+        fprintf(stderr,"error: %s, number of channels must be even\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence
+#if 0
+    unsigned int h_len = 2*m*num_channels;
+    float h[h_len];
+    for (i=0; i<h_len; i++) h[i] = randnf();
+#else
+    unsigned int h_len = 2*m*num_channels+1;
+    float h[h_len];
+    // NOTE: 81.29528 dB > beta = 8.00000 (6 channels, m=4)
+    liquid_firdes_kaiser(h_len, 1.0f/(float)num_channels, 81.29528f, 0.0f, h);
+#endif
+    // normalize
+    float hsum = 0.0f;
+    for (i=0; i<h_len; i++) hsum += h[i];
+    for (i=0; i<h_len; i++) h[i] = h[i] * num_channels / hsum;
+
+    // sub-sampled filters for M=6 channels, m=4, beta=8.0
+    //  -3.2069e-19  -6.7542e-04  -1.3201e-03   2.2878e-18   3.7613e-03   5.8033e-03
+    //  -7.2899e-18  -1.2305e-02  -1.7147e-02   1.6510e-17   3.1187e-02   4.0974e-02
+    //  -3.0032e-17  -6.8026e-02  -8.6399e-02   4.6273e-17   1.3732e-01   1.7307e-01
+    //  -6.2097e-17  -2.8265e-01  -3.7403e-01   7.3699e-17   8.0663e-01   1.6438e+00
+    //   2.0001e+00   1.6438e+00   8.0663e-01   7.3699e-17  -3.7403e-01  -2.8265e-01
+    //  -6.2097e-17   1.7307e-01   1.3732e-01   4.6273e-17  -8.6399e-02  -6.8026e-02
+    //  -3.0032e-17   4.0974e-02   3.1187e-02   1.6510e-17  -1.7147e-02  -1.2305e-02
+    //  -7.2899e-18   5.8033e-03   3.7613e-03   2.2878e-18  -1.3201e-03  -6.7542e-04
+
+    // create filterbank manually
+    dotprod_crcf dp[num_channels];  // vector dot products
+    windowcf w[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("h_prototype:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h[%3u] = %12.8f\n", i, h[i]);
+#endif
+
+    // create objects
+    unsigned int h_sub_len = 2*m;
+    float h_sub[h_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter
+#if 0
+        for (j=0; j<h_sub_len; j++)
+            h_sub[j] = h[j*num_channels+i];
+#else
+        // load coefficients in reverse order
+        for (j=0; j<h_sub_len; j++)
+            h_sub[h_sub_len-j-1] = h[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_crcf_create(h_sub, h_sub_len);
+        w[i]  = windowcf_create(h_sub_len);
+
+#if DEBUG
+        printf("h_sub[%u] : \n", i);
+        for (j=0; j<h_sub_len; j++)
+            printf("  h[%3u] = %12.8f\n", j, h_sub[j]);
+#endif
+    }
+
+    // generate DFT object
+    float complex x[num_channels];  // time-domain buffer
+    float complex X[num_channels];  // freq-domain buffer
+#if 1
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+
+    float complex y[num_samples];                   // time-domain input
+    float complex Y0[2*num_symbols][num_channels];  // channelizer output
+    float complex Y1[2*num_symbols][num_channels];  // conventional output
+
+    // generate input sequence
+    for (i=0; i<num_samples; i++) {
+        //y[i] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+        y[i] = (i==0) ? 1.0f : 0.0f;
+        y[i] = cexpf(_Complex_I*sqrtf(2.0f)*i*i);
+        printf("y[%3u] = %12.8f + %12.8fj\n", i, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // 
+    // run analysis filter bank
+    //
+#if 0
+    unsigned int filter_index = 0;
+#else
+    unsigned int filter_index = num_channels/2-1;
+#endif
+    float complex y_hat;    // input sample
+    float complex * r;      // buffer read pointer
+    int toggle = 0;         // flag indicating buffer/filter alignment
+
+    //
+    for (i=0; i<2*num_symbols; i++) {
+
+        // load buffers in blocks of num_channels/2
+        for (j=0; j<num_channels/2; j++) {
+            // grab sample
+            y_hat = y[i*num_channels/2 + j];
+
+            // push sample into buffer at filter index
+            windowcf_push(w[filter_index], y_hat);
+
+            // decrement filter index
+            filter_index = (filter_index + num_channels - 1) % num_channels;
+            //filter_index = (filter_index + 1) % num_channels;
+        }
+
+        // execute filter outputs
+        // reversing order of output (not sure why this is necessary)
+        unsigned int offset = toggle ? num_channels/2 : 0;
+        toggle = 1-toggle;
+        for (j=0; j<num_channels; j++) {
+            unsigned int buffer_index  = (offset+j)%num_channels;
+            unsigned int dotprod_index = j;
+
+            windowcf_read(w[buffer_index], &r);
+            //dotprod_crcf_execute(dp[dotprod_index], r, &X[num_channels-j-1]);
+            dotprod_crcf_execute(dp[dotprod_index], r, &X[buffer_index]);
+        }
+
+        printf("***** i = %u\n", i);
+        for (j=0; j<num_channels; j++)
+            printf("  v2[%4u] = %12.8f + %12.8fj\n", j, crealf(X[j]), cimagf(X[j]));
+        // execute DFT, store result in buffer 'x'
+        fft_execute(fft);
+        // scale fft output
+        for (j=0; j<num_channels; j++)
+            x[j] *= 1.0f / (num_channels);
+
+        // move to output array
+        for (j=0; j<num_channels; j++)
+            Y0[i][j] = x[j];
+    }
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_crcf_destroy(dp[i]);
+        windowcf_destroy(w[i]);
+    }
+    fft_destroy_plan(fft);
+
+
+    // 
+    // run traditional down-converter (inefficient)
+    //
+    // generate filter object
+    firfilt_crcf f = firfilt_crcf_create(h, h_len);
+
+    float dphi; // carrier frequency
+    unsigned int n=0;
+    for (i=0; i<num_channels; i++) {
+
+        // reset filter
+        firfilt_crcf_reset(f);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+            // push down-converted sample into filter
+            firfilt_crcf_push(f, y[j]*cexpf(-_Complex_I*j*dphi));
+
+            // compute output at the appropriate sample time
+            assert(n<2*num_symbols);
+            if ( ((j+1)%(num_channels/2))==0 ) {
+                firfilt_crcf_execute(f, &Y1[n][i]);
+                n++;
+            }
+        }
+        assert(n==2*num_symbols);
+
+    }
+    firfilt_crcf_destroy(f);
+
+    // print filterbank channelizer
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<2*num_symbols; i++) {
+        printf("%2u:", i);
+        for (j=0; j<num_channels; j++) {
+            printf("%6.3f+%6.3fj, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+
+#if 0
+    // print traditional channelizer
+    printf("\n");
+    printf("traditional channelizer:\n");
+    for (i=0; i<2*num_symbols; i++) {
+        printf("%2u:", i);
+        for (j=0; j<num_channels; j++) {
+            printf("%6.3f+%6.3fj, ", crealf(Y1[i][j]), cimagf(Y1[i][j]));
+        }
+        printf("\n");
+    }
+
+    // 
+    // compare results
+    // 
+    float mse[num_channels];
+    float complex d;
+    for (i=0; i<num_channels; i++) {
+        mse[i] = 0.0f;
+        for (j=0; j<2*num_symbols; j++) {
+            d = Y0[j][i] - Y1[j][i];
+            mse[i] += crealf(d*conjf(d));
+        }
+
+        mse[i] /= num_symbols;
+    }
+    printf("\n");
+    printf(" e:");
+    for (i=0; i<num_channels; i++)
+        printf("%12.4e    ", sqrt(mse[i]));
+    printf("\n");
+#endif
+
+    printf("done.\n");
+    return 0;
+
+}
+
diff --git a/sandbox/firpfbch2_test.c b/sandbox/firpfbch2_test.c
new file mode 100644
index 0000000..8794c5d
--- /dev/null
+++ b/sandbox/firpfbch2_test.c
@@ -0,0 +1,380 @@
+//
+// firpfbch2_analysis_equivalence_test.c
+//
+// This script tests the equivalence of a finite impulse response (fir)
+// polyphase filter bank (pfb) channelizer with two outputs per branch
+// per input symbol.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// print debug status
+#define DEBUG 0
+
+// use pseudo-random coefficients; this is useful for determining
+// functional correctness (no symmetry in filter)
+#define RANDOM_COEFFICIENTS 0
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int num_channels=6;    // number of channels (must be even)
+    unsigned int m=4;               // filter delay
+    unsigned int num_symbols=4*m;   // number of symbols
+
+    // validate input
+    if (num_channels%2) {
+        fprintf(stderr,"error: %s, number of channels must be even\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    //
+    // ANALYSIS FILTERBANK
+    //
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence
+    unsigned int h_len = 2*m*num_channels+1;
+    float h[h_len];
+#if RANDOM_COEFFICIENTS
+    // testing: set filter coefficients to random values
+    unsigned int s0 = 1;
+    unsigned int p0 = 524287;   // large prime number
+    unsigned int g0 =   1031;   // another large prime number
+    for (i=0; i<h_len; i++) {
+        s0 = (s0 * p0) % g0;
+        h[i] = (float)s0 / (float)g0 - 0.5f;
+    }
+#else
+    // NOTE: 81.29528 dB > beta = 8.00000 (6 channels, m=4)
+    liquid_firdes_kaiser(h_len, 1.0f/(float)num_channels, 81.29528f, 0.0f, h);
+#endif
+    // normalize
+    float hsum = 0.0f;
+    for (i=0; i<h_len; i++) hsum += h[i];
+    for (i=0; i<h_len; i++) h[i] = h[i] * num_channels / hsum;
+
+    // sub-sampled filters for M=6 channels, m=4, beta=8.0
+    //  -3.2069e-19  -6.7542e-04  -1.3201e-03   2.2878e-18   3.7613e-03   5.8033e-03
+    //  -7.2899e-18  -1.2305e-02  -1.7147e-02   1.6510e-17   3.1187e-02   4.0974e-02
+    //  -3.0032e-17  -6.8026e-02  -8.6399e-02   4.6273e-17   1.3732e-01   1.7307e-01
+    //  -6.2097e-17  -2.8265e-01  -3.7403e-01   7.3699e-17   8.0663e-01   1.6438e+00
+    //   2.0001e+00   1.6438e+00   8.0663e-01   7.3699e-17  -3.7403e-01  -2.8265e-01
+    //  -6.2097e-17   1.7307e-01   1.3732e-01   4.6273e-17  -8.6399e-02  -6.8026e-02
+    //  -3.0032e-17   4.0974e-02   3.1187e-02   1.6510e-17  -1.7147e-02  -1.2305e-02
+    //  -7.2899e-18   5.8033e-03   3.7613e-03   2.2878e-18  -1.3201e-03  -6.7542e-04
+
+    // create filterbank manually
+    dotprod_crcf dp[num_channels];  // vector dot products
+    windowcf w[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("h_prototype:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h[%3u] = %12.8f\n", i, h[i]);
+#endif
+
+    // create objects
+    unsigned int h_sub_len = 2*m;
+    float h_sub[h_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter
+#if 0
+        for (j=0; j<h_sub_len; j++)
+            h_sub[j] = h[j*num_channels+i];
+#else
+        // load coefficients in reverse order
+        for (j=0; j<h_sub_len; j++)
+            h_sub[h_sub_len-j-1] = h[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_crcf_create(h_sub, h_sub_len);
+        w[i]  = windowcf_create(h_sub_len);
+
+#if DEBUG
+        printf("h_sub[%u] : \n", i);
+        for (j=0; j<h_sub_len; j++)
+            printf("  h[%3u] = %12.8f\n", j, h_sub[j]);
+#endif
+    }
+
+    // generate DFT object
+    float complex x[num_channels];  // time-domain buffer
+    float complex X[num_channels];  // freq-domain buffer
+#if 1
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+
+    float complex y[num_samples];                   // time-domain input
+    float complex Y0[2*num_symbols][num_channels];  // channelizer output
+    float complex z[num_samples];                   // time-domain output
+
+    // generate input sequence
+    for (i=0; i<num_samples; i++) {
+        //y[i] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+        //y[i] = (i==0) ? 1.0f : 0.0f;
+        //y[i] = expf(-0.1f*i);
+        y[i] = cexpf( (0.1f*_Complex_I - 0.1f)*i );
+        printf("y[%3u] = %12.8f + %12.8fj\n", i, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // 
+    // run analysis filter bank
+    //
+    float complex y_hat;    // input sample
+    float complex * r;      // buffer read pointer
+    int toggle = 0;         // flag indicating buffer/filter alignment
+
+    //
+    for (i=0; i<2*num_symbols; i++) {
+
+        // load buffers in blocks of num_channels/2 starting
+        // in the middle of the filter bank and moving in the
+        // negative direction
+        unsigned int base_index = toggle ? num_channels : num_channels/2;
+        for (j=0; j<num_channels/2; j++) {
+            // grab sample
+            y_hat = y[i*num_channels/2 + j];
+
+            // push sample into buffer at filter index
+            windowcf_push(w[base_index-j-1], y_hat);
+        }
+
+        // execute filter outputs
+        // reversing order of output (not sure why this is necessary)
+        unsigned int offset = toggle ? num_channels/2 : 0;
+        for (j=0; j<num_channels; j++) {
+            unsigned int buffer_index  = (offset+j)%num_channels;
+            unsigned int dotprod_index = j;
+
+            windowcf_read(w[buffer_index], &r);
+            //dotprod_crcf_execute(dp[dotprod_index], r, &X[num_channels-j-1]);
+            dotprod_crcf_execute(dp[dotprod_index], r, &X[buffer_index]);
+        }
+
+#if DEBUG
+        printf("***** i = %u\n", i);
+        for (j=0; j<num_channels; j++)
+            printf("  v2[%4u] = %12.8f + %12.8fj\n", j, crealf(X[j]), cimagf(X[j]));
+#endif
+        // execute DFT, store result in buffer 'x'
+        fft_execute(fft);
+        // scale result by 1/num_channels (C transform)
+        for (j=0; j<num_channels; j++)
+            x[j] *= 1.0f / (num_channels);
+
+        // move to output array
+        for (j=0; j<num_channels; j++)
+            Y0[i][j] = x[j];
+        toggle = 1-toggle;
+    }
+
+    // print filterbank channelizer
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<2*num_symbols; i++) {
+        printf("%2u:", i);
+        for (j=0; j<num_channels; j++) {
+            printf("%6.3f+%6.3fj, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_crcf_destroy(dp[i]);
+        windowcf_destroy(w[i]);
+    }
+    fft_destroy_plan(fft);
+
+
+    //
+    // SYNTHESIS FILTERBANK
+    //
+
+    // generate synthesis filter
+    unsigned int f_len = 2*m*num_channels+1;
+    float f[f_len];
+#if RANDOM_COEFFICIENTS
+    // testing: set filter coefficients to random values
+    unsigned int s1 = 1;
+    unsigned int p1 = 131071;   // large prime number
+    unsigned int g1 =   1031;   // another large prime number
+    for (i=0; i<f_len; i++) {
+        s1 = (s1 * p1) % g1;
+        f[i] = (float)s1 / (float)g1 - 0.5f;
+    }
+#else
+    // NOTE: 81.29528 dB > beta = 8.00000 (6 channels, m=4)
+    liquid_firdes_kaiser(f_len, 0.5f/(float)num_channels, 81.29528f, 0.0f, f);
+#endif
+    // normalize
+    float fsum = 0.0f;
+    for (i=0; i<f_len; i++) fsum += f[i];
+    for (i=0; i<f_len; i++) f[i] = f[i] * num_channels / fsum;
+
+    // sub-sampled filters for M=6 channels, m=4, beta=8.0
+    //  -1.6032e-19  -3.8990e-04  -1.3199e-03  -2.6684e-03  -3.7608e-03  -3.3501e-03
+    //   3.6445e-18   7.1034e-03   1.7145e-02   2.6960e-02   3.1183e-02   2.3653e-02
+    //  -1.5014e-17  -3.9269e-02  -8.6387e-02  -1.2593e-01  -1.3730e-01  -9.9906e-02
+    //   3.1044e-17   1.6316e-01   3.7398e-01   6.0172e-01   8.0652e-01   9.4890e-01
+    //   9.9990e-01   9.4890e-01   8.0652e-01   6.0172e-01   3.7398e-01   1.6316e-01
+    //   3.1044e-17  -9.9906e-02  -1.3730e-01  -1.2593e-01  -8.6387e-02  -3.9269e-02
+    //  -1.5014e-17   2.3653e-02   3.1183e-02   2.6960e-02   1.7145e-02   7.1034e-03
+    //   3.6445e-18  -3.3501e-03  -3.7608e-03  -2.6684e-03  -1.3199e-03  -3.8990e-04
+
+    // create filterbank manually
+    //dotprod_crcf dp[num_channels];  // vector dot products
+    windowcf w0[num_channels];       // window buffers
+    windowcf w1[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("f_prototype:\n");
+    for (i=0; i<f_len; i++)
+        printf("  f[%3u] = %12.8f\n", i, f[i]);
+#endif
+
+    // create objects
+    unsigned int f_sub_len = 2*m;
+    float f_sub[f_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter
+#if 0
+        for (j=0; j<f_sub_len; j++)
+            f_sub[j] = f[j*num_channels+i];
+#else
+        // load coefficients in reverse order
+        for (j=0; j<f_sub_len; j++)
+            f_sub[f_sub_len-j-1] = f[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_crcf_create(f_sub, f_sub_len);
+        w0[i] = windowcf_create(f_sub_len);
+        w1[i] = windowcf_create(f_sub_len);
+
+#if DEBUG
+        printf("f_sub[%u] : \n", i);
+        for (j=0; j<f_sub_len; j++)
+            printf("  f[%3u] = %12.8f\n", j, f_sub[j]);
+#endif
+    }
+
+    // generate DFT object
+#if 1
+    fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+    
+    // 
+    // run synthesis filter bank
+    //
+    toggle = 0;
+    unsigned int n=0;
+    for (i=0; i<2*num_symbols; i++) {
+        // load ifft input
+        // TODO: select frequency-band filtering
+        for (j=0; j<num_channels; j++) {
+            X[j] = Y0[i][j];
+        }
+        // execute inverse DFT, store result in buffer 'x'
+        fft_execute(fft);
+
+        // scale result by 1/num_channels (C transform)
+        for (j=0; j<num_channels; j++)
+            x[j] *= 1.0f / (num_channels);
+        // scale result by num_channels/2
+        for (j=0; j<num_channels; j++)
+            x[j] *= (float)num_channels / 2.0f;
+
+#if DEBUG
+        // print result
+        printf("***** i = %u\n", i);
+        for (j=0; j<num_channels; j++)
+            printf("  v5[%4u] = %12.8f + %12.8fj\n", j, crealf(x[j]), cimagf(x[j]));
+#endif
+
+        // push samples into appropriate buffer
+        windowcf * buffer = (toggle == 0 ? w1 : w0);
+        for (j=0; j<num_channels; j++)
+            windowcf_push(buffer[j], x[j]);
+
+        // compute filter outputs
+        float complex * r0;
+        float complex * r1;
+        float complex z0;
+        float complex z1;
+        for (j=0; j<num_channels/2; j++) {
+            // buffer index
+            unsigned int b = (toggle == 0) ? j : j+num_channels/2;
+
+            windowcf_read(w0[b], &r0);
+            windowcf_read(w1[b], &r1);
+
+            // buffer pointers
+            float complex * p0 = toggle ? r0 : r1;
+            float complex * p1 = toggle ? r1 : r0;
+
+#if DEBUG
+            // plot registers
+            unsigned int k;
+            printf("reg[0] : ");
+            for (k=0; k<f_sub_len; k++)
+                printf("(%9.2e,%9.2e),", crealf(r0[k]), cimagf(r0[k]));
+            printf("\n");
+            printf("reg[1] : ");
+            for (k=0; k<f_sub_len; k++)
+                printf("(%9.2e,%9.2e),", crealf(r1[k]), cimagf(r1[k]));
+            printf("\n");
+#endif
+
+            // run dot products
+            dotprod_crcf_execute(dp[j],                p0, &z0);
+            dotprod_crcf_execute(dp[j+num_channels/2], p1, &z1);
+
+#if DEBUG
+            printf("  z(%3u) = %12.8f + %12.8fj\n", i, crealf(z0+z1), cimagf(z0+z1));
+#endif
+            z[n++] = z0 + z1;
+        }
+        toggle = 1-toggle;
+    }
+    assert( n == num_samples );
+    
+    // print output
+    printf("\n");
+    printf("filterbank synthesizer:\n");
+    for (i=0; i<num_samples; i++) {
+        printf("%6u : %12.8f+%12.8fj\n", i, crealf(z[i]), cimagf(z[i]));
+    }
+
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_crcf_destroy(dp[i]);
+        windowcf_destroy(w0[i]);
+        windowcf_destroy(w1[i]);
+    }
+    fft_destroy_plan(fft);
+
+    printf("done.\n");
+    return 0;
+
+}
+
diff --git a/sandbox/firpfbch_analysis_alignment_test.c b/sandbox/firpfbch_analysis_alignment_test.c
new file mode 100644
index 0000000..857a0a3
--- /dev/null
+++ b/sandbox/firpfbch_analysis_alignment_test.c
@@ -0,0 +1,148 @@
+//
+// sandbox/firpfbch_anayalis_sync_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "firpfbch_analysis_sync_test.m"
+
+// forward declaration of internal methods
+
+void firpfbch_crcf_analyzer_push(firpfbch_crcf _q,
+                                 float complex _x);
+
+void firpfbch_crcf_analyzer_run(firpfbch_crcf   _q,
+                                unsigned int    _k,
+                                float complex * _X);
+
+int main() {
+    // options
+    unsigned int num_channels=4;    // must be even number
+    unsigned int num_symbols=12;    // number of symbols
+    unsigned int m=3;               // filter delay (symbols)
+    float beta = 0.9f;              // filter excess bandwidth factor
+    unsigned int delay = 3;         // initial sampling delay
+    int ftype = LIQUID_FIRFILT_ARKAISER;
+
+    // derived values
+    unsigned int num_frames = num_symbols + 2*m;            // compensate for filter delay
+    unsigned int num_samples = num_channels * num_frames;   // total number of samples
+
+    // data arrays
+    float complex x[num_samples];
+    float complex Y0[num_frames][num_channels];
+    float complex Y1[num_frames][num_channels];
+
+    // create analyzer objects
+    firpfbch_crcf ca0 = firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, num_channels, m, beta, ftype);
+    firpfbch_crcf ca1 = firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER, num_channels, m, beta, ftype);
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate random input sequence
+    for (i=0; i<num_samples; i++)
+        x[i] = randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+    // push several dummy samples into first analyzer (emulates
+    // timing offset)
+    float complex x_hat;
+    for (i=0; i<delay; i++) {
+        x_hat = randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+        firpfbch_crcf_analyzer_push(ca0, x_hat);
+    }
+
+    // run analyzers
+    for (i=0; i<num_frames; i++) {
+#if 1
+        for (j=0; j<num_channels; j++) {
+            // push samples into analyzers
+            firpfbch_crcf_analyzer_push(ca0, x[i*num_channels+j]);
+            firpfbch_crcf_analyzer_push(ca1, x[i*num_channels+j]);
+        }
+        firpfbch_crcf_analyzer_run(ca0,     0, &Y0[i][0]);
+        firpfbch_crcf_analyzer_run(ca1, delay, &Y1[i][0]);
+#else
+        // execute analysis filterbank channelizers
+        firpfbch_crcf_analyzer_execute(ca0, &x[i*num_channels], &Y0[i][0]);
+        firpfbch_crcf_analyzer_execute(ca1, &x[i*num_channels], &Y1[i][0]);
+#endif
+    }
+
+    // destroy objects
+    firpfbch_crcf_destroy(ca0);
+    firpfbch_crcf_destroy(ca1);
+
+    // print channelizer 0
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%c %3u: ", i<2*m ? ' ' : '*', i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+
+    // print channelizer 1
+    printf("\n");
+    printf("traditional channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%c %3u: ", i<2*m ? ' ' : '*', i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y1[i][j]), cimagf(Y1[i][j]));
+        }
+        printf("\n");
+    }
+
+
+    // 
+    // compare results
+    //
+
+    float mse[num_channels];
+    float complex d;
+    for (i=0; i<num_channels; i++) {
+        mse[i] = 0.0f;
+
+        // compute MSE for this channel, removing initial
+        // timing delay
+        for (j=2*m; j<num_frames; j++) {
+            d = Y0[j][i] - Y1[j][i];
+            mse[i] += crealf(d*conjf(d));
+        }
+
+        mse[i] /= num_symbols;
+    }
+    printf("\n");
+    printf("  rmse: ");
+    for (i=0; i<num_channels; i++)
+        printf("%12.4e          ", sqrt(mse[i]));
+    printf("\n");
+
+
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_channels=%u;\n", num_channels);
+    fprintf(fid,"num_symbols=%u;\n", num_symbols);
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+
+    fprintf(fid,"X = zeros(%u,%u);\n", num_channels, num_frames);
+    fprintf(fid,"y = zeros(1,%u);\n",  num_samples);
+    fprintf(fid,"Y = zeros(%u,%u);\n", num_channels, num_frames);
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/firpfbch_analysis_equivalence_test.c b/sandbox/firpfbch_analysis_equivalence_test.c
new file mode 100644
index 0000000..ec48c8f
--- /dev/null
+++ b/sandbox/firpfbch_analysis_equivalence_test.c
@@ -0,0 +1,217 @@
+//
+// firpfbch_analysis_equivalence_test.c
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG 1
+
+int main() {
+    // options
+    unsigned int num_channels=4;    // number of channels
+    unsigned int m=5;               // filter delay
+    unsigned int num_symbols=12;    // number of symbols
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence
+    unsigned int h_len = 2*m*num_channels;
+    float h[h_len];
+    for (i=0; i<h_len; i++) h[i] = randnf();
+    //for (i=0; i<h_len; i++) h[i] = 0.1f*i;
+    //for (i=0; i<h_len; i++) h[i] = (i<=m) ? 1.0f : 0.0f;
+    //for (i=0; i<h_len; i++) h[i] = 1.0f;
+
+    // create filterbank manually
+    dotprod_crcf dp[num_channels];  // vector dot products
+    windowcf w[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("h_prototype:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h[%3u] = %12.8f\n", i, h[i]);
+#endif
+
+    // create objects
+    unsigned int h_sub_len = 2*m;
+    float h_sub[h_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter, loading coefficients in
+        // reverse order
+#if 0
+        for (j=0; j<h_sub_len; j++)
+            h_sub[j] = h[j*num_channels+i];
+#else
+        for (j=0; j<h_sub_len; j++)
+            h_sub[h_sub_len-j-1] = h[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_crcf_create(h_sub, h_sub_len);
+        w[i]  = windowcf_create(h_sub_len);
+
+#if DEBUG
+        printf("h_sub[%u] : \n", i);
+        for (j=0; j<h_sub_len; j++)
+            printf("  h[%3u] = %12.8f\n", j, h_sub[j]);
+#endif
+    }
+
+    // generate DFT object
+    float complex x[num_channels];  // time-domain buffer
+    float complex X[num_channels];  // freq-domain buffer
+#if 0
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+
+    // generate filter object
+    firfilt_crcf f = firfilt_crcf_create(h, h_len);
+
+    float complex y[num_samples];                   // time-domain input
+    float complex Y0[num_symbols][num_channels];    // channelized output
+    float complex Y1[num_symbols][num_channels];    // channelized output
+
+    // generate input sequence (complex noise)
+    for (i=0; i<num_samples; i++)
+        y[i] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+
+    // 
+    // run analysis filter bank
+    //
+#if 0
+    unsigned int filter_index = 0;
+#else
+    unsigned int filter_index = num_channels-1;
+#endif
+    float complex y_hat;    // input sample
+    float complex * r;      // read pointer
+    for (i=0; i<num_symbols; i++) {
+
+        // load buffers
+        for (j=0; j<num_channels; j++) {
+            // grab sample
+            y_hat = y[i*num_channels + j];
+
+            // push sample into buffer at filter index
+            windowcf_push(w[filter_index], y_hat);
+
+            // decrement filter index
+            filter_index = (filter_index + num_channels - 1) % num_channels;
+            //filter_index = (filter_index + 1) % num_channels;
+        }
+
+        // execute filter outputs, reversing order of output (not
+        // sure why this is necessary)
+        for (j=0; j<num_channels; j++) {
+            windowcf_read(w[j], &r);
+            dotprod_crcf_execute(dp[j], r, &X[num_channels-j-1]);
+        }
+
+        // execute DFT, store result in buffer 'x'
+        fft_execute(fft);
+
+        // move to output array
+        for (j=0; j<num_channels; j++)
+            Y0[i][j] = x[j];
+    }
+
+    // 
+    // run traditional down-converter (inefficient)
+    //
+    float dphi; // carrier frequency
+    unsigned int n=0;
+    for (i=0; i<num_channels; i++) {
+
+        // reset filter
+        firfilt_crcf_reset(f);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+            // push down-converted sample into filter
+            firfilt_crcf_push(f, y[j]*cexpf(-_Complex_I*j*dphi));
+
+            // compute output at the appropriate sample time
+            assert(n<num_symbols);
+            if ( ((j+1)%num_channels)==0 ) {
+                firfilt_crcf_execute(f, &Y1[n][i]);
+                n++;
+            }
+        }
+        assert(n==num_symbols);
+
+    }
+
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_crcf_destroy(dp[i]);
+        windowcf_destroy(w[i]);
+    }
+    fft_destroy_plan(fft);
+
+    firfilt_crcf_destroy(f);
+
+    // print filterbank channelizer
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+
+    // print traditional channelizer
+    printf("\n");
+    printf("traditional channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y1[i][j]), cimagf(Y1[i][j]));
+        }
+        printf("\n");
+    }
+
+    // 
+    // compare results
+    // 
+    float mse[num_channels];
+    float complex d;
+    for (i=0; i<num_channels; i++) {
+        mse[i] = 0.0f;
+        for (j=0; j<num_symbols; j++) {
+            d = Y0[j][i] - Y1[j][i];
+            mse[i] += crealf(d*conjf(d));
+        }
+
+        mse[i] /= num_symbols;
+    }
+    printf("\n");
+    printf("rmse: ");
+    for (i=0; i<num_channels; i++)
+        printf("%12.4e          ", sqrt(mse[i]));
+    printf("\n");
+
+    printf("done.\n");
+    return 0;
+
+}
+
diff --git a/sandbox/firpfbch_analysis_test.c b/sandbox/firpfbch_analysis_test.c
new file mode 100644
index 0000000..5519e86
--- /dev/null
+++ b/sandbox/firpfbch_analysis_test.c
@@ -0,0 +1,104 @@
+//
+// firpfbch_analysis_test.c
+//
+
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+int main() {
+    // options
+    unsigned int num_channels=8;    // number of channels
+    unsigned int m=2;               // filter delay
+    float As=60;                    // stop-band attenuation [dB]
+    unsigned int num_symbols=8;     // number of symbols
+
+    // create filterbank objects
+    firpfbch ca0 = firpfbch_create(num_channels, m, As, 0, FIRPFBCH_NYQUIST, 0);
+    firpfbch ca1 = firpfbch_create(num_channels, m, As, 0, FIRPFBCH_NYQUIST, 0);
+
+    unsigned int i, j;
+    float complex x[num_channels];  // time-domain input
+    float complex y0[num_channels]; // channelized output
+    float complex y1[num_channels]; // channelized output
+
+#if 0
+    // push samples channelizer 1
+    float complex x_hat;
+    for (i=0; i<delay; i++) {
+        // generate random sample
+        x_hat = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+
+        // push sample into channelizer
+        firpfbch_analyzer_push(ca0, x_hat);
+
+        // run analyzer
+        firpfbch_analyzer_run(ca0, y0);
+
+        // save run state
+        firpfbch_analyzer_saverunstate(ca0);
+    }
+#endif
+
+    // run analyzers
+    for (i=0; i<num_symbols; i++) {
+
+        // generate input sequence (complex noise)
+        for (j=0; j<num_channels; j++)
+            x[j] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+
+        // 
+        // analyzer 0 : run individual sample exection
+        //
+
+        // push samples into channelizer 1
+        for (j=0; j<num_channels; j++) {
+            firpfbch_analyzer_push(ca0, x[j]);
+    
+            // run analyzer every time, just for kicks
+            firpfbch_analyzer_run(ca0, y0);
+        }
+
+        // run analyzer as many times as desired
+        firpfbch_analyzer_run(ca0, y0);
+        firpfbch_analyzer_run(ca0, y0);
+        firpfbch_analyzer_run(ca0, y0);
+
+        // save run state ONLY at the appropriate time
+        firpfbch_analyzer_saverunstate(ca0);
+        
+        // 
+        // analyzer 1 : run regular execution
+        //
+        firpfbch_analyzer_execute(ca1, x, y1);
+
+        for (j=0; j<num_channels; j++) {
+            printf("      %3u [%8.5f + j%8.5f, %8.5f + j%8.5f]\n",
+                    j,
+                    crealf(y0[j]),
+                    cimagf(y0[j]),
+                    crealf(y1[j]),
+                    cimagf(y1[j]));
+        }
+
+        // 
+        // compare result
+        // 
+        float mse = 0.0f;
+        for (j=0; j<num_channels; j++) {
+            float complex d = y0[j] - y1[j];
+            mse += crealf( d*conjf(d) );
+        }
+        mse /= num_channels;
+        printf("  %4u : mse = %12.4e\n", i, mse);
+    }
+
+    // destroy objects
+    firpfbch_destroy(ca0);
+    firpfbch_destroy(ca1);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/firpfbch_synthesis_equivalence_test.c b/sandbox/firpfbch_synthesis_equivalence_test.c
new file mode 100644
index 0000000..7ec89c4
--- /dev/null
+++ b/sandbox/firpfbch_synthesis_equivalence_test.c
@@ -0,0 +1,203 @@
+//
+// firpfbch_synthesis_equivalence_test.c
+//
+
+#include <stdio.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG 1
+
+int main() {
+    // options
+    unsigned int num_channels=4;    // number of channels
+    unsigned int m=3;               // filter delay
+    unsigned int num_symbols=5;     // number of symbols
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence
+    unsigned int h_len = 2*m*num_channels;
+    float h[h_len];
+    for (i=0; i<h_len; i++) h[i] = randnf();
+    //for (i=0; i<h_len; i++) h[i] = 0.1f*i;
+    //for (i=0; i<h_len; i++) h[i] = (i<=m) ? 1.0f : 0.0f;
+    //for (i=0; i<h_len; i++) h[i] = 1.0f;
+
+    // create filterbank manually
+    dotprod_crcf dp[num_channels];  // vector dot products
+    windowcf w[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("h_prototype:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h[%3u] = %12.8f\n", i, h[i]);
+#endif
+
+    // create objects
+    unsigned int h_sub_len = 2*m;
+    float h_sub[h_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter, loading coefficients in
+        // reverse order
+#if 0
+        for (j=0; j<h_sub_len; j++)
+            h_sub[j] = h[j*num_channels+i];
+#else
+        for (j=0; j<h_sub_len; j++)
+            h_sub[h_sub_len-j-1] = h[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_crcf_create(h_sub, h_sub_len);
+        w[i]  = windowcf_create(h_sub_len);
+
+#if DEBUG
+        printf("h_sub[%u] : \n", i);
+        for (j=0; j<h_sub_len; j++)
+            printf("  h[%3u] = %12.8f\n", j, h_sub[j]);
+#endif
+    }
+
+    // generate inverse DFT object
+    float complex x[num_channels];  // time-domain buffer
+    float complex X[num_channels];  // freq-domain buffer
+#if 1
+    fftplan ifft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fftplan ifft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+
+    // generate filter object
+    firfilt_crcf f = firfilt_crcf_create(h, h_len);
+
+    float complex Y[num_symbols][num_channels];     // channelized input
+    float complex y0[num_samples];                  // time-domain output
+    float complex y1[num_samples];                  // time-domain output
+
+    // generate input sequence (complex noise)
+    for (i=0; i<num_symbols; i++) {
+        for (j=0; j<num_channels; j++)
+            Y[i][j] = randnf() * cexpf(_Complex_I*randf()*2*M_PI);
+
+#if 0
+        for (j=0; j<num_channels; j++)
+            Y[i][j] = i==0 ? randnf() * cexpf(_Complex_I*randf()*2*M_PI) : 0.0f;
+#endif
+    }
+
+    // 
+    // run synthesis filter bank
+    //
+
+    float complex * r;      // read pointer
+    for (i=0; i<num_symbols; i++) {
+
+        // load buffers
+        for (j=0; j<num_channels; j++) {
+            X[j] = Y[i][j];
+        }
+
+        // execute inverse DFT, store result in buffer 'x'
+        fft_execute(ifft);
+
+        // push samples into filter bank and execute
+        for (j=0; j<num_channels; j++) {
+            windowcf_push(w[j], x[j]);
+            windowcf_read(w[j], &r);
+            dotprod_crcf_execute(dp[j], r, &y0[i*num_channels+j]);
+
+            // normalize by DFT scaling factor
+            //y0[i*num_channels+j] /= (float) num_channels;
+        }
+    }
+
+    // 
+    // run traditional up-converter (inefficient)
+    //
+
+    // clear output array
+    for (i=0; i<num_samples; i++)
+        y1[i] = 0.0f;
+
+    unsigned int n;
+    float dphi; // carrier frequency
+    float complex y_hat;
+    for (i=0; i<num_channels; i++) {
+        // reset filter
+        firfilt_crcf_reset(f);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset input symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+
+            // interpolate sequence
+            if ( (j%num_channels)==0 ) {
+                assert(n<num_symbols);
+                firfilt_crcf_push(f, Y[n][i]);
+                n++;
+            } else {
+                firfilt_crcf_push(f, 0);
+            }
+            firfilt_crcf_execute(f, &y_hat);
+
+            // accumulate up-converted sample
+            y1[j] += y_hat * cexpf(_Complex_I*j*dphi);
+        }
+        assert(n==num_symbols);
+    }
+
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_crcf_destroy(dp[i]);
+        windowcf_destroy(w[i]);
+    }
+    fft_destroy_plan(ifft);
+
+    firfilt_crcf_destroy(f);
+
+
+    // 
+    // print channelizer outputs
+    //
+    printf("\n");
+    printf("output: filterbank:             traditional:\n");
+    for (i=0; i<num_samples; i++) {
+        printf("%3u: %10.5f+%10.5fj  %10.5f+%10.5fj\n",
+            i,
+            crealf(y0[i]), cimagf(y0[i]),
+            crealf(y1[i]), cimagf(y1[i]));
+    }
+
+
+    // 
+    // compare results
+    // 
+    float mse = 0.0f;
+    float complex d;
+    for (i=0; i<num_samples; i++) {
+        d = y0[i] - y1[i];
+        mse += crealf(d*conjf(d));
+    }
+    mse /= num_samples;
+    printf("\n");
+    printf("rmse: %12.4e\n", sqrtf(mse));
+
+    printf("done.\n");
+    return 0;
+
+}
+
diff --git a/sandbox/fskmodem_test.c b/sandbox/fskmodem_test.c
new file mode 100644
index 0000000..e8137cb
--- /dev/null
+++ b/sandbox/fskmodem_test.c
@@ -0,0 +1,302 @@
+// 
+// fskmodem_test.c
+//
+// This example demostrates the M-ary frequency-shift keying
+// (MFSK) modem in liquid. A message signal is modulated and the
+// resulting signal is recovered using a demodulator object.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "fskmodem_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("fskmodem_example -- frequency-shift keying test\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  m     : bits/symbol,              default:  1\n");
+    printf("  k     : samples/symbol,           default:  2*2^m\n");
+    printf("  b     : signal bandwidth          default:  0.2\n");
+    printf("  n     : number of data symbols,   default: 80\n");
+    printf("  s     : SNR [dB],                 default: 40\n");
+    printf("  F     : carrier frequency offset, default:  0\n");
+    printf("  P     : carrier phase offset,     default:  0\n");
+    printf("  T     : fractional symbol offset, default:  0\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int m           =   3;     // number of bits/symbol
+    unsigned int k           =   0;     // filter samples/symbol
+    unsigned int num_symbols = 200;     // number of data symbols
+    float        SNRdB       = 40.0f;   // signal-to-noise ratio [dB]
+    float        cfo         = 0.0f;    // carrier frequency offset
+    float        cpo         = 0.0f;    // carrier phase offset
+    float        tau         = 0.0f;    // fractional symbol timing offset
+    float        bandwidth   = 0.20;    // frequency spacing
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hm:k:b:n:s:F:P:T:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'm': m           = atoi(optarg);   break;
+        case 'k': k           = atoi(optarg);   break;
+        case 'b': bandwidth   = atof(optarg);   break;
+        case 'n': num_symbols = atoi(optarg);   break;
+        case 's': SNRdB       = atof(optarg);   break;
+        case 'F': cfo         = atof(optarg);   break;
+        case 'P': cpo         = atof(optarg);   break;
+        case 'T': tau         = atof(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+    unsigned int j;
+
+    // derived values
+    if (k == 0) k = 2 << m; // set samples per symbol if not otherwise specified
+    unsigned int num_samples = k*num_symbols;
+    unsigned int M           = 1 << m;
+    float        nstd        = powf(10.0f, -SNRdB/20.0f);
+    float        M2          = 0.5f*(float)(M-1);
+
+    // validate input
+    if (k < M) {
+        fprintf(stderr,"errors: %s, samples/symbol must be at least modulation size (M=%u)\n", __FILE__,M);
+        exit(1);
+    } else if (k > 2048) {
+        fprintf(stderr,"errors: %s, samples/symbol exceeds maximum (2048)\n", __FILE__);
+        exit(1);
+    } else if (M > 1024) {
+        fprintf(stderr,"errors: %s, modulation size (M=%u) exceeds maximum (1024)\n", __FILE__, M);
+        exit(1);
+    } else if (bandwidth <= 0.0f || bandwidth >= 0.5f) {
+        fprintf(stderr,"errors: %s, bandwidht must be in (0,0.5)\n", __FILE__);
+        exit(1);
+    }
+
+    // compute demodulation FFT size such that FFT output bin frequencies are
+    // as close to modulated frequencies as possible
+    unsigned int K = 0;                 // demodulation FFT size
+    float        df = bandwidth / M2;   // frequency spacing
+    float        err_min = 1e9f;
+    unsigned int K_min = k;                     // minimum FFT size
+    unsigned int K_max = k*4 < 16 ? 16 : k*4;   // maximum FFT size
+    unsigned int K_hat;
+    for (K_hat=K_min; K_hat<=K_max; K_hat++) {
+        // compute candidate FFT size
+        float v     = 0.5f*df * (float)K_hat;   // bin spacing
+        float err = fabsf( roundf(v) - v );     // fractional bin spacing
+
+        // print results
+        printf("  K_hat = %4u : v = %12.8f, err=%12.8f %s\n", K_hat, v, err, err < err_min ? "*" : "");
+
+        // save best result
+        if (K_hat==K_min || err < err_min) {
+            K = K_hat;
+            err_min = err;
+        }
+
+        // perfect match; no need to continue searching
+        if (err < 1e-6f)
+            break;
+    }
+
+    // arrays
+    unsigned int  sym_in[num_symbols];      // input symbols
+    float complex x[num_samples];           // transmitted signal
+    float complex y[num_samples];           // received signal
+    unsigned int  sym_out[num_symbols];     // output symbols
+
+    // determine demodulation mapping between tones and frequency bins
+    // TODO: use gray coding
+    unsigned int demod_map[M];
+    for (i=0; i<M; i++) {
+        // print frequency bins
+        float freq = ((float)i - M2) * bandwidth / M2;
+        float idx  = freq * (float)K;
+        unsigned int index = (unsigned int) (idx < 0 ? roundf(idx + K) : roundf(idx));
+        demod_map[i] = index;
+        printf("  s=%3u, f = %12.8f, index=%3u\n", i, freq, index);
+    }
+
+    // check for uniqueness
+    for (i=1; i<M; i++) {
+        if (demod_map[i] == demod_map[i-1]) {
+            fprintf(stderr,"warning: demod map is not unique; consider increasing bandwidth\n");
+            break;
+        }
+    }
+
+    // generate message symbols and modulate
+    // TODO: use gray coding
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        sym_in[i] = rand() % M;
+
+        // compute frequency
+        float dphi = 2*M_PI*((float)sym_in[i] - M2) * bandwidth / M2;
+
+        // generate random phase
+        float phi  = randf() * 2 * M_PI;
+        
+        // modulate symbol
+        for (j=0; j<k; j++)
+            x[i*k+j] = cexpf(_Complex_I*phi + _Complex_I*j*dphi);
+    }
+
+    // push through channel
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+#if 0
+    // demodulate signal: high SNR method
+    float complex buf_time[k];
+    unsigned int n = 0;
+    j = 0;
+    for (i=0; i<num_samples; i++) {
+        // start filling time buffer with samples (assume perfect symbol timing)
+        buf_time[n++] = y[i];
+
+        // demodulate symbol
+        if (n==k) {
+            // reset counter
+            n = 0;
+
+            // estimate frequency
+            float complex metric = 0;
+            unsigned int s;
+            for (s=1; s<k; s++)
+                metric += buf_time[s] * conjf(buf_time[s-1]);
+            float dphi_hat = cargf(metric) / (2*M_PI);
+            unsigned int v=( (unsigned int) roundf(dphi_hat*M2/bandwidth + M2) ) % M;
+            sym_out[j++] = v;
+            printf("%3u : %12.8f : %u\n", j, dphi_hat, v);
+        }
+    }
+#else
+    // demodulate signal: least-squares method
+    float complex buf_time[K];
+    float complex buf_freq[K];
+    fftplan fft = fft_create_plan(K, buf_time, buf_freq, LIQUID_FFT_FORWARD, 0);
+
+    for (i=0; i<K; i++)
+        buf_time[i] = 0.0f;
+    unsigned int n = 0;
+    j = 0;
+    for (i=0; i<num_samples; i++) {
+        // start filling time buffer with samples (assume perfect symbol timing)
+        buf_time[n++] = y[i];
+
+        // demodulate symbol
+        if (n==k) {
+            // reset counter
+            n = 0;
+
+            // compute transform, storing result in 'buf_freq'
+            fft_execute(fft);
+
+            // find maximum by looking at particular bins
+            float vmax = 0;
+            unsigned int s;
+            unsigned int s_opt = 0;
+            for (s=0; s<M; s++) {
+                float v = cabsf( buf_freq[demod_map[s]] );
+                if (s==0 || v > vmax) {
+                    s_opt = s;
+                    vmax  =v;
+                }
+            }
+
+            // save best result
+            sym_out[j++] = s_opt;
+        }
+    }
+    // destroy fft object
+    fft_destroy_plan(fft);
+#endif
+
+    // count errors
+    unsigned int num_symbol_errors = 0;
+    for (i=0; i<num_symbols; i++)
+        num_symbol_errors += (sym_in[i] == sym_out[i]) ? 0 : 1;
+
+    printf("symbol errors: %u / %u\n", num_symbol_errors, num_symbols);
+
+    // compute power spectral density of received signal
+    unsigned int nfft = 1200;
+    float psd[nfft];
+    spgramcf periodogram = spgramcf_create_kaiser(nfft, nfft/2, 8.0f);
+    spgramcf_estimate_psd(periodogram, y, num_samples, psd);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export results
+    //
+    
+    // truncate to at most 10 symbols
+    if (num_symbols > 10)
+        num_symbols = 10;
+    num_samples = k*num_symbols;
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"M = %u;\n", M);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+
+    fprintf(fid,"x   = zeros(1,num_samples);\n");
+    fprintf(fid,"y   = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+    // save power spectral density
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%4u) = %12.8f;\n", i+1, psd[i]);
+
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"i = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+
+    // plot time signal
+    fprintf(fid,"subplot(2,1,1),\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"  plot(t,real(y),'-', 'Color',[0 0.3 0.5]);\n");
+    fprintf(fid,"  plot(t,imag(y),'-', 'Color',[0 0.5 0.3]);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"ymax = ceil(max(abs(y))*5)/5;\n");
+    fprintf(fid,"axis([0 num_symbols -ymax ymax]);\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('x(t)');\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot PSD
+    fprintf(fid,"subplot(2,1,2),\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"plot(f,psd,'LineWidth',1.5,'Color',[0.5 0 0]);\n");
+    fprintf(fid,"axis([-0.5 0.5 -40 40]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/sandbox/gmskmodem_coherent_test.c b/sandbox/gmskmodem_coherent_test.c
new file mode 100644
index 0000000..f482938
--- /dev/null
+++ b/sandbox/gmskmodem_coherent_test.c
@@ -0,0 +1,191 @@
+// 
+// gmskmodem_coherent_test.c
+//
+// This example demostrates the continuous phase frequency-shift keying
+// (CP-FSK) modem in liquid. A message signal is modulated and the
+// resulting signal is recovered using a demodulator object.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gmskmodem_coherent_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("gmskmodem_coherent_test -- coherent GMSK demodulation example\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  k     : samples/symbol,           default:  8\n");
+    printf("  m     : filter delay (symbols),   default:  3\n");
+    printf("  b     : filter roll-off,          default:  0.3\n");
+    printf("  n     : number of data symbols,   default: 80\n");
+    printf("  s     : SNR [dB],                 default: 40\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    unsigned int k           = 8;       // filter samples/symbol
+    unsigned int m           = 3;       // filter delay (symbols)
+    float        beta        = 0.25f;   // GMSK bandwidth-time factor
+    unsigned int num_symbols = 80;      // number of data symbols
+    float        SNRdB       = 40.0f;   // signal-to-noise ratio [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hk:m:b:n:s:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'k': k           = atoi(optarg);   break;
+        case 'm': m           = atoi(optarg);   break;
+        case 'b': beta        = atof(optarg);   break;
+        case 'n': num_symbols = atoi(optarg);   break;
+        case 's': SNRdB       = atof(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_samples = k*num_symbols;
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    unsigned int  sym_in [num_symbols];     // input symbols
+    float complex x      [num_samples];     // transmitted signal
+    float complex y      [num_samples];     // received signal
+    float complex z      [num_samples];     // received signal
+    unsigned int  sym_out[num_symbols];     // output symbols
+
+    // create modem objects
+    gmskmod mod = gmskmod_create(k, m, beta);
+
+    // generate message signal
+    for (i=0; i<num_symbols; i++) {
+        sym_in[i] = rand() % 2;
+        sym_out[i] = 0;
+    }
+
+    // modulate signal
+    for (i=0; i<num_symbols; i++)
+        gmskmod_modulate(mod, sym_in[i], &x[k*i]);
+
+    // destroy modem object
+    gmskmod_destroy(mod);
+
+    // add noise
+    for (i=0; i<num_samples; i++) {
+        // add noise
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+
+    // run through equalizing 'matched' filter
+    firfilt_crcf mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k,m,0.8,0);
+    firfilt_crcf_set_scale(mf, 1.5f / (float)k);
+    for (i=0; i<num_samples; i++) {
+        firfilt_crcf_push(mf, y[i]);
+        firfilt_crcf_execute(mf, &z[i]);
+    }
+    firfilt_crcf_destroy(mf);
+
+    // compute power spectral density of transmitted signal
+    unsigned int nfft = 1024;
+    float psd[nfft];
+    spgramcf periodogram = spgramcf_create_kaiser(nfft, nfft/2, 8.0f);
+    spgramcf_estimate_psd(periodogram, x, num_samples, psd);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %12.8f;\n", beta);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+
+    fprintf(fid,"x   = zeros(1,num_samples);\n");
+    fprintf(fid,"y   = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(z[i]), cimagf(z[i]));
+    }
+    // save power spectral density
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%4u) = %12.8f;\n", i+1, psd[i]);
+
+#if 0
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"i = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,4,1:3);\n");
+    fprintf(fid,"  plot(t,real(x),'-', t(i),real(x(i)),'ob',...\n");
+    fprintf(fid,"       t,imag(x),'-', t(i),imag(x(i)),'og');\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('x(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,5:7);\n");
+    fprintf(fid,"  plot(t,real(z),'-', t(i),real(z(i)),'ob',...\n");
+    fprintf(fid,"       t,imag(z),'-', t(i),imag(z(i)),'og');\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('y(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    // plot I/Q constellations
+    fprintf(fid,"subplot(3,4,4);\n");
+    fprintf(fid,"  plot(real(x),imag(x),'-',real(x(i)),imag(x(i)),'rs','MarkerSize',4);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,8);\n");
+    fprintf(fid,"  plot(real(z),imag(z),'-',real(z(i)),imag(z(i)),'rs','MarkerSize',4);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    // plot PSD
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"subplot(3,4,9:12);\n");
+    fprintf(fid,"  plot(f,psd,'LineWidth',1.5);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -60 20]);\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  grid on;\n");
+#else
+    fprintf(fid,"i = (1+2*k*m):k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"  plot(real(z),imag(z),'-','Color',[1 1 1]*0.8);\n");
+    fprintf(fid,"  plot(real(z(i)),imag(z(i)),'rs','MarkerSize',4);\n");
+    fprintf(fid,"  plot([-1 -1 1 1]/sqrt(2), [-1 1 -1 1]/sqrt(2), 'x', 'MarkerSize',8);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"xlabel('real');\n");
+    fprintf(fid,"ylabel('imag');\n");
+    fprintf(fid,"grid on;\n");
+#endif
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/sandbox/gmskmodem_equalizer_test.c b/sandbox/gmskmodem_equalizer_test.c
new file mode 100644
index 0000000..81ee44b
--- /dev/null
+++ b/sandbox/gmskmodem_equalizer_test.c
@@ -0,0 +1,270 @@
+// 
+// gmskmodem_equalizer_test.c
+//
+// Tests least mean-squares (LMS) equalizer (EQ) on a received GMSK
+// signal. The equalizer is updated using decision-directed demodulator
+// output samples.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <getopt.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gmskmodem_equalizer_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: gmskmodem_equalizer_test [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  n     : number of symbols, default: 500\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter semi-length (symbols), default: 4\n");
+    printf("  b     : filter excess bandwidth factor, default: 0.3\n");
+    printf("  p     : equalizer semi-length (symbols), default: 3\n");
+    printf("  u     : equalizer learning rate, default; 0.05\n");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int num_symbols = 1200;    // number of symbols to observe
+    unsigned int k           = 2;       // matched filter samples/symbol
+    unsigned int m           = 3;       // matched filter delay (symbols)
+    float        beta        = 0.30f;   // GMSK bandwidth-time factor
+    unsigned int p           = 3;       // equalizer length (symbols, hp_len = 2*k*p+1)
+    float        mu          = 0.10f;   // learning rate
+    
+    unsigned int nfft        = 1200;    // spectrum estimate FFT size
+    float        alpha       = 0.001f;  // spectrum averaging bandwidth
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hn:k:m:b:p:u:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                    return 0;
+        case 'n': num_symbols = atoi(optarg); break;
+        case 'k': k           = atoi(optarg); break;
+        case 'm': m           = atoi(optarg); break;
+        case 'b': beta        = atof(optarg); break;
+        case 'p': p           = atoi(optarg); break;
+        case 'u': mu          = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (num_symbols == 0) {
+        fprintf(stderr,"error: %s, number of symbols must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (k < 2) {
+        fprintf(stderr,"error: %s, samples/symbol must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m == 0) {
+        fprintf(stderr,"error: %s, filter semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (beta < 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: %s, filter excess bandwidth must be in [0,1]\n", argv[0]);
+        exit(1);
+    } else if (p == 0) {
+        fprintf(stderr,"error: %s, equalizer semi-length must be at least 1 symbol\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f || mu > 1.0f) {
+        fprintf(stderr,"error: %s, equalizer learning rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int hm_len = 2*k*m+1;   // matched filter length
+    unsigned int hp_len = 2*k*p+1;   // equalizer filter length
+    unsigned int num_samples = k*num_symbols;
+
+    // bookkeeping variables
+    float complex x[num_samples];       // interpolated time series
+    float complex y[num_samples];       // equalized output
+
+    float hm[hm_len];                   // matched filter response
+    float complex hp[hp_len];           // equalizer filter coefficients
+
+    unsigned int i;
+
+    // generate matched filter response
+    liquid_firdes_prototype(LIQUID_FIRFILT_GMSKTX, k, m, beta, 0.0f, hm);
+
+    // create the modem objects
+    modem demod = modem_create(LIQUID_MODEM_QPSK);
+
+    gmskmod gmod = gmskmod_create(k,m,beta);
+    for (i=0; i<num_symbols; i++)
+        gmskmod_modulate(gmod, rand()%2, &x[i*k]);
+    
+    // push through equalizer
+    // create equalizer, intialized with square-root Nyquist filter
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_RRC, k, p, beta, 0.0f);
+    eqlms_cccf_set_bw(eq, mu);
+
+    // get initialized weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // filtered error vector magnitude (emperical RMS error)
+    float evm_hat = 0.03f;
+
+    float complex d_hat = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        // print filtered evm (emperical rms error)
+        if ( ((i+1)%50)==0 )
+            printf("%4u : rms error = %12.8f dB\n", i+1, 10*log10(evm_hat));
+
+        eqlms_cccf_push(eq, x[i]);
+        eqlms_cccf_execute(eq, &d_hat);
+
+        // store output
+        y[i] = d_hat;
+
+        // decimate by k
+        if ( (i%k) != 0 ) continue;
+
+        // estimate transmitted signal
+        unsigned int sym_out;   // output symbol
+        float complex d_prime;  // estimated input sample
+        modem_demodulate(demod, d_hat, &sym_out);
+        modem_get_demodulator_sample(demod, &d_prime);
+
+        // update equalizer
+        eqlms_cccf_step(eq, d_prime, d_hat);
+
+        // update filtered evm estimate
+        float evm = crealf( (d_prime-d_hat)*conjf(d_prime-d_hat) );
+        evm_hat = 0.98f*evm_hat + 0.02f*evm;
+    }
+
+    // get equalizer weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    //
+    // run many trials get get average spectrum
+    //
+    spgramcf periodogram_tx = spgramcf_create_kaiser(nfft, nfft/2, 8.0f);
+    spgramcf periodogram_rx = spgramcf_create_kaiser(nfft, nfft/2, 8.0f);
+
+    firfilt_cccf mf = firfilt_cccf_create(hp, hp_len);
+
+    float complex buf_tx[k];
+    float complex buf_rx[k];
+    for (i=0; i<500e3; i++) {
+        // generate random symbol
+        gmskmod_modulate(gmod, rand()%2, buf_tx);
+
+        // run matched filter on result
+        firfilt_cccf_execute_block(mf, buf_tx, k, buf_rx);
+
+        // accumulate spectrum average
+        spgramcf_accumulate_psd(periodogram_tx, buf_tx, alpha, k);
+        spgramcf_accumulate_psd(periodogram_rx, buf_rx, alpha, k);
+    }
+    firfilt_cccf_destroy(mf);
+
+    // write accumulated output PSD
+    float X[nfft];
+    float Y[nfft];
+    spgramcf_write_accumulation(periodogram_tx, X);
+    spgramcf_write_accumulation(periodogram_rx, Y);
+
+    // destroy periodogram objects
+    spgramcf_destroy(periodogram_tx);
+    spgramcf_destroy(periodogram_rx);
+
+
+    // destroy objects
+    eqlms_cccf_destroy(eq);
+    modem_destroy(demod);
+    gmskmod_destroy(gmod);
+
+    // 
+    // export output
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_symbols*k;\n");
+
+    // save transmit matched-filter response
+    fprintf(fid,"hm_len = 2*k*m+1;\n");
+    fprintf(fid,"hm = zeros(1,hm_len);\n");
+    for (i=0; i<hm_len; i++)
+        fprintf(fid,"hm(%4u) = %12.4e;\n", i+1, hm[i]);
+
+    // save equalizer response
+    fprintf(fid,"hp_len = %u;\n", hp_len);
+    fprintf(fid,"hp = zeros(1,hp_len);\n");
+    for (i=0; i<hp_len; i++)
+        fprintf(fid,"hp(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(hp[i]), cimagf(hp[i]));
+
+    // save input/output spectrum
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"X = zeros(1,nfft);\n");
+    fprintf(fid,"Y = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++) {
+        fprintf(fid,"X(%4u) = %12.4e;\n", i+1, X[i]);
+        fprintf(fid,"Y(%4u) = %12.4e;\n", i+1, Y[i]);
+    }
+
+    // save sample sets
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // plot time response
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"tsym = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(y),...\n");
+    fprintf(fid,"     t(tsym),real(y(tsym)),'x');\n");
+
+    // plot constellation
+    fprintf(fid,"tsym0 = tsym(1:(length(tsym)/2));\n");
+    fprintf(fid,"tsym1 = tsym((length(tsym)/2):end);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(y(tsym1)),imag(y(tsym1)),'x','Color',[1 1 1]*0.0);\n");
+    fprintf(fid,"xlabel('In-Phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    // plot responses
+    //fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"X  = X - 20*log10(k);\n");
+    fprintf(fid,"Y  = Y - 20*log10(k);\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp,nfft))));\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,X, f,Hp, f,Y, '-k','LineWidth',2, [-0.5/k 0.5/k],[-6.026 -6.026],'or');\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density');\n");
+    fprintf(fid,"legend('transmit','equalizer','composite','half-power points','location','south');\n");
+    //fprintf(fid,"axis([-0.5 0.5 -12 8]);\n");
+    fprintf(fid,"axis([-0.5 0.5 -50 10]);\n");
+    fprintf(fid,"grid on;\n");
+    
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/sandbox/gmskmodem_test.c b/sandbox/gmskmodem_test.c
new file mode 100644
index 0000000..8b26902
--- /dev/null
+++ b/sandbox/gmskmodem_test.c
@@ -0,0 +1,174 @@
+// 
+// gmskmodem_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "gmskmodem_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("gmskmodem_test -- Gaussian minimum-shift keying modem example\n");
+    printf("options (default values in <>):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : samples/symbol <4>\n");
+    printf("  m     : filter delay [symbols], <3>\n");
+    printf("  n     : number of data symbols <32>\n");
+    printf("  b     : bandwidth-time product, 0 <= b <= 1, <0.3>\n");
+    printf("  s     : SNR [dB] <30>\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;                   // filter samples/symbol
+    unsigned int m=3;                   // filter delay (symbols)
+    float BT=0.5f;                      // bandwidth-time product
+    unsigned int num_data_symbols = 32; // number of data symbols
+    float SNRdB = 30.0f;                // signal-to-noise ratio [dB]
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:n:b:s:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage();              return 0;
+        case 'k': k = atoi(optarg); break;
+        case 'm': m = atoi(optarg); break;
+        case 'n': num_data_symbols = atoi(optarg); break;
+        case 'b': BT = atof(optarg); break;
+        case 's': SNRdB = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (BT <= 0.0f || BT >= 1.0f) {
+        fprintf(stderr,"error: %s, bandwidth-time product must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_symbols = num_data_symbols + 2*m;
+    unsigned int num_samples = k*num_symbols;
+    unsigned int h_len = 2*k*m+1;
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    unsigned char sym_in[num_symbols];      // input symbols
+    float phi[num_samples];                 // transmitted phase
+    float complex x[num_samples];           // transmitted signal
+    float complex y[num_samples];           // received signal
+    float phi_hat[num_samples];             // received phase
+    float phi_prime[num_samples];           // matched-filter output
+    unsigned char sym_out[num_symbols];     // output symbols
+
+    // create transmit/receive interpolator/decimator
+#if 0
+    float ht[h_len];    // transmit filter coefficients
+    liquid_firdes_gmsktx(k,m,BT,0.0f,ht);
+    firfilt_rrrf ft = firfilt_rrrf_create(ht,h_len);
+#else
+    firinterp_rrrf interp_tx = firinterp_rrrf_create_prototype(LIQUID_FIRFILT_GMSKTX,k,m,BT,0);
+#endif
+    float hr[h_len];    // receive filter coefficients
+    liquid_firdes_gmskrx(k,m,BT,0.0f,hr);
+    firfilt_rrrf decim_rx = firfilt_rrrf_create(hr,h_len);
+
+    // generate symbols and interpolate
+    float theta = 0.0f;
+    float k_inv = 1.0f / (float)k;
+    for (i=0; i<num_symbols; i++) {
+        sym_in[i] = rand() % 2;
+        firinterp_rrrf_execute(interp_tx, sym_in[i] ? k_inv : -k_inv, &phi[k*i]);
+        //firinterp_rrrf_execute(interp_tx, i==0 ? 1.0f : 0.0f, &phi[k*i]);
+
+        // accumulate phase
+        unsigned int j;
+        for (j=0; j<k; j++) {
+            theta += phi[i*k + j];
+            x[i*k+j] = cexpf(_Complex_I*theta);
+        }
+    }
+
+    // push through channel
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    
+    // run receiver
+    float complex x_prime = 0.0f;
+    unsigned int n=0;
+    for (i=0; i<num_samples; i++) {
+        phi_hat[i] = cargf( conjf(x_prime)*y[i] );
+        x_prime = y[i];
+
+        // push through filter
+        firfilt_rrrf_push(decim_rx, phi_hat[i]);
+        firfilt_rrrf_execute(decim_rx, &phi_prime[i]);
+
+        // decimate output
+        if ( (i%k)==0 ) {
+            sym_out[n] = phi_prime[i] > 0.0f ? 1 : 0;
+            printf("%3u : %12.8f (%1u)", n, phi_prime[i], sym_out[n]);
+            if (n >= 2*m) printf(" (%1u)\n", sym_in[n-2*m]);
+            else          printf("\n");
+            n++;
+        }
+    }
+
+    // compute number of errors
+    unsigned int num_errors = 0;
+    for (i=0; i<num_data_symbols; i++)
+        num_errors += sym_in[i] == sym_out[i+2*m] ? 0 : 1;
+
+    printf("errors : %3u / %3u\n", num_errors, num_data_symbols);
+
+    // destroy objects
+    firinterp_rrrf_destroy(interp_tx);
+    firfilt_rrrf_destroy(decim_rx);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"BT = %f;\n", BT);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+
+    // save filters
+    for (i=0; i<h_len; i++)
+        fprintf(fid,"hr(%3u) = %12.8f;\n", i+1, hr[i]);
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u)       = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u)       = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"phi(%4u) = %12.8f;\n", i+1, phi[i]);
+        fprintf(fid,"phi_hat(%4u) = %12.8f;\n", i+1, phi_hat[i]);
+        fprintf(fid,"phi_prime(%4u) = %12.8f;\n", i+1, phi_prime[i]);
+    }
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"i = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,phi_prime,'-', t(i),phi_prime(i),'or');\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('matched filter output');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/sandbox/householder_test.c b/sandbox/householder_test.c
new file mode 100644
index 0000000..31e9f1b
--- /dev/null
+++ b/sandbox/householder_test.c
@@ -0,0 +1,210 @@
+// 
+// householder_test.c : test householder reflections
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define DEBUG 1
+
+int main() {
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+#if 0
+    // problem definition
+    float A[40] = {
+        22,  10,   2,   3,  7,
+        14,   7,  10,   0,  8,
+        -1,  13,  -1, -11,  3,
+        -3,  -2,  13,  -2,  4,
+         9,   8,   1,  -2,  4,
+         9,   1,  -7,   5, -1,
+         2,  -6,   6,   5,  1,
+         4,   5,   0,  -2,  2};
+
+    unsigned int m = 8;
+    unsigned int n = 5;
+#else
+    unsigned int m=8;
+    unsigned int n=5;
+    float A[m*n];
+
+    // make random matrix
+    unsigned int t;
+    for (t=0; t<m*n; t++)
+        A[t] = randnf();
+
+    // Hilbert matrix
+    for (i=0; i<m; i++) {
+        for (j=0; j<n; j++)
+            matrix_access(A,m,n,i,j) = 1.0 / ((float)(i + j + 1));
+    }
+#endif
+
+    matrixf_print(A,m,n);
+
+#if 0
+    // compute Householder reflections
+    // arrays
+    float Ah[m*n];
+    float P[m*n];
+    float Q[m*n];
+    matrixf_householder(A,m,n,P,Ah,Q);
+#endif
+
+    // internal data structures
+    float A0[m*n];  // A^(k)
+    float A1[m*n];  // A^(k+1/2)
+    float A2[m*n];  // A^(k+1  )
+    float xi[m];
+    float alphak;
+    float sk;
+    float yj[n];
+    float betak;
+    float tk;
+
+    // initialize...
+    memmove(A0,A,m*n*sizeof(float));
+
+    // 
+    for (k=0; k<n; k++) {
+#if DEBUG
+        printf("%u------------------------------\n", k);
+#endif
+        // compute sk, alphak
+        sk = 0.0;
+        for (i=k; i<m; i++) {
+            float a0_ik = matrix_access(A0,m,n,i,k);
+            sk += creal(a0_ik*conj(a0_ik));
+        }
+        sk = sqrt(sk);
+        float a0_kk = matrix_access(A0,m,n,k,k);
+        alphak = -sk * a0_kk / fabs(a0_kk); // TODO : use copysign
+#if DEBUG
+        printf("  sk        :   %12.8f\n", sk);
+        printf("  alphak    :   %12.8f\n", alphak);
+#endif
+
+        if (sk == 0) {
+            for (i=0; i<k; i++) xi[i] = 0.0;
+        } else {
+            // compute x_i^(k) for i<k
+            for (i=0; i<k; i++)
+                xi[i] = 0.0;
+
+            // compute x_i^(k) for i=k
+            float xk = sqrt( 0.5*(1 + fabs(a0_kk)/sk) );
+            xi[k] = xk;
+
+            // compute x_i^(k) for i>k
+            float ck = 1.0 / (2*sk*a0_kk/fabs(a0_kk)*xk); // TODO : use copysign
+            for (i=k+1; i<m; i++) {
+                float a0_ik = matrix_access(A0,m,n,i,k);
+                xi[i] = ck*a0_ik;
+            }
+        }
+
+        // compute A^(k+1/2) = A^(k) - x^(k)*2[ x^(k)^T *A^(k) ]
+        float xixiT[m*m];  // xi*xi^T
+        matrixf_mul_transpose(xi,m,1,xixiT);
+        float S[m*n];
+        matrixf_mul(xixiT, m, m,
+                    A0,    m, n,
+                    S,     m, n);
+        // 
+        for (i=0; i<m*n; i++)
+            A1[i] = A0[i] - 2.0*S[i];
+#if DEBUG
+        printf("xi:\n");    matrixf_print(xi,   m,1);
+        //printf("xi*xi^T:\n");matrixf_print(xixiT,m,m);
+        //printf("S:\n");     matrixf_print(S,    m,n);
+        printf("A(k+1/2):\n");    matrixf_print(A1,   m,n);
+#endif
+
+        // compute tk, betak
+        tk = 0.0;
+        for (j=k+1; j<n; j++) {
+            float a_kj = matrix_access(A1,m,n,k,j);
+            tk += creal(a_kj*conj(a_kj));
+        }
+        tk = sqrt(tk);
+        // TODO : check dimension size... [n > m]
+        float a1_kk1 = matrix_access(A1,m,n,k,k+1);
+        betak = -tk * a1_kk1 / fabs(a1_kk1);  // TODO : use copysign
+#if DEBUG
+        printf("  tk        :   %12.8f\n", tk);
+        printf("  betak     :   %12.8f\n", betak);
+        printf("  A1[k,k+1] :   %12.8f\n", a1_kk1);
+#endif
+
+        // TODO : check dimension size
+        if ( (k+1)==n ) {
+            fprintf(stderr,"warning: maximum dimension exceeded\n");
+
+            // copy and exit
+            memmove(A0, A1, m*n*sizeof(float));
+            break;
+        }
+
+        if (tk == 0) {
+            for (j=0; j<k; j++) yj[j] = 0.0;
+        } else {
+            // compute y_j(k) for j <= k
+            for (j=0; j<=k; j++)
+                yj[j] = 0.0;
+
+            // compute y_j(k) for j = k+1
+            // TODO : check dimension size... [n > m]
+            float yk1 = (tk == 0) ? 0.0 : sqrt( 0.5*(1 + fabs(a1_kk1)/tk) );
+            yj[k+1] = yk1;
+
+            // compute y_j(k) for j > k+1
+            float dk = 1.0 / (2*tk*a1_kk1/fabs(a1_kk1)*yk1);
+#if DEBUG
+            printf("dk : %12.8f\n", dk);
+#endif
+            for (j=k+2; j<n; j++) {
+                float a1_kj = matrix_access(A1,m,n,k,j);
+                yj[j] = dk*a1_kj;
+            }
+        }
+
+        // compute A(k+1) = A(k+1/2) - 2*[A(k+1/2)*y(k)]*y(k)^T
+        float yjyjT[n*n];
+        matrixf_mul_transpose(yj,n,1,yjyjT);
+        float T[m*n];
+        matrixf_mul(A1,    m, n,
+                    yjyjT, n, n,
+                    T,     m, n);
+        //
+        for (j=0; j<m*n; j++)
+            A2[j] = A1[j] - 2*T[j];
+#if DEBUG
+        printf("yj:\n");        matrixf_print(yj,   n,1);
+        printf("yi*yi^T:\n");   matrixf_print(yjyjT,n,n);
+        printf("T:\n");         matrixf_print(T,    m,n);
+        printf("A(k+1):\n");    matrixf_print(A2,   m,n);
+#endif
+
+        // copy...
+        memmove(A0, A2, m*n*sizeof(float));
+
+        // exit prematurely
+        //break;
+        //exit(1);
+    }
+
+    printf("--------------\n\n");
+    matrixf_print(A0, m, n);
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/iirdes_example.c b/sandbox/iirdes_example.c
new file mode 100644
index 0000000..0880ae5
--- /dev/null
+++ b/sandbox/iirdes_example.c
@@ -0,0 +1,410 @@
+// iirdes_example.c
+//
+// Tests infinite impulse reponse (IIR) filter design.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirdes_example.m"
+
+// print usage/help message
+void usage()
+{
+    printf("iirdes_example -- infinite impulse response filter design\n");
+    printf("options (default values in []):\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [butter], cheby1, cheby2, ellip, bessel\n");
+    printf("  b     : filter transformation: [LP], HP, BP, BS\n");
+    printf("  n     : filter order, n > 0 [5]\n");
+    printf("  r     : passband ripple in dB (cheby1, ellip), r > 0 [1.0]\n");
+    printf("  s     : stopband attenuation in dB (cheby2, ellip), s > 0 [60.0]\n");
+    printf("  f     : passband cut-off, 0 < f < 0.5 [0.2]\n");
+    printf("  c     : center frequency (BP, BS cases), 0 < c < 0.5 [0.25]\n");
+    printf("  o     : format [sos], tf\n");
+    printf("          sos   : second-order sections form\n");
+    printf("          tf    : regular transfer function form (potentially\n");
+    printf("                  unstable for large orders\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int n=5;       // filter order
+    float fc = 0.20f;       // cutoff frequency (low-pass prototype)
+    float f0 = 0.25f;       // center frequency (band-pass, band-stop)
+    float As = 60.0f;       // stopband attenuation [dB]
+    float ripple = 1.0f;    // passband ripple [dB]
+
+    // filter type
+    liquid_iirdes_filtertype ftype = LIQUID_IIRDES_BUTTER;
+
+    // band type
+    liquid_iirdes_bandtype btype = LIQUID_IIRDES_LOWPASS;
+
+    // output format: second-order sections or transfer function
+    liquid_iirdes_format format = LIQUID_IIRDES_SOS;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:b:n:r:s:f:c:o:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':
+            usage();
+            return 0;
+        case 't':
+            if (strcmp(optarg,"butter")==0) {
+                ftype = LIQUID_IIRDES_BUTTER;
+            } else if (strcmp(optarg,"cheby1")==0) {
+                ftype = LIQUID_IIRDES_CHEBY1;
+            } else if (strcmp(optarg,"cheby2")==0) {
+                ftype = LIQUID_IIRDES_CHEBY2;
+            } else if (strcmp(optarg,"ellip")==0) {
+                ftype = LIQUID_IIRDES_ELLIP;
+            } else if (strcmp(optarg,"bessel")==0) {
+                ftype = LIQUID_IIRDES_BESSEL;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown filter type \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        case 'b':
+            if (strcmp(optarg,"LP")==0) {
+                btype = LIQUID_IIRDES_LOWPASS;
+            } else if (strcmp(optarg,"HP")==0) {
+                btype = LIQUID_IIRDES_HIGHPASS;
+            } else if (strcmp(optarg,"BP")==0) {
+                btype = LIQUID_IIRDES_BANDPASS;
+            } else if (strcmp(optarg,"BS")==0) {
+                btype = LIQUID_IIRDES_BANDSTOP;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown band type \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        case 'n': n = atoi(optarg);         break;
+        case 'r': ripple = atof(optarg);    break;
+        case 's': As = atof(optarg);        break;
+        case 'f': fc = atof(optarg);        break;
+        case 'c': f0 = atof(optarg);        break;
+        case 'o':
+            if (strcmp(optarg,"sos")==0) {
+                format = LIQUID_IIRDES_SOS;
+            } else if (strcmp(optarg,"tf")==0) {
+                format = LIQUID_IIRDES_TF;
+            } else {
+                fprintf(stderr,"error: iirdes_example, unknown output format \"%s\"\n", optarg);
+                usage();
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (fc <= 0 || fc >= 0.5) {
+        fprintf(stderr,"error: %s, cutoff frequency out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (f0 < 0 || f0 > 0.5) {
+        fprintf(stderr,"error: %s, center frequency out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (ripple <= 0) {
+        fprintf(stderr,"error: %s, pass-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    } else if (As <= 0) {
+        fprintf(stderr,"error: %s, stop-band ripple out of range\n", argv[0]);
+        usage();
+        exit(1);
+    }
+
+
+    // number of analaog poles/zeros
+    unsigned int npa = n;
+    unsigned int nza;
+
+    // analog poles/zeros/gain
+    float complex pa[n];
+    float complex za[n];
+    float complex ka;
+    float complex k0;
+
+    unsigned int r = n%2;
+    unsigned int L = (n-r)/2;
+
+    unsigned int i;
+    // specific filter variables
+    float epsilon, Gp, Gs, ep, es;
+
+    switch (ftype) {
+    case LIQUID_IIRDES_BUTTER:
+        printf("Butterworth filter design:\n");
+        nza = 0;
+        k0 = 1.0f;
+        butter_azpkf(n,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY1:
+        printf("Cheby-I filter design:\n");
+        nza = 0;
+        epsilon = sqrtf( powf(10.0f, ripple / 10.0f) - 1.0f );
+        k0 = r ? 1.0f : 1.0f / sqrtf(1.0f + epsilon*epsilon);
+        cheby1_azpkf(n,epsilon,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY2:
+        printf("Cheby-II filter design:\n");
+        nza = 2*L;
+        epsilon = powf(10.0f, -As/20.0f);
+        k0 = 1.0f;
+        cheby2_azpkf(n,epsilon,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_ELLIP:
+        printf("elliptic filter design:\n");
+        nza = 2*L;
+        Gp = powf(10.0f, -ripple  / 20.0f);
+        Gs = powf(10.0f, -As      / 20.0f);
+        printf("  Gp = %12.8f\n", Gp);
+        printf("  Gs = %12.8f\n", Gs);
+
+        // epsilon values
+        ep = sqrtf(1.0f/(Gp*Gp) - 1.0f);
+        es = sqrtf(1.0f/(Gs*Gs) - 1.0f);
+        k0 = r ? 1.0f : 1.0f / sqrtf(1.0f + ep*ep);
+
+        ellip_azpkf(n,ep,es,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_BESSEL:
+        printf("Bessel filter design:\n");
+        bessel_azpkf(n,za,pa,&ka);
+        k0 = 1.0f;
+        nza = 0;
+        break;
+    default:
+        fprintf(stderr,"error: iirdes_example: unknown filter type\n");
+        exit(1);
+    }
+
+    printf("poles (analog):\n");
+    for (i=0; i<npa; i++)
+        printf("  pa[%3u] = %12.8f + j*%12.8f\n", i, crealf(pa[i]), cimagf(pa[i]));
+    printf("zeros (analog):\n");
+    for (i=0; i<nza; i++)
+        printf("  za[%3u] = %12.8f + j*%12.8f\n", i, crealf(za[i]), cimagf(za[i]));
+    printf("gain (analog):\n");
+    printf("  ka : %12.8f + j*%12.8f\n", crealf(ka), cimagf(ka));
+
+    // complex digital poles/zeros/gain
+    // NOTE: allocated double the filter order to cover band-pass, band-stop cases
+    float complex zd[2*n];
+    float complex pd[2*n];
+    float complex kd;
+    float m = iirdes_freqprewarp(btype,fc,f0);
+    printf("m : %12.8f\n", m);
+    bilinear_zpkf(za,    nza,
+                  pa,    npa,
+                  k0,    m,
+                  zd, pd, &kd);
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+#if 0
+    // print analog z/p/k
+    fprintf(fid,"za = zeros(1,nza);\n");
+    for (i=0; i<nza; i++)
+        fprintf(fid,"  za(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(za[i]), cimagf(za[i]));
+    fprintf(fid,"pa = zeros(1,npa);\n");
+    for (i=0; i<nza; i++)
+        fprintf(fid,"  pa(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(pa[i]), cimagf(pa[i]));
+#endif
+    printf("zeros (digital, low-pass prototype):\n");
+    for (i=0; i<n; i++)
+        printf("  zd[%3u] = %12.4e + j*%12.4e;\n", i, crealf(zd[i]), cimagf(zd[i]));
+    printf("poles (digital, low-pass prototype):\n");
+    for (i=0; i<n; i++)
+        printf("  pd[%3u] = %12.4e + j*%12.4e;\n", i, crealf(pd[i]), cimagf(pd[i]));
+    printf("gain (digital):\n");
+    printf("  kd : %12.8f + j*%12.8f\n", crealf(kd), cimagf(kd));
+
+    // negate zeros, poles for high-pass and band-stop cases
+    if (btype == LIQUID_IIRDES_HIGHPASS ||
+        btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        for (i=0; i<n; i++) {
+            zd[i] = -zd[i];
+            pd[i] = -pd[i];
+        }
+    }
+
+    // transform zeros, poles in band-pass, band-stop cases
+    // NOTE: this also doubles the filter order
+    if (btype == LIQUID_IIRDES_BANDPASS ||
+        btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        // allocate memory for transformed zeros, poles
+        float complex zd1[2*n];
+        float complex pd1[2*n];
+
+        // run zeros, poles trasform
+        iirdes_dzpk_lp2bp(zd, pd,   // low-pass prototype zeros, poles
+                          n,        // filter order
+                          f0,       // center frequency
+                          zd1,pd1); // transformed zeros, poles (length: 2*n)
+
+        // copy transformed zeros, poles
+        memmove(zd, zd1, 2*n*sizeof(float complex));
+        memmove(pd, pd1, 2*n*sizeof(float complex));
+
+        // update paramteres : n -> 2*n
+        r = 0;
+        L = n;
+        n = 2*n;
+    }
+
+    fprintf(fid,"n=%u;\n", n);
+    fprintf(fid,"r=%u;\n", r);
+    fprintf(fid,"L=%u;\n", L);
+    fprintf(fid,"nfft=1024;\n");
+
+    // print digital z/p/k
+    fprintf(fid,"zd = zeros(1,n);\n");
+    fprintf(fid,"pd = zeros(1,n);\n");
+    for (i=0; i<n; i++) {
+        fprintf(fid,"  zd(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(zd[i]), cimagf(zd[i]));
+        fprintf(fid,"  pd(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(pd[i]), cimagf(pd[i]));
+    }
+
+
+    if (format == LIQUID_IIRDES_TF) {
+        float b[n+1];       // numerator
+        float a[n+1];       // denominator
+
+        // convert complex digital poles/zeros/gain into transfer function
+        iirdes_dzpk2tff(zd,pd,n,kd,b,a);
+
+        // print coefficients
+        for (i=0; i<=n; i++) printf("a[%3u] = %12.8f;\n", i, a[i]);
+        for (i=0; i<=n; i++) printf("b[%3u] = %12.8f;\n", i, b[i]);
+
+        fprintf(fid,"a = zeros(1,n+1);\n");
+        fprintf(fid,"b = zeros(1,n+1);\n");
+        for (i=0; i<=n; i++) {
+            fprintf(fid,"a(%3u) = %12.4e;\n", i+1, a[i]);
+            fprintf(fid,"b(%3u) = %12.4e;\n", i+1, b[i]);
+        }
+        fprintf(fid,"\n");
+        fprintf(fid,"H = fft(b,nfft)./fft(a,nfft);\n");
+        fprintf(fid,"H = fftshift(H);\n");
+        fprintf(fid,"%% group delay\n");
+        fprintf(fid,"c = conv(b,fliplr(conj(a)));\n");
+        fprintf(fid,"cr = c.*[0:(length(c)-1)];\n");
+        fprintf(fid,"t0 = fftshift(fft(cr,nfft));\n");
+        fprintf(fid,"t1 = fftshift(fft(c, nfft));\n");
+        fprintf(fid,"polebins = find(abs(t1)<1e-6);\n");
+        fprintf(fid,"t0(polebins)=0;\n");
+        fprintf(fid,"t1(polebins)=1;\n");
+        fprintf(fid,"gd = real(t0./t1) - length(a) + 1;\n");
+
+    } else {
+        // second-order sections
+        float A[3*(L+r)];
+        float B[3*(L+r)];
+
+        // convert complex digital poles/zeros/gain into second-
+        // order sections form
+        iirdes_dzpk2sosf(zd,pd,n,kd,B,A);
+
+        // print coefficients
+        printf("B [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", B[3*i+0], B[3*i+1], B[3*i+2]);
+        printf("A [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", A[3*i+0], A[3*i+1], A[3*i+2]);
+
+        unsigned int j;
+        for (i=0; i<L+r; i++) {
+            for (j=0; j<3; j++) {
+                fprintf(fid,"B(%3u,%3u) = %16.8e;\n", i+1, j+1, B[3*i+j]);
+                fprintf(fid,"A(%3u,%3u) = %16.8e;\n", i+1, j+1, A[3*i+j]);
+            }
+        }
+        fprintf(fid,"\n");
+        fprintf(fid,"H = ones(1,nfft);\n");
+        fprintf(fid,"gd = zeros(1,nfft);\n");
+        fprintf(fid,"t0 = zeros(1,nfft);\n");
+        fprintf(fid,"t1 = zeros(1,nfft);\n");
+        fprintf(fid,"for i=1:(L+r),\n");
+        fprintf(fid,"    H = H .* fft(B(i,:),nfft)./fft(A(i,:),nfft);\n");
+        fprintf(fid,"    %% group delay\n");
+        fprintf(fid,"    c = conv(B(i,:),fliplr(conj(A(i,:))));\n");
+        fprintf(fid,"    cr = c.*[0:4];\n");
+        fprintf(fid,"    t0 = fftshift(fft(cr,nfft));\n");
+        fprintf(fid,"    t1 = fftshift(fft(c, nfft));\n");
+        fprintf(fid,"    polebins = find(abs(t1)<1e-6);\n");
+        fprintf(fid,"    t0(polebins)=0;\n");
+        fprintf(fid,"    t1(polebins)=1;\n");
+        fprintf(fid,"    gd = gd + real(t0./t1) - 2;\n");
+        fprintf(fid,"end;\n");
+        fprintf(fid,"H = fftshift(H);\n");
+    }
+
+    // plot zeros, poles
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"k=0:0.01:1;\n");
+    fprintf(fid,"ti = cos(2*pi*k);\n");
+    fprintf(fid,"tq = sin(2*pi*k);\n");
+    fprintf(fid,"plot(ti,tq,'-','LineWidth',1,'Color',[1 1 1]*0.7,...\n");
+    fprintf(fid,"     real(zd),imag(zd),'o','LineWidth',2,'Color',[0.5 0   0],'MarkerSize',2,...\n");
+    fprintf(fid,"     real(pd),imag(pd),'x','LineWidth',2,'Color',[0   0.5 0],'MarkerSize',2);\n");
+    fprintf(fid,"xlabel('real');\n");
+    fprintf(fid,"ylabel('imag');\n");
+    fprintf(fid,"title('z-plane');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"axis square;\n");
+
+    // plot group delay
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"  plot(f,gd,'-','Color',[0 0.5 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 0 ceil(1.1*max(gd))]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Group delay [samples]');\n");
+
+    // plot magnitude response
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1),\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'-','Color',[0.5 0 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 -4 1]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Filter PSD [dB]');\n");
+    fprintf(fid,"subplot(2,1,2),\n");
+    fprintf(fid,"  plot(f,20*log10(abs(H)),'-','Color',[0.5 0 0],'LineWidth',2);\n");
+    fprintf(fid,"  axis([0.0 0.5 -100 10]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Filter PSD [dB]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/iirfilt_intdiff_test.c b/sandbox/iirfilt_intdiff_test.c
new file mode 100644
index 0000000..40b67f1
--- /dev/null
+++ b/sandbox/iirfilt_intdiff_test.c
@@ -0,0 +1,250 @@
+// iirfilt_intdiff_test
+//
+// Tests infinite impulse reponse (IIR) integrator and differentiator
+// filters. The filters are each order 8 and have a near-integer group
+// delay of 7 samples. The magnitude response of the ideal is on the
+// order of -140 dB error.
+//
+// References:
+//  [Pintelon:1990] Rik Pintelon and Johan Schoukens, "Real-Time
+//      Integration and Differentiation of Analog Signals by Means of
+//      Digital Filtering," IEEE Transactions on Instrumentation and
+//      Measurement, vol 39 no. 6, December 1990.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "iirfilt_intdiff_test.m"
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int num_samples = 801;
+
+    // 
+    // integrator digital zeros/poles/gain, [Pintelon:1990] Table II
+    //
+    // zeros, digital, integrator
+    float complex zdi[8] = {
+        1.175839 * -1.0f,
+        3.371020 * cexpf(_Complex_I * M_PI / 180.0f * -125.1125f),
+        3.371020 * cexpf(_Complex_I * M_PI / 180.0f *  125.1125f),
+        4.549710 * cexpf(_Complex_I * M_PI / 180.0f *  -80.96404f),
+        4.549710 * cexpf(_Complex_I * M_PI / 180.0f *   80.96404f),
+        5.223966 * cexpf(_Complex_I * M_PI / 180.0f *  -40.09347f),
+        5.223966 * cexpf(_Complex_I * M_PI / 180.0f *   40.09347f),
+        5.443743,};
+    // poles, digital, integrator
+    float complex pdi[8] = {
+        0.5805235f * -1.0f,
+        0.2332021f * cexpf(_Complex_I * M_PI / 180.0f * -114.0968f),
+        0.2332021f * cexpf(_Complex_I * M_PI / 180.0f *  114.0968f),
+        0.1814755f * cexpf(_Complex_I * M_PI / 180.0f *  -66.33969f),
+        0.1814755f * cexpf(_Complex_I * M_PI / 180.0f *   66.33969f),
+        0.1641457f * cexpf(_Complex_I * M_PI / 180.0f *  -21.89539f),
+        0.1641457f * cexpf(_Complex_I * M_PI / 180.0f *   21.89539f),
+        1.0f,};
+    // gain, digital, integrator
+    float complex kdi = -1.89213380759321e-05f;
+
+#if 1
+    // second-order sections
+    // allocate 12 values for 4 second-order sections each with
+    // 2 roots (order 8), e.g. (1 + r0 z^-1)(1 + r1 z^-1)
+    float Bi[12];
+    float Ai[12];
+    iirdes_dzpk2sosf(zdi, pdi, 8, kdi, Bi, Ai);
+    // create integrator
+    iirfilt_crcf integrator = iirfilt_crcf_create_sos(Bi,Ai,4);
+#else
+    // regular transfer function form
+    // 9 = order + 1
+    float bi[9];
+    float ai[9];
+    iirdes_dzpk2tff(zdi, pdi, 8, kdi, bi, ai);
+    iirfilt_crcf integrator = iirfilt_crcf_create(bi,9,ai,9);
+#endif
+
+
+    // 
+    // differentiator digital zeros/poles/gain, [Pintelon:1990] Table IV
+    //
+    // zeros, digital, differentiator
+    float complex zdd[8] = {
+        1.702575f * -1.0f,
+        5.877385f * cexpf(_Complex_I * M_PI / 180.0f * -221.4063f),
+        5.877385f * cexpf(_Complex_I * M_PI / 180.0f *  221.4063f),
+        4.197421f * cexpf(_Complex_I * M_PI / 180.0f * -144.5972f),
+        4.197421f * cexpf(_Complex_I * M_PI / 180.0f *  144.5972f),
+        5.350284f * cexpf(_Complex_I * M_PI / 180.0f *  -66.88802f),
+        5.350284f * cexpf(_Complex_I * M_PI / 180.0f *   66.88802f),
+        1.0f,};
+    // poles, digital, differentiator
+    float complex pdd[8] = {
+        0.8476936f * -1.0f,
+        0.2990781f * cexpf(_Complex_I * M_PI / 180.0f * -125.5188f),
+        0.2990781f * cexpf(_Complex_I * M_PI / 180.0f *  125.5188f),
+        0.2232427f * cexpf(_Complex_I * M_PI / 180.0f *  -81.52326f),
+        0.2232427f * cexpf(_Complex_I * M_PI / 180.0f *   81.52326f),
+        0.1958670f * cexpf(_Complex_I * M_PI / 180.0f *  -40.51510f),
+        0.1958670f * cexpf(_Complex_I * M_PI / 180.0f *   40.51510f),
+        0.1886088f,};
+    // gain, digital, differentiator
+    float complex kdd = 2.09049284907492e-05f;
+
+#if 1
+    // second-order sections
+    // allocate 12 values for 4 second-order sections each with
+    // 2 roots (order 8), e.g. (1 + r0 z^-1)(1 + r1 z^-1)
+    float Bd[12];
+    float Ad[12];
+    iirdes_dzpk2sosf(zdd, pdd, 8, kdd, Bd, Ad);
+    // create differentiator
+    iirfilt_crcf differentiator = iirfilt_crcf_create_sos(Bd,Ad,4);
+#else
+    // regular transfer function form
+    // 9 = order + 1
+    float bd[9];
+    float ad[9];
+    iirdes_dzpk2tff(zdd, pdd, 8, kdd, bd, ad);
+    iirfilt_crcf differentiator = iirfilt_crcf_create(bd,9,ad,9);
+#endif
+
+    // allocate arrays
+    float complex x[num_samples];
+    float complex y[num_samples];
+    float complex z[num_samples];
+
+    float tmin = 0.0f;
+    float tmax = 1.0f;
+    float dt   = (tmax-tmin)/(float)(num_samples-1);
+    unsigned int i;
+    for (i=0; i<num_samples; i++) {
+        // time vector
+        float t = tmin + dt*i;
+
+        // generate input signal
+        x[i] = 0.2*cosf(2*M_PI*4.1*t + 0.0f) +
+               0.4*cosf(2*M_PI*6.9*t + 0.9f) +
+               0.7*cosf(2*M_PI*9.7*t + 0.2f) +
+               _Complex_I*cosf(2*M_PI*4*t);
+
+        // run integrator
+        iirfilt_crcf_execute(integrator, x[i]*dt, &y[i]);
+
+        // run differentitator
+        iirfilt_crcf_execute(differentiator, y[i]/dt, &z[i]);
+    }
+
+    // open output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"delay = 7; %% fixed group delay\n");
+
+    // save
+    fprintf(fid,"n = %u;\n", num_samples);
+    fprintf(fid,"x = zeros(1,n);\n");
+    fprintf(fid,"y = zeros(1,n);\n");
+    fprintf(fid,"z = zeros(1,n);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%5u)=%12.4e+%12.4ej; ", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%5u)=%12.4e+%12.4ej; ", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%5u)=%12.4e+%12.4ej; ", i+1, crealf(z[i]), cimagf(z[i]));
+        fprintf(fid,"\n");
+    }
+
+    // plot results
+    fprintf(fid,"tmin = %f;\n", tmin);
+    fprintf(fid,"tmax = %f;\n", tmax);
+    fprintf(fid,"dt = (tmax-tmin)/(n-1);\n");
+    fprintf(fid,"tx = tmin + [[0:(n-1)]        ]*dt;\n");
+    fprintf(fid,"ty = tmin + [[0:(n-1)]-delay  ]*dt;\n");
+    fprintf(fid,"tz = tmin + [[0:(n-1)]-2*delay]*dt;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(tx, real(x), 'LineWidth', 1.2, tx, imag(x),'LineWidth',1.2);\n");
+    fprintf(fid,"  axis([tmin tmax -2 2]);\n");
+    fprintf(fid,"  ylabel('input');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(ty, real(y),'LineWidth',1.2, ty, imag(y),'LineWidth',1.2);\n");
+    fprintf(fid,"  ymax = round(10000*max(abs(y)))/10000;\n");
+    fprintf(fid,"  axis([tmin tmax -ymax ymax]);\n");
+    fprintf(fid,"  ylabel('integrated');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(tz, real(z),'LineWidth',1.2, tz, imag(z),'LineWidth',1.2);\n");
+    fprintf(fid,"  axis([tmin tmax -2 2]);\n");
+    fprintf(fid,"  ylabel('int/diff');\n");
+    fprintf(fid,"  grid on;\n");
+
+    // compute frequency response
+    unsigned int nfft = 256;
+    float complex Hi[nfft], Hd[nfft];
+    float gdi[nfft], gdd[nfft];
+    for (i=0; i<nfft; i++) {
+        float f = 1e-4f + 0.5f * (float)i / (float)nfft;
+
+        // compute magnitude/phase response
+        iirfilt_crcf_freqresponse(integrator,     f, &Hi[i]);
+        iirfilt_crcf_freqresponse(differentiator, f, &Hd[i]);
+
+        // compute group delay
+        gdi[i] = iirfilt_crcf_groupdelay(integrator, f);
+        gdd[i] = iirfilt_crcf_groupdelay(differentiator, f);
+    }
+
+    // save frequency response
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"Hi  = zeros(1,nfft);\n");
+    fprintf(fid,"Hd  = zeros(1,nfft);\n");
+    fprintf(fid,"gdi = zeros(1,nfft);\n");
+    fprintf(fid,"gdd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++) {
+        fprintf(fid,"Hi(%5u)=%12.4e+%12.4ej; ", i+1, crealf(Hi[i]), cimagf(Hi[i]));
+        fprintf(fid,"Hd(%5u)=%12.4e+%12.4ej; ", i+1, crealf(Hd[i]), cimagf(Hd[i]));
+        fprintf(fid,"gdi(%5u)=%12.4e; ",        i+1, gdi[i]);
+        fprintf(fid,"gdd(%5u)=%12.4e; ",        i+1, gdd[i]);
+        fprintf(fid,"\n");
+    }
+
+    // plot magnitude response
+    fprintf(fid,"f = 0.5*[0:(nfft-1)]/nfft;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1),\n");
+    fprintf(fid,"  plot(f,20*log10(Hi),    '-','Color',[0.0 0.5 0.0],'LineWidth',1.2,...\n");
+    fprintf(fid,"       f,20*log10(Hd),    '-','Color',[0.0 0.0 0.5],'LineWidth',1.2,...\n");
+    fprintf(fid,"       f,20*log10(Hi.*Hd),'-','Color',[0.5 0.3 0.0],'LineWidth',1.8);\n");
+    fprintf(fid,"  axis([0.0 0.5 -20 20]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('Integrator','Differentiator','Composite','location','northeast');\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Filter PSD [dB]');\n");
+
+    // plot group delay
+    fprintf(fid,"subplot(2,1,2),\n");
+    fprintf(fid,"  plot(f,gdi,'-','Color',[0 0.5 0.0],'LineWidth',1.2,...\n");
+    fprintf(fid,"       f,gdd,'-','Color',[0 0.0 0.5],'LineWidth',1.2);\n");
+    fprintf(fid,"  axis([0.0 0.5 0 ceil(1.1*max([gdi gdd]))]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('Integrator','Differentiator','location','southeast');\n");
+    fprintf(fid,"  xlabel('Normalized Frequency');\n");
+    fprintf(fid,"  ylabel('Group delay [samples]');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // destroy filter objects
+    iirfilt_crcf_destroy(integrator);
+    iirfilt_crcf_destroy(differentiator);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/levinson_test.c b/sandbox/levinson_test.c
new file mode 100644
index 0000000..1238f88
--- /dev/null
+++ b/sandbox/levinson_test.c
@@ -0,0 +1,141 @@
+//
+// levinson_test.c
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+
+int main() {
+    unsigned int p=10;
+
+    // derived values
+    float r[p+1];
+    float a_matrixinv[p+1];
+    float a_levinson[p+1];
+
+    unsigned int i;
+    unsigned int j;
+
+    for (i=0; i<p+1; i++) {
+        r[i] = randnf()*expf(-0.6f*i) + 2.00f*randnf();
+
+        printf("r[%2u] = %12.8f\n", i, r[i]);
+    }
+
+    // 
+    // matrix inversion
+    //
+
+    // use low inversion method
+    float R[p*p];
+    for (i=0; i<p; i++) {
+        for (j=0; j<p; j++)
+            matrix_access(R,p,p,i,j) = r[(i>j) ? (i-j) : (j-i)]; // abs(i-j)
+    }
+
+    // invert matrix (using Gauss-Jordan elimination)
+    matrixf_inv(R,p,p);
+
+    float rt[p];
+    float a_hat[p];
+
+    for (i=0; i<p; i++)
+        rt[i] = -r[i+1];
+
+    // multiply R_inv with r to get _a vector
+    matrixf_mul(R,     p, p,
+                rt,    p, 1,
+                a_hat, p, 1);
+
+    a_matrixinv[0] = 1.0f;
+    memmove(&a_matrixinv[1], a_hat, p*sizeof(float));
+
+    // 
+    // Levinson-Durbin algorithm
+    //
+
+    float a0[p+1];  // ...
+    float a1[p+1];  // ...
+    float e[p+1];     // prediction error
+    float k[p+1];     // reflection coefficients
+
+    // initialize
+    k[0] = 1.0f;
+    e[0] = r[0];
+
+    for (i=0; i<p+1; i++) {
+        a0[i] = (i==0) ? 1.0f : 0.0f;
+        a1[i] = (i==0) ? 1.0f : 0.0f;
+    }
+
+    unsigned int n;
+    for (n=1; n<p+1; n++) {
+
+        float q = 0.0f;
+        for (i=0; i<n; i++)
+            q += a0[i]*r[n-i];
+
+        k[n] = -q/e[n-1];
+        e[n] = e[n-1]*(1.0f - k[n]*k[n]);
+
+        // compute new coefficients
+        for (i=0; i<n; i++)
+            a1[i] = a0[i] + k[n]*a0[n-i];
+
+        a1[n] = k[n];
+#if 0
+        printf("iteration [n=%u]\n", n);
+        for (i=0; i<n; i++)
+            printf("  ** i = %3u, n-i = %3u\n", i, n-i);
+
+        // print results
+        printf("  k   = %12.8f\n", k[n]);
+        printf("  q   = %12.8f\n", q);
+        printf("  e   = %12.8f\n", e[n]);
+
+        printf("  a_%u = {", n-1);
+        for (i=0; i<n; i++)
+            printf("%6.3f, ", a0[i]);
+        printf("}\n");
+
+        printf("  a_%u = {", n);
+        for (i=0; i<n+1; i++)
+            printf("%6.3f, ", a1[i]);
+        printf("}\n");
+#endif
+
+        // copy temporary vector a1 to a0
+        memmove(a0, a1, (p+1)*sizeof(float));
+
+    }
+
+#if 0
+    // copy result
+    for (i=0; i<p+1; i++)
+        a_levinson[i] = a1[i];
+#else
+    memmove(a_levinson, a1, (p+1)*sizeof(float));
+#endif
+
+#if 0
+    //
+    // test using liquid_levinson
+    //
+    liquid_levinson(r,p,a_levinson,k);
+#endif
+
+    // 
+    // print results
+    //
+
+    for (i=0; i<p+1; i++)
+        printf("%3u : %12.8f (%12.8f)\n", i, a_matrixinv[i], a_levinson[i]);
+
+    return 0;
+}
+
diff --git a/sandbox/matched_filter_cfo_test.c b/sandbox/matched_filter_cfo_test.c
new file mode 100644
index 0000000..1f991f2
--- /dev/null
+++ b/sandbox/matched_filter_cfo_test.c
@@ -0,0 +1,216 @@
+//
+// sandbox/matched_filter_cfo_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "matched_filter_cfo_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("matched_filter_cfo_test options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  t     : filter type: [rrcos], rkaiser, arkaiser, hM3\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 2\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 3\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.5\n");
+    printf("  n     : number of symbols, default: 16\n");
+    printf("  P     : carrier phase offset, default: 0\n");
+    printf("  F     : carrier frequency offset, default: 0.8\n");
+    printf("  c     : compensate for carrier frequency offset in matched filter? default: no\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=2;       // samples/symbol
+    unsigned int m=4;       // symbol delay
+    float beta=0.3f;        // excess bandwidth factor
+    float phi = 0.0f;       // carrier phase offset
+    float dphi = 0.8f;      // carrier frequency offset
+    unsigned int num_symbols=128;
+    int cfo_compensation=0; // compensate for carrier offset in matched filter?
+    enum {
+        FILTER_RRCOS=0,
+        FILTER_RKAISER,
+        FILTER_ARKAISER,
+        FILTER_hM3
+    } ftype = 0;            // filter prototype
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uht:k:m:b:n:P:F:c")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();            return 0;
+        case 't':
+            if (strcmp(optarg,"rrcos")==0) {
+                ftype = FILTER_RRCOS;
+            } else if (strcmp(optarg,"rkaiser")==0) {
+                ftype = FILTER_RKAISER;
+            } else if (strcmp(optarg,"arkaiser")==0) {
+                ftype = FILTER_ARKAISER;
+            } else if (strcmp(optarg,"hM3")==0) {
+                ftype = FILTER_hM3;
+            } else {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'k':   k = atoi(optarg);           break;
+        case 'm':   m = atoi(optarg);           break;
+        case 'b':   beta = atof(optarg);        break;
+        case 'n':   num_symbols = atoi(optarg); break;
+        case 'P':   phi = atof(optarg);         break;
+        case 'F':   dphi = atof(optarg);        break;
+        case 'c':   cfo_compensation=1;         break;
+        default:
+            exit(1);
+        }
+    }
+
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, beta must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // initialize objects
+    unsigned int h_len = 2*k*m+1;
+    float h[h_len];
+
+    switch (ftype) {
+    case FILTER_RRCOS:
+        liquid_firdes_rrcos(k,m,beta,0,h);
+        break;
+    case FILTER_RKAISER:
+        liquid_firdes_rkaiser(k,m,beta,0,h);
+        break;
+    case FILTER_ARKAISER:
+        liquid_firdes_arkaiser(k,m,beta,0,h);
+        break;
+    case FILTER_hM3:
+        liquid_firdes_hM3(k,m,beta,0,h);
+        break;
+    default:
+        // should never get to this point
+        fprintf(stderr,"error: %s, invalid filter type\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_samples = num_symbols*k;
+
+    // generate receive filter coefficients (reverse of transmit)
+    float complex gc[h_len];
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        gc[i] = h[h_len-i-1];
+    // compensate for carrier frequency offset in matched filter?
+    if (cfo_compensation) {
+        for (i=0; i<h_len; i++)
+            gc[i] *= cexpf(_Complex_I*dphi*i);
+    }
+
+    // create interpolator and decimator
+    firinterp_crcf interp = firinterp_crcf_create(k,h,h_len);
+    firdecim_cccf  decim  = firdecim_cccf_create(k,gc,h_len);
+
+    // generate signal
+    float complex sym_in[num_symbols];
+    float complex y[num_samples];
+    float complex sym_out[num_symbols];
+
+    for (i=0; i<h_len; i++)
+        printf("h(%3u) = %12.8f;\n", i+1, h[i]);
+
+    // interpolation
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        sym_in[i] = cexpf(_Complex_I*( 0.25f*M_PI + 0.5f*M_PI*(rand()%4)) );
+
+        // interpolate
+        firinterp_crcf_execute(interp, sym_in[i], &y[i*k]);
+
+#if 0
+        printf("  %3u : %8.5f + j*%8.5f\n", i, crealf(sym_in[i]), cimagf(sym_in[i]));
+#endif
+    }
+
+    // channel
+    for (i=0; i<num_samples; i++) {
+        y[i] *= cexpf(_Complex_I*(phi + dphi*i));
+    }
+
+    // decimation
+    for (i=0; i<num_symbols; i++) {
+        // decimate
+        firdecim_cccf_execute(decim, &y[i*k], &sym_out[i]);
+
+        // normalize output
+        sym_out[i] /= k;
+
+        // compensate for carrier offset
+        sym_out[i] *= cexpf(-_Complex_I*(dphi*i*k));
+
+        // compensate for consequental carrier phase offset
+        if (!cfo_compensation)
+            sym_out[i] *= cexpf(_Complex_I*dphi*m*k);
+
+#if 0
+        printf("  %3u : %8.5f + j*%8.5f", i, crealf(sym_out[i]), cimagf(sym_out[i]));
+        if (i>=2*m) printf(" *\n");
+        else        printf("\n");
+#endif
+    }
+
+    // clean up objects
+    firinterp_crcf_destroy(interp);
+    firdecim_cccf_destroy(decim);
+
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %12.8f;\n", beta);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = k*num_symbols;\n");
+
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++)
+        fprintf(fid," y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    fprintf(fid,"s = zeros(1,num_symbols);\n");
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid," s(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(s), imag(s), 'x');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"xlabel('I');\n");
+    fprintf(fid,"ylabel('Q');\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+    
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/matched_filter_test.c b/sandbox/matched_filter_test.c
new file mode 100644
index 0000000..ed0eeda
--- /dev/null
+++ b/sandbox/matched_filter_test.c
@@ -0,0 +1,96 @@
+//
+// matched_filter_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "matched_filter_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("matched_filter_test options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  k     : filter samples/symbol, k >= 2, default: 4\n");
+    printf("  m     : filter delay (symbols), m >= 1, default: 2\n");
+    printf("  b     : filter excess bandwidth factor, 0 < b < 1, default: 0.9\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k=4;   // samples/symbol
+    unsigned int m=2;   // symbol delay
+    float beta=0.9f;    // excess bandwidth factor
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();            return 0;
+        case 'k':   k = atoi(optarg);           break;
+        case 'm':   m = atoi(optarg);           break;
+        case 'b':   beta = atof(optarg);        break;
+        default:
+            exit(1);
+        }
+    }
+
+    if (k < 2) {
+        fprintf(stderr,"error: %s, k must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s, m must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, beta must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // design filter
+    unsigned int h_len = 2*k*m+1;
+    float h[h_len];
+    liquid_firdes_rrcos(k,m,beta,0,h);
+
+    // truncate filter to be of length 2*k*m
+    unsigned int g_len = 2*k*m;
+
+    // generate receive filter coefficients (reverse of transmit)
+    float g[g_len];
+    unsigned int i;
+    for (i=0; i<g_len; i++)
+        g[i] = h[g_len-i-1];
+
+    // create interpolator and decimator
+    firfilt_rrrf q = firfilt_rrrf_create(g,g_len);
+
+    // compute auto-correlation of g
+    float x;    // input sample
+    float y;    // output sample
+    for (i=0; i<2*g_len-1; i++) {
+
+        // generate, push input sample
+        x = (i < g_len) ? h[i] : 0.0f;
+        firfilt_rrrf_push(q, x);
+        
+        // compute (normalized) output sample
+        firfilt_rrrf_execute(q, &y);
+        y /= (float)k;
+
+        // print result, highlighting optimal decimation point
+        printf("%3u : %c %12.8f\n", i, ((i+1)%k)==0 ? '*' : ' ', y);
+    }
+
+    // clean it up
+    firfilt_rrrf_destroy(q);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_cacosf_test.c b/sandbox/math_cacosf_test.c
new file mode 100644
index 0000000..3f78306
--- /dev/null
+++ b/sandbox/math_cacosf_test.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_cacosf_test.c
+//
+// complex arc-cosine test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_cacosf(float complex _z)
+{
+    // return based on quadrant
+    int sign_i = crealf(_z) > 0;
+    int sign_q = cimagf(_z) > 0;
+
+    if (sign_i == sign_q) {
+        return - _Complex_I*clogf(_z + csqrtf(_z*_z - 1.0f));
+    } else {
+        return - _Complex_I*clogf(_z - csqrtf(_z*_z - 1.0f));
+    }
+
+    // should never get to this state
+    return 0.0f;
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    float complex err_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 2.0f*(2.0f*sandbox_randf() - 1.0f) +
+               2.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = cacosf(z[i]);
+        float complex acosz_hat = sandbox_cacosf(z[i]);
+
+        float complex err = test[i] - acosz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, acos(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(z[i]),       cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(acosz_hat),  cimagf(acosz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_casinf_test.c b/sandbox/math_casinf_test.c
new file mode 100644
index 0000000..79a240f
--- /dev/null
+++ b/sandbox/math_casinf_test.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_casinf_test.c
+//
+// complex arc-sine test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_cacosf(float complex _z)
+{
+    // return based on quadrant
+    int sign_i = crealf(_z) > 0;
+    int sign_q = cimagf(_z) > 0;
+
+    if (sign_i == sign_q) {
+        return - _Complex_I*clogf(_z + csqrtf(_z*_z - 1.0f));
+    } else {
+        return - _Complex_I*clogf(_z - csqrtf(_z*_z - 1.0f));
+    }
+
+    // should never get to this state
+    return 0.0f;
+}
+
+float complex sandbox_casinf(float complex _z)
+{
+    return 0.5f*M_PI - sandbox_cacosf(_z);
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    float complex err_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 2.0f*(2.0f*sandbox_randf() - 1.0f) +
+               2.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = casinf(z[i]);
+        float complex asinz_hat = sandbox_casinf(z[i]);
+
+        float complex err = test[i] - asinz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, asin(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(z[i]),       cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(asinz_hat),  cimagf(asinz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_catanf_test.c b/sandbox/math_catanf_test.c
new file mode 100644
index 0000000..254ece1
--- /dev/null
+++ b/sandbox/math_catanf_test.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_catanf_test.c
+//
+// complex arc-tangent test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_catanf(float complex _z)
+{
+    return 0.5f*_Complex_I*clogf( (1.0f-_Complex_I*_z)/(1.0f+_Complex_I*_z) );
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    float complex err_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 2.0f*(2.0f*sandbox_randf() - 1.0f) +
+               2.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = catanf(z[i]);
+        float complex atanz_hat = sandbox_catanf(z[i]);
+
+        float complex err = test[i] - atanz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, atan(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(z[i]),       cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(atanz_hat),  cimagf(atanz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_cexpf_test.c b/sandbox/math_cexpf_test.c
new file mode 100644
index 0000000..b154029
--- /dev/null
+++ b/sandbox/math_cexpf_test.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_cexpf_test.c
+//
+// complex exponent test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_cexpf(float complex _z)
+{
+    float r = expf( crealf(_z) );
+    float complex re = cosf( cimagf(_z) );
+    float complex im = sinf( cimagf(_z) );
+
+    return r*(re + _Complex_I*im);
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    unsigned int i;
+    float complex err_max = 0.0f;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 4.0f*(2.0f*sandbox_randf() - 1.0f) +
+               4.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = cexpf(z[i]);
+        float complex expz_hat = sandbox_cexpf(z[i]);
+
+        float complex err = test[i] - expz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, exp(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(z[i]),       cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(expz_hat),   cimagf(expz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%14.6e+_Complex_I*%14.6e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%14.6e+_Complex_I*%14.6e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_clogf_test.c b/sandbox/math_clogf_test.c
new file mode 100644
index 0000000..0c00daa
--- /dev/null
+++ b/sandbox/math_clogf_test.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_clogf_test.c
+//
+// complex logarithm test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_clogf(float complex _z)
+{
+    return logf(cabsf(_z)) + _Complex_I*cargf(_z);
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    unsigned int i;
+    float complex err_max = 0.0f;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 4.0f*(2.0f*sandbox_randf() - 1.0f) +
+               4.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = clogf(z[i]);
+        float complex logz_hat = sandbox_clogf(z[i]);
+
+        float complex err = test[i] - logz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, log(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(z[i]),       cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(logz_hat),   cimagf(logz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_csqrtf_test.c b/sandbox/math_csqrtf_test.c
new file mode 100644
index 0000000..1be818f
--- /dev/null
+++ b/sandbox/math_csqrtf_test.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_csqrtf_test.c
+//
+// complex square root test
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+
+#define sandbox_randf() ((float) rand() / (float) RAND_MAX)
+
+float complex sandbox_csqrtf(float complex _z)
+{
+    float r = cabsf(_z);            // magnitude of _z
+    float a = crealf(_z);           // real component of _z
+
+    float re = sqrtf(0.5f*(r+a));   // real component of return value
+    float im = sqrtf(0.5f*(r-a));   // imag component of return value
+
+    // return value, retaining sign of imaginary component
+    return cimagf(_z) > 0 ? re + _Complex_I*im :
+                            re - _Complex_I*im;
+}
+
+int main() {
+    unsigned int n=32;  // number of tests
+    unsigned int d=2;   // number of items per line
+
+    // data arrays
+    float complex z[n];
+    float complex test[n];
+
+    float complex err_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // generate random complex number
+        z[i] = 2.0f*(2.0f*sandbox_randf() - 1.0f) +
+               2.0f*(2.0f*sandbox_randf() - 1.0f) * _Complex_I;
+
+        test[i] = csqrtf(z[i]);
+        float complex sqrtz_hat = sandbox_csqrtf(z[i]);
+
+        float complex err = test[i] - sqrtz_hat;
+
+        printf("%3u: z=%6.2f+j%6.2f, sqrt(z)=%6.2f+j%6.2f (%6.2f+j%6.2f) e=%12.4e\n",
+                i,
+                crealf(test[i]),    cimagf(z[i]),
+                crealf(test[i]),    cimagf(test[i]),
+                crealf(sqrtz_hat),  cimagf(sqrtz_hat),
+                cabsf(err));
+
+        if ( cabsf(err) > cabsf(err_max) )
+            err_max = err;
+    }
+
+    printf("maximum error: %12.4e;\n", cabsf(err_max));
+
+
+    // 
+    // print autotest lines
+    //
+
+    printf("\n");
+    printf("    float complex z[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(z[i]), cimagf(z[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+
+    printf("\n");
+    printf("    float complex test[%u] = {\n      ", n);
+    for (i=0; i<n; i++) {
+        printf("%12.4e+_Complex_I*%12.4e", crealf(test[i]), cimagf(test[i]));
+
+        if ( i == n-1)
+            printf(" ");
+        else if ( ((i+1)%d)==0 )
+            printf(",\n      ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/math_lngamma_test.c b/sandbox/math_lngamma_test.c
new file mode 100644
index 0000000..e7f3c7c
--- /dev/null
+++ b/sandbox/math_lngamma_test.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// math_lngamma_test.c
+//
+// compute approximation to log(Gamma(z))
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.h"
+
+float z[41] = {
+      0.00100000,   0.00125890,   0.00158490,   0.00199530, 
+      0.00251190,   0.00316230,   0.00398110,   0.00501190, 
+      0.00630960,   0.00794330,   0.01000000,   0.01258900, 
+      0.01584900,   0.01995300,   0.02511900,   0.03162300, 
+      0.03981100,   0.05011900,   0.06309600,   0.07943300, 
+      0.10000000,   0.12589000,   0.15849000,   0.19953000, 
+      0.25119000,   0.31623000,   0.39811000,   0.50119000, 
+      0.63096000,   0.79433000,   1.00000000,   1.25890000, 
+      1.58490000,   1.99530000,   2.51190000,   3.16230000, 
+      3.98110000,   5.01190000,   6.30960000,   7.94330000, 
+     10.00000000};
+
+float lngamma_test[41] = {
+      6.90720000,   6.67680000,   6.44630000,   6.21580000, 
+      5.98530000,   5.75460000,   5.52390000,   5.29310000, 
+      5.06210000,   4.83090000,   4.59950000,   4.36780000, 
+      4.13570000,   3.90320000,   3.67010000,   3.43640000, 
+      3.20190000,   2.96640000,   2.72990000,   2.49200000, 
+      2.25270000,   2.01200000,   1.76980000,   1.52660000, 
+      1.28300000,   1.04050000,   0.80154000,   0.57004000, 
+      0.35224000,   0.15757000,   0.00000000,  -0.10025000, 
+     -0.11440000,  -0.00199580,   0.29308000,   0.84799000, 
+      1.76800000,   3.19600000,   5.32420000,   8.41110000, 
+     12.80200000};
+
+float sandbox_lngammaf(float _z,
+                       float * _v)
+{
+    float v0 = _v[0];
+    float v1 = _v[1];
+
+    // z = 10.^[-3:0.1:1];
+    // t0 = log(gamma(z));
+    // t1 = (1+z-0.5).*log(1+z) - (1+z) + 0.5*log(2*pi) - log(z) + 0.0405*(1-tanh(0.5*log(z)));
+    //
+    float g_hat = (0.5f+_z)*logf(1.0f+_z) - (1.0f+_z) + 0.5f*logf(2*M_PI) - logf(_z) +
+           v0*(1.0f - tanhf(v1*logf(_z)));
+
+    return g_hat;
+}
+
+// gradient search curve-fit error
+float gserror(void * _userdata,
+              float * _v,
+              unsigned int _n)
+{
+    // compute error
+    float rmse = 0.0f;
+    unsigned int i;
+    for (i=0; i<41; i++) {
+        float g = sandbox_lngammaf(z[i], _v);
+        float e = g - lngamma_test[i];
+        rmse += e*e;
+    }
+    return sqrtf(rmse / 41.0f);
+}
+
+int main() {
+    unsigned int num_iterations = 1000; // number of iterations to run
+    float v[2] = {0.0405f, 0.5f};       // 
+
+    gradsearch gs = gradsearch_create(NULL,v,2,gserror,LIQUID_OPTIM_MINIMIZE);
+
+    // execute search one iteration at a time
+    unsigned int i;
+    float rmse;
+    for (i=0; i<num_iterations; i++) {
+        rmse = gserror(NULL,v,2);
+
+        gradsearch_step(gs);
+
+        if (((i+1)%100)==0)
+            gradsearch_print(gs);
+    }
+
+    gradsearch_destroy(gs);
+
+    // print results
+    for (i=0; i<41; i++)
+        printf(" z = %12.8f, g = %12.8f (%12.8f)\n", z[i], lngamma_test[i], sandbox_lngammaf(z[i], v));
+    printf("rmse = %12.4e;\n", rmse);
+
+    printf("v0 = %12.8f\n", v[0]);
+    printf("v1 = %12.8f\n", v[1]);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/matrix_eig_test.c b/sandbox/matrix_eig_test.c
new file mode 100644
index 0000000..082a82a
--- /dev/null
+++ b/sandbox/matrix_eig_test.c
@@ -0,0 +1,64 @@
+// 
+// matrix_eig_test.c
+//
+// Test eigenvalue computation using Q/R decomposition. Eigenvalues
+// will be on diagonal if they are all real.
+//
+
+#include <stdio.h>
+#include <string.h>
+#include "liquid.h"
+
+int main() {
+    unsigned int num_iterations=8;
+
+#if 0
+    unsigned int n=4;
+    float A[16] = {
+        1,2,3,4,
+        5,5,7,8,
+        6,4,8,7,
+        1,0,3,1};
+#else
+    unsigned int n=3;
+    float A[9] = {
+        1.0f, 1.0f, 1.0f,
+        1.0f, 2.0f, 1.0f,
+        1.0f, 1.0f, 2.0f};
+#endif
+
+    float eig[n];
+    float A0[n*n];
+    float Q[n*n], R[n*n];
+
+    memmove(A0, A, n*n*sizeof(float));
+
+    printf("\n");
+    printf("testing Q/R decomposition [Gram-Schmidt]\n");
+    matrixf_qrdecomp_gramschmidt(A,n,n,Q,R);
+    //matrixf_print(Q,n,n);
+    //matrixf_print(R,n,n);
+
+    unsigned int k;
+    for (k=0; k<num_iterations; k++) {
+        // compute Q/R decomposition
+        matrixf_qrdecomp_gramschmidt(A0,n,n,Q,R);
+
+        // compute A1 = R*Q
+        matrixf_mul(R,n,n, Q,n,n, A0,n,n);
+
+        matrixf_print(A0,n,n);
+    }
+
+    // extract eigen values
+    unsigned int i;
+    for (i=0; i<n; i++)
+        eig[i] = matrix_access(A0,n,n,i,i);
+
+    for (i=0; i<n; i++)
+        printf("eig[%3u] = %12.8f\n", i, eig[i]);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/matrix_test.c b/sandbox/matrix_test.c
new file mode 100644
index 0000000..5612c2f
--- /dev/null
+++ b/sandbox/matrix_test.c
@@ -0,0 +1,246 @@
+// 
+// matrix_test.c
+//
+// This example tests basic matrix operations.
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.h"
+
+int main() {
+
+    float x[6] = {
+        1, 2, 3,
+        4, 5, 6};
+
+    float y[9] = {
+        1, 2, 3,
+        4, 5, 6,
+        7, 8, 9};
+
+    float z[6];
+
+    // compute z = x * y
+    printf("z = x * y :\n");
+    matrixf_mul(x,2,3,y,3,3,z,2,3);
+    matrixf_print(z,2,3);
+
+    /*
+    // compute z = y * x'
+    matrixf_transpose(x);
+    printf("x' : \n");
+    matrixf_print(x);
+    matrixf_transpose(z);
+    matrixf_multiply(y,x,z);
+    printf("z = y * x' :\n");
+    matrixf_print(z);
+
+    matrixf_destroy(x);
+    matrixf_destroy(y);
+    matrixf_destroy(z);
+    */
+
+    float s[16] = {
+        1,2,3,4,
+        5,5,7,8,
+        6,4,8,7,
+        1,0,3,1};
+    float s_inv[16];
+    memmove(s_inv,s,16*sizeof(float));
+    matrixf_inv(s_inv,4,4);
+
+    float i4[16];
+    matrixf_mul(s,4,4,s_inv,4,4,i4,4,4);
+
+    printf("\ns:\n");
+    matrixf_print(s,4,4);
+    printf("\ninv(s):\n");
+    matrixf_print(s_inv,4,4);
+    printf("\ns*inv(s):\n");
+    matrixf_print(i4,4,4);
+
+    printf("\n");
+    float det = matrixf_det(s,4,4);
+    printf("det(s) = %12.8f\n", det);
+
+#if 0
+    // pivot test (matrix inversion)
+    float t[32] = {
+        1,2,3,4,  1,0,0,0,
+        5,5,7,8,  0,1,0,0,
+        6,4,8,7,  0,0,1,0,
+        1,0,3,1,  0,0,0,1};
+
+    unsigned int i;
+    for (i=0; i<4; i++) {
+        matrixf_pivot(t,4,8,i,i);
+        matrixf_print(t,4,8);
+    }
+
+    unsigned int j;
+    for (i=0; i<4; i++) {
+        float v = matrix_access(t,4,8,i,i);
+        for (j=0; j<8; j++)
+            matrix_access(t,4,8,i,j) /= v;
+    }
+    matrixf_print(t,4,8);
+#endif
+
+    printf("\n");
+    printf("testing L/U decomposition [Crout's method]\n");
+    float L[16], U[16], P[16];
+    matrixf_ludecomp_crout(s,4,4,L,U,P);
+    matrixf_print(L,4,4);
+    matrixf_print(U,4,4);
+
+    printf("\n");
+    printf("testing L/U decomposition [Doolittle's method]\n");
+    matrixf_ludecomp_doolittle(s,4,4,L,U,P);
+    matrixf_print(L,4,4);
+    matrixf_print(U,4,4);
+
+    printf("\n\n");
+    float X[16] = {
+       0.84382,  -2.38304,   1.43061,  -1.66604,
+       3.99475,   0.88066,   4.69373,   0.44563,
+       7.28072,  -2.06608,   0.67074,   9.80657,
+       6.07741,  -3.93099,   1.22826,  -0.42142};
+    float Y[16];
+    printf("\nX:\n");
+    matrixf_print(X,4,4);
+
+    // swaprows
+    memmove(Y,X,16*sizeof(float));
+    matrixf_swaprows(Y,4,4,0,2);
+    printf("\nmatrixf_swaprows(X,4,4,0,2):\n");
+    matrixf_print(Y,4,4);
+
+    // pivot test
+    memmove(Y,X,16*sizeof(float));
+    matrixf_pivot(Y,4,4,1,2);
+    printf("\nmatrixf_pivot(X,4,4,1,2):\n");
+    matrixf_print(Y,4,4);
+
+    // inverse test
+    memmove(Y,X,16*sizeof(float));
+    matrixf_inv(Y,4,4);
+    printf("\nmatrixf_inv(X,4,4):\n");
+    matrixf_print(Y,4,4);
+
+    // determinant test
+    float D = matrixf_det(X,4,4);
+    printf("\nmatrixf_det(X,4) = %12.8f\n", D);
+
+    // L/U decomp (Crout's method)
+    matrixf_ludecomp_crout(X,4,4,L,U,P);
+    printf("\nmatrixf_ludecomp_crout(X,4,4,L,U,P)\n");
+    printf("L:\n");
+    matrixf_print(L,4,4);
+    printf("U:\n");
+    matrixf_print(U,4,4);
+
+    // L/U decomp (Doolittle's method)
+    matrixf_ludecomp_doolittle(X,4,4,L,U,P);
+    printf("\nmatrixf_ludecomp_doolittle(X,4,4,L,U,P)\n");
+    printf("L:\n");
+    matrixf_print(L,4,4);
+    printf("U:\n");
+    matrixf_print(U,4,4);
+
+    printf("\n");
+    printf("testing Q/R decomposition [Gram-Schmidt]\n");
+    float Q[16], R[16];
+    matrixf_qrdecomp_gramschmidt(X,4,4,Q,R);
+    matrixf_print(Q,4,4);
+    matrixf_print(R,4,4);
+
+    /*
+    float b[4] = {
+       0.91489,
+       0.71789,
+       1.06553,
+      -0.81707};
+    */
+
+    float Xb[20] = {
+       0.84382,  -2.38304,   1.43061,  -1.66604,   0.91489,
+       3.99475,   0.88066,   4.69373,   0.44563,   0.71789,
+       7.28072,  -2.06608,   0.67074,   9.80657,   1.06553,
+       6.07741,  -3.93099,   1.22826,  -0.42142,  -0.81707};
+    printf("\n[X b] =\n");
+    matrixf_print(Xb,4,5);
+
+    matrixf_gjelim(Xb,4,5);
+    printf("\nmatrixf_gjelim(Xb,4,5)\n");
+    matrixf_print(Xb,4,5);
+
+    // compute a*a'
+    float a[20] = {
+      -0.24655,  -1.78843,   0.39477,   0.43735,  -1.08998,
+      -0.42751,   0.62496,   1.43802,   0.19814,   0.78155,
+      -0.35658,  -0.81875,  -1.09984,   1.87006,  -0.94191,
+       0.39553,  -2.02036,   1.17393,   1.54591,   1.29663};
+
+    printf("\na =\n");
+    matrixf_print(a,4,5);
+
+    printf("\n\n");
+    printf("computing a*a'\n");
+    float aaT[16];
+    matrixf_mul_transpose(a,4,5,aaT);
+    matrixf_print(aaT,4,4);
+
+    printf("\n\n");
+    printf("computing a'*a\n");
+    float aTa[25];
+    matrixf_transpose_mul(a,4,5,aTa);
+    matrixf_print(aTa,5,5);
+
+    printf("\n");
+    printf("testing Gram-Schmidt\n");
+    float Xgs[12] = {
+        1., 2., 1.,
+        0., 2., 0.,
+        2., 3., 1.,
+        1., 1., 0.};
+    float Ugs[12];
+    float Ugs_test[12] = {
+        sqrtf(6.)/6.,   sqrtf(2.)/6.,   2./3.,
+        0.,             2.*sqrtf(2.)/3.,-1./3.,
+        sqrtf(6.)/3.,   0.,             0.,
+        sqrtf(6.)/6.,   -sqrtf(2.)/6.,  -2./3.};
+    matrixf_gramschmidt(Xgs,4,3,Ugs);
+
+    printf("X:\n");
+    matrixf_print(Xgs,4,3);
+    printf("normalized X:\n");
+    matrixf_print(Ugs,4,3);
+    printf("expected:\n");
+    matrixf_print(Ugs_test,4,3);
+
+
+    // 
+    // test Cholesky decomposition
+    //
+
+    printf("\n");
+    printf("testing Cholesky decomposition\n");
+    // generate  input matrix
+    float complex Lp[9] = { 1.0,                   0.0,                   0.0,
+                           -3.1 + 0.2*_Complex_I,  0.3,                   0.0,
+                            1.7 + 0.5*_Complex_I, -0.6 - 0.3*_Complex_I,  2.9};
+    float complex Ap[9];
+    matrixcf_mul_transpose(Lp, 3, 3, Ap);
+    float complex Lc[9];
+    matrixcf_chol(Ap, 3, Lc);
+
+    printf("Lp:\n"); matrixcf_print(Lp, 3, 3);
+    printf("Ap:\n"); matrixcf_print(Ap, 3, 3);
+    printf("Lc:\n"); matrixcf_print(Lc, 3, 3);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/mdct_test.c b/sandbox/mdct_test.c
new file mode 100644
index 0000000..18b8f60
--- /dev/null
+++ b/sandbox/mdct_test.c
@@ -0,0 +1,146 @@
+//
+// mdct_example.c
+//
+// Modified discrete cosine transform (MDCT) example. Demonstrates
+// the functionality and interface for the mdct and imdct routines.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "mdct_example.m"
+
+int main() {
+    // MDCT options
+    unsigned int num_channels=64; // MDCT size
+    unsigned int num_symbols=16;
+
+    // filter options
+    unsigned int h_len=21;  // filter length
+    float fc=0.01f;         // filter cutoff
+    float As=60.0f;         // stop-band attenuation [dB]
+    float mu=0.0f;          // timing offset
+
+    // derived values
+    unsigned int i,j;
+    unsigned int num_samples = num_channels*num_symbols;
+    float x[num_samples];
+    float X[num_samples];
+    float y[num_samples];
+    for (i=0; i<num_samples; i++) {
+        x[i] = 0.0f;
+        X[i] = 0.0f;
+        y[i] = 0.0f;
+    }
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"num_channels = %u;\n", num_channels);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = num_channels*num_symbols;\n");
+
+    // generate window
+    float w[2*num_channels];
+    for (i=0; i<2*num_channels; i++) {
+#if 0
+        // raised half sine
+        w[i] = sinf(M_PI/(2.0f*num_channels)*(i+0.5f));
+#elif 0
+        // shaped pulse
+        float t0 = sinf(M_PI/(2*num_channels)*(i+0.5));
+        w[i] = sinf(M_PI*0.5f*t0*t0);
+#else
+        w[i] = liquid_kbd(i,2*num_channels,10.0f);
+#endif
+    }
+
+    // generate basic filter
+    float h[h_len];
+    liquid_firdes_kaiser(h_len,fc,As,mu,h);
+    firfilt_rrrf f = firfilt_rrrf_create(h,h_len);
+
+    // generate random signal (filtered noise)
+    for (i=0; i<num_samples; i++) {
+        // generate filtered noise
+        firfilt_rrrf_push(f, randnf());
+        firfilt_rrrf_execute(f, &x[i]); 
+
+        fprintf(fid,"x(%4u) = %12.4e;", i+1, x[i]);
+    }
+    firfilt_rrrf_destroy(f);
+
+    // run analyzer, accounting for input overlap
+    float buffer[2*num_channels];
+    memset(buffer, 0x00, 2*num_channels*sizeof(float));
+    for (i=0; i<num_symbols; i++) {
+        // copy last half of buffer to first half
+        memmove(buffer, &buffer[num_channels], num_channels*sizeof(float));
+
+        // copy input block to last half of buffer
+        memmove(&buffer[num_channels], &x[i*num_channels], num_channels*sizeof(float));
+
+        // run transform
+        mdct(buffer, &X[i*num_channels], w, num_channels);
+
+        //mdct(&x[i*num_channels],&X[i*num_channels],w,num_channels);
+    }
+
+    // run synthesizer, accounting for output overlap
+    for (i=0; i<num_symbols; i++) {
+        // run inverse MDCT
+        imdct(&X[i*num_channels],buffer,w,num_channels);
+
+        // accumulate first half of buffer to output
+        for (j=0; j<num_channels; j++)
+            y[i*num_channels+j] += buffer[j];
+
+        // copy last half of buffer to output (only if we aren't at the last symbol)
+        if (i==num_symbols-1) break;
+        memmove(&y[(i+1)*num_channels], &buffer[num_channels], num_channels*sizeof(float));
+    }
+
+    // print results to file
+    fprintf(fid,"w = zeros(1,2*num_channels);\n");
+    for (i=0; i<2*num_channels; i++)
+        fprintf(fid,"w(%3u) = %12.4e;\n",i+1,w[i]);
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%3u) = %12.4e;\n", i+1, x[i]);
+        fprintf(fid,"y(%3u) = %12.4e;\n", i+1, y[i]);
+    }
+    fprintf(fid,"X = zeros(num_symbols,num_channels);\n");
+    for (i=0; i<num_symbols; i++) {
+        for (j=0; j<num_channels; j++)
+            fprintf(fid,"X(%3u,%3u) = %12.4e;\n", i+1, j+1, X[i*num_channels+j]);
+    }
+
+    // plot spectral response
+    /*
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"f = [0:(num_channels-1)]/(2*num_channels);\n");
+    fprintf(fid,"plot(f,20*log10(abs(X')),'Color',[1 1 1]*0.5);\n");
+    */
+
+    // plot time-domain reconstruction
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,x,t-num_channels,y);\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('signal output');\n");
+    fprintf(fid,"legend('original','reconstructed',0);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/minsearch2_test.c b/sandbox/minsearch2_test.c
new file mode 100644
index 0000000..61314cd
--- /dev/null
+++ b/sandbox/minsearch2_test.c
@@ -0,0 +1,95 @@
+// 
+// minsearch2.c
+//
+// search to find minimum of a function using parabolic
+// interpolation using mid-point between x0 and x2
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+
+double function(double _x) {
+#if 0
+    // -sin(x) minimum at x = pi/2
+    double y = 1.0f-sin(_x);
+#else
+    // exp(-(x-pi/2)^2)
+    double t = _x - M_PI/2;
+    double y = 1-exp(-t*t);
+#endif
+    return y;
+}
+
+int main() {
+    // options
+    unsigned int num_iterations = 10;
+    double x0;          // lower value
+    double x1 = 1.2f;   // middle value (initial guess)
+    double x2;          // upper value
+    double dx = 2.0f;   // step size
+    double gamma = 0.7f;// step size reduction
+    double tol = 1e-6f; // error tolerance
+
+    // choose x0, x2
+    x0 = x1 - dx;
+    x2 = x1 + dx;
+
+    //
+    double y0, y1, y2;
+
+    double x_hat;
+    double del = 0.0f;
+    unsigned int i;
+    for (i=0; i<num_iterations; i++) {
+        // choose center point between [x0,x2]
+        x1 = 0.5f*(x0 + x2);
+
+        // compute functions
+        y0 = function(x0);
+        y1 = function(x1);
+        y2 = function(x2);
+
+#if 0
+        printf("%4u : (%8.3f,%8.3f,%8.3f) (%8.3f,%8.3f,%8.3f)\n",
+                i,
+                x0, x1, x2,
+                y0, y1, y2);
+#endif
+
+        // ensure values are reasonable
+        assert(x2 > x1);
+        assert(x1 > x0);
+
+        // compute minimum
+        // TODO : exploit uniform spacing...
+        double t0 = y0*(x1*x1 - x2*x2) +
+                    y1*(x2*x2 - x0*x0) +
+                    y2*(x0*x0 - x1*x1);
+
+        double t1 = y0*(x1 - x2) +
+                    y1*(x2 - x0) +
+                    y2*(x0 - x1);
+
+        x_hat = 0.5f * t0 / t1;
+        del = x_hat - x1;
+
+        // print
+        printf("%4u : %12.8f, e=%12.4e, del: %12.4e\n", i, x_hat, x_hat-M_PI/2, del);
+
+        // reduce step size
+        dx *= gamma;
+
+        // 
+        x0 = x_hat - dx;
+        x2 = x_hat + dx;
+
+        if (fabs(del) < tol)
+            break;
+    }
+
+    printf("estimated minimum : %f (%f)\n", x_hat, M_PI/2);
+
+    return 0;
+}
diff --git a/sandbox/minsearch_test.c b/sandbox/minsearch_test.c
new file mode 100644
index 0000000..8f56043
--- /dev/null
+++ b/sandbox/minsearch_test.c
@@ -0,0 +1,89 @@
+// 
+// minsearch.c
+//
+// search to find minimum of a function using parabolic
+// interpolation
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+
+double function(double _x) {
+#if 0
+    // -sin(x) minimum at x = pi/2
+    double y = 1.0f-sin(_x);
+#else
+    // exp(-(x-pi/2)^2)
+    double t = _x - M_PI/2;
+    double y = -exp(-t*t);
+#endif
+    return y;
+}
+
+int main() {
+    // options
+    unsigned int num_iterations = 10;
+    double x0 = 0.3f;    // lower value
+    double x1;           // middle value (ignored)
+    double x2 = 3.1f;    // upper value
+    double tol = 1e-6f;  // error tolerance
+
+    //
+    double y0 = function(x0);
+    double y1;
+    double y2 = function(x2);
+
+    double x_hat;
+    double del = 0.0f;
+    unsigned int i;
+    for (i=0; i<num_iterations; i++) {
+        // choose center point between [x0,x2]
+        x1 = 0.5f*(x0 + x2);
+        y1 = function(0.5f*(x0+x2));
+
+#if 0
+        printf("%4u : (%8.3f,%8.3f,%8.3f) (%8.3f,%8.3f,%8.3f)\n",
+                i,
+                x0, x1, x2,
+                y0, y1, y2);
+#endif
+
+        // ensure values are reasonable
+        assert(x2 > x1);
+        assert(x1 > x0);
+
+        // compute minimum
+        double t0 = y0*(x1*x1 - x2*x2) +
+                    y1*(x2*x2 - x0*x0) +
+                    y2*(x0*x0 - x1*x1);
+
+        double t1 = y0*(x1 - x2) +
+                    y1*(x2 - x0) +
+                    y2*(x0 - x1);
+
+        x_hat = 0.5f * t0 / t1;
+        del = x_hat - x1;
+
+        // print
+        printf("%4u : %12.8f, e=%12.4e\n", i, x_hat, x_hat-M_PI/2);
+
+        if (x_hat > x1) {
+            // new minimum
+            x0 = x1;
+            y0 = y1;
+        } else {
+            // new maximum
+            x2 = x1;
+            y2 = y1;
+        }
+
+        if (fabs(del) < tol)
+            break;
+    }
+
+    printf("estimated minimum : %f (%f)\n", x_hat, M_PI/2);
+
+    return 0;
+}
diff --git a/sandbox/modem_demodulate_arb_gentab.c b/sandbox/modem_demodulate_arb_gentab.c
new file mode 100644
index 0000000..03ea8d0
--- /dev/null
+++ b/sandbox/modem_demodulate_arb_gentab.c
@@ -0,0 +1,320 @@
+// 
+// modem_demodulate_arb_gentab.c
+//
+// Generate fast demodulation tables for arbitrary modems; use existing
+// modulation scheme with fast decoding (e.g. 16-QAM) and associate
+// these constellation points with the arbitrary constellation points.
+// The goal is to allow the demodulator to first demodulate the
+// received point using the fast demodulator and then perform a search
+// over only the set of nearby points. This script computes the look-up
+// table for these nearby points and stores them as an array of
+// indices.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_demodulate_arb_gentab.m"
+#define DEBUG 0
+
+// print usage/help message
+void usage()
+{
+    printf("sandbox/modem_demodulate_arb_gentab [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  m     : input modulation scheme (arb64vt default)\n");
+    printf("  r     : reference modulation scheme (qam16 default)\n");
+    liquid_print_modulation_schemes();
+    printf("  a     : alpha (reference scheme gain), default: 1.0\n");
+}
+
+// search for nearest constellation points to reference points
+void modem_arbref_search(float complex * _c,
+                         unsigned int _M,
+                         float complex * _cref,
+                         unsigned int _p,
+                         unsigned char * _index,
+                         unsigned int _s);
+
+// search for nearest constellation points to single reference point
+void modem_arbref_search_point(float complex * _c,
+                               unsigned int _M,
+                               float complex _cref,
+                               unsigned char * _index,
+                               unsigned int _s);
+
+// find unassigned constellation points
+unsigned int modem_arbref_search_unassigned(unsigned char * _index,
+                                            unsigned int _M,
+                                            unsigned int _p,
+                                            unsigned int _s,
+                                            unsigned char * _assigned);
+
+int main(int argc, char*argv[])
+{
+    // create mod/demod objects
+    modulation_scheme ms = LIQUID_MODEM_ARB64VT;
+    modulation_scheme mref = LIQUID_MODEM_QAM64;
+    float alpha = 1.0f;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhp:m:r:a:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h': usage(); return 0;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        case 'r':
+            mref = liquid_getopt_str2mod(optarg);
+            if (mref == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported modulation scheme '%s'\n", argv[0], optarg);
+                return 1;
+            }
+            break;
+        case 'a': alpha = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+
+    unsigned int i;
+    unsigned int j;
+
+    // initialize reference points
+    modem qref = modem_create(mref);
+    unsigned int kref = modem_get_bps(qref);
+    unsigned int p = 1 << kref;
+    float complex cref[p];
+    for (i=0; i<p; i++) {
+        modem_modulate(qref, i, &cref[i]);
+        cref[i] *= alpha;
+    }
+    modem_destroy(qref);
+
+    // generate the constellation
+    modem q = modem_create(ms);
+    unsigned int bps = modem_get_bps(q);
+    unsigned int M = 1 << bps;
+    float complex constellation[M];
+    for (i=0; i<M; i++)
+        modem_modulate(q, i, &constellation[i]);
+    modem_destroy(q);
+
+    // perform search
+    unsigned char * link = NULL;
+    unsigned int num_unassigned=M;
+    unsigned char unassigned[M];
+    unsigned int s=0;  // number of points per reference
+
+    // run search until all points are found
+    do {
+        // increment number of points per reference
+        s++;
+
+        // reallocte memory for links
+        link = (unsigned char*) realloc(link, p*s);
+
+        // search for nearest constellation points to reference points
+        modem_arbref_search(constellation, M, cref, p, link, s);
+
+        // find unassigned constellation points
+        num_unassigned = modem_arbref_search_unassigned(link,M,p,s,unassigned);
+        printf("%3u : number of unassigned points: %3u / %3u\n", s, num_unassigned, M);
+    } while (num_unassigned > 0);
+
+    // print table
+    printf("\n");
+    printf("unsigned char modem_demodulate_gentab[%u][%u] = {\n", p, s);
+    for (i=0; i<p; i++) {
+        printf("    {");
+        for (j=0; j<s; j++) {
+            printf("%3u%s", link[i*s+j], j==(s-1) ? "" : ",");
+        }
+        printf("}%s", i==(p-1) ? "};\n" : ",\n");
+    }
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"bps = %u;\n", bps);
+    fprintf(fid,"M = %u;\n", M);
+    fprintf(fid,"p = %u;\n", p);
+    fprintf(fid,"s = %u;\n", s);
+
+    // save constellation points
+    for (i=0; i<M; i++) {
+        fprintf(fid,"c(%3u) = %12.4e + j*%12.4e;\n", i+1,
+                                                     crealf(constellation[i]),
+                                                     cimagf(constellation[i]));
+    }
+
+    // save reference points
+    for (i=0; i<p; i++)
+        fprintf(fid,"cref(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(cref[i]), cimagf(cref[i]));
+
+    // save reference matrix
+    fprintf(fid,"link = zeros(p,s);\n");
+    for (i=0; i<p; i++) {
+        for (j=0; j<s; j++) {
+            fprintf(fid,"link(%3u,%3u) = %u;\n", i+1, j+1, link[i*s+j]+1);
+        }
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(c),    imag(c),   'bx',\n");
+    fprintf(fid,"     real(cref), imag(cref),'or');\n");
+
+    // draw lines between reference points and associated constellation points
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"  for i=1:p,\n");
+    fprintf(fid,"    for j=1:s,\n");
+    fprintf(fid,"      plot([real(cref(i)) real(c(link(i,j)))], [imag(cref(i)) imag(c(link(i,j)))], '-', 'Color', 0.8*[1 1 1]);\n");
+    fprintf(fid,"    end;\n");
+    fprintf(fid,"  end;\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+    fprintf(fid,"%%legend('constellation','reference','links',0);\n");
+    fprintf(fid,"title(['Arbitrary ' num2str(M) '-QAM']);\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.9);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fclose(fid);
+
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+    printf("done.\n");
+
+    // free allocated memory
+    free(link);
+
+    return 0;
+}
+
+// search for nearest constellation points to reference points
+//  _c      :   input constellation [size: _M x 1]
+//  _M      :   input constellation size
+//  _cref   :   reference points [size: _p x 1]
+//  _p      :   reference points size
+//  _index  :   indices of nearest constellation points [size: _p x _s]
+//  _s      :   number of nearest constellation points
+void modem_arbref_search(float complex * _c,
+                         unsigned int _M,
+                         float complex * _cref,
+                         unsigned int _p,
+                         unsigned char * _index,
+                         unsigned int _s)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: modem_arbref_search(), input constellation size too small\n");
+        exit(1);
+    } else if (_s > _M) {
+        fprintf(stderr,"error: modem_arbref_search(), index size exceeds constellation size\n");
+        exit(1);
+    }
+
+    //
+    unsigned int i;
+    for (i=0; i<_p; i++)
+        modem_arbref_search_point(_c, _M, _cref[i], &_index[_s*i], _s);
+}
+
+// search for nearest constellation points to single reference point
+void modem_arbref_search_point(float complex * _c,
+                               unsigned int _M,
+                               float complex _cref,
+                               unsigned char * _index,
+                               unsigned int _s)
+{
+#if DEBUG
+    printf("searching neighbors to (%8.3f,%8.3f)\n", crealf(_cref), cimagf(_cref));
+#endif
+    // initialize array of selected element flags
+    unsigned char selected[_M];
+    memset(selected, 0x00, _M);
+
+    unsigned int i;
+    unsigned int n;
+    
+    for (n=0; n<_s; n++) {
+        int min_found = 0;
+        float d;
+        float dmin = 0.0f;
+        unsigned int index_min = 0;
+        for (i=0; i<_M; i++) {
+            // ignore constellation point if it has already been
+            // selected
+            if (selected[i]==1)
+                continue;
+
+            // compute distance
+            d = crealf( (_c[i]-_cref)*conjf(_c[i]-_cref) );
+            if ( d < dmin || !min_found ) {
+                dmin = d;
+                index_min = i;
+                min_found = 1;
+            }
+        }
+
+        // save minimum index
+        _index[n] = index_min;
+
+        // flag point as 'selected'
+        selected[index_min] = 1;
+
+#if DEBUG
+        printf("%6u (%8.3f,%8.3f)\n", index_min, crealf(_c[index_min]), cimagf(_c[index_min]));
+#endif
+    }
+}
+
+// find unassigned constellation points
+unsigned int modem_arbref_search_unassigned(unsigned char * _index,
+                                            unsigned int _M,
+                                            unsigned int _p,
+                                            unsigned int _s,
+                                            unsigned char * _assigned)
+{
+    unsigned int num_unassigned = 0;
+
+    //
+    unsigned int i;
+    for (i=0; i<_M; i++) {
+        // initialized 'assigned' flag to false
+        _assigned[i] = 0;
+
+        unsigned int j;
+        for (j=0; j<_p; j++) {
+
+            // see if this constellation point has been assigned
+            // to this reference
+            unsigned int k;
+            for (k=0; k<_s; k++) {
+                if (_index[j*_s+k] == i)
+                    _assigned[i] = 1;
+            }
+        }
+
+        if (!_assigned[i])
+            num_unassigned++;
+    }
+
+    return num_unassigned;
+}
+
diff --git a/sandbox/modem_demodulate_soft_gentab.c b/sandbox/modem_demodulate_soft_gentab.c
new file mode 100644
index 0000000..70f9b22
--- /dev/null
+++ b/sandbox/modem_demodulate_soft_gentab.c
@@ -0,0 +1,404 @@
+// 
+// modem_demodulate_soft_gentab.c
+//
+// Generates table of nearest symbols and tests soft demodulation
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_demodulate_soft_gentab.m"
+#define DEBUG           0
+
+// print a string of bits to the standard output
+void print_bitstring(unsigned int _x,
+                     unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+// soft demodulation
+//  _q          :   demodulator object
+//  _c          :   constellation table
+//  _cp         :   nearest neighbor table [size: _p x 1]
+//  _p          :   size of table
+//  _r          :   received sample
+//  _s          :   hard demodulator output
+//  _soft_bits  :   soft bit ouput (approximate log-likelihood ratio)
+void modem_demodulate_soft_tab(modem _q,
+                               float complex * _c,
+                               unsigned int * _cp,
+                               unsigned int _p,
+                               float complex _r,
+                               unsigned int * _s,
+                               float * _soft_bits);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    modulation_scheme ms = LIQUID_MODEM_QAM16;  // modulation scheme
+    unsigned int p = 4;                         // number of 'nearest symbols'
+    float complex e = 0.1f + _Complex_I*0.15f;  // error
+
+    // validate input
+    if (ms == LIQUID_MODEM_UNKNOWN || ms >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: %s, invalid modulation scheme\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    // derived values
+    unsigned int bps = modulation_types[ms].bps;
+    unsigned int M = 1 << bps;  // constellation size
+    // ensure number of nearest symbols is not too large
+    if (p > (M-1)) {
+        fprintf(stderr,"error: requesting too many neighbors\n");
+        exit(1);
+    }
+    float sig = 0.2f;           // noise standard deviation
+
+    // generate constellation
+    modem q = modem_create(ms);
+    float complex c[M];         // constellation
+    for (i=0; i<M; i++)
+        modem_modulate(q, i, &c[i]);
+    modem_destroy(q);
+
+    // 
+    // find nearest symbols
+    //
+    unsigned int cp[M*p];
+
+    // initialize table (with 'M' as invalid symbol)
+    for (i=0; i<M; i++) {
+        for (k=0; k<p; k++)
+            cp[i*p + k] = M;
+    }
+
+    int symbol_valid;
+    for (i=0; i<M; i++) {
+#if DEBUG
+        printf("\n******** %3u ********\n", i);
+#endif
+        for (k=0; k<p; k++) {
+#if DEBUG
+            printf("iteration %u\n", k);
+#endif
+            float dmin=1e9f;
+            for (j=0; j<M; j++) {
+                symbol_valid = 1;
+                // ignore this symbol
+                if (i==j) symbol_valid = 0;
+
+                // also ignore symbol if it is already in the index
+                unsigned int l;
+                for (l=0; l<p; l++) {
+                    if ( cp[i*p + l] == j )
+                        symbol_valid = 0;
+                }
+
+                // compute distance
+                float d = cabsf( c[i] - c[j] );
+#if DEBUG
+                printf("  testing %3u... ", j);
+                if (!symbol_valid)
+                    printf("invalid\n");
+                else
+                    printf(" d = %8.6f ", d);
+#endif
+
+                if ( d < dmin && symbol_valid ) {
+                    dmin = d;
+                    cp[i*p + k] = j;
+#if DEBUG
+                    printf("*\n");
+                } else {
+                    if (symbol_valid)
+                        printf("\n");
+#endif
+                }
+
+                // ...
+                //cp[i*p + k] = rand() % (M+4);
+            }
+#if DEBUG
+            printf("new symbol : %3u, dmin=%8.6f\n", cp[i*p + k], dmin);
+#endif
+        }
+    }
+
+    // 
+    // print results
+    //
+    for (i=0; i<M; i++) {
+        printf(" %4u [", i);
+        print_bitstring(i,bps);
+        printf("] : ");
+        for (k=0; k<p; k++) {
+            printf(" ");
+            print_bitstring(cp[i*p + k],bps);
+            if (cp[i*p + k] < M)
+                printf("(%6.4f)", cabsf( c[i]-c[cp[i*p+k]] ));
+            else
+                printf("        ");
+        }
+        printf("\n");
+    }
+
+    // print c-type array
+    printf("\n");
+    printf("// %s soft demodulation nearest neighbors (p=%u)\n", modulation_types[ms].name, p);
+    printf("const unsigned char %s_demod_soft_neighbors[%u] = {\n", modulation_types[ms].name, p*M);
+    printf("    ");
+    for (i=0; i<M; i++) {
+        for (k=0; k<p; k++) {
+            printf("%4u", cp[i*p+k]);
+            if (k != p-1) printf(",");
+        }
+        if (i != M-1) {
+            printf(",   // ");
+            print_bitstring(i,bps);
+            printf("\n    ");
+        } else {
+            printf("};  // ");
+            print_bitstring(i,bps);
+            printf("\n\n");
+        }
+    }
+
+    // select input symbol and compute received symbol
+    unsigned int sym_in = rand() % M;
+    if (ms == LIQUID_MODEM_QAM16)
+        sym_in = 13;
+    float complex r = c[sym_in] + e;
+
+    // run soft demodulation for each bit
+    float soft_bits[bps];
+    for (k=0; k<bps; k++) {
+#if DEBUG
+        printf("\n");
+        printf("********* bit index %u ************\n", k);
+#endif
+        // reset soft bit value
+        soft_bits[k] = 0.0f;
+        float bit_0 = 0.0f;
+        float bit_1 = 0.0f;
+
+        // compute LLR for this bit
+        for (i=0; i<M; i++) {
+            // compute distance between received point and symbol
+            float d = crealf( (r-c[i])*conjf(r-c[i]) );
+            float t = expf( -d / (2.0f*sig*sig) );
+
+            // check if this symbol has a '0' or '1' at this bit index
+            unsigned int bit = (i >> (bps-k-1)) & 0x01;
+            //printf("%c", bit ? '1' : '0');
+
+            if (bit) bit_1 += t;
+            else     bit_0 += t;
+
+#if DEBUG
+            printf("  symbol : ");
+            print_bitstring(i,bps);
+            printf(" [%c]", bit ? '1' : '0');
+            printf(" { %7.4f %7.4f}, d=%7.4f, t=%12.8f\n", crealf(c[i]), cimagf(c[i]), d, t);
+#endif
+        }
+
+        soft_bits[k] = logf(bit_1) - logf(bit_0);
+#if DEBUG
+        printf(" {0 : %12.8f, 1 : %12.8f}\n", bit_0, bit_1);
+#endif
+    }
+
+    // 
+    // demodulate using internal method
+    //
+    q = modem_create(ms);
+    unsigned int sym_out_tab;
+    float soft_bits_tab[bps];
+    modem_demodulate_soft_tab(q,c,cp,p,r,&sym_out_tab,soft_bits_tab);
+    modem_destroy(q);
+
+    // print results
+    printf("\n");
+    printf("  input symbol : ");
+    print_bitstring(sym_in,bps);
+    printf(" {%12.8f, %12.8f}\n", crealf(c[sym_in]), cimagf(c[sym_in]));
+
+    printf("  soft bits :\n");
+    printf("   /----------- LLR -----------\\       /------- min dist --------\\\n");
+    for (k=0; k<bps; k++) {
+        printf("    %1u : ", (sym_in >> (bps-k-1)) & 0x01);
+        printf("%12.8f > ", soft_bits[k]);
+        int soft_bit = (soft_bits[k]*16 + 127);
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit =   0;
+        printf("%5d > %1u", soft_bit, soft_bit & 0x80 ? 1 : 0);
+
+        //
+        // print tabular method
+        //
+        printf("        ");
+        printf("%12.8f > ", soft_bits_tab[k]);
+        soft_bit = (soft_bits_tab[k]*16 + 127);
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit =   0;
+        printf("%5d > %1u", soft_bit, soft_bit & 0x80 ? 1 : 0);
+
+        printf("\n");
+    }
+
+    // 
+    // export results to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"m = %u;\n", bps);
+    fprintf(fid,"M = %u;\n", 1<<bps);
+    fprintf(fid,"c = zeros(1,M);\n");
+    fprintf(fid,"i_str = cell(1,M);\n");
+
+    for (i=0; i<M; i++) {
+        // write symbol to output file
+        fprintf(fid,"c(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(c[i]), cimagf(c[i]));
+        fprintf(fid,"i_str{%3u} = '", i+1);
+        for (j=0; j<bps; j++)
+            fprintf(fid,"%c", (i >> (bps-j-1)) & 0x01 ? '1' : '0');
+        fprintf(fid,"';\n");
+    }
+    fprintf(fid,"x = %12.8f + j*%12.8f;\n", crealf(c[sym_in]), cimagf(c[sym_in]));
+    fprintf(fid,"r = %12.8f + j*%12.8f;\n", crealf(r), cimagf(r));
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(c,'o','MarkerSize',4,r,'rx',[x r]);\n");
+    fprintf(fid,"hold on;\n");
+    // print lines to 'nearest neighbor' points
+    fprintf(fid,"plot(");
+    for (i=0; i<p; i++) {
+        float complex x_hat = c[ cp[sym_out_tab*p+i] ];
+        fprintf(fid,"[%12.8f %12.8f],[%12.8f %12.8f],'Color',[1 1 1]*0.8",
+                crealf(r), crealf(x_hat),
+                cimagf(r), cimagf(x_hat));
+        if (i == p-1) fprintf(fid,");\n");
+        else          fprintf(fid,",...\n     ");
+    }
+    fprintf(fid,"text(real(c)+0.02, imag(c)+0.02, i_str);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('in phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// soft demodulation
+//  _q          :   demodulator object
+//  _c          :   constellation table
+//  _cp         :   nearest neighbor table [size: M*_p x 1]
+//  _p          :   size of table
+//  _r          :   received sample
+//  _s          :   hard demodulator output
+//  _soft_bits  :   soft bit ouput (approximate log-likelihood ratio)
+void modem_demodulate_soft_tab(modem _q,
+                               float complex * _c,
+                               unsigned int * _cp,
+                               unsigned int _p,
+                               float complex _r,
+                               unsigned int * _s,
+                               float * _soft_bits)
+{
+#if DEBUG
+    printf("\nmodem_demodulate_soft_tab() invoked\n");
+#endif
+    // run hard demodulation
+    modem_demodulate(_q, _r, _s);
+#if DEBUG
+    printf("  hard demod    :   %3u\n", *_s);
+#endif
+
+    unsigned int bps = modem_get_bps(_q);
+
+    float sig = 0.2f;
+
+    unsigned int k;
+    unsigned int i;
+    unsigned int bit;
+    float d;
+    for (k=0; k<bps; k++) {
+        // initialize soft bit value
+        _soft_bits[k] = 0.0f;
+
+        // find nearest 0 and nearest 1
+        float dmin_0 = 1.0f;
+        float dmin_1 = 1.0f;
+
+        // check bit of hard demodulation
+        d = crealf( (_r-_c[*_s])*conjf(_r-_c[*_s]) );
+        bit = (*_s >> (bps-k-1)) & 0x01;
+        if (bit) dmin_1 = d;
+        else     dmin_0 = d;
+
+        // check symbols in table
+#if DEBUG
+        printf("  index %2u : ", k);
+#endif
+        for (i=0; i<_p; i++) {
+            bit = (_cp[(*_s)*_p+i] >> (bps-k-1)) & 0x01;
+
+#if DEBUG
+            print_bitstring(_cp[(*_s)*_p+i],bps);
+            printf("[%1u]", bit);
+#endif
+
+            // compute distance
+            float complex x_hat = _c[ _cp[(*_s)*_p + i] ];
+            d = crealf( (_r-x_hat)*conjf(_r-x_hat) );
+#if DEBUG
+            printf("(%8.6f) ", d);
+#endif
+            if (bit) {
+                if (d < dmin_1) dmin_1 = d;
+            } else {
+                if (d < dmin_0) dmin_0 = d;
+            }
+        }
+#if DEBUG
+        printf("\n");
+        printf("  dmin_0 : %12.8f\n", dmin_0);
+        printf("  dmin_1 : %12.8f\n", dmin_1);
+#endif
+
+        // make assignments
+#if 0
+        _soft_bits[k] =   logf(expf(-dmin_1/(2.0f*sig*sig)))
+                        - logf(expf(-dmin_0/(2.0f*sig*sig)));
+#else
+        _soft_bits[k] =   (-dmin_1/(2.0f*sig*sig))
+                        - (-dmin_0/(2.0f*sig*sig));
+#endif
+    }
+}
+
diff --git a/sandbox/modem_demodulate_soft_test.c b/sandbox/modem_demodulate_soft_test.c
new file mode 100644
index 0000000..178a365
--- /dev/null
+++ b/sandbox/modem_demodulate_soft_test.c
@@ -0,0 +1,139 @@
+// 
+// modem_demodulate_soft_test.c
+//
+// Tests soft demoulation using log-likelihood ratio
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "modem_demodulate_soft_test.m"
+
+int main() {
+    srand(time(NULL));
+
+    // options
+    modulation_scheme ms = LIQUID_MODEM_QAM16;  // modulation scheme
+    float complex e = 0.1f + _Complex_I*0.2f;   // error
+
+    unsigned int i;
+
+    // derived values
+    float sig = 0.2f;           // noise standard deviation
+
+    // generate constellation
+    modem q = modem_create(ms);
+    unsigned int bps = modem_get_bps(q);
+    unsigned int M = 1 << bps;  // constellation size
+    float complex c[M];         // constellation
+    for (i=0; i<M; i++)
+        modem_modulate(q, i, &c[i]);
+    modem_destroy(q);
+
+    // select input symbol and compute received symbol
+    unsigned int sym_in = rand() % M;
+    float complex r = c[sym_in] + e;
+
+    // run soft demodulation for each bit
+    float soft_bits[bps];
+    unsigned int k;
+    for (k=0; k<bps; k++) {
+        printf("\n");
+        printf("********* bit index %u ************\n", k);
+        // reset soft bit value
+        soft_bits[k] = 0.0f;
+        float bit_0 = 0.0f;
+        float bit_1 = 0.0f;
+
+        // compute LLR for this bit
+        for (i=0; i<M; i++) {
+            // compute distance between received point and symbol
+            float d = crealf( (r-c[i])*conjf(r-c[i]) );
+            float t = expf( -d / (2.0f*sig*sig) );
+
+            // check if this symbol has a '0' or '1' at this bit index
+            unsigned int bit = (i >> (bps-k-1)) & 0x01;
+            //printf("%c", bit ? '1' : '0');
+
+            if (bit) bit_1 += t;
+            else     bit_0 += t;
+
+            printf("  symbol : ");
+            unsigned int j;
+            for (j=0; j<bps; j++)
+                printf("%c", (i >> (bps-j-1)) & 0x01 ? '1' : '0');
+            printf(" [%c]", bit ? '1' : '0');
+            printf(" { %7.4f %7.4f}, d=%7.4f, t=%12.8f\n", crealf(c[i]), cimagf(c[i]), d, t);
+
+        }
+
+        soft_bits[k] = logf(bit_1) - logf(bit_0);
+        printf(" {0 : %12.8f, 1 : %12.8f}\n", bit_0, bit_1);
+    }
+
+    // print results
+    printf("\n");
+    printf("  input symbol : ");
+    for (k=0; k<bps; k++)
+        printf("%c", (sym_in >> (bps-k-1)) & 0x01 ? '1' : '0');
+    printf(" {%12.8f, %12.8f}\n", crealf(c[sym_in]), cimagf(c[sym_in]));
+
+    printf("  soft bits :\n");
+    for (k=0; k<bps; k++) {
+        printf("    %1u : ", (sym_in >> (bps-k-1)) & 0x01);
+        printf("%12.8f > ", soft_bits[k]);
+        int soft_bit = (soft_bits[k]*16 + 127);
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit =   0;
+        printf("%5d > %1u\n", soft_bit, soft_bit & 0x80 ? 1 : 0);
+    }
+
+    // 
+    // export results to file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"m = %u;\n", bps);
+    fprintf(fid,"M = %u;\n", 1<<bps);
+    fprintf(fid,"c = zeros(1,M);\n");
+    fprintf(fid,"i_str = cell(1,M);\n");
+
+    for (i=0; i<M; i++) {
+        // write symbol to output file
+        fprintf(fid,"c(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(c[i]), cimagf(c[i]));
+        fprintf(fid,"i_str{%3u} = '", i+1);
+        unsigned int j;
+        for (j=0; j<bps; j++)
+            fprintf(fid,"%c", (i >> (bps-j-1)) & 0x01 ? '1' : '0');
+        fprintf(fid,"';\n");
+    }
+    fprintf(fid,"x = %12.8f + j*%12.8f;\n", crealf(c[sym_in]), cimagf(c[sym_in]));
+    fprintf(fid,"r = %12.8f + j*%12.8f;\n", crealf(r), cimagf(r));
+
+    // plot results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(c,'o','MarkerSize',4,r,'rx',[x r]);\n");
+    fprintf(fid,"hold on;\n");
+    fprintf(fid,"text(real(c)+0.02, imag(c)+0.02, i_str);\n");
+    fprintf(fid,"hold off;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('in phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/mskmodem_test.c b/sandbox/mskmodem_test.c
new file mode 100644
index 0000000..63b4559
--- /dev/null
+++ b/sandbox/mskmodem_test.c
@@ -0,0 +1,423 @@
+// 
+// mskmodem_test.c
+//
+
+#if 0
+ NOTES
+
+  mod.    signal median(|z|)
+  index                             (GMSK)
+  (h)     (rcos-full)   (rcos-half) BT=0.35
+  0.1     0.84078       0.72392     0.66000
+  0.15    0.84900       0.73578     0.66909
+  0.2     0.86020       0.75219     0.68140
+  0.25    0.87410       0.77380     0.69923
+  0.3     0.89051       0.80083     0.72071
+  0.35    0.90936       0.83370     0.75068
+  0.4     0.93050       0.87366     0.78479
+  0.45    0.95379       0.92185     0.81376
+  0.5     0.97901       0.97473     0.85897
+  0.55    1.0055        1.0301      0.91067
+  0.6     1.0324        1.0915      0.96746
+  0.65    1.0582        1.1631      1.0117
+  0.7     1.0813        1.2295      1.0777
+  0.75    1.0998        1.3012      1.1524
+  0.8     1.1116        1.3691      1.2040
+  0.85    1.1146        1.4171      1.2628
+  0.9     1.1074        1.4333      1.3158
+ 
+ rcos-full: median(|z|) ~ 0.34146  * h^2  +   0.14371  * h  +  0.81937
+ rcos-half: median(|z|) ~ 1.143219 * h^2  +  -0.064886 * h  +  0.718770
+ GMSK:      median(|z|) ~ 0.967978 * h^2  +  -0.077964 * h  +  0.658288
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "mskmodem_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("mskmodem_test -- minimum-shift keying modem example\n");
+    printf("options:\n");
+    printf("  h     : print help\n");
+    printf("  t     : filter type: [square], rcos-full, rcos-half, gmsk\n");
+    printf("  k     : samples/symbol,           default:  8\n");
+    printf("  b     : bits/symbol,              default:  1\n");
+    printf("  H     : modulation index,         default:  0.5\n");
+    printf("  B     : filter roll-off,          default:  0.35\n");
+    printf("  n     : number of data symbols,   default: 20\n");
+    printf("  s     : SNR [dB],                 default: 80\n");
+    printf("  F     : carrier freq. offset,     default:  0.0\n");
+    printf("  P     : carrier phase offset,     default:  0.0\n");
+    printf("  T     : fractional symbol offset, default:  0.0\n");
+}
+
+int main(int argc, char*argv[]) {
+    // options
+    unsigned int k  = 8;        // filter samples/symbol
+    unsigned int bps= 1;        // number of bits/symbol
+    float h         = 0.5f;     // modulation index (h=1/2 for MSK)
+    unsigned int num_data_symbols = 20; // number of data symbols
+    float SNRdB     = 80.0f;    // signal-to-noise ratio [dB]
+    float cfo       = 0.0f;     // carrier frequency offset
+    float cpo       = 0.0f;     // carrier phase offset
+    float tau       = 0.0f;     // fractional symbol offset
+    enum {
+        TXFILT_SQUARE=0,
+        TXFILT_RCOS_FULL,
+        TXFILT_RCOS_HALF,
+        TXFILT_GMSK,
+    } tx_filter_type = TXFILT_SQUARE;
+    float gmsk_bt = 0.35f;              // GMSK bandwidth-time factor
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"ht:k:b:H:B:n:s:F:P:T:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();                         return 0;
+        case 't':
+            if (strcmp(optarg,"square")==0) {
+                tx_filter_type = TXFILT_SQUARE;
+            } else if (strcmp(optarg,"rcos-full")==0) {
+                tx_filter_type = TXFILT_RCOS_FULL;
+            } else if (strcmp(optarg,"rcos-half")==0) {
+                tx_filter_type = TXFILT_RCOS_HALF;
+            } else if (strcmp(optarg,"gmsk")==0) {
+                tx_filter_type = TXFILT_GMSK;
+            } else {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'k': k = atoi(optarg);                break;
+        case 'b': bps = atoi(optarg);              break;
+        case 'H': h = atof(optarg);                break;
+        case 'B': gmsk_bt = atof(optarg);          break;
+        case 'n': num_data_symbols = atoi(optarg); break;
+        case 's': SNRdB = atof(optarg);            break;
+        case 'F': cfo   = atof(optarg);            break;
+        case 'P': cpo   = atof(optarg);            break;
+        case 'T': tau   = atof(optarg);            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_symbols = num_data_symbols;
+    unsigned int num_samples = k*num_symbols;
+    unsigned int M = 1 << bps;              // constellation size
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    unsigned char sym_in[num_symbols];      // input symbols
+    float phi[num_samples];                 // transmitted phase
+    float complex x[num_samples];           // transmitted signal
+    float complex y[num_samples];           // received signal
+    float complex z[num_samples];           // output...
+    //unsigned char sym_out[num_symbols];     // output symbols
+
+    unsigned int ht_len = 0;
+    unsigned int tx_delay = 0;
+    float * ht = NULL;
+    switch (tx_filter_type) {
+    case TXFILT_SQUARE:
+        // regular MSK
+        ht_len = k;
+        tx_delay = 1;
+        ht = (float*) malloc(ht_len *sizeof(float));
+        for (i=0; i<ht_len; i++)
+            ht[i] = h * M_PI / (float)k;
+        break;
+    case TXFILT_RCOS_FULL:
+        // full-response raised-cosine pulse
+        ht_len = k;
+        tx_delay = 1;
+        ht = (float*) malloc(ht_len *sizeof(float));
+        for (i=0; i<ht_len; i++)
+            ht[i] = h * M_PI / (float)k * (1.0f - cosf(2.0f*M_PI*i/(float)ht_len));
+        break;
+    case TXFILT_RCOS_HALF:
+        // partial-response raised-cosine pulse
+        ht_len = 3*k;
+        tx_delay = 2;
+        ht = (float*) malloc(ht_len *sizeof(float));
+        for (i=0; i<ht_len; i++)
+            ht[i] = 0.0f;
+        for (i=0; i<2*k; i++)
+            ht[i+k/2] = h * 0.5f * M_PI / (float)k * (1.0f - cosf(2.0f*M_PI*i/(float)(2*k)));
+        break;
+    case TXFILT_GMSK:
+        ht_len = 2*k*3+1+k;
+        tx_delay = 4;
+        ht = (float*) malloc(ht_len *sizeof(float));
+        for (i=0; i<ht_len; i++)
+            ht[i] = 0.0f;
+        liquid_firdes_gmsktx(k,3,gmsk_bt,0.0f,&ht[k/2]);
+        for (i=0; i<ht_len; i++)
+            ht[i] *= h * 2.0f / (float)k;
+        break;
+    default:
+        fprintf(stderr,"error: %s, invalid tx filter type\n", argv[0]);
+        exit(1);
+    }
+    for (i=0; i<ht_len; i++)
+        printf("ht(%3u) = %12.8f;\n", i+1, ht[i]);
+    firinterp_rrrf interp_tx = firinterp_rrrf_create(k, ht, ht_len);
+
+    // generate symbols and interpolate
+    // phase-accumulating filter (trapezoidal integrator)
+    float b[2] = {0.5f,  0.5f};
+    if (tx_filter_type == TXFILT_SQUARE) {
+        // square filter: rectangular integration with one sample of delay
+        b[0] = 0.0f;
+        b[1] = 1.0f;
+    }
+    float a[2] = {1.0f, -1.0f};
+    iirfilt_rrrf integrator = iirfilt_rrrf_create(b,2,a,2);
+    float theta = 0.0f;
+    for (i=0; i<num_symbols; i++) {
+        sym_in[i] = rand() % M;
+        float v = 2.0f*sym_in[i] - (float)(M-1);    // +/-1, +/-3, ... +/-(M-1)
+        firinterp_rrrf_execute(interp_tx, v, &phi[k*i]);
+
+        // accumulate phase
+        unsigned int j;
+        for (j=0; j<k; j++) {
+            iirfilt_rrrf_execute(integrator, phi[i*k+j], &theta);
+            x[i*k+j] = cexpf(_Complex_I*theta);
+        }
+    }
+    iirfilt_rrrf_destroy(integrator);
+
+    // push through channel
+    for (i=0; i<num_samples; i++) {
+        // add carrier frequency/phase offset
+        y[i] = x[i]*cexpf(_Complex_I*(cfo*i + cpo));
+
+        // add noise
+        y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+    }
+    
+    // create decimator
+    unsigned int m = 3;
+    float bw = 0.0f;
+    float beta = 0.0f;
+    float scale = 1.0f;
+    firfilt_crcf decim_rx = NULL;
+    switch (tx_filter_type) {
+    case TXFILT_SQUARE:
+        //bw = 0.9f / (float)k;
+        bw = 0.4f;
+        decim_rx = firfilt_crcf_create_kaiser(2*k*m+1, bw, 60.0f, 0.0f);
+        scale = 2.0f * bw;
+        break;
+    case TXFILT_RCOS_FULL:
+        if (M==2) {
+            decim_rx = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k,m,0.5f,0);
+            scale = 1.33f / (float)k;
+        } else {
+            decim_rx = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k/2,2*m,0.9f,0);
+            scale = 3.25f / (float)k;
+        }
+        break;
+    case TXFILT_RCOS_HALF:
+        if (M==2) {
+            // rcos-half:               
+            decim_rx = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k,m,0.3f,0);
+            scale = 1.1f / (float)k;
+            //scale = 1.1f / (float)k * 1.0f / ( 1.24887*h*h  -0.16103*h +  0.73256 );
+        } else {
+            decim_rx = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k/2,2*m,0.27f,0);
+            scale = 2.90f / (float)k;
+        }
+        break;
+    case TXFILT_GMSK:
+        bw = 0.5f / (float)k;
+        // TODO: figure out beta value here
+        beta = (M == 2) ? 0.8*gmsk_bt : 1.0*gmsk_bt;
+        decim_rx = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,k,m,beta,0);
+        // compensate gain for modulation index
+        scale = 2.0f * bw / (0.967978*h*h + -0.077964*h + 0.658288);
+        break;
+    default:
+        fprintf(stderr,"error: %s, invalid tx filter type\n", argv[0]);
+        exit(1);
+    }
+    firfilt_crcf_set_scale(decim_rx, scale);
+    printf("bw = %f\n", bw);
+
+    // run receiver
+    unsigned int n=0;
+    unsigned int num_errors = 0;
+    unsigned int num_symbols_checked = 0;
+    float complex z_prime = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        // push through filter
+        firfilt_crcf_push(decim_rx, y[i]);
+        firfilt_crcf_execute(decim_rx, &z[i]);
+
+        // decimate output
+        if ( (i%k)==0 ) {
+            // compute instantaneous frequency scaled by modulation index
+            float phi_hat = cargf(conjf(z_prime) * z[i]) / (h * M_PI);
+
+            // estimate transmitted symbol
+            float v = (phi_hat + (M-1.0))*0.5f;
+            unsigned int sym_out = ((int) roundf(v)) % M;
+
+            // save current point
+            z_prime = z[i];
+
+            // print result to screen
+            printf("%3u : %12.8f + j%12.8f, <f=%8.4f : %8.4f> (%1u)",
+                    n, crealf(z[i]), cimagf(z[i]), phi_hat, v, sym_out);
+            if (n >= m+tx_delay) {
+                num_errors += (sym_out == sym_in[n-m-tx_delay]) ? 0 : 1;
+                num_symbols_checked++;
+                printf(" (%1u)\n", sym_in[n-m-tx_delay]);
+            } else {
+                printf("\n");
+            }
+            n++;
+        }
+    }
+
+    // print number of errors
+    printf("errors : %3u / %3u\n", num_errors, num_symbols_checked);
+
+    // destroy objects
+    firinterp_rrrf_destroy(interp_tx);
+    firfilt_crcf_destroy(decim_rx);
+
+    // compute power spectral density of transmitted signal
+    unsigned int nfft = 1024;
+    float psd[nfft];
+    spgramcf periodogram = spgramcf_create_kaiser(nfft, nfft/2, 8.0f);
+    spgramcf_estimate_psd(periodogram, y, num_samples, psd);
+    spgramcf_destroy(periodogram);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"h = %f;\n", h);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"nfft        = %u;\n", nfft);
+    fprintf(fid,"delay       = %u; %% receive filter delay\n", tx_delay);
+
+    fprintf(fid,"x   = zeros(1,num_samples);\n");
+    fprintf(fid,"y   = zeros(1,num_samples);\n");
+    fprintf(fid,"z   = zeros(1,num_samples);\n");
+    fprintf(fid,"phi = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        fprintf(fid,"y(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        fprintf(fid,"z(%4u) = %12.8f + j*%12.8f;\n", i+1, crealf(z[i]), cimagf(z[i]));
+        fprintf(fid,"phi(%4u) = %12.8f;\n", i+1, phi[i]);
+    }
+    // save PSD
+    fprintf(fid,"psd = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"psd(%4u) = %12.8f;\n", i+1, psd[i]);
+
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"i = 1:k:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(3,4,1:3);\n");
+    fprintf(fid,"  plot(t,real(x),'-', t(i),real(x(i)),'bs','MarkerSize',4,...\n");
+    fprintf(fid,"       t,imag(x),'-', t(i),imag(x(i)),'gs','MarkerSize',4);\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('x(t)');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,5:7);\n");
+    fprintf(fid,"  plot(t-delay,real(z),'-', t(i)-delay,real(z(i)),'bs','MarkerSize',4,...\n");
+    fprintf(fid,"       t-delay,imag(z),'-', t(i)-delay,imag(z(i)),'gs','MarkerSize',4);\n");
+    fprintf(fid,"  axis([0 num_symbols -1.2 1.2]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('\"matched\" filter output');\n");
+    fprintf(fid,"  grid on;\n");
+    // plot I/Q constellations
+    fprintf(fid,"subplot(3,4,4);\n");
+    fprintf(fid,"  plot(real(y),imag(y),'-',real(y(i)),imag(y(i)),'rs','MarkerSize',3);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,4,8);\n");
+    fprintf(fid,"  plot(real(z),imag(z),'-',real(z(i)),imag(z(i)),'rs','MarkerSize',3);\n");
+    fprintf(fid,"  xlabel('I');\n");
+    fprintf(fid,"  ylabel('Q');\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    // plot PSD
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"subplot(3,4,9:12);\n");
+    fprintf(fid,"  plot(f,psd,'LineWidth',1.5);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -40 20]);\n");
+    fprintf(fid,"  xlabel('Normalized Frequency [f/F_s]');\n");
+    fprintf(fid,"  ylabel('PSD [dB]');\n");
+    fprintf(fid,"  grid on;\n");
+
+#if 0
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"  %% compute instantaneous received frequency\n");
+    fprintf(fid,"  freq_rx = arg( conj(z(:)) .* circshift(z(:),-1) )';\n");
+    fprintf(fid,"  freq_rx(1:(k*delay)) = 0;\n");
+    fprintf(fid,"  freq_rx(end) = 0;\n");
+    fprintf(fid,"  %% compute instantaneous tx/rx phase\n");
+    if (tx_filter_type == TXFILT_SQUARE) {
+        fprintf(fid,"  theta_tx = filter([0 1],[1 -1],phi)/(h*pi);\n");
+        fprintf(fid,"  theta_rx = filter([0 1],[1 -1],freq_rx)/(h*pi);\n");
+    } else {
+        fprintf(fid,"  theta_tx = filter([0.5 0.5],[1 -1],phi)/(h*pi);\n");
+        fprintf(fid,"  theta_rx = filter([0.5 0.5],[1 -1],freq_rx)/(h*pi);\n");
+    }
+    fprintf(fid,"  %% plot instantaneous tx/rx phase\n");
+    fprintf(fid,"  plot(t,      theta_tx,'-b', t(i),      theta_tx(i),'sb',...\n");
+    fprintf(fid,"       t-delay,theta_rx,'-r', t(i)-delay,theta_rx(i),'sr');\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('instantaneous phase/(h \\pi)');\n");
+    fprintf(fid,"  legend('transmitted','syms','received/filtered','syms','location','northwest');\n");
+    fprintf(fid,"  grid on;\n");
+#else
+    // plot filter response
+    fprintf(fid,"ht_len = %u;\n", ht_len);
+    fprintf(fid,"ht     = zeros(1,ht_len);\n");
+    for (i=0; i<ht_len; i++)
+        fprintf(fid,"ht(%4u) = %12.8f;\n", i+1, ht[i]);
+    fprintf(fid,"gt1 = filter([0.5 0.5],[1 -1],ht) / (pi*h);\n");
+    fprintf(fid,"gt2 = filter([0.0 1.0],[1 -1],ht) / (pi*h);\n");
+    fprintf(fid,"tfilt = [0:(ht_len-1)]/k - delay + 0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(tfilt,ht, '-x','MarkerSize',4,...\n");
+    fprintf(fid,"     tfilt,gt1,'-x','MarkerSize',4,...\n");
+    fprintf(fid,"     tfilt,gt2,'-x','MarkerSize',4);\n");
+    fprintf(fid,"axis([tfilt(1) tfilt(end) -0.1 1.1]);\n");
+    fprintf(fid,"legend('pulse','trap. int.','rect. int.','location','northwest');\n");
+    fprintf(fid,"grid on;\n");
+#endif
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+    
+    // free allocated filter memory
+    free(ht);
+
+    return 0;
+}
diff --git a/sandbox/msresamp_crcf_test.c b/sandbox/msresamp_crcf_test.c
new file mode 100644
index 0000000..8eb635f
--- /dev/null
+++ b/sandbox/msresamp_crcf_test.c
@@ -0,0 +1,161 @@
+//
+// msresamp_crcf_test.c
+//
+// Testing the multi-stage arbitrary resampler
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "msresamp_crcf_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("Usage: msresamp_crcf_test [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  r     : resampling rate (output/input), default: 0.117\n");
+    printf("  s     : stop-band attenuation [dB], default: 60\n");
+    printf("  n     : number of input samples, default: 500\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // options
+    float r=1.2f;           // resampling rate (output/input)
+    float As=80.0f;         // resampling filter stop-band attenuation [dB]
+    unsigned int nx=400;    // number of input samples
+    float fc=0.40f;         // complex sinusoid frequency
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hr:s:n:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();            return 0;
+        case 'r':   r   = atof(optarg); break;
+        case 's':   As  = atof(optarg); break;
+        case 'n':   nx  = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (nx == 0) {
+        fprintf(stderr,"error: %s, number of input samples must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if (r <= 0.0f) {
+        fprintf(stderr,"error: %s, resampling rate must be greater than zero\n", argv[0]);
+        exit(1);
+    } else if ( fabsf(log2f(r)) > 10 ) {
+        fprintf(stderr,"error: %s, resampling rate unreasonable\n", argv[0]);
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int ny_alloc = (unsigned int) (2*(float)nx * r);  // allocation for output
+
+    // allocate memory for arrays
+    float complex x[nx];
+    float complex y[ny_alloc];
+
+    // generate input
+    unsigned int window_len = (3*nx)/4;
+    for (i=0; i<nx; i++)
+        x[i] = i < window_len ? cexpf(_Complex_I*2*M_PI*fc*i) * kaiser(i,window_len,10.0f,0.0f) : 0.0f;
+
+    // create multi-stage arbitrary resampler object
+    msresamp_crcf q = msresamp_crcf_create(r,As);
+    msresamp_crcf_print(q);
+    float delay = msresamp_crcf_get_delay(q);
+
+    // run resampler
+    unsigned int ny;
+    msresamp_crcf_execute(q, x, nx, y, &ny);
+
+    // print basic results
+    printf("input samples   : %u\n", nx);
+    printf("output samples  : %u\n", ny);
+    printf("delay           : %f samples\n", delay);
+
+    // clean up allocated objects
+    msresamp_crcf_destroy(q);
+
+    //
+    // export output
+    //
+    // open/initialize output file
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"r=%12.8f;\n", r);
+    fprintf(fid,"delay = %f;\n", delay);
+
+    // save input series
+    fprintf(fid,"nx = %u;\n", nx);
+    fprintf(fid,"x = zeros(1,nx);\n");
+    for (i=0; i<nx; i++)
+        fprintf(fid,"x(%6u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    // save output series
+    fprintf(fid,"ny = %u;\n", ny);
+    fprintf(fid,"y = zeros(1,ny);\n");
+    for (i=0; i<ny; i++)
+        fprintf(fid,"y(%6u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // output results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"%% estimate PSD, normalize by array length\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x,nfft)/length(x))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y,nfft)/length(y))));\n");
+    fprintf(fid,"G = max(X);\n");
+    fprintf(fid,"X = X - G;\n");
+    fprintf(fid,"Y = Y - G;\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"if r>1, fx = f/r; fy = f;   %% interpolated\n");
+    fprintf(fid,"else,   fx = f;   fy = f*r; %% decimated\n");
+    fprintf(fid,"end;\n");
+    fprintf(fid,"plot(fx,X,'Color',[0.5 0.5 0.5],fy,Y,'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','resampled','location','northeast');");
+    fprintf(fid,"axis([-0.5 0.5 -120 10]);\n");
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot time-domain result\n");
+    fprintf(fid,"tx=[0:(length(x)-1)];\n");
+    fprintf(fid,"ty=[0:(length(y)-1)]/r-delay;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx,real(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,real(y),'-s','Color',[0.5 0 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  grid on;\n\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx,imag(x),'-s','Color',[0.5 0.5 0.5],'MarkerSize',1,...\n");
+    fprintf(fid,"       ty,imag(y),'-s','Color',[0 0.5 0],    'MarkerSize',1);\n");
+    fprintf(fid,"  legend('original','resampled','location','northeast');");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  grid on;\n\n");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/newbench_example.c b/sandbox/newbench_example.c
new file mode 100644
index 0000000..360a054
--- /dev/null
+++ b/sandbox/newbench_example.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Example of a benchmark header
+//
+
+// compile and run:
+//   $ gcc -Wall -I. -I./include -c newbench_example.c -o newbench_example.o 
+//   $ gcc newbench_example.o libliquid.a -lm -lfec -lpthread -lc -lfftw3f -o newbench_example 
+//   $ ./newbench_example
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <getopt.h>
+#include <math.h>
+#include "include/liquid.h"
+
+double calculate_execution_time(struct rusage _start, struct rusage _finish)
+{
+    return _finish.ru_utime.tv_sec - _start.ru_utime.tv_sec
+        + 1e-6*(_finish.ru_utime.tv_usec - _start.ru_utime.tv_usec)
+        + _finish.ru_stime.tv_sec - _start.ru_stime.tv_sec
+        + 1e-6*(_finish.ru_stime.tv_usec - _start.ru_stime.tv_usec);
+}
+
+struct decim_crcf_opts {
+    unsigned int n; // filter length
+    unsigned int D; // decimation factor
+};
+
+void benchmark_decim_crcf(
+    void * _opts,
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+{
+    // retrieve options
+    struct decim_crcf_opts * opts = (struct decim_crcf_opts*) _opts;
+
+    unsigned long int i;
+    // DSP initiazation goes here
+    float h[opts->n];
+    for (i=0; i<opts->n; i++)
+        h[i] = 0.0f;
+    decim_crcf decim = decim_crcf_create(opts->D,h,opts->n);
+
+    float complex x[opts->D];
+    float complex y;
+    for (i=0; i<opts->D; i++)
+        x[i] = 1.0f;
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // DSP execution goes here
+        decim_crcf_execute(decim,x,&y,opts->D-1);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // DSP cleanup goes here
+    decim_crcf_destroy(decim);
+}
+
+void precision_decim_crcf(
+    unsigned int argc,
+    char *argv[],
+    float * _error)
+{
+    // blah...
+    *_error = 0.0f;
+}
+
+int main() {
+    struct rusage start,finish;
+    unsigned long int num_trials = 1000000;
+    unsigned int n;
+    unsigned int D;
+    double extime;
+    double cpuclock=2.4e9;
+    double cycles_per_trial;
+    struct decim_crcf_opts opts;
+    for (D=2; D<=8; D*=2) {
+        printf("***** D = %u\n",D);
+        for (n=5; n<31; n+=4) {
+            opts.D = D;
+            opts.n = n;
+            benchmark_decim_crcf((void*)(&opts),&start,&finish,&num_trials);
+            extime = calculate_execution_time(start,finish);
+            cycles_per_trial = cpuclock * extime / (double)(num_trials);
+            printf("n : %3u, D : %3u, cycles/trial : %6.2f\n", n,D,cycles_per_trial);
+        }
+    }
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/ofdm_ber_test.c b/sandbox/ofdm_ber_test.c
new file mode 100644
index 0000000..1c51737
--- /dev/null
+++ b/sandbox/ofdm_ber_test.c
@@ -0,0 +1,156 @@
+//
+// ofdm_ber_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+void usage()
+{
+    printf("ofdm_ber_test [options]\n");
+    printf("  h     : print usage\n");
+    printf("  s     : signal-to-noise ratio [dB], default: 6.5\n");
+    printf("  M     : number of subcarriers (must be even), default: 64\n");
+    printf("  C     : cyclic prefix length, default: 16\n");
+    printf("  m     : modulation scheme (bpsk default)\n");
+    liquid_print_modulation_schemes();
+    printf("  n     : number of OFDM symbols, default: 40\n");
+    printf("  c     : number of channel taps, default: 1\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // set random number generator seed
+    srand(time(NULL));
+
+    // options
+    unsigned int M = 64;                // number of subcarriers
+    unsigned int cp_len = 16;           // cyclic prefix length
+    modulation_scheme ms = LIQUID_MODEM_BPSK;
+    float SNRdB = 6.5f;                 // signal-to-noise ratio [dB]
+    unsigned int hc_len = 1;            // channel impulse response length
+    unsigned int num_symbols = 40;      // number of OFDM symbols
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hs:M:C:m:n:c:")) != EOF){
+        switch (dopt) {
+        case 'h': usage(); return 0;
+        case 's': SNRdB  = atof(optarg); break;
+        case 'M': M      = atoi(optarg); break;
+        case 'C': cp_len = atoi(optarg); break;
+        case 'm':
+            ms = liquid_getopt_str2mod(optarg);
+            if (ms == LIQUID_MODEM_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown/unsupported mod. scheme: %s\n", argv[0], optarg);
+                exit(-1);
+            }
+            break;
+        case 'n': num_symbols = atoi(optarg); break;
+        case 'c': hc_len      = atoi(optarg); break;
+        default:
+            exit(-1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate options
+    if (M < 4) {
+        fprintf(stderr,"error: %s, must have at least 4 subcarriers\n", argv[0]);
+        exit(1);
+    } else if (hc_len == 0) {
+        fprintf(stderr,"error: %s, must have at least 1 channel tap\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int symbol_len = M + cp_len;
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    float fft_gain = 1.0f / sqrtf(M);   // 'gain' due to taking FFT
+    
+    // buffers
+    unsigned int sym_in[M];             // input data symbols
+    unsigned int sym_out[M];            // output data symbols
+    float complex x[M];                 // time-domain buffer
+    float complex X[M];                 // freq-domain buffer
+    float complex buffer[symbol_len];   // 
+
+    // create modulator/demodulator objects
+    modem mod   = modem_create(ms);
+    modem demod = modem_create(ms);
+    unsigned int bps = modem_get_bps(mod);  // modem bits/symbol
+
+    // create channel filter (random taps)
+    float complex hc[hc_len];
+    hc[0] = 1.0f;
+    for (i=1; i<hc_len; i++)
+        hc[i] = 0.1f * (randnf() + _Complex_I*randnf());
+    firfilt_cccf fchannel = firfilt_cccf_create(hc, hc_len);
+
+    //
+    unsigned int n;
+    unsigned int num_bit_errors = 0;
+    for (n=0; n<num_symbols; n++) {
+        // generate random data symbols and modulate onto subcarriers
+        for (i=0; i<M; i++) {
+            sym_in[i] = rand() % (1<<bps);
+
+            modem_modulate(mod, sym_in[i], &X[i]);
+        }
+
+        // run inverse transform
+        fft_run(M, X, x, LIQUID_FFT_BACKWARD, 0);
+
+        // scale by FFT gain so E{|x|^2} = 1
+        for (i=0; i<M; i++)
+            x[i] *= fft_gain;
+
+        // apply channel impairments
+        for (i=0; i<M + cp_len; i++) {
+            // push samples through channel filter, starting with cyclic prefix
+            firfilt_cccf_push(fchannel, x[(M-cp_len+i)%M]);
+
+            // compute output
+            firfilt_cccf_execute(fchannel, &buffer[i]);
+
+            // add noise
+            buffer[i] += nstd*( randnf() + _Complex_I*randnf() ) * M_SQRT1_2;
+        }
+
+        // run forward transform
+        fft_run(M, &buffer[cp_len], X, LIQUID_FFT_FORWARD, 0);
+
+        // TODO : apply equalizer to 'X' here
+
+        // demodulate and compute bit errors
+        for (i=0; i<M; i++) {
+            // scale by fft size
+            X[i] *= fft_gain;
+
+            modem_demodulate(demod, X[i], &sym_out[i]);
+
+            num_bit_errors += liquid_count_ones(sym_in[i] ^ sym_out[i]);
+        }
+    }
+
+    // destroy objects
+    modem_destroy(mod);
+    modem_destroy(demod);
+    firfilt_cccf_destroy(fchannel);
+
+    // print results
+    unsigned int total_bits = M*bps*num_symbols;
+    float ber = (float)num_bit_errors / (float)total_bits;
+    printf("  bit errors : %6u / %6u (%12.4e)\n", num_bit_errors, total_bits, ber);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/ofdmframe_papr_test.c b/sandbox/ofdmframe_papr_test.c
new file mode 100644
index 0000000..801d4a4
--- /dev/null
+++ b/sandbox/ofdmframe_papr_test.c
@@ -0,0 +1,223 @@
+//
+// sandbox/ofdmframe_papr_test.c
+//
+// Test OFDM frame's peak-to-average power ratio (PAPR).
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmframe_papr_test.m"
+
+void usage()
+{
+    printf("Usage: ofdmframe_papr_test [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  M     : number of subcarriers (must be even), default: 64\n");
+    printf("  C     : cyclic prefix length, default: 16\n");
+    printf("  T     : taper length, default: 0\n");
+    printf("  N     : number of data symbols, default: 1000\n");
+}
+
+// compute peak-to-average power ratio
+//  _x  :   input time series
+//  _n  :   number of samples
+float ofdmframe_PAPR(float complex * _x,
+                     unsigned int    _n);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int M           = 64;   // number of subcarriers
+    unsigned int cp_len      = 16;   // cyclic prefix length
+    unsigned int taper_len   = 0;    // taper length
+    unsigned int num_symbols = 1000; // number of data symbols
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hM:C:T:N:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                      return 0;
+        case 'M': M           = atoi(optarg);   break;
+        case 'C': cp_len      = atoi(optarg);   break;
+        case 'T': taper_len   = atoi(optarg);   break;
+        case 'N': num_symbols = atoi(optarg);   break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int frame_len = M + cp_len;
+
+    // initialize subcarrier allocation
+    unsigned char p[M];
+    ofdmframe_init_default_sctype(M, p);
+
+    // create frame generator
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, p);
+    ofdmframegen_print(fg);
+
+    modem mod = modem_create(ms);
+
+    float complex X[M];             // channelized symbols
+    float complex buffer[frame_len];// output time series
+    float * PAPR = (float*) malloc(num_symbols*sizeof(float));
+
+    // histogram display
+    float xmin = -1.29f + 0.70f*log2f(M);
+    float xmax =  8.97f + 0.34f*log2f(M);
+    unsigned int num_bins = 30;
+    float bin_width = (xmax - xmin) / (num_bins);
+    unsigned int hist[num_bins];
+    for (i=0; i<num_bins; i++)
+        hist[i] = 0;
+
+    // modulate data symbols
+    unsigned int j;
+    unsigned int s;
+    float PAPR_min  = 0.0f;
+    float PAPR_max  = 0.0f;
+    float PAPR_mean = 0.0f;
+    for (i=0; i<num_symbols; i++) {
+        // data symbol
+        for (j=0; j<M; j++) {
+            s = modem_gen_rand_sym(mod);
+            modem_modulate(mod,s,&X[j]);
+        }
+        // generate symbol
+        ofdmframegen_writesymbol(fg, X, buffer);
+#if 0
+        // preamble
+        if      (i==0) ofdmframegen_write_S0a(fg, buffer); // S0 symbol (first)
+        else if (i==1) ofdmframegen_write_S0b(fg, buffer); // S0 symbol (second)
+        else if (i==2) ofdmframegen_write_S1( fg, buffer); // S1 symbol
+#endif
+
+        // compute PAPR
+        PAPR[i] = ofdmframe_PAPR(buffer, frame_len);
+
+        // compute bin index
+        unsigned int index;
+        float ihat = num_bins * (PAPR[i] - xmin) / (xmax - xmin);
+        if (ihat < 0.0f) index = 0;
+        else             index = (unsigned int)ihat;
+        
+        if (index >= num_bins)
+            index = num_bins-1;
+
+        hist[index]++;
+
+        // save min/max PAPR values
+        if (i==0 || PAPR[i] < PAPR_min) PAPR_min = PAPR[i];
+        if (i==0 || PAPR[i] > PAPR_max) PAPR_max = PAPR[i];
+        PAPR_mean += PAPR[i];
+    }
+    PAPR_mean /= (float)num_symbols;
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    modem_destroy(mod);
+
+    // print results to screen
+    // find max(hist)
+    unsigned int hist_max = 0;
+    for (i=0; i<num_bins; i++)
+        hist_max = hist[i] > hist_max ? hist[i] : hist_max;
+
+    printf("%8s : %6s [%6s]\n", "PAPR[dB]", "count", "prob.");
+    for (i=0; i<num_bins; i++) {
+        printf("%8.2f : %6u [%6.4f]", xmin + i*bin_width, hist[i], (float)hist[i] / (float)num_symbols);
+
+        unsigned int k;
+        unsigned int n = round(60 * (float)hist[i] / (float)hist_max);
+        for (k=0; k<n; k++)
+            printf("#");
+        printf("\n");
+    }
+    printf("\n");
+    printf("min  PAPR: %12.4f dB\n", PAPR_min);
+    printf("max  PAPR: %12.4f dB\n", PAPR_max);
+    printf("mean PAPR: %12.4f dB\n", PAPR_mean);
+
+    // 
+    // export output file
+    //
+
+    // count subcarrier types
+    unsigned int M_data  = 0;
+    unsigned int M_pilot = 0;
+    unsigned int M_null  = 0;
+    ofdmframe_validate_sctype(p, M, &M_null, &M_pilot, &M_data);
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"M           = %u;\n", M);
+    fprintf(fid,"M_data      = %u;\n", M_data);
+    fprintf(fid,"M_pilot     = %u;\n", M_pilot);
+    fprintf(fid,"M_null      = %u;\n", M_null);
+    fprintf(fid,"cp_len      = %u;\n", cp_len);
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+
+    fprintf(fid,"PAPR = zeros(1,num_symbols);\n");
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"  PAPR(%6u) = %12.4e;\n", i+1, PAPR[i]);
+
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"hist(PAPR,25);\n");
+
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"cdf = [(1:num_symbols)-0.5]/num_symbols;\n");
+    fprintf(fid,"semilogy(sort(PAPR),1-cdf);\n");
+    fprintf(fid,"xlabel('PAPR [dB]');\n");
+    fprintf(fid,"ylabel('Complementary CDF');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    // free allocated array
+    free(PAPR);
+
+    printf("done.\n");
+    return 0;
+}
+
+// compute peak-to-average power ratio
+//  _x  :   input time series
+//  _n  :   number of samples
+float ofdmframe_PAPR(float complex * _x,
+                     unsigned int    _n)
+{
+    float e;
+    float e_mean = 0.0f;
+    float e_max  = 0.0f;
+
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // compute |_x[i]|^2
+        e = crealf( _x[i] * conjf(_x[i]) );
+
+        e_mean += e;
+        e_max   = (e > e_max) ? e : e_max;
+    }
+
+    e_mean = e_mean / (float)_n;
+
+    return 10*log10f(e_max / e_mean);
+}
+
diff --git a/sandbox/ofdmframesync_cfo_test.c b/sandbox/ofdmframesync_cfo_test.c
new file mode 100644
index 0000000..58a76f6
--- /dev/null
+++ b/sandbox/ofdmframesync_cfo_test.c
@@ -0,0 +1,212 @@
+//
+// ofdmframesync_cfo_test : test carrier frequency offset estimation
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmframesync_cfo_test.dat"
+
+void usage()
+{
+    printf("Usage: ofdmframesync_cfo_test [OPTION]\n");
+    printf("  h     : print help\n");
+    printf("  M     : number of subcarriers (must be even), default: 64\n");
+    printf("  C     : cyclic prefix length, default: 16\n");
+    printf("  T     : taper length, default: 4\n");
+    printf("  S     : signal-to-noise ratio [dB], default: 20\n");
+    printf("  t     : number of trials, default: 500\n");
+    printf("  s     : number of steps, default: 21\n");
+}
+
+static int callback(float complex * _X,
+                    unsigned char * _p,
+                    unsigned int    _M,
+                    void *          _userdata);
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // options
+    unsigned int M           = 64;      // number of subcarriers
+    unsigned int cp_len      = 16;      // cyclic prefix length
+    unsigned int taper_len   = 4;       // taper length
+    float noise_floor        = -30.0f;  // noise floor [dB]
+    float SNRdB              = 20.0f;   // signal-to-noise ratio [dB]
+    unsigned int num_trials  = 500;     // number of trials to simulate
+    unsigned int num_steps   = 21;      // number of steps
+
+    // get options
+    int dopt;
+    while((dopt = getopt(argc,argv,"hM:C:T:S:t:s:")) != EOF){
+        switch (dopt) {
+        case 'h': usage();                  return 0;
+        case 'M': M         = atoi(optarg); break;
+        case 'C': cp_len    = atoi(optarg); break;
+        case 'T': taper_len = atoi(optarg); break;
+        case 'S': SNRdB     = atof(optarg); break;
+        case 't': num_trials= atoi(optarg); break;
+        case 's': num_steps = atoi(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_symbols = 1;             // number of data symbols
+    unsigned int symbol_len = M + cp_len;
+    unsigned int num_samples = (3+num_symbols)*symbol_len;  // data symbols
+    float nstd = powf(10.0f, noise_floor/20.0f);
+    float gamma = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+    printf("gamma : %f\n", gamma);
+
+    // initialize subcarrier allocation
+    unsigned char p[M];
+    ofdmframe_init_default_sctype(M, p);
+
+    // create frame generator
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, p);
+    ofdmframegen_print(fg);
+
+    // create frame synchronizer
+    int frame_detected;
+    ofdmframesync fs = ofdmframesync_create(M, cp_len, taper_len, p, callback, (int*)&frame_detected);
+    ofdmframesync_print(fs);
+
+    float complex X[M];                 // channelized symbols
+    float complex frame[num_samples];   // initial frame
+    float complex y[num_samples];       // output time series
+
+    unsigned int n=0;
+
+    // generate frame
+    ofdmframegen_write_S0a(fg, &frame[n]);  n += symbol_len;
+    ofdmframegen_write_S0b(fg, &frame[n]);  n += symbol_len;
+    ofdmframegen_write_S1( fg, &frame[n]);  n += symbol_len;
+    // generate single data symbol (random BPSK)
+    for (i=0; i<M; i++)
+        X[i] = rand() % 2 ? 1.0f : -1.0f;
+    ofdmframegen_writesymbol(fg, X, &frame[n]);
+    
+    // carrier frequency offset
+    float nu_min = 0.0f;            // minimum
+    float nu_max = 0.9f*M_PI / (float)M; // maximum
+    float nu_step = (nu_max - nu_min) / (float)(num_steps-1);
+
+    // count subcarrier types
+    unsigned int M_data  = 0;
+    unsigned int M_pilot = 0;
+    unsigned int M_null  = 0;
+    ofdmframe_validate_sctype(p, M, &M_null, &M_pilot, &M_data);
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"# %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"#\n");
+    fprintf(fid,"#  M           = %u\n", M);
+    fprintf(fid,"#  M_data      = %u\n", M_data);
+    fprintf(fid,"#  M_pilot     = %u\n", M_pilot);
+    fprintf(fid,"#  M_null      = %u\n", M_null);
+    fprintf(fid,"#  cp_len      = %u\n", cp_len);
+    fprintf(fid,"#  num_samples = %u\n", num_samples);
+    fprintf(fid,"#  noise_floor = %f\n", noise_floor);
+    fprintf(fid,"#  SNRdB = %f\n", SNRdB);
+    fprintf(fid,"#\n");
+    fprintf(fid,"# %12s %12s %12s %12s %12s %12s\n",
+            "id", "nu", "detected", "total", "bias", "rmse");
+
+    // run trials
+    for (n=0; n<num_steps; n++) {
+        float nu = nu_min + nu_step*n;
+        unsigned int t;
+        unsigned int num_frames_detected=0;
+        float nu_hat_bias = 0.0f;
+        float nu_hat_rmse = 0.0f;
+        for (t=0; t<num_trials; t++) {
+
+            // reset frame synchronizer
+            ofdmframesync_reset(fs);
+
+            // set initial estimate to zero
+            frame_detected = 0;
+
+            // add noise, carrier offset
+            float phi = randf()*2*M_PI; // random initial phase offset
+            for (i=0; i<num_samples; i++) {
+                // add carrier offset
+                y[i] = gamma * frame[i] * cexpf(_Complex_I*(phi + nu*i));
+
+                // add noise
+                y[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+            }
+
+            // execute synchronizer
+            ofdmframesync_execute(fs,y,num_samples);
+
+            // increment number of frames detected
+            num_frames_detected += frame_detected;
+
+            // assume frame was detected; save carrier offset estimate
+            float nu_hat = ofdmframesync_get_cfo(fs);
+
+            // accumulate statistics
+            float nu_hat_error = nu - nu_hat;
+            nu_hat_bias += nu_hat_error;
+            nu_hat_rmse += nu_hat_error*nu_hat_error;
+
+        }
+        nu_hat_bias /= (float)num_trials;
+        nu_hat_rmse /= (float)num_trials;
+        nu_hat_rmse  = sqrtf(nu_hat_rmse);
+
+        // print results
+        printf("%6u, nu=%12.8f", n, nu);
+        printf(", frames: %6u / %6u", num_frames_detected, num_trials);
+        printf(", bias: %12.8f", nu_hat_bias);
+        printf(", rmse: %12.8f", nu_hat_rmse);
+        printf("\n");
+        fprintf(fid,"  %12u %12f %12u %12u %12.4e %12.4e\n",
+                n, nu, num_frames_detected, num_trials,
+                nu_hat_bias, nu_hat_rmse);
+    }
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    ofdmframesync_destroy(fs);
+
+
+    // 
+    // export output file
+    //
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+static int callback(float complex * _X,
+                    unsigned char * _p,
+                    unsigned int    _M,
+                    void *          _userdata)
+{
+    //printf("**** callback invoked\n");
+
+    // set flag that frame was detected
+    int * frame_detected = (int*)_userdata;
+    *frame_detected = 1;
+
+    // return zero as not to reset synchronizer (need to retain
+    // internal carrier frequency offset estimate)
+    return 0;
+}
+
diff --git a/sandbox/ofdmoqam_firpfbch_cfo_test.c b/sandbox/ofdmoqam_firpfbch_cfo_test.c
new file mode 100644
index 0000000..c90cbea
--- /dev/null
+++ b/sandbox/ofdmoqam_firpfbch_cfo_test.c
@@ -0,0 +1,243 @@
+//
+// sandbox/ofdmoqam_firpfbch_cfo_test.c
+//
+// Tests the validity of OFDM/OQAM carrier frequency offset
+// recovery using firpfbch_cccf channelizer objects.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <complex.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmoqam_firpfbch_cfo_test.m"
+
+#define DEBUG 1
+
+
+int main() {
+    // options
+    unsigned int num_channels=64;   // must be even number
+    unsigned int num_symbols=16;    // number of symbols
+    unsigned int m=3;               // filter delay (symbols)
+    float beta = 0.9f;              // filter excess bandwidth factor
+    float phi = 0.0f;               // carrier phase offset;
+    float dphi = 0.04f;            // carrier frequency offset
+
+    // number of frames (compensate for filter delay)
+    unsigned int num_frames = num_symbols + 2*m;
+    unsigned int num_samples = num_channels * num_frames;
+    unsigned int i;
+    unsigned int j;
+
+    // create filter prototype
+    unsigned int h_len = 2*num_channels*m + 1;
+    float h[h_len];
+    float complex hc[h_len];
+    float complex gc[h_len];
+    liquid_firdes_rkaiser(num_channels, m, beta, 0.0f, h);
+    unsigned int g_len = 2*num_channels*m;
+    for (i=0; i<g_len; i++) {
+        hc[i] = h[i];
+        gc[i] = h[g_len-i-1] * cexpf(_Complex_I*dphi*i);
+    }
+
+    // data arrays
+    float complex s[num_channels];                  // input symbols
+    float complex y[num_samples];                   // time-domain samples
+    float complex Y0[num_frames][num_channels];     // channelized output
+    float complex Y1[num_frames][num_channels];     // channelized output
+
+    // create ofdm/oqam generator object and generate data
+    ofdmoqam qs = ofdmoqam_create(num_channels, m, beta, 0.0f, LIQUID_SYNTHESIZER, 0);
+    for (i=0; i<num_frames; i++) {
+        for (j=0; j<num_channels; j++) {
+            if (i<num_symbols) {
+#if 0
+                // QPSK on all subcarriers
+                s[j] = (rand() % 2 ? 1.0f : -1.0f) +
+                       (rand() % 2 ? 1.0f : -1.0f) * _Complex_I;
+                s[j] *= 1.0f / sqrtf(2.0f);
+#else
+                // BPSK on even subcarriers
+                s[j] =  rand() % 2 ? 1.0f : -1.0f;
+                s[j] *= (j%2)==0 ? 1.0f : 0.0f;
+#endif
+            } else {
+                s[j] = 0.0f;
+            }
+        }
+
+        // run synthesizer
+        ofdmoqam_execute(qs, s, &y[i*num_channels]);
+    }
+    ofdmoqam_destroy(qs);
+
+    // channel
+    for (i=0; i<num_samples; i++)
+        y[i] *= cexpf(_Complex_I*(phi + dphi*i));
+
+
+    //
+    // analysis filterbank (receiver)
+    //
+
+    // create filterbank manually
+    dotprod_cccf dp[num_channels];  // vector dot products
+    windowcf w[num_channels];       // window buffers
+
+#if DEBUG
+    // print coefficients
+    printf("h_prototype:\n");
+    for (i=0; i<h_len; i++)
+        printf("  h[%3u] = %12.8f\n", i, h[i]);
+#endif
+
+    // create objects
+    unsigned int gc_sub_len = 2*m;
+    float complex gc_sub[gc_sub_len];
+    for (i=0; i<num_channels; i++) {
+        // sub-sample prototype filter, loading coefficients in
+        // reverse order
+#if 0
+        for (j=0; j<gc_sub_len; j++)
+            gc_sub[j] = h[j*num_channels+i];
+#else
+        for (j=0; j<gc_sub_len; j++)
+            gc_sub[gc_sub_len-j-1] = gc[j*num_channels+i];
+#endif
+
+        // create window buffer and dotprod objects
+        dp[i] = dotprod_cccf_create(gc_sub, gc_sub_len);
+        w[i]  = windowcf_create(gc_sub_len);
+
+#if DEBUG
+        printf("gc_sub[%u] : \n", i);
+        for (j=0; j<gc_sub_len; j++)
+            printf("  g[%3u] = %12.8f + %12.8f\n", j, crealf(gc_sub[j]), cimagf(gc_sub[j]));
+#endif
+    }
+
+    // generate DFT object
+    float complex x[num_channels];  // time-domain buffer
+    float complex X[num_channels];  // freq-domain buffer
+#if 0
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_BACKWARD, 0);
+#else
+    fftplan fft = fft_create_plan(num_channels, X, x, LIQUID_FFT_FORWARD, 0);
+#endif
+
+    // 
+    // run analysis filter bank
+    //
+#if 0
+    unsigned int filter_index = 0;
+#else
+    unsigned int filter_index = num_channels-1;
+#endif
+    float complex y_hat;    // input sample
+    float complex * r;      // read pointer
+    for (i=0; i<num_frames; i++) {
+
+        // load buffers
+        for (j=0; j<num_channels; j++) {
+            // grab sample
+            y_hat = y[i*num_channels + j];
+
+            // push sample into buffer at filter index
+            windowcf_push(w[filter_index], y_hat);
+
+            // decrement filter index
+            filter_index = (filter_index + num_channels - 1) % num_channels;
+            //filter_index = (filter_index + 1) % num_channels;
+        }
+
+        // execute filter outputs, reversing order of output (not
+        // sure why this is necessary)
+        for (j=0; j<num_channels; j++) {
+            windowcf_read(w[j], &r);
+            dotprod_cccf_execute(dp[j], r, &X[num_channels-j-1]);
+        }
+
+#if 1
+        // compensate for carrier frequency offset (before transform)
+        for (j=0; j<num_channels; j++) {
+            X[j] *= cexpf(-_Complex_I*(dphi*i*num_channels));
+        }
+#endif
+
+        // execute DFT, store result in buffer 'x'
+        fft_execute(fft);
+
+#if 0
+        // compensate for carrier frequency offset (after transform)
+        for (j=0; j<num_channels; j++) {
+            x[j] *= cexpf(-_Complex_I*(dphi*i*num_channels));
+        }
+#endif
+
+        // move to output array
+        for (j=0; j<num_channels; j++)
+            Y0[i][j] = x[j];
+    }
+
+
+    // destroy objects
+    for (i=0; i<num_channels; i++) {
+        dotprod_cccf_destroy(dp[i]);
+        windowcf_destroy(w[i]);
+    }
+    fft_destroy_plan(fft);
+
+#if 0
+    // print filterbank channelizer
+    printf("\n");
+    printf("filterbank channelizer:\n");
+    for (i=0; i<num_symbols; i++) {
+        printf("%3u: ", i);
+        for (j=0; j<num_channels; j++) {
+            printf("  %8.5f+j%8.5f, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+        }
+        printf("\n");
+    }
+#endif
+
+    // 
+    // export data
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_channels=%u;\n", num_channels);
+    fprintf(fid,"num_symbols=%u;\n", num_symbols);
+    fprintf(fid,"num_frames = %u;\n", num_frames);
+    fprintf(fid,"num_samples = num_frames*num_channels;\n");
+
+    fprintf(fid,"y = zeros(1,%u);\n",  num_samples);
+    fprintf(fid,"Y0 = zeros(%u,%u);\n", num_frames, num_channels);
+    fprintf(fid,"Y1 = zeros(%u,%u);\n", num_frames, num_channels);
+    
+    for (i=0; i<num_frames; i++) {
+        for (j=0; j<num_channels; j++) {
+            fprintf(fid,"Y0(%4u,%4u) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(Y0[i][j]), cimagf(Y0[i][j]));
+            fprintf(fid,"Y1(%4u,%4u) = %12.4e + j*%12.4e;\n", i+1, j+1, crealf(Y1[i][j]), cimagf(Y1[i][j]));
+        }
+    }
+
+    // plot BPSK results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(Y0(:,1:2:end),'x');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.2*sqrt(num_channels));\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/ofdmoqam_firpfbch_test.c b/sandbox/ofdmoqam_firpfbch_test.c
new file mode 100644
index 0000000..0fbecb0
--- /dev/null
+++ b/sandbox/ofdmoqam_firpfbch_test.c
@@ -0,0 +1,184 @@
+//
+// sandbox/ofdmoqam_firpfbch_test.c
+//
+// Tests the validity of OFDM/OQAM using firpfbch_crcf channelizer
+// objects.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "ofdmoqam_firpfbch_test.m"
+
+int main() {
+    // options
+    unsigned int num_channels=6;    // must be even number
+    unsigned int num_symbols=32;    // number of symbols
+    unsigned int m=3;               // filter delay (symbols)
+    float beta = 0.9f;              // filter excess bandwidth factor
+    int ftype = LIQUID_FIRFILT_ARKAISER;
+
+    // number of frames (compensate for filter delay)
+    unsigned int num_frames = num_symbols + 2*m;
+
+    unsigned int num_samples = num_channels * num_frames;
+
+    // create synthesizer/analyzer objects
+    firpfbch_crcf cs0 = firpfbch_crcf_create_rnyquist(LIQUID_SYNTHESIZER, num_channels, m, beta, ftype);
+    firpfbch_crcf cs1 = firpfbch_crcf_create_rnyquist(LIQUID_SYNTHESIZER, num_channels, m, beta, ftype);
+
+    firpfbch_crcf ca0 = firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER,    num_channels, m, beta, ftype);
+    firpfbch_crcf ca1 = firpfbch_crcf_create_rnyquist(LIQUID_ANALYZER,    num_channels, m, beta, ftype);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"num_channels=%u;\n", num_channels);
+    fprintf(fid,"num_symbols=%u;\n", num_symbols);
+    fprintf(fid,"num_samples=%u;\n", num_samples);
+
+    fprintf(fid,"X = zeros(%u,%u);\n", num_channels, num_frames);
+    fprintf(fid,"y = zeros(1,%u);\n",  num_samples);
+    fprintf(fid,"Y = zeros(%u,%u);\n", num_channels, num_frames);
+
+    unsigned int i, j, n=0;
+    unsigned int k2 = num_channels/2;
+    float complex X[num_channels];  // channelized symbols
+    float complex y[num_channels];  // interpolated time-domain samples
+    float complex Y[num_channels];  // received symbols
+
+    // temporary buffers
+    float complex X0[num_channels];
+    float complex X1[num_channels];
+    float complex y0[num_channels];
+    float complex y1[num_channels];
+    float complex y1_prime[num_channels];
+    for (i=0; i<num_channels; i++) y1_prime[i] = 0.0f;
+
+    float complex Z0[num_channels];
+    float complex Z1[num_channels];
+    float complex z0[num_channels];
+    float complex z1[num_channels];
+    //float complex z0_prime[num_channels];
+    //float complex z1_prime[num_channels];
+    for (i=0; i<num_channels; i++) z0[i] = 0.0f;
+    for (i=0; i<num_channels; i++) z1[i] = 0.0f;
+
+    for (i=0; i<num_frames; i++) {
+
+        // generate frame data (random QPSK symbols)
+        for (j=0; j<num_channels; j++) {
+            X[j] = (rand()%2 ? 1.0f : -1.0f) +
+                   (rand()%2 ? 1.0f : -1.0f)*_Complex_I;
+        }
+
+        // execute synthesyzer
+        //ofdmoqam_execute(cs, X, y);
+        for (j=0; j<num_channels; j+=2) {
+            // even channels
+            X0[j+0] = crealf(X[j+0]);
+            X1[j+0] = cimagf(X[j+0])*_Complex_I;
+
+            // odd channels
+            X0[j+1] = cimagf(X[j+1])*_Complex_I;
+            X1[j+1] = crealf(X[j+1]);
+        }
+        /*
+        for (j=0; j<num_channels; j++)
+            printf("X(%3u)  = %12.8f + j*%12.8f\n", j, crealf(X[j]), cimagf(X[j]));
+        printf("\n");
+        for (j=0; j<num_channels; j++)
+            printf("X0(%3u) = %12.8f + j*%12.8f\n", j, crealf(X0[j]), cimagf(X0[j]));
+        printf("\n");
+        */
+        firpfbch_crcf_synthesizer_execute(cs0, X0, y0);
+        firpfbch_crcf_synthesizer_execute(cs1, X1, y1);
+        // delay lower branch by half a symbol:
+        // copy first half of symbol from lower branch
+        memmove(&y1_prime[k2], &y1[0], k2*sizeof(float complex));
+        // add symbols
+        for (j=0; j<num_channels; j++)
+            y[j] = y0[j] + y1_prime[j];
+        // finish delay
+        // copy last half of symbol from lower branch
+        memmove(&y1_prime[0], &y1[k2], k2*sizeof(float complex));
+
+        // channel
+
+        // execute analyzer
+        //ofdmoqam_execute(ca, y, Y);
+        // delay lower branch by half a symbol
+        // copy first half of symbol to lower branch
+        memmove(&z1[k2], &y[0], k2*sizeof(float complex));
+        // run analyzer
+        firpfbch_crcf_analyzer_execute(ca0, z0, Z0);
+        firpfbch_crcf_analyzer_execute(ca1, z1, Z1);
+        // finish delay
+        // copy last half of symbol to lower branch
+        memmove(&z1[0], &y[k2], k2*sizeof(float complex));
+        // copy full symbol on upper branch
+        memmove(z0,y,num_channels*sizeof(float complex));
+        for (j=0; j<num_channels; j+=2) {
+            // even channels
+            Y[j+0] = crealf(Z0[j+0]) + cimagf(Z1[j+0])*_Complex_I;
+
+            // odd channels
+            Y[j+1] = cimagf(Z0[j+1])*_Complex_I + crealf(Z1[j+1]);
+        }
+
+        // write output to file
+        for (j=0; j<num_channels; j++) {
+            // frequency data
+            fprintf(fid,"X(%4u,%4u) = %12.4e + j*%12.4e;\n", j+1, i+1, crealf(X[j]), cimagf(X[j]));
+
+            // time data
+            fprintf(fid,"y(%4u) = %12.4e + j*%12.4e;\n", n+1, crealf(y[j]), cimag(y[j]));
+
+            // received data
+            fprintf(fid,"Y(%4u,%4u) = %12.4e + j*%12.4e;\n", j+1, i+1, crealf(Y[j]), cimagf(Y[j]));
+            n++;
+        }
+    }
+
+    // print results
+    fprintf(fid,"\n\n");
+    fprintf(fid,"s0 =  1:%u;\n", num_symbols);
+    fprintf(fid,"s1 = %u:%u;\n", 2*m+1, num_symbols + 2*m);
+    fprintf(fid,"Y = Y / num_channels; %% normalize by fft size\n");
+    fprintf(fid,"for i=1:min(6,num_channels),\n");
+    fprintf(fid,"    figure;\n");
+    fprintf(fid,"    subplot(2,1,1);\n");
+    fprintf(fid,"    plot(1:num_symbols,real(X(i,s0)),'-x',1:num_symbols,real(Y(i,s1)),'-x');\n");
+    fprintf(fid,"    ylabel('Re');\n");
+    fprintf(fid,"    title(['channel ' num2str(i-1)]);\n");
+    fprintf(fid,"    subplot(2,1,2);\n");
+    fprintf(fid,"    plot(1:num_symbols,imag(X(i,s0)),'-x',1:num_symbols,imag(Y(i,s1)),'-x');\n");
+    fprintf(fid,"    ylabel('Im');\n");
+    fprintf(fid,"    pause(0.2);\n");
+    fprintf(fid,"end;\n");
+
+    // plot time-domain output
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t =  1:num_samples;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,real(y),'-', t,imag(y),'-');\n");
+    fprintf(fid,"xlabel('time');\n");
+    fprintf(fid,"ylabel('time series');\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    // destroy objects
+    firpfbch_crcf_destroy(cs0);
+    firpfbch_crcf_destroy(cs1);
+    firpfbch_crcf_destroy(ca0);
+    firpfbch_crcf_destroy(ca1);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/packetizer_persistent_ber_test.c b/sandbox/packetizer_persistent_ber_test.c
new file mode 100644
index 0000000..841e257
--- /dev/null
+++ b/sandbox/packetizer_persistent_ber_test.c
@@ -0,0 +1,396 @@
+// 
+// packetizer_persistent_ber_test.c
+//
+// Simulate persistent decoding error rate using persistent vs.
+// regular decoding
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <time.h>
+#include <getopt.h>
+
+#include "liquid.internal.h"
+
+#define OUTPUT_FILENAME "packetizer_persistent_ber_test.m"
+
+void usage()
+{
+    printf("Usage: packetizer_persistent_ber_test\n");
+    printf("  Simulates persistent decoding (best for small packets, large CRC)\n");
+    printf("options:\n");
+    printf("  u/h   : print usage/help\n");
+    printf("  s     : SNR min [dB], default: 0\n");
+    printf("  x     : SNR max [dB], default: 8\n");
+    printf("  n     : number of SNR steps, default: 33\n");
+    printf("  t     : number of trials, default: 2000\n");
+    printf("  f     : frame size, default: 20\n");
+    printf("  v     : data integrity check: crc32 default\n");
+    liquid_print_crc_schemes();
+    printf("  c     : coding scheme (inner), default: h128\n");
+    printf("  k     : coding scheme (outer), default: none\n");
+    liquid_print_fec_schemes();
+}
+
+// persistent decoding methods
+int packetizer_decode_persistent(packetizer _p, unsigned char * _pkt, unsigned char * _msg);
+int packetizer_decode_persistent2(packetizer _p, unsigned char * _pkt, unsigned char * _msg);
+
+int main(int argc, char *argv[]) {
+    // set initial seed to random
+    srand(time(NULL));
+
+    // options
+    unsigned int n = 20;                    // frame size (bytes)
+    float SNRdB_min =  0.0f;                // signal-to-noise ratio (minimum)
+    float SNRdB_max =  8.0f;                // signal-to-noise ratio (maximum)
+    unsigned int num_snr = 33;              // number of SNR steps
+    unsigned int num_trials=2000;           // number of trials
+    crc_scheme check = LIQUID_CRC_32;       // error-detection scheme
+    fec_scheme fec0 = LIQUID_FEC_HAMMING128;// error-correcting scheme (inner)
+    fec_scheme fec1 = LIQUID_FEC_NONE;      // error-correcting scheme (outer)
+
+    // get command-line options
+    int dopt;
+    while((dopt = getopt(argc,argv,"uhs:x:n:t:f:v:c:k:")) != EOF){
+        switch (dopt) {
+        case 'h':
+        case 'u': usage(); return 0;
+        case 's': SNRdB_min = atof(optarg);     break;
+        case 'x': SNRdB_max = atof(optarg);     break;
+        case 'n': num_snr = atoi(optarg);       break;
+        case 't': num_trials = atoi(optarg);    break;
+        case 'f': n = atoi(optarg);             break;
+        case 'v':
+            // data integrity check
+            check = liquid_getopt_str2crc(optarg);
+            if (check == LIQUID_CRC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported CRC scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'c':
+            fec0 = liquid_getopt_str2fec(optarg);
+            if (fec0 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported fec scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        case 'k':
+            fec1 = liquid_getopt_str2fec(optarg);
+            if (fec1 == LIQUID_FEC_UNKNOWN) {
+                fprintf(stderr,"error: unknown/unsupported fec scheme \"%s\"\n\n",optarg);
+                exit(1);
+            }
+            break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // create arrays
+    unsigned int k = packetizer_compute_enc_msg_len(n,check,fec0,fec1);
+    printf("dec msg len : %u\n", n);
+    printf("enc msg len : %u\n", k);
+    float rate = (float)n / (float)k;
+    unsigned char msg_org[n];       // original data message
+    unsigned char msg_enc[k];       // encoded data message
+    float complex sym_rec[8*k];     // received BPSK symbols
+    unsigned char msg_cor[k];       // corrupted data message
+    unsigned char msg_dec_per0[n];  // decoded data message (regular decoding)
+    unsigned char msg_dec_per2[n];  // decoded data message (persistent decoding)
+
+    // create object
+    packetizer q = packetizer_create(n,check,fec0,fec1);
+    packetizer_print(q);
+
+    unsigned int packet_errors_per0[num_snr];
+    unsigned int packet_errors_per2[num_snr];
+
+    //
+    // set up parameters
+    //
+    float SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr-1);
+
+    // 
+    // start trials
+    //
+    
+    printf("  %8s %8s [%6s] %8s %8s %8s %8s\n",
+            "SNR [dB]", "Eb/N0", "trials", "per", "(PER)", "reg", "(PER)");
+    unsigned int s;
+    for (s=0; s<num_snr; s++) {
+        // compute SNR for this level
+        float SNRdB = SNRdB_min + s*SNRdB_step; // SNR in dB for this round
+        float nstd = powf(10.0f, -SNRdB/20.0f); // noise standard deviation
+
+        // reset results
+        packet_errors_per0[s] = 0;
+        packet_errors_per2[s] = 0;
+
+        unsigned int t;
+        for (t=0; t<num_trials; t++) {
+            // generate data
+            for (i=0; i<n; i++)
+                msg_org[i] = rand() & 0xff;
+
+            // encode message
+            packetizer_encode(q, msg_org, msg_enc);
+
+            // modulate with BPSK
+            for (i=0; i<k; i++) {
+                sym_rec[8*i+0] = (msg_enc[i] & 0x80) ? 1.0f : -1.0f;
+                sym_rec[8*i+1] = (msg_enc[i] & 0x40) ? 1.0f : -1.0f;
+                sym_rec[8*i+2] = (msg_enc[i] & 0x20) ? 1.0f : -1.0f;
+                sym_rec[8*i+3] = (msg_enc[i] & 0x10) ? 1.0f : -1.0f;
+                sym_rec[8*i+4] = (msg_enc[i] & 0x08) ? 1.0f : -1.0f;
+                sym_rec[8*i+5] = (msg_enc[i] & 0x04) ? 1.0f : -1.0f;
+                sym_rec[8*i+6] = (msg_enc[i] & 0x02) ? 1.0f : -1.0f;
+                sym_rec[8*i+7] = (msg_enc[i] & 0x01) ? 1.0f : -1.0f;
+            }
+
+            // add noise
+            for (i=0; i<8*k; i++)
+                sym_rec[i] += nstd*(randnf() + _Complex_I*randf())*M_SQRT1_2;
+
+            // convert to hard bits (hard decoding)
+            for (i=0; i<k; i++) {
+                msg_cor[i] = 0x00;
+
+                msg_cor[i] |= crealf(sym_rec[8*i+0]) > 0.0f ? 0x80 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+1]) > 0.0f ? 0x40 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+2]) > 0.0f ? 0x20 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+3]) > 0.0f ? 0x10 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+4]) > 0.0f ? 0x08 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+5]) > 0.0f ? 0x04 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+6]) > 0.0f ? 0x02 : 0x00;
+                msg_cor[i] |= crealf(sym_rec[8*i+7]) > 0.0f ? 0x01 : 0x00;
+            }
+
+            // decode
+            packetizer_decode(            q, msg_cor, msg_dec_per0);
+            packetizer_decode_persistent2(q, msg_cor, msg_dec_per2);
+            
+            // tabulate results
+            packet_errors_per0[s] += count_bit_errors_array(msg_org, msg_dec_per0, n) ? 1 : 0;
+            packet_errors_per2[s] += count_bit_errors_array(msg_org, msg_dec_per2, n) ? 1 : 0;
+        }
+
+        // print results for this SNR step
+        printf("  %8.3f %8.3f [%6u] %8u %8.2f %8u %8.2f\n",
+                SNRdB,
+                SNRdB - 10*log10f(rate),
+                num_trials,
+                packet_errors_per2[s], 100.0f * (float)(packet_errors_per2[s]) / (float)(num_trials),
+                packet_errors_per0[s], 100.0f * (float)(packet_errors_per0[s]) / (float)(num_trials) );
+    }
+
+    // clean up objects
+    packetizer_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME, "w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"n = %u;    %% frame size [bytes]\n", n);
+    fprintf(fid,"k = %u;    %% encoded frame size [bytes]\n", k);
+    fprintf(fid,"r = n / k; %% true rate\n");
+    fprintf(fid,"num_snr = %u;\n", num_snr);
+    fprintf(fid,"num_trials = %u;\n", num_trials);
+    for (i=0; i<num_snr; i++) {
+        fprintf(fid,"SNRdB(%4u) = %12.8f;\n",i+1, SNRdB_min + i*SNRdB_step);
+        fprintf(fid,"packet_errors_per0(%6u) = %u;\n", i+1, packet_errors_per0[i]);
+        fprintf(fid,"packet_errors_per2(%6u) = %u;\n", i+1, packet_errors_per2[i]);
+    }
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(SNRdB, packet_errors_per0 / num_trials + 1e-12,  '-x',\n");
+    fprintf(fid,"         SNRdB, packet_errors_per2 / num_trials + 1e-12,  '-x');\n");
+    fprintf(fid,"axis([%f %f 1e-3 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"legend('regular','persistent',1);\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('Packet Error Rate');\n");
+    fprintf(fid,"title('BER vs. SNR for %s//%s//%s, r=%f');\n", fec_scheme_str[fec0][1],
+                                                                fec_scheme_str[fec1][1],
+                                                                crc_scheme_str[check][1],
+                                                                (float)n / (float)k);
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data; persistent attempts to find error(s)
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded bytes)
+//  _msg    :   decoded output message
+int packetizer_decode_persistent(packetizer _p,
+                                 unsigned char * _pkt,
+                                 unsigned char * _msg)
+{
+    // try regular decoding
+    int crc_pass = packetizer_decode(_p, _pkt, _msg);
+
+    // return if decoding was successful
+    if (crc_pass)
+        return crc_pass;
+
+    unsigned int i;
+    unsigned int key=0;
+
+    // result is stored in _p->buffer_0; flip bits to try to get
+    // CRC to pass
+    for (i=0; i<_p->msg_len + _p->crc_length; i++) {
+        unsigned int j;
+        for (j=0; j<8; j++) {
+            // flip bit
+            unsigned char mask = 1 << (8-j-1);
+            _p->buffer_0[i] ^= mask;
+
+            // strip crc, validate message
+            key = 0;
+            unsigned int k;
+            for (k=0; k<_p->crc_length; k++) {
+                key <<= 8;
+
+                key |= _p->buffer_0[_p->msg_len+k];
+            }
+
+            // compute crc validity
+            crc_pass = crc_validate_message(_p->check,
+                                            _p->buffer_0,
+                                            _p->msg_len,
+                                            key);
+
+            // check validity
+            if (crc_pass) {
+                // copy result to output and return
+                memmove(_msg, _p->buffer_0, _p->msg_len);
+                //printf("persistent decoding worked!\n");
+                return crc_pass;
+            } else {
+                // flip bit back
+                _p->buffer_0[i] ^= mask;
+            }
+        }
+    }
+    
+    // copy result to output and return
+    memmove(_msg, _p->buffer_0, _p->msg_len);
+    return crc_pass;
+}
+    
+// double bit errors in a single byte
+//  nchoosek(8,2) = 28
+unsigned char packetizer_persistent_mask2[28] = {
+    0x03,   // 00000011
+    0x06,   // 00000110
+    0x0C,   // 00001100
+    0x18,   // 00011000
+    0x30,   // 00110000
+    0x60,   // 01100000
+    0xC0,   // 11000000
+
+    0x05,   // 00000101
+    0x0A,   // 00001010
+    0x14,   // 00010100
+    0x28,   // 00101000
+    0x50,   // 01010000
+    0xA0,   // 10100000
+
+    0x09,   // 00001001
+    0x12,   // 00010010
+    0x24,   // 00100100
+    0x48,   // 01001000
+    0x90,   // 10010000
+
+    0x11,   // 00010001
+    0x22,   // 00100010
+    0x44,   // 01000100
+    0x88,   // 10001000
+
+    0x21,   // 00100001
+    0x42,   // 01000010
+    0x84,   // 10000100
+
+    0x41,   // 01000001
+    0x82,   // 10000010
+
+    0x81,   // 10000001
+};
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data; persistent attempts to find error(s)
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded bytes)
+//  _msg    :   decoded output message
+int packetizer_decode_persistent2(packetizer _p,
+                                  unsigned char * _pkt,
+                                  unsigned char * _msg)
+{
+    // try first-order persistent decoding
+    int crc_pass = packetizer_decode_persistent(_p, _pkt, _msg);
+
+    // return if decoding was successful
+    if (crc_pass)
+        return crc_pass;
+
+    unsigned int i;
+    unsigned int key=0;
+
+    // result is stored in _p->buffer_0; flip bits to try to get
+    // CRC to pass
+    for (i=0; i<_p->msg_len + _p->crc_length; i++) {
+        unsigned int j;
+        for (j=0; j<28; j++) {
+            // apply mask
+            _p->buffer_0[i] ^= packetizer_persistent_mask2[j];
+
+            // strip crc, validate message
+            key = 0;
+            unsigned int k;
+            for (k=0; k<_p->crc_length; k++) {
+                key <<= 8;
+
+                key |= _p->buffer_0[_p->msg_len+k];
+            }
+
+            // compute crc validity
+            crc_pass = crc_validate_message(_p->check,
+                                            _p->buffer_0,
+                                            _p->msg_len,
+                                            key);
+
+            // check validity
+            if (crc_pass) {
+                // copy result to output and return
+                memmove(_msg, _p->buffer_0, _p->msg_len);
+                //printf("persistent decoding worked! (2)\n");
+                return crc_pass;
+            } else {
+                // flip bit back
+                _p->buffer_0[i] ^= packetizer_persistent_mask2[j];
+            }
+        }
+    }
+    
+    // copy result to output and return
+    memmove(_msg, _p->buffer_0, _p->msg_len);
+    return crc_pass;
+}
+
diff --git a/sandbox/pll_design_test.c b/sandbox/pll_design_test.c
new file mode 100644
index 0000000..86c0955
--- /dev/null
+++ b/sandbox/pll_design_test.c
@@ -0,0 +1,133 @@
+//
+// pll_design_test.c
+// 
+// Demonstrates a basic phase-locked loop to track the phase of a
+// complex sinusoid.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <complex.h>
+#include <math.h>
+#include <time.h>
+
+#include "liquid.h"
+
+// output to octave-friendly format
+#define OUTPUT_FILENAME "pll_design_test.m"
+
+int main() {
+    // parameters
+    float phase_offset = 0.8f;
+    float frequency_offset = 0.01f;
+    float pll_bandwidth = 0.05f;
+    float pll_damping_factor = 0.707f;
+    unsigned int n=256;     // number of iterations
+    unsigned int d=n/32;    // print every "d" lines
+
+    //
+    float theta[n];         // input phase
+    float complex x[n];     // input sinusoid
+    float phi[n];           // output phase
+    float complex y[n];     // output sinusoid
+
+    // generate iir loop filter(s)
+    float a[3];
+    float b[3];
+    float wn = pll_bandwidth;
+    float zeta = pll_damping_factor;
+    float K = 10; // loop gain
+
+#if 0
+    // loop filter (active lag)
+    float t1 = K/(wn*wn);
+    float t2 = 2*zeta/wn - 1/K;
+
+    b[0] = 2*K*(1.+t2/2.0f);
+    b[1] = 2*K*2.;
+    b[2] = 2*K*(1.-t2/2.0f);
+
+    a[0] =  1 + t1/2.0f;
+    a[1] = -t1;
+    a[2] = -1 + t1/2.0f;
+#else
+    // loop filter (active PI)
+    float t1 = K/(wn*wn);
+    float t2 = 2*zeta/wn;
+
+    b[0] = 2*K*(1.+t2/2.0f);
+    b[1] = 2*K*2.;
+    b[2] = 2*K*(1.-t2/2.0f);
+
+    a[0] =  t1/2.0f;
+    a[1] = -t1;
+    a[2] =  t1/2.0f;
+#endif
+    iirfilt_rrrf H = iirfilt_rrrf_create(b,3,a,3);
+    iirfilt_rrrf_print(H);
+
+    unsigned int i;
+
+    // generate input
+    float t=phase_offset;
+    float dt = frequency_offset;
+    for (i=0; i<n; i++) {
+        theta[i] = t;
+        x[i] = cexpf(_Complex_I*theta[i]);
+
+        t += dt;
+    }
+
+    // run loop
+    float phi_hat=0.0f;
+    for (i=0; i<n; i++) {
+        y[i] = cexpf(_Complex_I*phi_hat);
+
+        // compute error
+        float e = cargf(x[i]*conjf(y[i]));
+
+        if ( (i%d)==0 )
+            printf("e(%3u) = %12.8f;\n", i, e);
+
+        // filter error
+        iirfilt_rrrf_execute(H,e,&phi_hat);
+
+        phi[i] = phi_hat;
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(H);
+
+    // open output file
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"n=%u;\n",n);
+
+    fprintf(fid,"a(1) = %16.8e;\n", a[0]);
+    fprintf(fid,"a(2) = %16.8e;\n", a[1]);
+    fprintf(fid,"a(3) = %16.8e;\n", a[2]);
+
+    fprintf(fid,"b(1) = %16.8e;\n", b[0]);
+    fprintf(fid,"b(2) = %16.8e;\n", b[1]);
+    fprintf(fid,"b(3) = %16.8e;\n", b[2]);
+
+    //fprintf(fid,"figure;\n");
+    //fprintf(fid,"freqz(b,a);\n");
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"theta(%3u) = %16.8e;\n", i+1, theta[i]);
+        fprintf(fid,"  phi(%3u) = %16.8e;\n", i+1, phi[i]);
+    }
+    fprintf(fid,"t=0:(n-1);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,theta,t,phi);\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('phase');\n");
+
+    fclose(fid);
+
+    printf("output written to %s.\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/predemod_sync_test.c b/sandbox/predemod_sync_test.c
new file mode 100644
index 0000000..0706bd2
--- /dev/null
+++ b/sandbox/predemod_sync_test.c
@@ -0,0 +1,218 @@
+// 
+// predemod_sync_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <math.h>
+#include <assert.h>
+#include <time.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "predemod_sync_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("predemod_sync_test -- test pre-demodulation synchronization\n");
+    printf("options:\n");
+    printf("  h     : print usage/help\n");
+    printf("  k     : samples/symbol, default: 2\n");
+    printf("  m     : filter delay [symbols], default: 4\n");
+    printf("  n     : number of data symbols, default: 64\n");
+    printf("  b     : bandwidth-time product, (0,1), default: 0.3\n");
+    printf("  t     : fractional sample offset, (-0.5,0.5), default: 0\n");
+    printf("  F     : frequency offset, default: 0\n");
+    printf("  P     : phase offset, default: 0\n");
+    printf("  s     : SNR [dB], default: 30\n");
+}
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+    // options
+    unsigned int k=2;                   // filter samples/symbol
+    unsigned int m=4;                   // filter delay (symbols)
+    float beta=0.3f;                    // bandwidth-time product
+    float dt = 0.0f;                    // fractional sample timing offset
+    unsigned int num_sync_symbols = 64; // number of data symbols
+    float SNRdB = 30.0f;                // signal-to-noise ratio [dB]
+    float dphi = 0.0f;                  // carrier frequency offset
+    float phi  = 0.0f;                  // carrier phase offset
+    
+    unsigned int num_delay_symbols = 12;
+    unsigned int num_dphi_hat = 21;     // number of frequency offset estimates
+    float dphi_hat_step = 0.01f;        // frequency offset step size
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:n:b:t:F:P:s:")) != EOF) {
+        switch (dopt) {
+        case 'h': usage();              return 0;
+        case 'k': k     = atoi(optarg); break;
+        case 'm': m     = atoi(optarg); break;
+        case 'n': num_sync_symbols = atoi(optarg); break;
+        case 'b': beta  = atof(optarg); break;
+        case 't': dt    = atof(optarg); break;
+        case 'F': dphi  = atof(optarg); break;
+        case 'P': phi   = atof(optarg); break;
+        case 's': SNRdB = atof(optarg); break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    // validate input
+    if (beta <= 0.0f || beta >= 1.0f) {
+        fprintf(stderr,"error: %s, bandwidth-time product must be in (0,1)\n", argv[0]);
+        exit(1);
+    } else if (dt < -0.5 || dt > 0.5) {
+        fprintf(stderr,"error: %s, fractional sample offset must be in (0,1)\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int num_symbols = num_delay_symbols + num_sync_symbols + 2*m;
+    unsigned int num_samples = k*num_symbols;
+    unsigned int num_sync_samples = k*num_sync_symbols;
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    // arrays
+    float complex seq[num_sync_symbols];    // data sequence (symbols)
+    float complex s0[num_sync_samples];     // data sequence (interpolated samples)
+    float complex x[num_samples];           // transmitted signal
+    float complex y[num_samples];           // received signal
+    float rxy[num_dphi_hat][num_samples];   // pre-demod output matrix
+
+    // generate sequence
+    for (i=0; i<num_sync_symbols; i++) {
+        float sym_i = rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2;
+        float sym_q = rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2;
+        seq[i] = sym_i + _Complex_I*sym_q;
+    }
+
+    // create interpolated sequence, compensating for filter delay
+    firinterp_crcf interp_seq = firinterp_crcf_create_prototype(LIQUID_FIRFILT_RRC,k,m,beta,0.0f);
+    for (i=0; i<num_sync_symbols+m; i++) {
+        if      (i < m)                firinterp_crcf_execute(interp_seq, seq[i], &s0[0]);
+        else if (i < num_sync_symbols) firinterp_crcf_execute(interp_seq, seq[i], &s0[k*(i-m)]);
+        else                           firinterp_crcf_execute(interp_seq,      0, &s0[k*(i-m)]);
+    }
+    firinterp_crcf_destroy(interp_seq);
+    
+    // compute g = E{ |s0|^2 }
+    float g = 0.0f;
+    for (i=0; i<num_sync_samples; i++)
+        g += crealf( s0[i]*conjf(s0[i]) );
+
+    // create transmit interpolator and generate sequence
+    firinterp_crcf interp_tx = firinterp_crcf_create_prototype(LIQUID_FIRFILT_RRC,k,m,beta,dt);
+    unsigned int n=0;
+    for (i=0; i<num_delay_symbols; i++) {
+        firinterp_crcf_execute(interp_tx, 0, &x[k*n]);
+        n++;
+    }
+    for (i=0; i<num_sync_symbols; i++) {
+        firinterp_crcf_execute(interp_tx, seq[i], &x[k*n]);
+        n++;
+    }
+    for (i=0; i<2*m; i++) {
+        firinterp_crcf_execute(interp_tx, 0, &x[k*n]);
+        n++;
+    }
+    assert(n==num_symbols);
+    firinterp_crcf_destroy(interp_tx);
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i] = x[i]*cexp(_Complex_I*(dphi*i + phi)) + nstd*( randnf() + _Complex_I*randnf() );
+    }
+
+    float complex z;    // filter output sample
+    for (n=0; n<num_dphi_hat; n++) {
+        float dphi_hat = ((float)n - 0.5*(float)(num_dphi_hat-1)) * dphi_hat_step;
+        printf("  dphi_hat : %12.8f\n", dphi_hat);
+
+        // create flipped, conjugated coefficients
+        float complex s1[num_sync_samples];
+        for (i=0; i<num_sync_samples; i++)
+            s1[i] = conjf( s0[num_sync_samples-i-1]*cexpf(_Complex_I*(dphi_hat*i)) );
+
+        // create matched filter and detect signal
+        firfilt_cccf fsync = firfilt_cccf_create(s1, num_sync_samples);
+        for (i=0; i<num_samples; i++) {
+            firfilt_cccf_push(fsync, y[i]);
+            firfilt_cccf_execute(fsync, &z);
+
+            rxy[n][i] = cabsf(z) / g;
+        }
+        // destroy filter
+        firfilt_cccf_destroy(fsync);
+    }
+    
+    // print results
+    //printf("rxy (max) : %12.8f\n", rxy_max);
+
+    // 
+    // export results
+    //
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"k = %u;\n", k);
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"beta = %f;\n", beta);
+    fprintf(fid,"num_sync_symbols = %u;\n", num_sync_symbols);
+    fprintf(fid,"num_sync_samples = k*num_sync_symbols;\n");
+    fprintf(fid,"num_symbols = %u;\n", num_symbols);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"num_dphi_hat = %u;\n", num_dphi_hat);
+    fprintf(fid,"dphi_hat_step = %f;\n", dphi_hat_step);
+
+    // save sequence symbols
+    fprintf(fid,"seq = zeros(1,num_sync_symbols);\n");
+    for (i=0; i<num_sync_symbols; i++)
+        fprintf(fid,"seq(%4u)   = %12.8f + j*%12.8f;\n", i+1, crealf(seq[i]), cimagf(seq[i]));
+
+    // save interpolated sequence
+    fprintf(fid,"s   = zeros(1,num_sync_samples);\n");
+    for (i=0; i<num_sync_samples; i++)
+        fprintf(fid,"s(%4u)     = %12.8f + j*%12.8f;\n", i+1, crealf(s0[i]), cimagf(s0[i]));
+
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    fprintf(fid,"y = zeros(1,num_samples);\n");
+    for (i=0; i<num_samples; i++) {
+        fprintf(fid,"x(%6u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]),   cimagf(x[i]));
+        fprintf(fid,"y(%6u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]),   cimagf(y[i]));
+    }
+
+    // save cross-correlation output
+    fprintf(fid,"rxy = zeros(num_dphi_hat,num_samples);\n");
+    for (n=0; n<num_dphi_hat; n++) {
+        for (i=0; i<num_samples; i++) {
+            fprintf(fid,"rxy(%6u,%6u) = %12.8f;\n", n+1, i+1, rxy[n][i]);
+        }
+    }
+    fprintf(fid,"t=[0:(num_samples-1)]/k;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:length(s),real(s), 1:length(s),imag(s));\n");
+    
+    fprintf(fid,"dphi_hat = ( [0:(num_dphi_hat-1)] - (num_dphi_hat-1)/2 ) * dphi_hat_step;\n");
+    fprintf(fid,"mesh(dphi_hat, t, rxy');\n");
+    
+#if 0
+    fprintf(fid,"z = abs( z );\n");
+    fprintf(fid,"[zmax i] = max(z);\n");
+    fprintf(fid,"plot(1:length(z),z,'-x');\n");
+    fprintf(fid,"axis([(i-8*k) (i+8*k) 0 zmax*1.2]);\n");
+    fprintf(fid,"grid on\n");
+#endif
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    return 0;
+}
diff --git a/sandbox/quasinewton_test.c b/sandbox/quasinewton_test.c
new file mode 100644
index 0000000..6d130b8
--- /dev/null
+++ b/sandbox/quasinewton_test.c
@@ -0,0 +1,427 @@
+// 
+// quasinewton_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.h"
+
+#define DEBUG_BFGS 0
+
+// utility function pointer
+typedef float (*utility)(float * _x, unsigned int _n);
+
+// utility function
+float myutility(float * _x, unsigned int _n);
+
+// useful function to print vector to screen
+void print_vector(const char * _str, float * _v, unsigned int _n);
+
+// estimate gradient at point _x
+//  _x          :   input point, [size: _n x 1]
+//  _n          :   dimension
+//  _u          :   utility function pointer
+//  _gradient   :   resulting gradient (normalized?)
+void estimate_gradient(float * _x,
+                       unsigned int _n,
+                       utility _u,
+                       float * _gradient);
+
+// update BFGS estimate of Hessian matrix inverse
+//  _n      :   dimension
+//  _dx     :   step size (previous), [size: _n x 1]
+//  _y      :   gradient difference
+//  _H0     :   Hessian matrix inverse estimate (previous), [size: _n x _n]
+//  _H1     :   Hessian matrix inverse estimate [size: _n x _n]
+void bfgs_update(unsigned int _n,
+                 float * _dx,
+                 float * _y,
+                 float * _H0,
+                 float * _H1);
+
+// check the strong Wolfe conditions (c1=0.0001, c2=0.9)
+//  _alpha  :   step length
+//  _u0     :   f(x)
+//  _u1     :   f(x + alpha*p)
+//  _gamma0 :   p'*grad(x) (NOTE: should be less than zero)
+//  _gamma1 :   p'*grad(x + alpha*p)
+int check_wolfe_conditions(float _alpha,
+                           float _u0,
+                           float _u1,
+                           float _gamma0,
+                           float _gamma1);
+
+// check Wolfe conditions
+//  _x      :   input vector [size: _n x 1]
+//  _p      :   step direction vector [size: _n x 1]
+//  _n      :   dimension
+//  _alpha  :   test step length
+//  _u      :   utility function pointer
+int check_wolfe_conditions2(float * _x,
+                            float * _p,
+                            unsigned int _n,
+                            float _alpha,
+                            utility _u);
+
+int main() {
+    // options
+    unsigned int n = 4;                 // search dimension
+    unsigned int num_iterations = 10;   // number of steps
+
+    // allocate memory for arrays
+    float x[n];     // search vector
+    float p[n];     // direction
+    float dx[n];    // search vector step
+    float gradient0[n];
+    float gradient1[n];
+    float y[n];
+    float H0[n*n];
+    float H1[n*n];
+    float alpha = 0.2f;
+
+    unsigned int i;
+    for (i=0; i<n; i++) x[i] = 0.5f;
+
+    // initialize gradient
+    for (i=0; i<n; i++) gradient1[i] = 0.0f;
+
+    // initialize Hessian... as identity matrix
+    matrixf_eye(H0, n);
+    matrixf_eye(H1, n);
+
+    // compute initial gradient estimate
+    estimate_gradient(x, n, myutility, gradient1);
+
+    // run search
+    printf("\n");
+
+    // print status
+    float u = myutility(x, n);
+    printf(" -   : x =       {");
+    for (i=0; i<n; i++) printf("%8.4f ", x[i]);
+    printf("} : %12.8f\n", u);
+
+
+    unsigned int t;
+    for (t=0; t<num_iterations; t++) {
+        // copy old gradient estimate
+        memmove(gradient0, gradient1, n*sizeof(float));
+        memmove(H0, H1, n*n*sizeof(float));
+
+        // compute direction
+        matrixf_mul(H0,        n, n,
+                    gradient0, n, 1,
+                    p,         n, 1);
+        for (i=0; i<n; i++) p[i] = -p[i];
+
+        // coarse search for alpha...
+        // TODO : improve this search!!!
+        float alpha_min = 0.0f;
+        float du_min = 0.0f;
+        float u0 = myutility(x, n);
+        for (i=0; i<100; i++) {
+            alpha = 0.001f + 0.01f*i;
+            float x_prime[n];
+            unsigned int j;
+            for (j=0; j<n; j++)
+                x_prime[j] = x[j] + alpha*p[j];
+            float u1 = myutility(x_prime, n);
+            float du = u1 - u0;
+            if (i==0 || du < du_min) {
+                alpha_min = alpha;
+                du_min = du;
+            }
+        }
+        alpha = alpha_min;
+        //printf("  alpha = %12.8f\n", alpha);
+        // test Wolfe conditions
+        if (check_wolfe_conditions2(x,p,n,alpha,myutility)==0)
+            printf("warning: Wolfe conditions failed\n");
+
+
+        // compute step size
+        for (i=0; i<n; i++) dx[i] = alpha*p[i];
+
+        // step x
+        for (i=0; i<n; i++) x[i] += dx[i];
+
+        // compute gradient estimate
+        estimate_gradient(x, n, myutility, gradient1);
+
+        // update y
+        for (i=0; i<n; i++) y[i] = gradient1[i] - gradient0[i];
+
+        // update H1
+        bfgs_update(n, dx, y, H0, H1);
+
+        // print status
+        u = myutility(x, n);
+        printf(" %-3u : x =       {", t);
+        for (i=0; i<n; i++) printf("%8.4f ", x[i]);
+        printf("} : %12.8f\n", u);
+
+#if 0
+        print_vector("       gradient0", gradient0, n);
+        print_vector("       gradient1", gradient1, n);
+        print_vector("       p        ", p, n);
+        print_vector("       dx       ", dx, n);
+        print_vector("       y        ", y, n);
+        printf("H0 =\n"); matrixf_print(H0, n, n);
+        printf("H1 =\n"); matrixf_print(H1, n, n);
+#endif
+    }
+
+
+    printf("done.\n");
+    return 0;
+}
+
+// my utility function
+float myutility(float * _x, unsigned int _n)
+{
+#if 0
+    float u = 1.0f;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        float di = _x[i] - 1.0f;
+        float ui = expf(-di*di);
+        u *= ui;
+    }
+
+    return 1.0f - u;
+#else
+    return liquid_rosenbrock(NULL, _x, _n);
+#endif
+}
+
+// useful function to print vector to screen
+void print_vector(const char * _str, float * _v, unsigned int _n)
+{
+    printf("%s {", _str);
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%8.4f ", _v[i]);
+    printf("}\n");
+}
+
+// estimate gradient at point _x
+//  _x          :   input point, [size: _n x 1]
+//  _n          :   dimension
+//  _u          :   utility function pointer
+//  _gradient   :   resulting gradient (normalized?)
+void estimate_gradient(float * _x,
+                       unsigned int _n,
+                       utility _u,
+                       float * _gradient)
+{
+    // temporary array
+    float x_prime[_n];
+    float dx = 1e-6f;
+
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        x_prime[i] = _x[i];
+    
+    // evaluate at nominal point
+    float f0 = _u(x_prime, _n);
+
+    // evaluate 
+    float f1 = 0.0f;
+    for (i=0; i<_n; i++) {
+        // step along dimension i
+        x_prime[i] = _x[i] + dx;
+
+        // evaluate
+        f1 = _u(x_prime, _n);
+
+        // reset
+        x_prime[i] = _x[i];
+
+        // compute derivative
+        _gradient[i] = (f1 - f0) / dx;
+    }
+
+    // normalize derivative?
+}
+
+// update BFGS estimate of Hessian matrix inverse
+//  _n      :   dimension
+//  _dx     :   step size (previous), [size: _n x 1]
+//  _y      :   gradient difference
+//  _H0     :   Hessian matrix inverse estimate (previous), [size: _n x _n]
+//  _H1     :   Hessian matrix inverse estimate [size: _n x _n]
+void bfgs_update(unsigned int _n,
+                 float * _dx,
+                 float * _y,
+                 float * _H0,
+                 float * _H1)
+{
+#if DEBUG_BFGS
+    // print inputs
+    printf("********************\n");
+    print_vector("  dx      ", _dx, _n);
+    print_vector("  y       ", _y, _n);
+    printf("H0 = \n");
+    matrixf_print(_H0,_n,_n);
+#endif
+    // 
+    //float In[_n*_n];        // I(_n)                        [_n x _n]
+    float ydxT[_n*_n];      // _y * _dx'                    [_n x _n]
+    float yTdx;             // _y' * _dx                    [1  x  1]
+    float q[_n*_n];         // I(_n) - (_y*_dx' / _y'_dx)   [_n x _n]
+    float dxdxT[_n*_n];     // _dx * _dx'                   [_n x _n]
+    float tmp[_n*_n];       // temporary array              [_n x _n]
+
+    // compute _y * _dx'
+    matrixf_mul(_y,     _n,     1,
+                _dx,    1,      _n,
+                ydxT,   _n,     _n);
+#if DEBUG_BFGS
+    printf("y*dx' = \n");
+    matrixf_print(ydxT,_n,_n);
+#endif
+
+
+    // compute _y' * _dx'
+    matrixf_mul(_y,     1,      _n,
+                _dx,    _n,     1,
+                &yTdx,  1,      1);
+
+    // compute _n x _n identity matrix
+
+    // compute q
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_n; i++) {
+        for (j=0; j<_n; j++) {
+            matrix_access(q,_n,_n,i,j) = (i==j ? 1.0f : 0.0f) -
+                                         matrix_access(ydxT,_n,_n,i,j)/yTdx;
+        }
+    }
+#if DEBUG_BFGS
+    printf("q = \n");
+    matrixf_print(q,_n,_n);
+#endif
+
+    // compute _dx*_dx'
+    matrixf_mul(_dx,   _n,  1,
+                _dx,   1,  _n,
+                dxdxT, _n, _n);
+#if DEBUG_BFGS
+    printf("dx*dx' = \n");
+    matrixf_print(dxdxT,_n,_n);
+#endif
+
+    // compute _H0 * q, store in tmp
+    matrixf_mul(_H0, _n, _n,
+                q,   _n, _n,
+                tmp, _n, _n);
+
+    // compute q' (transpose)
+    matrixf_trans(q, _n, _n);
+
+    // compute q' * _H0 * _q, store in _H1
+    matrixf_mul(q,   _n, _n,
+                tmp, _n, _n,
+                _H1, _n, _n);
+
+    // add dxdxT / yTdx to _H1
+    for (i=0; i<_n; i++) {
+        for (j=0; j<_n; j++) {
+            matrix_access(_H1,_n,_n,i,j) += matrix_access(dxdxT,_n,_n,i,j) / yTdx;
+        }
+    }
+
+#if DEBUG_BFGS
+    printf("H1 = \n");
+    matrixf_print(_H1,_n,_n);
+#endif
+
+}
+
+// check the strong Wolfe conditions (c1=0.0001, c2=0.9)
+//  _alpha  :   step length
+//  _u0     :   f(x)
+//  _u1     :   f(x + alpha*p)
+//  _gamma0 :   p'*grad(x) (NOTE: should be less than zero)
+//  _gamma1 :   p'*grad(x + alpha*p)
+int check_wolfe_conditions(float _alpha,
+                           float _u0,
+                           float _u1,
+                           float _gamma0,
+                           float _gamma1)
+{
+    // TODO : validate input
+    // _alpha > 0
+    // _gamma < 0
+
+    //
+    float c1 = 1e-4f;
+    float c2 = 0.9f;
+
+    // check first condition
+    int cond1 = (_u1 <= _u0 + c1*_alpha*_gamma0) ? 1 : 0;
+
+    // check second condition
+    int cond2 = ( fabsf(_gamma1) <= c2*fabsf(_gamma0) ) ? 1 : 0;
+
+#if 0
+    printf("(g0: %12.8f g1: %12.8f) [%c %c]",
+            _gamma0,
+            _gamma1,
+            cond1 ? '1' : '0',
+            cond2 ? '1' : '0');
+#endif
+
+    //
+    return cond1 && cond2;
+}
+
+// check Wolfe conditions
+//  _x      :   input vector [size: _n x 1]
+//  _p      :   step direction vector [size: _n x 1]
+//  _n      :   dimension
+//  _alpha  :   test step length
+//  _u      :   utility function pointer
+int check_wolfe_conditions2(float * _x,
+                            float * _p,
+                            unsigned int _n,
+                            float _alpha,
+                            utility _u)
+{
+    // allocate temporary arrays
+    float grad0[_n];
+    float grad1[_n];
+    float x_prime[_n];
+
+    // compute f(x)
+    float u0 = _u(_x,_n);
+
+    // compute gradient(f(x))
+    estimate_gradient(_x,_n,_u,grad0);
+
+    // compute f(x + alpha*p)
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        x_prime[i] = _x[i] + _alpha*_p[i];
+    float u1 = _u(x_prime, _n);
+
+    // compute gradient(f(x + alpha*p))
+    estimate_gradient(x_prime, _n, _u, grad1);
+
+    // compute p'*grad0
+    float gamma0 = 0.0f;
+    for (i=0; i<_n; i++)
+        gamma0 += _p[i] * grad0[i];
+
+    // compute p'*grad1
+    float gamma1 = 0.0f;
+    for (i=0; i<_n; i++)
+        gamma1 += _p[i] * grad1[i];
+
+    // test Wolfe conditions
+    return check_wolfe_conditions(_alpha, u0, u1, gamma0, gamma1);
+}
+
diff --git a/sandbox/recursive_qpsk_test.c b/sandbox/recursive_qpsk_test.c
new file mode 100644
index 0000000..f550577
--- /dev/null
+++ b/sandbox/recursive_qpsk_test.c
@@ -0,0 +1,150 @@
+// 
+// recursive_qpsk_test.c
+//
+// Run recursive QPSK modulation/demodulation test.
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "recursive_qpsk_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("%s [options]\n", __FILE__);
+    printf("  -h        : print usage\n");
+    printf("  -b<beta>  : beta value for constellation, beta in (0,1)\n");
+}
+
+int main(int argc, char*argv[])
+{
+    // create mod/demod objects
+    float           beta        = 0.25f;
+    float           SNRdB_min   = 0.0f;
+    float           SNRdB_max   = 30.0f;
+    unsigned int    num_snr     = 31;
+    unsigned int    num_trials  = 1e6;
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"hb:")) != EOF) {
+        switch (dopt) {
+        case 'h':   usage();                return 0;
+        case 'b':   beta = atof(optarg);    break;
+        default:
+            exit(1);
+        }
+    }
+
+    unsigned int i;
+
+    float complex map_qpsk[4] = {
+        M_SQRT1_2 + _Complex_I*M_SQRT1_2,   // 00
+       -M_SQRT1_2 + _Complex_I*M_SQRT1_2,   // 01
+        M_SQRT1_2 - _Complex_I*M_SQRT1_2,   // 10
+       -M_SQRT1_2 - _Complex_I*M_SQRT1_2,   // 11
+    };
+
+    // generate composite constellation
+    float complex map[16];
+    for (i=0; i<4; i++) {
+        map[4*i+0] = map_qpsk[i] + beta*map_qpsk[0];
+        map[4*i+1] = map_qpsk[i] + beta*map_qpsk[1];
+        map[4*i+2] = map_qpsk[i] + beta*map_qpsk[2];
+        map[4*i+3] = map_qpsk[i] + beta*map_qpsk[3];
+    }
+
+    // create arbitrary modem
+    modem mod = modem_create_arbitrary(map, 16);
+
+    // open file for writing
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    unsigned int    sym_tx_0;
+    unsigned int    sym_tx_1;
+    unsigned int    sym_tx;
+    float complex   s;
+    unsigned int    sym_rx;
+    unsigned int    sym_rx_0;
+    unsigned int    sym_rx_1;
+    unsigned int    num_bit_errors_0 = 0;
+    unsigned int    num_bit_errors_1 = 0;
+
+    // print constellation
+    fprintf(fid,"map = zeros(1,16);\n");
+    for (i=0; i<16; i++) {
+        modem_modulate(mod, i, &s);
+        fprintf(fid,"map(%2u) = %12.4e + %12.4ei;\n", i+1, crealf(s), cimagf(s));
+    }
+
+    unsigned int n;
+    float complex SNRdB_step = (SNRdB_max - SNRdB_min) / (num_snr - 1);
+    for (n=0; n<num_snr; n++) {
+        float SNRdB = SNRdB_min + n*SNRdB_step;
+        float nstd  = powf(10.0f, -SNRdB/20.0f);
+
+        num_bit_errors_0 = 0;
+        num_bit_errors_1 = 0;
+
+        for (i=0; i<num_trials; i++) {
+            // generate independent streams, each 2 bits long
+            sym_tx_0 = rand() & 0x3;
+            sym_tx_1 = rand() & 0x3;
+
+            // composite
+            sym_tx = (sym_tx_0 << 2) | sym_tx_1;
+
+            // modulate
+            modem_modulate(mod, sym_tx, &s);
+            
+            // add noise
+            s += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+            // demodulate
+            modem_demodulate(mod, s, &sym_rx);
+
+            // recover streams
+            sym_rx_0 = (sym_rx >> 2) & 0x3;
+            sym_rx_1 = (sym_rx     ) & 0x3;
+
+            // accumulate errors
+            num_bit_errors_0 += count_bit_errors(sym_tx_0, sym_rx_0);
+            num_bit_errors_1 += count_bit_errors(sym_tx_1, sym_rx_1);
+        }
+        float BER_QPSK = 0.5f*erfcf(powf(10.0f,SNRdB/20.0f)*M_SQRT1_2);
+        float BER_0    = num_bit_errors_0 / (float)(2*num_trials);
+        float BER_1    = num_bit_errors_1 / (float)(2*num_trials);
+
+        // print results to screen
+        printf("%3u : SNR = %6.3f dB, {%8u %8u} / %8u, QPSK: %12.4e, BER : {%12.4e %12.4e}\n",
+                n, SNRdB, num_bit_errors_0, num_bit_errors_1, 2*num_trials, BER_QPSK, BER_0, BER_1);
+
+        // print results to file
+        fprintf(fid,"SNRdB(%3u)=%6.3f; BER_QPSK(%3u)=%12.4e; BER_0(%3u)=%12.4e; BER_1(%3u)=%12.4e;\n",
+                n+1, SNRdB, n+1, BER_QPSK, n+1, BER_0, n+1, BER_1);
+    }
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"semilogy(SNRdB,BER_QPSK+1e-12,SNRdB,BER_0+1e-12,SNRdB,BER_1+1e-12);\n");
+    fprintf(fid,"axis([%.3f %.3f 1e-5 1]);\n", SNRdB_min, SNRdB_max);
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('SNR [dB]');\n");
+    fprintf(fid,"ylabel('BER');\n");
+    fprintf(fid,"legend('qpsk  ', 'stream 0  ', 'stream 1  ', 'location', 'northeast');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    // destroy modem object
+    modem_destroy(mod);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/resamp2_crcf_filterbank_test.c b/sandbox/resamp2_crcf_filterbank_test.c
new file mode 100644
index 0000000..7d55e46
--- /dev/null
+++ b/sandbox/resamp2_crcf_filterbank_test.c
@@ -0,0 +1,135 @@
+//
+// resamp2_crcf_filterbank_test.c
+//
+// Halfband (two-channel) filterbank example. This example demonstrates
+// the analyzer/synthesizer execute() methods for the resamp2_xxxt
+// family of objects.
+//
+// NOTE: The filterbank is not a perfect reconstruction filter; a
+//       significant amount of distortion occurs in the transition band
+//       of the half-band filters.
+//
+// SEE ALSO: resamp2_crcf_decim_example.c
+//           resamp2_crcf_interp_example.c
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_filterbank_example.m"
+
+int main() {
+    unsigned int m=9;               // filter semi-length
+    unsigned int num_samples=128;   // number of input samples
+    float As=60.0f;                 // stop-band attenuation [dB]
+
+    // derived values
+    unsigned int h_len = 4*m+1;     // half-band filter length
+    unsigned int N = num_samples + h_len;
+
+    // ensure N is even
+    N += (N%2);
+    unsigned int n = N/2;
+
+    // arrays
+    float complex x[N];             // input time series
+    float complex y[n][2];          // output time series (channelized)
+    float complex z[N];             // output time series
+
+    // generate input sequence
+    unsigned int i;
+    for (i=0; i<N; i++) {
+        //x[i] = randnf() * cexpf(_Complex_I*M_PI*randf());
+        x[i] = cexpf(_Complex_I*2*M_PI*0.072f*i) + 0.6f*cexpf(_Complex_I*2*M_PI*0.37f*i);
+        //x[i] = cexpf(_Complex_I*2*M_PI*0.072f*i);
+        //x[i] += 0.6*cexpf(_Complex_I*2*M_PI*0.572f*i);
+        x[i] *= (i < num_samples) ? hamming(i,num_samples) : 0.0f;
+    }
+
+    // create/print the half-band filter with a specified
+    // stop-band attenuation
+    resamp2_crcf q = resamp2_crcf_create(m,0.0f,As);
+    resamp2_crcf_print(q);
+
+    // run the resampler as a two-channel analysis filterbank
+    for (i=0; i<n; i++)
+        resamp2_crcf_analyzer_execute(q, &x[2*i], &y[i][0]);
+
+    // clear resampler
+    resamp2_crcf_clear(q);
+
+    // run the resampler as a two-channel synthesis filterbank
+    for (i=0; i<n; i++)
+        resamp2_crcf_synthesizer_execute(q, &y[i][0], &z[2*i]);
+
+    // clean up allocated objects
+    resamp2_crcf_destroy(q);
+
+    // 
+    // export output file
+    //
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"num_samples = %u;\n", num_samples);
+    fprintf(fid,"N = %u;\n", N);
+
+    // save results to output file
+    for (i=0; i<n; i++) {
+        fprintf(fid,"x(%3u)  = %12.4e + j*%12.4e;\n", 2*i+1, crealf(x[2*i+0]), cimagf(x[2*i+0]));
+        fprintf(fid,"x(%3u)  = %12.4e + j*%12.4e;\n", 2*i+2, crealf(x[2*i+1]), cimagf(x[2*i+1]));
+
+        fprintf(fid,"y0(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i][0]), cimagf(y[i][0]));
+        fprintf(fid,"y1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i][1]), cimagf(y[i][1]));
+
+        fprintf(fid,"z(%3u)  = %12.4e + j*%12.4e;\n", 2*i+1, crealf(z[2*i+0]), cimagf(z[2*i+0]));
+        fprintf(fid,"z(%3u)  = %12.4e + j*%12.4e;\n", 2*i+2, crealf(z[2*i+1]), cimagf(z[2*i+1]));
+    }
+
+    fprintf(fid,"tx = 0:(N-1);\n");
+    fprintf(fid,"ty = tx(1:2:end);\n");
+    fprintf(fid,"tz = tx - 4*m + 1;\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(tx, real(x), 'LineWidth',1,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"       tz, real(z), 'LineWidth',1,'Color',[0.00 0.25 0.50]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('real');\n");
+    fprintf(fid,"  legend('original','reconstructed',1);");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(tx, imag(x), 'LineWidth',1,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"       tz, imag(z), 'LineWidth',1,'Color',[0.00 0.50 0.25]);\n");
+    fprintf(fid,"  xlabel('time');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  legend('original','reconstructed',1);");
+
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"g = 1 / sqrt( real(x*x') );\n");
+    fprintf(fid,"X =20*log10(abs(fftshift(fft(x*g, nfft))));\n");
+    fprintf(fid,"Y0=20*log10(abs(fftshift(fft(y0*g,nfft))));\n");
+    fprintf(fid,"Y1=20*log10(abs(fftshift(fft(y1*g,nfft))));\n");
+    fprintf(fid,"Z =20*log10(abs(fftshift(fft(z*g, nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,X, 'LineWidth',2,'Color',[0.50 0.50 0.50],...\n");
+    fprintf(fid,"     f,Z, 'LineWidth',1,'Color',[0.00 0.25 0.50]);\n");
+    //fprintf(fid,"     f,Y0,'LineWidth',1,'Color',[0.00 0.25 0.50],...\n");
+    //fprintf(fid,"     f,Y1,'LineWidth',1,'Color',[0.00 0.50 0.25]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','reconstructed',2);");
+    fprintf(fid,"axis([-0.5 0.5 -80 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/resamp2_crcf_interp_recreate_test.c b/sandbox/resamp2_crcf_interp_recreate_test.c
new file mode 100644
index 0000000..2193ac4
--- /dev/null
+++ b/sandbox/resamp2_crcf_interp_recreate_test.c
@@ -0,0 +1,93 @@
+//
+// resamp2_crcf_interp_recreate_test.c
+//
+// Halfband interpolator (complex), recreate() example
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "resamp2_crcf_interp_recreate_test.m"
+
+int main() {
+    unsigned int m0=5;
+    unsigned int m1=4;
+    unsigned int h0_len = 4*m0+1; // initial filter length
+    unsigned int h1_len = 4*m1+1; // filter length (after recreate)
+    float fc=0.10f;
+    unsigned int N=32;
+    float As=60.0f;
+
+    resamp2_cccf f = resamp2_cccf_create(h0_len,0.0f,As);
+
+    resamp2_cccf_print(f);
+
+    FILE*fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",OUTPUT_FILENAME);
+    fprintf(fid,"clear all;\nclose all;\n\n");
+    fprintf(fid,"h0_len = %u;\n", h0_len);
+    fprintf(fid,"h1_len = %u;\n", h1_len);
+    fprintf(fid,"N      = %u;\n", N);
+
+    unsigned int i;
+    float theta=0.0f, dtheta=2*M_PI*fc;
+    unsigned int ix=0, iy=0;
+    float complex x, y[2];
+    for (i=0; i<N/2; i++) {
+        x = cexpf(_Complex_I*theta);
+        theta += dtheta;
+
+        resamp2_cccf_interp_execute(f, x, y);
+
+        ix++;
+        iy++;
+        fprintf(fid,"x(%3u) = %8.4f + j*%8.4f;\n", ix+1,   crealf(x),    cimagf(x));
+        fprintf(fid,"y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+1, crealf(y[0]), cimagf(y[0]));
+        fprintf(fid,"y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+2, crealf(y[1]), cimagf(y[1]));
+
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+1, crealf(y[0]), cimagf(y[0]));
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+2, crealf(y[1]), cimagf(y[1]));
+    }
+
+    // re-create interpolator
+    printf("recreating filter...\n");
+    f = resamp2_cccf_recreate(f,h1_len,0.0f,As);
+
+    // TODO: push additional values?
+
+
+    for (i=0; i<N/2; i++) {
+        x = cexpf(_Complex_I*theta);
+        theta += dtheta;
+
+        resamp2_cccf_interp_execute(f, x, y);
+
+        ix++;
+        iy++;
+        fprintf(fid,"x(%3u) = %8.4f + j*%8.4f;\n", ix+1,   crealf(x),    cimagf(x));
+        fprintf(fid,"y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+1, crealf(y[0]), cimagf(y[0]));
+        fprintf(fid,"y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+2, crealf(y[1]), cimagf(y[1]));
+
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+1, crealf(y[0]), cimagf(y[0]));
+        printf("y(%3u) = %8.4f + j*%8.4f;\n", 2*iy+2, crealf(y[1]), cimagf(y[1]));
+    }
+
+
+    fprintf(fid,"nfft=512;\n");
+    fprintf(fid,"X=20*log10(abs(fftshift(fft(x.*hamming(length(x))',nfft))));\n");
+    fprintf(fid,"Y=20*log10(abs(fftshift(fft(y.*hamming(length(y))',nfft))));\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure; plot(f/2,X,'Color',[0.5 0.5 0.5],f,Y,'LineWidth',2);\n");
+    fprintf(fid,"grid on;\nxlabel('normalized frequency');\nylabel('PSD [dB]');\n");
+    fprintf(fid,"legend('original','interpolated',1);");
+
+    fclose(fid);
+    printf("results written to %s\n",OUTPUT_FILENAME);
+
+    resamp2_cccf_destroy(f);
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/reverse_byte_gentab.c b/sandbox/reverse_byte_gentab.c
new file mode 100644
index 0000000..732ba45
--- /dev/null
+++ b/sandbox/reverse_byte_gentab.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// generate table for byte reversal
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// slow implementation of byte reversal
+unsigned char reverse_byte(unsigned char _x)
+{
+    unsigned char y = 0x00;
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        y <<= 1;
+        y |= _x & 1;
+        _x >>= 1;
+    }
+    return y;
+}
+
+
+int main()
+{
+    printf("// auto-generated file (do not edit)\n");
+    printf("\n");
+    printf("// reverse byte table\n");
+    printf("unsigned const char liquid_reverse_byte[256] = {\n    ");
+    unsigned int i;
+    for (i=0; i<256; i++) {
+        // reverse byte
+        unsigned char byte_rev = reverse_byte((unsigned char)i);
+
+        // print results
+        printf("0x%.2x", byte_rev);
+
+        if ( i != 255 ) {
+            printf(",");
+            if ( ((i+1)%8)==0 )
+                printf("\n    ");
+            else
+                printf(" ");
+        }
+
+    }
+    printf("};\n\n");
+    
+    return 0;
+}
diff --git a/sandbox/rkaiser2_test.c b/sandbox/rkaiser2_test.c
new file mode 100644
index 0000000..51d2bf8
--- /dev/null
+++ b/sandbox/rkaiser2_test.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Design root-Nyquist Kaiser filter
+//
+// References
+//  [Vaidyanathan:1993] Vaidyanathan, P. P., "Multirate Systems and
+//      Filter Banks," 1993, Prentice Hall, Section 3.2.1
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG 0
+#define OUTPUT_FILENAME "rkaiser2_test.m"
+
+struct gsuserdata_s {
+    unsigned int k; // samples/symbol
+    unsigned int m; // filter delay (symbols)
+    float beta;     // excess bandwidth factor
+    float dt;       // fractional delay
+
+    float w0;       // weighting for ISI
+    float w1;       // weighting for stop-band attenuation
+};
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+void liquid_firdes_rkaiser_filter2(unsigned int _k,
+                                   unsigned int _m,
+                                   float        _beta,
+                                   float        _dt,
+                                   float *      _h);
+
+// gradient search utility
+float gs_utility(void * _userdata,
+                 float * _h,
+                 unsigned int _n);
+
+int main() {
+    // options
+    unsigned int k=2;
+    unsigned int m=5;
+    float beta = 0.3;
+    float dt = 0.0;
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;
+    float h1[h_len];
+    float h2[h_len];
+
+    liquid_firdes_rkaiser(k,m,beta,dt,h1);
+    liquid_firdes_rkaiser_filter2(k,m,beta,dt,h2);
+
+    // print filters
+
+    // export results
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", OUTPUT_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    fprintf(fid,"\n");
+    fprintf(fid,"k  = %u;\n", k);
+    fprintf(fid,"m  = %u;\n", m);
+    fprintf(fid,"beta = %12.8f;\n", beta);
+    fprintf(fid,"n = 2*k*m+1;\n");
+    fprintf(fid,"h1 = zeros(1,n);\n");
+    fprintf(fid,"h2 = zeros(1,n);\n");
+
+    unsigned int i;
+    for (i=0; i<h_len; i++) {
+        fprintf(fid,"  h1(%3u) = %12.4e;\n", i+1, h1[i]);
+        fprintf(fid,"  h2(%3u) = %12.4e;\n", i+1, h2[i]);
+    }
+    fprintf(fid,"\n");
+    fprintf(fid,"nfft=1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"H1 = 20*log10(abs(fftshift(fft(h1/k,nfft))));\n");
+    fprintf(fid,"H2 = 20*log10(abs(fftshift(fft(h2/k,nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H1, f,H2);\n");
+    fprintf(fid,"axis([-0.5 0.5 -100 20]);\n");
+    fprintf(fid,"grid on\n");
+    fprintf(fid,"legend('r-Kaiser','r-Kaiser(2)',1);\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", OUTPUT_FILENAME);
+
+    printf("done.\n");
+    return 0;
+}
+
+// liquid_firdes_rkaiser()
+//
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+void liquid_firdes_rkaiser_filter2(unsigned int _k,
+                                   unsigned int _m,
+                                   float        _beta,
+                                   float        _dt,
+                                   float *      _h)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), k must be at least 2\n");
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), m must be at least 1\n");
+        exit(1);
+    } else if (_beta <= 0.0f || _beta >= 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), beta must be in (0,1)\n");
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), dt must be in [-1,1]\n");
+        exit(1);
+    }
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    // 
+#if 0
+    float fc = 0.5*(1.0 + _beta)/(float)(_k);
+    float As = 40.0f;
+#else
+    float rho_hat = rkaiser_approximate_rho(_m,_beta);
+    float fc = 0.5f*(1 + _beta*(1.0f-rho_hat))/(float)_k; // filter cutoff
+    float del = _beta*rho_hat / (float)_k;                // transition bandwidth
+    float As = estimate_req_filter_As(del, h_len);  // stop-band suppression
+
+    // reduce slightly to help ensure solution isn't stuck
+    // (not really necessary)
+    fc *= 0.995f;
+    As -= 1.0f;
+#endif
+    float v[2] = {fc, As/1000};
+
+    struct gsuserdata_s q = {
+        .k    = _k,
+        .m    = _m,
+        .beta = _beta,
+        .dt   = _dt,
+        .w0   = 0.5f,
+        .w1   = 0.5f};
+
+    // create gradsearch object
+    gradsearch gs = gradsearch_create((void*)&q,
+                                      v, 2,       // vector optimizer
+                                      gs_utility,
+                                      LIQUID_OPTIM_MINIMIZE);
+
+    // run search
+    unsigned int i;
+    unsigned int num_iterations = 100;
+    for (i=0; i<num_iterations; i++) {
+        // compute utility for plotting
+        //float utility = gs_utility((void*)&q,v,2);
+
+        gradsearch_step(gs);
+
+        gradsearch_print(gs);
+    }
+
+    // destroy gradient search object
+    gradsearch_destroy(gs);
+
+    // re-design filter and return
+    fc = v[0];
+    As = v[1]*1000;
+    liquid_firdes_kaiser(h_len,fc,As,_dt,_h);
+
+    // normalize coefficients
+    float e2 = 0.0f;
+    for (i=0; i<h_len; i++) e2 += _h[i]*_h[i];
+    for (i=0; i<h_len; i++) _h[i] *= sqrtf(_k/e2);
+}
+
+// gradient search utility
+float gs_utility(void * _userdata,
+                 float * _v,
+                 unsigned int _n)
+{
+    // get options from _userdata object
+    struct gsuserdata_s * q = (struct gsuserdata_s*)_userdata;
+    float w0        = q->w0;
+    float w1        = q->w1;
+    unsigned int k  = q->k;
+    unsigned int m  = q->m;
+    float beta      = q->beta;
+    float dt        = q->dt;
+    unsigned int nfft=512;
+
+    // parameters
+    float fc = _v[0];
+    float As = _v[1]*1000;
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;
+
+    // utilities
+    float u0 = 0.0;
+    float u1 = 0.0;
+
+    // compute filter
+    float h[h_len];
+    liquid_firdes_kaiser(h_len,fc,As,dt,h);
+
+    // normalize coefficients
+    float e2 = 0.0f;
+    unsigned int i;
+    for (i=0; i<h_len; i++) e2 += h[i]*h[i];
+    for (i=0; i<h_len; i++) h[i] *= sqrtf(k/e2);
+
+    // compute filter ISI
+    float isi_max;
+    float isi_rms;
+    liquid_filter_isi(h,k,m,&isi_rms,&isi_max);
+    u0 = 20*log10f(isi_rms);
+
+    // compute relative out-of-band energy
+    float e = liquid_filter_energy(h, h_len, 0.5*(1.0+beta)/(float)k, nfft);
+    u1 = 20*log10f(e);
+
+#if DEBUG
+    printf("  ISI (rms) : %12.8f, energy : %12.8f\n", u0, u1);
+#endif
+
+    // combine results
+    return w0*u0 + w1*u1;
+}
+
diff --git a/sandbox/shadowing_test.c b/sandbox/shadowing_test.c
new file mode 100644
index 0000000..6eef145
--- /dev/null
+++ b/sandbox/shadowing_test.c
@@ -0,0 +1,106 @@
+//
+// shadowing_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "shadowing_test.m"
+
+int main(int argc, char*argv[])
+{
+    srand(time(NULL));
+
+    // properties
+    unsigned int num_samples = 80e3;
+    float        sigma       = 1.0f;
+    float        fd          = 0.02f;
+    unsigned int nfft        = 2400;    // spectral periodogram FFT size
+
+    // generate Doppler filter
+#if 1
+    float alpha = fd;
+    float a[2] = {1.0f, alpha-1.0f};
+    float b[2] = {alpha, 0};
+    iirfilt_rrrf filter = iirfilt_rrrf_create(b,2,a,2);
+#else
+    iirfilt_rrrf filter = iirfilt_rrrf_create_lowpass(11,fd);
+#endif
+
+    unsigned int i;
+
+    // output channel gain
+    float G[num_samples];
+
+    // generate samples
+    for (i=0; i<num_samples; i++) {
+        iirfilt_rrrf_execute(filter, randnf()*sigma, &G[i]);
+        G[i] /= fd * 6.9f;
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(filter);
+
+    // create spectral periodogram for estimating
+    spgramf q = spgramf_create_default(nfft);
+
+    // push resulting sample through periodogram
+    spgramf_accumulate_psd(q, G, 0.01, num_samples);
+
+    // compute power spectral density output
+    float psd[nfft];
+    spgramf_write_accumulation(q, psd);
+
+    // destroy objects
+    spgramf_destroy(q);
+
+    //
+    // export output file
+    //
+
+    FILE * fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"num_samples=%u;\n",num_samples);
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"G(%3u) = %12.8f;\n", i+1, G[i]);
+
+    fprintf(fid,"g = 10.^[G/20];\n");
+
+    fprintf(fid,"nfft = %u;\n", nfft);
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"phi(%6u) = %12.4e;\n", i+1, psd[i]);
+
+    fprintf(fid,"figure('Color','white','position',[500 500 800 800]);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"plot(G,'-','LineWidth',1,'Color',[0 0.2 0.5]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('In-phase');\n");
+    fprintf(fid,"  ylabel('Gain [dB]');\n");
+    fprintf(fid,"  axis([0 5000 -2 2]);\n");
+    fprintf(fid,"  title('');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  hist(G,50);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(f,phi,'LineWidth',2,'Color',[0 0.5 0.2]);\n");
+    fprintf(fid,"  axis([-0.5 0.5 -40 40]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/simplex_test.c b/sandbox/simplex_test.c
new file mode 100644
index 0000000..ebd9506
--- /dev/null
+++ b/sandbox/simplex_test.c
@@ -0,0 +1,67 @@
+// 
+// simpelx.c : simplex method for solving linear programming problems
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "liquid.h"
+
+void matrixf_pivot2(float * _x,
+                    unsigned int _m,
+                    unsigned int _n,
+                    unsigned int _i,
+                    unsigned int _j)
+{
+    float v = 1.0f / matrix_access(_x,_m,_n,_i,_j);
+
+    float g;    // multiplier
+    unsigned int r,c;
+    for (r=0; r<_m; r++) {
+        // skip over pivot row
+        if (r == _i) continue;
+
+        // compute multiplier
+        g = matrix_access(_x,_m,_n,r,_j) * v;
+
+        // back-substitution
+        for (c=0; c<_n; c++) {
+            matrix_access(_x,_m,_n,r,c) -= matrix_access(_x,_m,_n,_i,c)*g;
+        }
+    }
+
+    // normalize row by multiplier
+    for (c=0; c<_n; c++)
+        matrix_access(_x,_m,_n,_i,c) *= v;
+}
+
+int main() {
+    // problem definition
+    float x[18] = {
+        1.0f,   -1.0f,  -1.0f,  0.0f,   0.0f,   0.0f,
+        0.0f,    2.0f,   1.0f,  1.0f,   0.0f,   4.0f,
+        0.0f,    1.0f,   2.0f,  0.0f,   1.0f,   3.0f
+    };
+    unsigned int r = 3;
+    unsigned int c = 6;
+
+    matrixf_print(x,r,c);
+
+    // pivot around x(1,1) = 2
+    matrixf_pivot2(x,r,c, 1,1);
+    matrixf_print(x,r,c);
+
+    // pivot around x(2,2) = 1.5
+    matrixf_pivot2(x,r,c, 2,2);
+    matrixf_print(x,r,c);
+
+    // print solution
+    printf("solution:\n");
+    printf("z     : %12.8f\n", matrixf_access(x,r,c,0,c-1));
+    unsigned int i;
+    for (i=1; i<r; i++)
+    printf("x[%2u] : %12.8f\n", i-1, matrixf_access(x,r,c,i,c-1));
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/svd_test.c b/sandbox/svd_test.c
new file mode 100644
index 0000000..2f62cf1
--- /dev/null
+++ b/sandbox/svd_test.c
@@ -0,0 +1,333 @@
+// 
+// svd_test.c : test singular value decomposition
+// 
+// References:
+//  [Golub:1970] G. H. Golub and C. Reinsch, "Singular Value
+//      Decomposition and Least Squares Solutions," Handbook Series
+//      Linear Algebra, Numerische Mathematik, vol. 14, pp 403-420,
+//      1970
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+//#include <complex.h>
+
+#include "liquid.h"
+
+#define DEBUG 1
+
+int main() {
+    // problem definition
+    //  sig1    =   sqrt(1248)
+    //  sig2    =   20
+    //  sig3    =   sqrt(384)
+    //  sig4    =   0
+    //  sig5    =   0
+    float a[40] = {
+        22,  10,   2,   3,  7,
+        14,   7,  10,   0,  8,
+        -1,  13,  -1, -11,  3,
+        -3,  -2,  13,  -2,  4,
+         9,   8,   1,  -2,  4,
+         9,   1,  -7,   5, -1,
+         2,  -6,   6,   5,  1,
+         4,   5,   0,  -2,  2};
+
+    // NOTE: m >= n
+    unsigned int m = 8;
+    unsigned int n = 5;
+
+#if 0
+    // initialize as random matrix
+    unsigned int ii;
+    for (ii=0; ii<m*n; ii++)
+        a[ii] = randnf();
+#endif
+
+    matrixf_print(a,m,n);
+
+    // 
+    float q[n];     // singular values of 'a'
+    float u[m*n];
+    float v[n*n];
+    float eps = 1e-8;
+    float tol = 1e-8;
+
+    // internal variables
+    int i,j,k,l,l1;
+    float c,f,g,h,s,x,y,z;
+    float e[n];
+
+    // initialization
+    memmove(u,a,m*n*sizeof(float));
+
+    // Householder's reduction to bidiagonal form
+    g=0.0;
+    x=0.0;
+    for (i=0; i<n; i++) {
+        e[i] = g;
+        s    = 0;
+        l    = i+1;
+
+        for (j=i; j<m; j++) {
+            float u_ji = matrix_access(u,m,n,j,i);
+            s += creal( u_ji*conj(u_ji) );
+        }
+        if (s < tol) {
+            g = 0.0;
+        } else {
+            f = matrix_access(u,m,n,i,i);
+            g = (f<0) ? sqrt(s) : -sqrt(s);
+            h = f*g - s;
+            matrix_access(u,m,n,i,i) = f-g;
+
+            for (j=l; j<n; j++) {
+                s = 0;
+                for (k=i; k<m; k++)
+                    s += matrix_access(u,m,n,k,i)*matrix_access(u,m,n,k,j);
+                f = s/h;
+                for (k=i; k<m; k++)
+                    matrix_access(u,m,n,k,j) += f*matrix_access(u,m,n,k,i);
+            } // for j
+        } // if (s < tol)
+
+        q[i] = g;
+        s = 0;
+        for (j=l; j<n; j++) {
+            float u_ij = matrix_access(u,m,n,i,j);
+            s += creal( u_ij*conj(u_ij) );
+        }
+        if (s < tol) {
+            g = 0;
+        } else {
+            f = matrix_access(u,m,n,i,i+1);
+            g = (f < 0) ? sqrt(s) : -sqrt(s);
+            h = f*g - s;
+            matrix_access(u,m,n,i,i+1) = f-g;
+            for (j=l; j<n; j++)
+                e[j] = matrix_access(u,m,n,i,j)/h;
+            for (j=l; j<m; j++) {
+                s = 0;
+                for (k=l; k<n; k++)
+                    s += matrix_access(u,m,n,j,k)*matrix_access(u,m,n,i,k);
+                for (k=l; k<n; k++)
+                    matrix_access(u,m,n,j,k) += s*e[k];
+            } // for j
+        } // if (s < tol)
+
+        y = fabsf(q[i]) + fabsf(e[i]);
+        if (y > x)
+            x = y;
+    }
+
+    // accumulation of right-hand transformations
+    for (i=n-1; i>=0; i--) {
+        //if (g != 0) {
+        if ( fabs(g) > 1e-3 ) {
+            h = matrix_access(u,m,n,i,i+1)*g;
+            for (j=l; j<n; j++)
+                matrix_access(v,n,n,j,i) = matrix_access(u,m,n,i,j)/h;
+            for (j=l; j<n; j++) {
+                s = 0.0;
+                for (k=l; k<n; k++)
+                    s += matrix_access(u,m,n,i,k)*matrix_access(v,n,n,k,j);
+                for (k=l; k<n; k++)
+                    matrix_access(v,n,n,k,j) += s*matrix_access(v,n,n,k,i);
+            } // for j
+        } // g != 0
+
+        for (j=l; j<n; j++) {
+            matrix_access(v,n,n,i,j) = 0.0;
+            matrix_access(v,n,n,j,i) = 0.0;
+        }
+        matrix_access(v,n,n,i,i) = 1;
+        g = e[i];
+        l = i;
+    }
+#if DEBUG
+    printf("U:\n"); matrixf_print(u,m,n);
+    printf("V:\n"); matrixf_print(v,n,n);
+#endif
+
+    // accumulation of left-hand transformations
+    printf("q:\n"); matrixf_print(q,n,1);
+    for (i=n-1; i>=0; i--) {
+        l = i+1;
+        g = q[i];
+        for (j=l; j<n; j++)
+            matrix_access(u,m,n,i,j) = 0.0;
+        //if (g != 0) {
+        if ( fabs(g) > 1e-3 ) {
+            h = matrix_access(u,m,n,i,i) * g;
+            for (j=l; j<n; j++) {
+                s = 0.0;
+                for (k=l; k<m; k++)
+                    s += matrix_access(u,m,n,k,i)*matrix_access(u,m,n,k,j);
+                f = s/h;
+                for (k=i; k<m; k++)
+                    matrix_access(u,m,n,k,j) += f*matrix_access(u,m,n,k,i);
+            } // for j
+            for (j=i; j<m; j++)
+                matrix_access(u,m,n,j,i) /= g;
+        } else { // g != 0
+            //printf("  treating g as zero\n");
+            for (j=i; j<m; j++)
+                matrix_access(u,m,n,j,i) = 0.0;
+        } // g != 0
+        matrix_access(u,m,n,i,i) += 1.0;
+    }
+#if DEBUG
+    printf("U:\n"); matrixf_print(u,m,n);
+    printf("V:\n"); matrixf_print(v,n,n);
+    printf("q:\n"); matrixf_print(q,n,1);
+    printf("e:\n"); matrixf_print(e,n,1);
+#endif
+
+    exit(1);
+
+    // diagonalization of the bidiagonal form
+    unsigned int t=10;
+    eps *= x;
+    for (k=n-1; k>=0; k--) {
+        if (k==(n-2)) {
+            printf("...\n");
+            break;
+        }
+        test_f_splitting:
+        printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
+        printf("test f splitting\n");
+        printf("  k = %u\n", k);
+        t--;
+        if (t==0) {
+            fprintf(stderr,"***********************************************\n");
+            fprintf(stderr,"    bailing\n");
+            fprintf(stderr,"***********************************************\n");
+            t=10;
+            continue;
+        }
+        for (l=k; l>0; l--) {
+            printf("  e[%3u] = %12.4e, q[%3u] = %12.4e (eps: %12.4e)\n", l, e[l], l-1, q[l-1], eps);
+            if ( fabs(e[l])   <= eps ) goto test_f_convergence;
+            if ( fabs(q[l-1]) <= eps ) goto cancellation;
+        } // for l
+        // FIXME: figure out logic here (need to set l > 0)
+        l = 1;
+
+        // cancellation of e[l] if l>0
+        cancellation:
+        printf("cancellation\n");
+        c = 0;
+        s = 1;
+        l1 = l-1;
+        for (i=l; i<k; i++) {
+            f = s*e[i];
+            e[i] *= c;
+            printf("  i=%u, f=%f (eps: %e)\n", i, f, eps);
+            if ( fabs(f) <= eps ) goto test_f_convergence;
+            g = q[i];
+            h = sqrt(f*f + g*g);
+            q[i] = h;
+            c = g/h;
+            s = -f/h;
+
+            // withu
+            for (j=0; j<m; j++) {
+                y = matrix_access(u,m,n,j,l1);
+                z = matrix_access(u,m,n,j,i);
+                matrix_access(u,m,n,j,l1) =  y*c + z*s;
+                matrix_access(u,m,n,j,i)  = -y*s + z*c;
+            } // for j
+        } // for i
+        printf("  y=%f, z=%f\n", y, z);
+
+        test_f_convergence:
+        printf("test f convergence\n");
+        z = q[k];
+        printf("  z = %f\n", z);
+        if (l==k)
+            goto convergence;
+
+        // shift from bottom 2x2 minor
+        x = q[l];
+        y = q[k-1];
+        g = e[k-1];
+        h = e[k];
+        f = ((y-z)*(y+z) + (g-h)*(g+h)) / (2*h*y);
+        printf("  l=%u, k=%u, z=%f, x=%f, y=%f, g=%e, h=%f, f=%e\n",
+                  l,    k,    z,    x,    y,    g,    h,    f);
+        g = sqrt(f*f+1);
+        f = ((x-z)*(x+z) + h*(y/( f<0 ? f-g : f+g )-h))/x;
+        printf("  g -> %f, f -> %f\n", g, f);
+
+        // next QR transformation
+        c = 1;
+        s = 1;
+        for (i=l+1; i<k; i++) {
+            g = e[i];
+            y = q[i];
+            h = s*g;
+            g = c*g;
+            z = sqrt(f*f+h*h);
+            e[i-1] = z;
+            c = f/z;
+            s = h/z;
+            f =  x*c + g*s;
+            g = -x*s + g*c;
+            h = y*s;
+            y = y*c;
+
+            // withv
+            for (j=0; j<n; j++) {
+                x = matrix_access(v,n,n,j,i-1);
+                z = matrix_access(v,n,n,j,i);
+                matrix_access(v,n,n,j,i-1) =  x*c + z*s;
+                matrix_access(v,n,n,j,i)   = -x*s + z*c;
+            } // for j
+
+            z = sqrt(f*f + h*h);
+            q[i-1] = z;
+            c = f/z;
+            s = h/z;
+            f =  c*g + s*y;
+            x = -s*g + c*y;
+
+            // withu
+            for (j=0; j<m; j++) {
+                y = matrix_access(u,m,n,j,i-1);
+                z = matrix_access(u,m,n,j,i);
+                matrix_access(u,m,n,j,i-1) =  y*c + z*s;
+                matrix_access(u,m,n,j,i)   = -y*s + z*c;
+            } // for j
+        } // for i
+
+        e[l] = 0;
+        e[k] = f;
+        q[k] = x;
+        goto test_f_splitting;
+
+        convergence:
+        printf("convergence\n");
+        printf("  z = %f\n", z);
+        if (z < 0) {
+            // q[k] is made non-negative
+            printf("  q[%3u] = %f\n", k, q[k]);
+            q[k] = -z;
+            for (j=0; j<n; j++)
+                matrix_access(v,n,n,j,k) = -matrix_access(v,n,n,j,k);
+        } // if (z < 0)
+    } // for k
+
+    // print results
+    printf("\n\n");
+    printf("A:\n"); matrixf_print(a,m,n);
+    printf("U:\n"); matrixf_print(u,m,n);
+    printf("V:\n"); matrixf_print(v,n,n);
+    printf("q:\n"); matrixf_print(q,n,1);
+    printf("e:\n"); matrixf_print(e,n,1);
+
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/sandbox/symsync_crcf_test.c b/sandbox/symsync_crcf_test.c
new file mode 100644
index 0000000..34cf8f7
--- /dev/null
+++ b/sandbox/symsync_crcf_test.c
@@ -0,0 +1,277 @@
+//
+// symsync_crcf_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symsync_crcf_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("symsync_crcf_test [options]\n");
+    printf("  u/h   : print usage\n");
+    printf("  T     : filter type: rrcos, rkaiser, [arkaiser], hM3, gmsk\n");
+    printf("  m     : filter delay (symbols), default: 3\n");
+    printf("  b     : filter excess bandwidth, default: 0.5\n");
+    printf("  B     : filter polyphase banks, default: 32\n");
+    printf("  s     : signal-to-noise ratio, default: 30dB\n");
+    printf("  w     : timing pll bandwidth, default: 0.02\n");
+    printf("  n     : number of symbols, default: 200\n");
+    printf("  t     : timing phase offset [%% symbol], t in [-0.5,0.5], default: -0.2\n");
+}
+
+// filter coefficients
+//  alpha   zeta    settling time (symbols)
+//  0.90    1.5     70
+//  0.92    1.4     100
+//  0.94    1.2     110
+//  0.96    0.5     140
+//  0.98    0.2     210
+//  0.985   0.08    410
+//  0.990   0.05    500
+//  0.995   0.01    1100
+//  0.998   0.002   2500
+//
+// Let bandwidth = 1/settling time,
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int k=2;               // samples/symbol (input)
+    unsigned int m=3;               // filter delay (symbols)
+    float beta=0.5f;                // filter excess bandwidth factor
+    unsigned int npfb=32;           // number of filters in the bank
+    unsigned int num_symbols=512;   // number of data symbols
+    float SNRdB = 30.0f;            // signal-to-noise ratio
+    liquid_firfilt_type ftype_tx = LIQUID_FIRFILT_ARKAISER;
+    liquid_firfilt_type ftype_rx = LIQUID_FIRFILT_ARKAISER;
+
+    float bt=0.02f;     // loop filter bandwidth
+    float tau=-0.2f;    // fractional symbol offset
+
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhT:m:b:B:s:w:n:t:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();                        return 0;
+        case 'T':
+            if (strcmp(optarg,"gmsk")==0) {
+                ftype_tx = LIQUID_FIRFILT_GMSKTX;
+                ftype_rx = LIQUID_FIRFILT_GMSKRX;
+            } else {
+                ftype_tx = liquid_getopt_str2firfilt(optarg);
+                ftype_rx = liquid_getopt_str2firfilt(optarg);
+            }
+            if (ftype_tx == LIQUID_FIRFILT_UNKNOWN) {
+                fprintf(stderr,"error: %s, unknown filter type '%s'\n", argv[0], optarg);
+                exit(1);
+            }
+            break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'B':   npfb = atoi(optarg);     break;
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'w':   bt          = atof(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (m < 1) {
+        fprintf(stderr,"error: m (filter delay) must be greater than 0\n");
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: beta (excess bandwidth factor) must be in (0,1]\n");
+        exit(1);
+    } else if (npfb == 0) {
+        fprintf(stderr,"error: number of polyphase filters must be greater than 0\n");
+        exit(1);
+    } else if (bt <= 0.0f) {
+        fprintf(stderr,"error: timing PLL bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: number of symbols must be greater than 0\n");
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: timing phase offset must be in [-1,1]\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    unsigned int num_samples = k*num_symbols;
+    float complex sym_in[num_symbols];          // data symbols
+    float complex x[num_samples];               // interpolated samples
+    float complex y[num_samples];               // noisy samples
+    float complex sym_out[num_symbols + 64];    // synchronized symbols
+
+    // generate random QPSK symbols
+    for (i=0; i<num_symbols; i++) {
+        sym_in[i] = ( rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2 ) +
+                    ( rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2 ) * _Complex_I;
+    }
+
+    // interpolate symbols
+    firinterp_crcf q = firinterp_crcf_create_prototype(ftype_tx, k, m, beta, tau);
+    for (i=0; i<num_symbols; i++) {
+        firinterp_crcf_execute(q, sym_in[i], &x[i*k]);
+    }
+    firinterp_crcf_destroy(q);
+
+    // add noise
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+    for (i=0; i<num_samples; i++)
+        y[i] = x[i] + nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+
+    // create matched and derivative-matched filters
+    firpfb_crcf  mf = firpfb_crcf_create_rnyquist( ftype_rx, npfb, k, m, beta);
+    firpfb_crcf dmf = firpfb_crcf_create_drnyquist(ftype_rx, npfb, k, m, beta);
+
+    // run symbol timing recovery algorithm
+    unsigned int n = 0;
+    //float tau_hat[num_samples];
+    float alpha     = 0.96;
+    float zeta      = 0.5f;
+    float pfb_error = 0.0f;
+    float pfb_q     = 0.0f;
+    float pfb_soft  = 0.0f;
+    int   pfb_index = 0;
+    int   pfb_timer = 0;   // output flag
+    // deubgging...
+    float debug_pfb_error[num_symbols + 64];
+    float debug_pfb_q[num_symbols + 64];
+    float debug_pfb_soft[num_symbols + 64];
+    for (i=0; i<num_samples; i++) {
+        // push sample into filters
+        firpfb_crcf_push(mf,  y[i]);
+        firpfb_crcf_push(dmf, y[i]);
+
+        //
+        if (pfb_timer <= 0) {
+            // reset timer
+            pfb_timer = 2;  // k samples/symbol
+
+            // compute filterbank outputs
+            float complex v  = 0.0f;
+            float complex dv = 0.0f;
+            firpfb_crcf_execute(mf,  pfb_index, &v);
+            firpfb_crcf_execute(dmf, pfb_index, &dv);
+
+            // scale output by samples/symbol
+            v /= (float)k;
+
+            // save output
+            sym_out[n] = v;
+
+            // compute error
+            pfb_error = crealf(conjf(v)*dv);
+            pfb_q     = alpha*pfb_q + zeta*pfb_error;
+            pfb_soft  += pfb_q;
+
+            // update index
+            pfb_index = (int) roundf(pfb_soft);
+            while (pfb_index < 0) {
+                pfb_index += npfb;
+                pfb_soft  += npfb;
+                pfb_timer--;
+            }
+            while (pfb_index > npfb-1) {
+                pfb_index -= npfb;
+                pfb_soft  -= npfb;
+                pfb_timer++;
+            }
+    
+            // save debugging outputs
+            debug_pfb_error[n]  = pfb_error;
+            debug_pfb_q[n]      = pfb_q;
+            debug_pfb_soft[n]   = pfb_soft;
+            
+            // increment output counter
+            n++;
+        }
+
+        // decrement timer
+        pfb_timer--;
+    }
+    
+    // destroy filterbanks
+    firpfb_crcf_destroy(mf);
+    firpfb_crcf_destroy(dmf);
+
+    // print last several symbols to screen
+    printf("output symbols:\n");
+    for (i=n-10; i<n; i++)
+        printf("  sym_out(%2u) = %8.4f + j*%8.4f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+    //
+    // export output file
+    //
+
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"k=%u;\n",k);
+    fprintf(fid,"m=%u;\n",m);
+    fprintf(fid,"beta=%12.8f;\n",beta);
+    fprintf(fid,"npfb=%u;\n",npfb);
+    fprintf(fid,"num_symbols=%u;\n",num_symbols);
+    fprintf(fid,"n=%u;\n", n);
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"sym_in(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_in[i]), cimagf(sym_in[i]));
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        
+    for (i=0; i<n; i++) {
+        fprintf(fid,"sym_out(%5u)   = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+        fprintf(fid,"pfb_error(%5u) = %12.4e;\n", i+1, debug_pfb_error[i]);
+        fprintf(fid,"pfb_q(%5u)     = %12.4e;\n", i+1, debug_pfb_q[i]);
+        fprintf(fid,"pfb_soft(%5u)  = %12.4e;\n", i+1, debug_pfb_soft[i]);
+    }
+
+    // plot results
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(sym_out),imag(sym_out),'x');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.3);\n");
+    fprintf(fid,"axis square\n");
+    fprintf(fid,"grid on;\n");
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(1:n, pfb_error, 'Color',[1 1 1]*0.8, ...\n");
+    fprintf(fid,"       1:n, pfb_q, '-b', 'LineWidth',1.2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('timing error');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(1:n, pfb_soft);\n");
+    //fprintf(fid,"  axis([0 n -1 npfb]);\n");
+    fprintf(fid,"  xlabel('output symbol');\n");
+    fprintf(fid,"  ylabel('filterbank index');\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/symsync_eqlms_test.c b/sandbox/symsync_eqlms_test.c
new file mode 100644
index 0000000..691bd86
--- /dev/null
+++ b/sandbox/symsync_eqlms_test.c
@@ -0,0 +1,326 @@
+//
+// symsync_eqlms_test.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <getopt.h>
+#include <time.h>
+#include <assert.h>
+
+#include "liquid.h"
+
+#define OUTPUT_FILENAME "symsync_eqlms_test.m"
+
+// print usage/help message
+void usage()
+{
+    printf("symsync_eqlms_test [options]\n");
+    printf("  u/h   : print usage\n");
+    // transmit filter properties
+    printf("  k     : filter samples/symbol, default: 2\n");
+    printf("  m     : filter delay (symbols), default: 3\n");
+    printf("  b     : filter excess bandwidth, default: 0.5\n");
+    printf("  n     : number of symbols, default: 400\n");
+    // symsync properties
+    printf("  B     : filter polyphase banks, default: 32\n");
+    printf("  w     : timing pll bandwidth, default: 0.02\n");
+    // equalizer properties
+    printf("  p     : equalizer order [symbols], default: 3\n");
+    printf("  W     : equalizer bandwidth, default: 0.05\n");
+    // channel
+    printf("  s     : signal-to-noise ratio, default: 30dB\n");
+    printf("  c     : channel impulse response length, default: 5\n");
+    printf("  t     : timing phase offset [%% symbol], -0.5 < t <= 0.5, default: 0\n");
+}
+
+
+int main(int argc, char*argv[]) {
+    srand(time(NULL));
+
+    // options
+    unsigned int k=2;               // samples/symbol (input)
+    unsigned int m=3;               // filter delay (symbols)
+    float beta=0.5f;                // filter excess bandwidth factor
+    unsigned int npfb=32;    // number of filters in the bank
+    unsigned int p=3;               // equalizer length (symbols, hp_len = 2*k*p+1)
+    float mu = 0.05f;               // equalizer learning rate
+    unsigned int num_symbols=500;   // number of data symbols
+    unsigned int hc_len=5;          // channel filter length
+    float SNRdB = 30.0f;            // signal-to-noise ratio
+    liquid_firfilt_type ftype = LIQUID_FIRFILT_ARKAISER;
+
+    float bt=0.05f;                 // symbol synchronizer loop filter bandwidth
+    float tau=-0.1f;                // fractional symbol offset
+    
+    int dopt;
+    while ((dopt = getopt(argc,argv,"uhk:m:b:n:B:w:p:W:s:c:t:")) != EOF) {
+        switch (dopt) {
+        case 'u':
+        case 'h':   usage();                        return 0;
+        // transmit filter properties
+        case 'k':   k           = atoi(optarg);     break;
+        case 'm':   m           = atoi(optarg);     break;
+        case 'b':   beta        = atof(optarg);     break;
+        case 'n':   num_symbols = atoi(optarg);     break;
+        // symsync properties
+        case 'B':   npfb        = atoi(optarg);     break;
+        case 'w':   bt          = atof(optarg);     break;
+        // equalizer properties
+        case 'p':   p           = atoi(optarg);     break;
+        case 'W':   mu          = atof(optarg);     break;
+        // equalizer properties
+        case 's':   SNRdB       = atof(optarg);     break;
+        case 'c':   hc_len      = atoi(optarg);     break;
+        case 't':   tau         = atof(optarg);     break;
+        default:
+            exit(1);
+        }
+    }
+
+    // validate input
+    if (k < 2) {
+        fprintf(stderr,"error: %s,k (samples/symbol) must be at least 2\n", argv[0]);
+        exit(1);
+    } else if (m < 1) {
+        fprintf(stderr,"error: %s,m (filter delay) must be greater than 0\n", argv[0]);
+        exit(1);
+    } else if (beta <= 0.0f || beta > 1.0f) {
+        fprintf(stderr,"error: %s,beta (excess bandwidth factor) must be in (0,1]\n", argv[0]);
+        exit(1);
+    } else if (num_symbols == 0) {
+        fprintf(stderr,"error: %s,number of symbols must be greater than 0\n", argv[0]);
+        exit(1);
+    } else if (npfb == 0) {
+        fprintf(stderr,"error: %s,number of polyphase filters must be greater than 0\n", argv[0]);
+        exit(1);
+    } else if (bt < 0.0f) {
+        fprintf(stderr,"error: %s,timing PLL bandwidth cannot be negative\n", argv[0]);
+        exit(1);
+    } else if (p == 0) {
+        fprintf(stderr,"error: %s, equalizer order must be at least 1\n", argv[0]);
+        exit(1);
+    } else if (mu < 0.0f || mu > 1.0f) {
+        fprintf(stderr,"error: %s, equalizer learning rate must be in [0,1]\n", argv[0]);
+        exit(1);
+    } else if (hc_len < 1) {
+        fprintf(stderr,"error: %s, channel response must have at least 1 tap\n", argv[0]);
+        exit(1);
+    } else if (tau < -1.0f || tau > 1.0f) {
+        fprintf(stderr,"error: %s,timing phase offset must be in [-1,1]\n", argv[0]);
+        exit(1);
+    }
+
+    // derived values
+    unsigned int ht_len = 2*k*m+1;  // transmit filter order
+    unsigned int hp_len = 2*k*p+1;  // equalizer order
+    float nstd = powf(10.0f, -SNRdB/20.0f);
+
+    float dt = tau;                 // fractional sample offset
+    unsigned int ds = 0;            // full sample delay
+
+    unsigned int i;
+
+    unsigned int num_samples = k*num_symbols;
+    float complex s[num_symbols];               // data symbols
+    float complex x[num_samples];               // interpolated samples
+    float complex y[num_samples];               // channel output
+    float complex z[k*num_symbols + 64];        // synchronized samples
+    float complex sym_out[num_symbols + 64];    // synchronized symbols
+
+    for (i=0; i<num_symbols; i++) {
+        s[i] = (rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2) +
+               (rand() % 2 ? M_SQRT1_2 : -M_SQRT1_2) * _Complex_I;
+    }
+
+    // 
+    // create and run interpolator
+    //
+
+    // design interpolating filter
+    float ht[ht_len];
+    liquid_firdes_prototype(ftype,k,m,beta,dt,ht);
+    firinterp_crcf q = firinterp_crcf_create(k, ht, ht_len);
+    for (i=0; i<num_symbols; i++)
+        firinterp_crcf_execute(q, s[i], &x[i*k]);
+    firinterp_crcf_destroy(q);
+
+
+    // 
+    // channel
+    //
+
+    // generate channel impulse response, filter
+    float complex hc[hc_len];
+    hc[0] = 1.0f;
+    for (i=1; i<hc_len; i++)
+        hc[i] = 0.07f*(randnf() + randnf()*_Complex_I);
+    firfilt_cccf fchannel = firfilt_cccf_create(hc, hc_len);
+    // push through channel
+    for (i=0; i<num_samples; i++) {
+        firfilt_cccf_push(fchannel, x[i]);
+        firfilt_cccf_execute(fchannel, &y[i]);
+
+        // add noise
+        y[i] += nstd*(randnf() + randnf()*_Complex_I)*M_SQRT1_2;
+    }
+    firfilt_cccf_destroy(fchannel);
+
+
+    // 
+    // symbol timing recovery
+    //
+
+    // create symbol synchronizer
+    symsync_crcf d = symsync_crcf_create_rnyquist(ftype, k, m, beta, npfb);
+    symsync_crcf_set_lf_bw(d,bt);
+    symsync_crcf_set_output_rate(d,k);
+
+    unsigned int num_samples_sync=0;
+    unsigned int nw;
+    for (i=ds; i<num_samples; i++) {
+        // push through symbol synchronizer
+        symsync_crcf_execute(d, &y[i], 1, &z[num_samples_sync], &nw);
+        num_samples_sync += nw;
+    }
+    printf("num samples : %6u (%6u synchronized)\n", num_samples, num_samples_sync);
+    symsync_crcf_destroy(d);
+
+
+    // 
+    // equalizer/decimator
+    //
+
+    // create equalizer as low-pass filter
+    float complex hp[hp_len];
+    eqlms_cccf eq = eqlms_cccf_create_lowpass(hp_len, 0.4f);
+    eqlms_cccf_set_bw(eq, mu);
+
+    // push through equalizer and decimate
+    unsigned int num_symbols_sync = 0;
+    float complex d_hat = 0.0f;
+    for (i=0; i<num_samples_sync; i++) {
+        // push sample into equalizer
+        eqlms_cccf_push(eq, z[i]);
+
+        // decimate by k
+        if ( (i%k) != 0) continue;
+
+        // compute output
+        eqlms_cccf_execute(eq, &d_hat);
+        sym_out[num_symbols_sync++] = d_hat;
+
+        // check if buffer is full
+        if ( i < hp_len ) continue;
+
+        // estimate transmitted signal
+        float complex d_prime = (crealf(d_hat) > 0.0f ? M_SQRT1_2 : -M_SQRT1_2) +
+                                (cimagf(d_hat) > 0.0f ? M_SQRT1_2 : -M_SQRT1_2) * _Complex_I;
+
+        // update equalizer
+        eqlms_cccf_step(eq, d_prime, d_hat);
+    }
+
+    // get equalizer weights
+    eqlms_cccf_get_weights(eq, hp);
+
+    // destroy equalizer object
+    eqlms_cccf_destroy(eq);
+
+    // print last several symbols to screen
+    printf("output symbols:\n");
+    for (i=num_symbols_sync-10; i<num_symbols_sync; i++)
+        printf("  sym_out(%2u) = %8.4f + j*%8.4f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+
+    //
+    // export output file
+    //
+
+    FILE* fid = fopen(OUTPUT_FILENAME,"w");
+    fprintf(fid,"%% %s, auto-generated file\n\n", OUTPUT_FILENAME);
+    fprintf(fid,"close all;\nclear all;\n\n");
+
+    fprintf(fid,"k=%u;\n",k);
+    fprintf(fid,"m=%u;\n",m);
+    fprintf(fid,"beta=%12.8f;\n",beta);
+    fprintf(fid,"npfb=%u;\n",npfb);
+    fprintf(fid,"num_symbols=%u;\n",num_symbols);
+
+    for (i=0; i<ht_len; i++)
+        fprintf(fid,"ht(%3u) = %12.5f;\n", i+1, ht[i]);
+
+    for (i=0; i<hc_len; i++)
+        fprintf(fid,"hc(%3u) = %12.5f + j*%12.8f;\n", i+1, crealf(hc[i]), cimagf(hc[i]));
+
+    for (i=0; i<hp_len; i++)
+        fprintf(fid,"hp(%3u) = %12.5f + j*%12.8f;\n", i+1, crealf(hp[i]), cimagf(hp[i]));
+
+    for (i=0; i<num_symbols; i++)
+        fprintf(fid,"s(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(s[i]), cimagf(s[i]));
+
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"x(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(x[i]), cimagf(x[i]));
+        
+    for (i=0; i<num_samples; i++)
+        fprintf(fid,"y(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+        
+    for (i=0; i<num_samples_sync; i++)
+        fprintf(fid,"z(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(z[i]), cimagf(z[i]));
+        
+    for (i=0; i<num_symbols_sync; i++)
+        fprintf(fid,"sym_out(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(sym_out[i]), cimagf(sym_out[i]));
+        
+#if 0
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% scale QPSK in-phase by sqrt(2)\n");
+    fprintf(fid,"z = z*sqrt(2);\n");
+    fprintf(fid,"\n\n");
+    fprintf(fid,"tz = [0:length(z)-1]/k;\n");
+    fprintf(fid,"iz = 1:k:length(z);\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(tz,     real(z),    '-',...\n");
+    fprintf(fid,"     tz(iz), real(z(iz)),'or');\n");
+    fprintf(fid,"xlabel('Time');\n");
+    fprintf(fid,"ylabel('Output Signal (real)');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"legend('output time series','optimim timing',1);\n");
+#endif
+
+    // compute composite response
+    fprintf(fid,"hd = real(conv(ht/k,conv(hc,hp)));\n");
+
+    // plot frequency response
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Ht = 20*log10(abs(fftshift(fft(ht/k,nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc,  nfft))));\n");
+    fprintf(fid,"Hp = 20*log10(abs(fftshift(fft(hp,  nfft))));\n");
+    fprintf(fid,"Hd = 20*log10(abs(fftshift(fft(hd,  nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Ht, f,Hc, f,Hp, f,Hd,'-k','LineWidth',2);\n");
+    fprintf(fid,"axis([-0.5 0.5 -20 10]);\n");
+    fprintf(fid,"axis([-0.5 0.5 -6  6 ]);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"legend('transmit','channel','equalizer','composite','location','northeast');\n");
+
+    fprintf(fid,"i0 = [1:round(length(sym_out)/2)];\n");
+    fprintf(fid,"i1 = [round(length(sym_out)/2):length(sym_out)];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(sym_out(i0)),imag(sym_out(i0)),'x','MarkerSize',4,'Color',[0.60 0.60 0.60],...\n");
+    fprintf(fid,"     real(sym_out(i1)),imag(sym_out(i1)),'x','MarkerSize',4,'Color',[0.00 0.25 0.50]);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.2);\n");
+    fprintf(fid,"xlabel('In-phase');\n");
+    fprintf(fid,"ylabel('Quadrature');\n");
+    fprintf(fid,"legend(['first 50%%'],['last 50%%'],'location','northeast');\n");
+
+    fclose(fid);
+
+    printf("results written to %s.\n", OUTPUT_FILENAME);
+
+    // clean it up
+    printf("done.\n");
+    return 0;
+}
diff --git a/sandbox/thiran_allpass_iir_test.c b/sandbox/thiran_allpass_iir_test.c
new file mode 100644
index 0000000..0b64ffd
--- /dev/null
+++ b/sandbox/thiran_allpass_iir_test.c
@@ -0,0 +1,71 @@
+//
+// thiran_allpass_iir_test.c
+//
+// Test thiran allpass interpolator
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+void compute_thiran_allpass(unsigned int _n,
+                            float _mu,
+                            float * _b,
+                            float * _a);
+
+int main()
+{
+    unsigned int n = 4;
+    float mu = 0.1f;
+
+    float b[n+1];
+    float a[n+1];
+
+    // compute filter coefficients
+    compute_thiran_allpass(n,mu,b,a);
+
+    // print coefficients to screen
+    unsigned int i;
+    for (i=0; i<=n; i++)
+        printf("a(%3u) = %12.8f;   b(%3u) = %12.8f;\n", i+1, a[i], i+1, b[i]);
+
+    // compute group delay
+    float g = iir_group_delay(b,n+1,a,n+1,0.0f);
+
+    printf(" g = %f\n", g);
+    
+    printf("done.\n");
+    return 0;
+}
+
+void compute_thiran_allpass(unsigned int _n,
+                            float _mu,
+                            float * _b,
+                            float * _a)
+{
+    float nf = (float)_n;
+
+    unsigned int k;
+    for (k=0; k<=_n; k++) {
+        _a[k] = ((k%2)==0 ? 1.0f : -1.0f) * liquid_nchoosek(_n, k);
+
+        // check condition when mu is very small (eliminate divide-by-zero)
+        if ( fabsf(_mu) < 1e-6f ) {
+            _a[k] = _a[k]*_a[k];
+            continue;
+        }
+
+        // TODO : expand as sum of logarithms
+        unsigned int n;
+        for (n=0; n<=_n; n++) {
+            _a[k] *= (_mu - nf + (float)n) / (_mu - nf + (float)(n+k));
+        }
+    }
+
+    for (k=0; k<=_n; k++)
+        _b[k] = _a[_n-k];
+}
+
diff --git a/sandbox/vectorcf_test.c b/sandbox/vectorcf_test.c
new file mode 100644
index 0000000..2993de1
--- /dev/null
+++ b/sandbox/vectorcf_test.c
@@ -0,0 +1,36 @@
+// 
+// vectorcf_test.c : test complex floating-point vector operations
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+//#include <complex.h>
+
+#include "liquid.h"
+
+int main()
+{
+    // options
+    unsigned int n = 64;
+    
+    //
+    unsigned int i;
+    float complex v[n];
+    for (i=0; i<n; i++)
+        v[i] = cexpf(_Complex_I*(float)i);
+
+    // test l2-norm operation
+    float norm = 0.0f;
+    for (i=0; i<n; i++)
+        norm += crealf( v[i]*conjf(v[i]) );
+    norm = sqrtf(norm);
+    float norm_test = liquid_vectorcf_norm(v, n);
+
+    printf("norm : %12.8f (expected %12.8f)\n", norm_test, norm);
+
+    printf("done.\n");
+    return 0;
+}
+
diff --git a/scripts/autoscript.c b/scripts/autoscript.c
new file mode 100644
index 0000000..80ca621
--- /dev/null
+++ b/scripts/autoscript.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// scripts/autoscript.c
+// 
+// auto-script generator object definition
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "autoscript.h"
+
+#define NAME_LEN            (256)
+#define AUTOSCRIPT_VERSION  "0.3.1"
+#define DEBUG_AUTOSCRIPT    0
+
+struct script_s {
+    char name[NAME_LEN];
+    struct package_s * package;
+};
+
+struct package_s {
+    char name[NAME_LEN];
+    char filename[NAME_LEN];
+    struct script_s * scripts;
+    unsigned int num_scripts;
+};
+
+struct autoscript_s {
+    char type[NAME_LEN];
+    char delim;
+    unsigned int num_packages;
+    struct package_s * packages;
+};
+
+// create autoscript generator object
+autoscript autoscript_create(char * _type,
+                             char _delim)
+{
+    autoscript q = (autoscript) malloc(sizeof(struct autoscript_s));
+
+    // copy type and delimiter (path separator)
+    strncpy(q->type, _type, NAME_LEN);
+    q->delim = _delim;
+
+    q->num_packages = 0;
+    q->packages = NULL;
+
+    return q;
+}
+
+void autoscript_destroy(autoscript _q)
+{
+    // free all internal scripts
+    unsigned int i;
+    for (i=0; i<_q->num_packages; i++) {
+        if (_q->packages[i].scripts != NULL)
+            free(_q->packages[i].scripts);
+    }
+
+    // free all internal packages
+    if (_q->packages != NULL)
+        free(_q->packages);
+
+    // free main object memory
+    free(_q);
+}
+
+// parse filename and file
+void autoscript_parse(autoscript _q,
+                      char * _filename)
+{
+#if DEBUG_AUTOSCRIPT
+    fprintf(stderr,"autoscript_parse('%s')...\n", _filename);
+#endif
+
+    char package_name[NAME_LEN];
+
+    // parse filename...
+    autoscript_parsefilename(_q, _filename, package_name);
+
+    // parse actual file...
+    autoscript_parsefile(_q, _filename, package_name);
+}
+
+void autoscript_print(autoscript _q)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int n;
+
+    // count total number of scripts
+    unsigned int num_scripts = 0;
+    for (i=0; i<_q->num_packages; i++)
+        num_scripts += _q->packages[i].num_scripts;
+
+    // print header
+    printf("// auto-generated file, do not edit\n");
+    printf("// invoked with script type '%s' and delimiter '%c'\n", _q->type, _q->delim);
+    printf("// the following types need to be defined externally:\n");
+    printf("//      typedef ...(%s_function_t)(...);\n", _q->type);
+    printf("//      typedef struct {\n");
+    printf("//          unsigned int id;        // script identification number\n");
+    printf("//          %s_function_t * api;    // pointer to function API\n", _q->type);
+    printf("//          const char * name;      // script name\n");
+    printf("//          ...\n");
+    printf("//      } %s_t;\n", _q->type);
+    printf("//      typedef struct {\n");
+    printf("//          unsigned int id;        // package identification number\n");
+    printf("//          unsigned int index;     // index of first script\n");
+    printf("//          unsigned int n;         // number of scripts in package\n");
+    printf("//          const char * name;      // name of package\n");
+    printf("//      } package_t;\n");
+    printf("\n");
+    
+    printf("#ifndef __LIQUID_AUTOSCRIPT_INCLUDE_H__\n");
+    printf("#define __LIQUID_AUTOSCRIPT_INCLUDE_H__\n\n");
+
+    printf("#define AUTOSCRIPT_VERSION \"%s\"\n\n", AUTOSCRIPT_VERSION);
+
+    printf("// number of packages\n");
+    printf("#define NUM_PACKAGES (%u)\n\n", _q->num_packages);
+
+    printf("// number of scripts\n");
+    printf("#define NUM_AUTOSCRIPTS (%u)\n\n", num_scripts);
+
+    printf("// function declarations\n");
+    for (i=0; i<_q->num_packages; i++) {
+        struct package_s * p = &_q->packages[i];
+        printf("// %s\n", p->filename);
+#if 0
+        for (j=0; j<p->num_scripts; j++)
+            printf("void benchmark_%s(AUTOSCRIPT_ARGS);\n", p->scripts[j].name);
+#else
+        for (j=0; j<p->num_scripts; j++)
+            printf("%s_function_t %s_%s;\n", _q->type,
+                                             _q->type,
+                                             p->scripts[j].name);
+#endif
+    }
+    printf("\n");
+
+    // find longest name
+    unsigned int max_len = 0;
+    for (i=0; i<_q->num_packages; i++) {
+        //struct package_s * p = &_q->packages[i];
+        for (j=0; j<_q->packages[i].num_scripts; j++) {
+            unsigned int str_len = strlen(_q->packages[i].scripts[j].name);
+            max_len = str_len > max_len ? str_len : max_len;
+        }
+    }
+    char space[max_len+1];
+    memset(space, ' ', max_len);
+    space[max_len] = '\0';
+
+    printf("// array of scripts\n");
+    printf("%s_t scripts[NUM_AUTOSCRIPTS] = {\n", _q->type);
+    n=0;
+    for (i=0; i<_q->num_packages; i++) {
+        struct package_s * p = &_q->packages[i];
+        for (j=0; j<p->num_scripts; j++) {
+            // print formatting line, adding appropriate space to align columns
+            printf("    {.id = %4u, .api = &%s_%s,%s .name = \"%s\"}",
+                n,
+                _q->type,
+                p->scripts[j].name,
+                &space[strlen(p->scripts[j].name)],
+                p->scripts[j].name);
+            if ( n < num_scripts-1 )
+                printf(",");
+            printf("\n");
+            n++;
+        }
+    }
+    printf("};\n\n");
+
+    n=0;
+    printf("// array of packages\n");
+    printf("package_t packages[NUM_PACKAGES] = {\n");
+    for (i=0; i<_q->num_packages; i++) {
+        printf("    {.id = %4u, .index = %4u, .num_scripts = %4u, .name = \"%s\"}",
+            i,
+            n,
+            _q->packages[i].num_scripts,
+            _q->packages[i].name);
+        if (i<_q->num_packages-1)
+            printf(",");
+        printf("\n");
+
+        n += _q->packages[i].num_scripts;
+    }
+    printf("};\n\n");
+
+    printf("#endif // __LIQUID_AUTOSCRIPT_INCLUDE_H__\n\n");
+
+}
+
+
+
+//
+// internal methods
+//
+
+// parse filename
+//  _q              :   generator object
+//  _filename       :   name of file
+//  _package_name   :   output package name (stripped filename)
+void autoscript_parsefilename(autoscript _q,
+                              char * _filename,
+                              char * _package_name)
+{
+#if DEBUG_AUTOSCRIPT
+    fprintf(stderr,"autoscript_parsefilename('%s')...\n", _filename);
+#endif
+    char * sptr;                // pointer to base name
+    char * tptr;                // pointer to terminating tag
+    char pathsep = _q->delim;   // path separator character
+
+    // generate tag (e.g. "_benchmark")
+    unsigned int tag_len = strlen(_q->type) + 2;
+    char tag[tag_len];
+    tag[0] = '_';
+    strcpy(tag+1, _q->type);
+    tag[tag_len-1] = '\0';
+    //printf("// tag : '%s'\n", tag);
+
+    // try to strip out path: find rightmost occurrence of pathsep
+    //printf("%s\n", _filename);
+    sptr = strrchr(_filename, pathsep);   // obtain index of last pathsep
+    if (sptr == NULL) {
+        // path delimiter not found
+        sptr = _filename;
+    } else {
+        sptr++;   // increment to remove path separator character
+    }
+
+    // try to strip out tag
+    tptr = strstr( sptr, tag );
+    if (tptr == NULL) {
+        // TODO : handle this case differently otherwise
+        fprintf(stderr,"error: autoscript_parsefilename('%s'), tag '%s' not found\n", _filename, tag);
+        exit(1);
+    }
+
+    // copy base name to output
+    int n = tptr - sptr;
+    strncpy(_package_name, sptr, n);
+    _package_name[n] = '\0';
+
+    //printf("// package name : '%s'\n", _package_name);
+}
+
+
+// parse file
+//  _q              :   generator object
+//  _filename       :   name of file
+//  _package_name   :   input package name (stripped filename)
+void autoscript_parsefile(autoscript _q,
+                          char * _filename,
+                          char * _package_name)
+{
+    // flag indicating if package has been added or not
+    int package_added = 0;
+
+#if DEBUG_AUTOSCRIPT
+    fprintf(stderr,"autoscript_parsefile('%s')...\n", _filename);
+#endif
+    // try to open file...
+    FILE * fid = fopen(_filename,"r");
+    if (!fid) {
+        fprintf(stderr,"error: autoscript_parsefile('%s'), could not open file for reading\n", _filename);
+        exit(1);
+    }
+    // generate tag (e.g. "void benchmark_");
+    unsigned int tag_len = 5 + strlen(_q->type) + 2;
+    char tag[tag_len];
+    strcpy(tag, "void ");
+    strcpy(tag+5, _q->type);
+    tag[tag_len-2] = '_';
+    tag[tag_len-1] = '\0';
+    //printf("// tag : '%s'\n", tag);
+
+    // parse file, looking for key
+    char buffer[1024];      // line buffer
+    char basename[1024];    // base script name
+    char * cptr = NULL;     // readline return value
+    char * sptr = NULL;     // tag string pointer
+    int cterm;              // terminating character
+    unsigned int n=0;       // line number
+    do {
+        // increment line number
+        n++;
+
+        // read line
+        cptr = fgets(buffer, 1024, fid);
+
+        if (cptr != NULL) {
+            // search for key
+            sptr = strstr(cptr, tag);
+            if (sptr == NULL) {
+                // no value found
+                continue;
+            }
+            // key found: find terminating character and strip out base name
+            sptr += strlen(tag);    // increment string by tag length
+            cterm = strcspn( sptr, " (\t\n\r" );
+            if (cterm == strlen(sptr)) {
+                // no terminating character found
+                continue;
+            }
+            // copy base name
+            strncpy( basename, sptr, cterm );
+            basename[cterm] = '\0';
+            //printf("// line %3u : '%s'\n", n, basename);
+                    
+            // key found: add package if not already done
+            if (!package_added) {
+                // TODO : add base name
+                autoscript_addpackage(_q, _package_name, _filename);
+                package_added = 1;
+            }
+            autoscript_addscript(_q, _package_name, basename);
+        }
+    } while (!feof(fid));
+
+    // close file
+    fclose(fid);
+}
+
+void autoscript_addpackage(autoscript _q,
+                           char * _package_name,
+                           char * _filename)
+{
+    // increase package size
+    _q->num_packages++;
+
+    // re-allocate memory for packages
+    _q->packages = (struct package_s *) realloc(_q->packages,
+                                                _q->num_packages*sizeof(struct package_s));
+
+    // initialize new package
+    strncpy(_q->packages[_q->num_packages-1].name,
+            _package_name,
+            NAME_LEN);
+
+    // initialize new package
+    strncpy(_q->packages[_q->num_packages-1].filename,
+            _filename,
+            NAME_LEN);
+
+    // initialize number of scripts
+    _q->packages[_q->num_packages-1].num_scripts = 0;
+
+    // set scripts pointer to NULL
+    _q->packages[_q->num_packages-1].scripts = NULL;
+}
+
+void autoscript_addscript(autoscript _q,
+                          char * _package_name,
+                          char * _script_name)
+{
+    // first validate that package exists
+    unsigned int i;
+    int package_found = 0;
+    for (i=0; i<_q->num_packages; i++) {
+        if ( strcmp(_q->packages[i].name, _package_name)==0 ) {
+            package_found = 1;
+            break;
+        }
+    }
+
+    if (!package_found) {
+        fprintf(stderr,"error: autoscript_addscript(), unknown package '%s'\n", _package_name);
+        exit(1);
+    }
+
+    struct package_s * p = &_q->packages[i];
+
+    // increase script size
+    p->num_scripts++;
+
+    // re-allocate memory for scripts
+    p->scripts = (struct script_s *) realloc(p->scripts,
+                                             p->num_scripts*sizeof(struct script_s));
+
+    // initialize new script
+    strncpy(p->scripts[p->num_scripts-1].name,
+            _script_name,
+            NAME_LEN);
+
+    // associate script with package
+    //_q->scripts[_q->num_scripts-1].package = &_q->packages[i];
+}
+
diff --git a/scripts/autoscript.h b/scripts/autoscript.h
new file mode 100644
index 0000000..c687393
--- /dev/null
+++ b/scripts/autoscript.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef __LIQUID_AUTOSCRIPT_H__
+#define __LIQUID_AUTOSCRIPT_H__
+
+typedef struct autoscript_s * autoscript;
+
+// create autoscript generator object
+autoscript autoscript_create(char * _type,
+                             char _delim);
+
+// parse file
+void autoscript_parse(autoscript _q,
+                      char * _filename);
+
+void autoscript_print(autoscript _q);
+
+void autoscript_destroy(autoscript _q);
+
+
+//
+// internal methods
+//
+
+// parse filename
+//  _q              :   generator object
+//  _filename       :   name of file
+//  _package_name   :   output package name (stripped filename)
+void autoscript_parsefilename(autoscript _q,
+                              char * _filename,
+                              char * _package_name);
+
+// parse file
+//  _q              :   generator object
+//  _filename       :   name of file
+//  _package_name   :   input package name (stripped filename)
+void autoscript_parsefile(autoscript _q,
+                          char * _filename,
+                          char * _package_name);
+
+void autoscript_addpackage(autoscript _q,
+                           char * _package_name,
+                           char * _filename);
+
+void autoscript_addscript(autoscript _q,
+                          char * _package_name,
+                          char * _script_name);
+
+#endif // __LIQUID_AUTOSCRIPT_H__
+
diff --git a/scripts/ax_check_compile_flag.m4 b/scripts/ax_check_compile_flag.m4
new file mode 100644
index 0000000..c3a8d69
--- /dev/null
+++ b/scripts/ax_check_compile_flag.m4
@@ -0,0 +1,72 @@
+# ===========================================================================
+#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
+#
+# DESCRIPTION
+#
+#   Check whether the given FLAG works with the current language's compiler
+#   or gives an error.  (Warnings, however, are ignored)
+#
+#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+#   success/failure.
+#
+#   If EXTRA-FLAGS is defined, it is added to the current language's default
+#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with
+#   the flags: "CFLAGS EXTRA-FLAGS FLAG".  This can for example be used to
+#   force the compiler to issue an error when a bad flag is given.
+#
+#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Guido U. Draheim <guidod at gmx.de>
+#   Copyright (c) 2011 Maarten Bosmans <mkbosmans at gmail.com>
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 2
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+  _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
+    [AS_VAR_SET(CACHEVAR,[yes])],
+    [AS_VAR_SET(CACHEVAR,[no])])
+  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
+  [m4_default([$2], :)],
+  [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/scripts/ax_ext.m4 b/scripts/ax_ext.m4
new file mode 100644
index 0000000..a946c88
--- /dev/null
+++ b/scripts/ax_ext.m4
@@ -0,0 +1,163 @@
+# ===========================================================================
+#          http://www.gnu.org/software/autoconf-archive/ax_ext.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_EXT
+#
+# DESCRIPTION
+#
+#   Find supported SIMD extensions by requesting cpuid. When an SIMD
+#   extension is found, the -m"simdextensionname" is added to SIMD_FLAGS
+#   (only if compilator support it) (ie : if "sse2" is available "-msse2" is
+#   added to SIMD_FLAGS)
+#
+#   This macro calls:
+#
+#     AC_SUBST(SIMD_FLAGS)
+#
+#   And defines:
+#
+#     HAVE_MMX / HAVE_SSE / HAVE_SSE2 / HAVE_SSE3 / HAVE_SSSE3 /
+#     HAVE_SSE41 / HAVE_SSE42 / HAVE_AVX
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Christophe Tournayre <turn3r at users.sourceforge.net>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+#
+# MODIFIED
+#   * March 23, 2012 Joseph D. Gaeddert <jgaedder at vt.edu>
+#     Including tests for proper intrinsics header files in addition to cpu
+#     capabilities.
+#     MMX           :   mmintrin.h
+#     SSE           :   xmmintrin.h
+#     SSE2          :   emmintrin.h
+#     SSE3          :   pmmintrin.h
+#     SSSE3         :   tmmintrin.h
+#     SSE4.1/4.2    :   smmintrin.h
+#     AVX           :   immintrin.h
+
+#serial 9
+
+AC_DEFUN([AX_EXT],
+[
+  AC_REQUIRE([AX_GCC_X86_CPUID])
+
+  AX_GCC_X86_CPUID(0x00000001)
+  ecx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 3`
+  edx=`echo $ax_cv_gcc_x86_cpuid_0x00000001 | cut -d ":" -f 4`
+ 
+ AC_CHECK_HEADERS(mmintrin.h xmmintrin.h emmintrin.h pmmintrin.h tmmintrin.h smmintrin.h immintrin.h)
+
+ AC_CACHE_CHECK([whether mmx is supported], [ax_cv_have_mmx_ext],
+  [
+    ax_cv_have_mmx_ext=no
+    if test "$((0x$edx>>23&0x01))" = 1; then
+      ax_cv_have_mmx_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether sse is supported], [ax_cv_have_sse_ext],
+  [
+    ax_cv_have_sse_ext=no
+    if test "$((0x$edx>>25&0x01))" = 1; then
+      ax_cv_have_sse_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether sse2 is supported], [ax_cv_have_sse2_ext],
+  [
+    ax_cv_have_sse2_ext=no
+    if test "$((0x$edx>>26&0x01))" = 1; then
+      ax_cv_have_sse2_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether sse3 is supported], [ax_cv_have_sse3_ext],
+  [
+    ax_cv_have_sse3_ext=no
+    if test "$((0x$ecx&0x01))" = 1; then
+      ax_cv_have_sse3_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether ssse3 is supported], [ax_cv_have_ssse3_ext],
+  [
+    ax_cv_have_ssse3_ext=no
+    if test "$((0x$ecx>>9&0x01))" = 1; then
+      ax_cv_have_ssse3_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether sse4.1 is supported], [ax_cv_have_sse41_ext],
+  [
+    ax_cv_have_sse41_ext=no
+    if test "$((0x$ecx>>19&0x01))" = 1; then
+      ax_cv_have_sse41_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether sse4.2 is supported], [ax_cv_have_sse42_ext],
+  [
+    ax_cv_have_sse42_ext=no
+    if test "$((0x$ecx>>20&0x01))" = 1; then
+      ax_cv_have_sse42_ext=yes
+    fi
+  ])
+
+ AC_CACHE_CHECK([whether avx is supported], [ax_cv_have_avx_ext],
+  [
+    ax_cv_have_avx_ext=no
+    if test "$((0x$ecx>>28&0x01))" = 1; then
+      ax_cv_have_avx_ext=yes
+    fi
+  ])
+
+  if [ test "$ax_cv_have_mmx_ext" = yes && test "$ac_cv_header_mmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_MMX,, [Support MMX instructions])
+    AX_CHECK_COMPILE_FLAG(-mmmx, SIMD_FLAGS="$SIMD_FLAGS -mmmx", [])
+  fi
+
+  if [ test "$ax_cv_have_sse_ext" = yes && test "$ac_cv_header_xmmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSE,, [Support SSE (Streaming SIMD Extensions) instructions])
+    AX_CHECK_COMPILE_FLAG(-msse, SIMD_FLAGS="$SIMD_FLAGS -msse", [])
+  fi
+
+  if [ test "$ax_cv_have_sse2_ext" = yes && test "$ac_cv_header_emmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSE2,, [Support SSE2 (Streaming SIMD Extensions 2) instructions])
+    AX_CHECK_COMPILE_FLAG(-msse2, SIMD_FLAGS="$SIMD_FLAGS -msse2", [])
+  fi
+
+  if [ test "$ax_cv_have_sse3_ext" = yes && test "$ac_cv_header_pmmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSE3,, [Support SSE3 (Streaming SIMD Extensions 3) instructions])
+    AX_CHECK_COMPILE_FLAG(-msse3, SIMD_FLAGS="$SIMD_FLAGS -msse3", [])
+  fi
+
+  if [ test "$ax_cv_have_ssse3_ext" = yes && test "$ac_cv_header_tmmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSSE3,, [Support SSSE3 (Supplemental Streaming SIMD Extensions 3) instructions])
+    AX_CHECK_COMPILE_FLAG(-mssse3, SIMD_FLAGS="$SIMD_FLAGS -mssse3", [])
+  fi
+
+  if [ test "$ax_cv_have_sse41_ext" = yes && test "$ac_cv_header_smmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSE41,, [Support SSE4.1 (Streaming SIMD Extensions 4.1) instructions])
+    AX_CHECK_COMPILE_FLAG(-msse4.1, SIMD_FLAGS="$SIMD_FLAGS -msse4.1", [])
+  fi
+
+  if [ test "$ax_cv_have_sse42_ext" = yes && test "$ac_cv_header_smmintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_SSE42, 1,[Support SSE4.2 (Streaming SIMD Extensions 4.2) instructions])
+    AX_CHECK_COMPILE_FLAG(-msse4.2, SIMD_FLAGS="$SIMD_FLAGS -msse4.2", [])
+  fi
+
+  if [ test "$ax_cv_have_avx_ext" = yes && test "$ac_cv_header_immintrin_h" = yes ]; then
+    AC_DEFINE(HAVE_AVX,,[Support AVX (Advanced Vector Extensions) instructions])
+    AX_CHECK_COMPILE_FLAG(-mavx, SIMD_FLAGS="$SIMD_FLAGS -mavx", [])
+  fi
+
+  AC_SUBST(SIMD_FLAGS)
+])
diff --git a/scripts/ax_gcc_archflag.m4 b/scripts/ax_gcc_archflag.m4
new file mode 100644
index 0000000..af5fc1f
--- /dev/null
+++ b/scripts/ax_gcc_archflag.m4
@@ -0,0 +1,225 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_gcc_archflag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_GCC_ARCHFLAG([PORTABLE?], [ACTION-SUCCESS], [ACTION-FAILURE])
+#
+# DESCRIPTION
+#
+#   This macro tries to guess the "native" arch corresponding to the target
+#   architecture for use with gcc's -march=arch or -mtune=arch flags. If
+#   found, the cache variable $ax_cv_gcc_archflag is set to this flag and
+#   ACTION-SUCCESS is executed; otherwise $ax_cv_gcc_archflag is set to
+#   "unknown" and ACTION-FAILURE is executed. The default ACTION-SUCCESS is
+#   to add $ax_cv_gcc_archflag to the end of $CFLAGS.
+#
+#   PORTABLE? should be either [yes] (default) or [no]. In the former case,
+#   the flag is set to -mtune (or equivalent) so that the architecture is
+#   only used for tuning, but the instruction set used is still portable. In
+#   the latter case, the flag is set to -march (or equivalent) so that
+#   architecture-specific instructions are enabled.
+#
+#   The user can specify --with-gcc-arch=<arch> in order to override the
+#   macro's choice of architecture, or --without-gcc-arch to disable this.
+#
+#   When cross-compiling, or if $CC is not gcc, then ACTION-FAILURE is
+#   called unless the user specified --with-gcc-arch manually.
+#
+#   Requires macros: AX_CHECK_COMPILE_FLAG, AX_GCC_X86_CPUID
+#
+#   (The main emphasis here is on recent CPUs, on the principle that doing
+#   high-performance computing on old hardware is uncommon.)
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Steven G. Johnson <stevenj at alum.mit.edu>
+#   Copyright (c) 2008 Matteo Frigo
+#   Copyright (c) 2012 Tsukasa Oi
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 13
+
+AC_DEFUN([AX_GCC_ARCHFLAG],
+[AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_CANONICAL_HOST])
+
+AC_ARG_WITH(gcc-arch, [AS_HELP_STRING([--with-gcc-arch=<arch>], [use architecture <arch> for gcc -march/-mtune, instead of guessing])],
+	ax_gcc_arch=$withval, ax_gcc_arch=yes)
+
+AC_MSG_CHECKING([for gcc architecture flag])
+AC_MSG_RESULT([])
+AC_CACHE_VAL(ax_cv_gcc_archflag,
+[
+ax_cv_gcc_archflag="unknown"
+
+if test "$GCC" = yes; then
+
+if test "x$ax_gcc_arch" = xyes; then
+ax_gcc_arch=""
+if test "$cross_compiling" = no; then
+case $host_cpu in
+  i[[3456]]86*|x86_64*|amd64*) # use cpuid codes
+     AX_GCC_X86_CPUID(0)
+     AX_GCC_X86_CPUID(1)
+     case $ax_cv_gcc_x86_cpuid_0 in
+       *:756e6547:*:*) # Intel
+          case $ax_cv_gcc_x86_cpuid_1 in
+	    *5[[48]]?:*:*:*) ax_gcc_arch="pentium-mmx pentium" ;;
+	    *5??:*:*:*) ax_gcc_arch=pentium ;;
+	    *1?6[[7d]]?:*:*:*) ax_gcc_arch="penryn core2 pentium-m pentium3 pentiumpro" ;;
+	    *1?6[[aef]]?:*:*:*|*2?6[[5cef]]?:*:*:*) ax_gcc_arch="corei7 core2 pentium-m pentium3 pentiumpro" ;;
+	    *1?6c?:*:*:*|*[[23]]?66?:*:*:*) ax_gcc_arch="atom core2 pentium-m pentium3 pentiumpro" ;;
+	    *2?6[[ad]]?:*:*:*) ax_gcc_arch="corei7-avx corei7 core2 pentium-m pentium3 pentiumpro" ;;
+	    *[[1-9a-f]]?6??:*:*:*) ax_gcc_arch="core2 pentiumpro" ;;
+	    *6[[3456]]?:*:*:*) ax_gcc_arch="pentium2 pentiumpro" ;;
+	    *6a?:*[[01]]:*:*) ax_gcc_arch="pentium2 pentiumpro" ;;
+	    *6a?:*[[234]]:*:*) ax_gcc_arch="pentium3 pentiumpro" ;;
+	    *6[[9de]]?:*:*:*) ax_gcc_arch="pentium-m pentium3 pentiumpro" ;;
+	    *6[[78b]]?:*:*:*) ax_gcc_arch="pentium3 pentiumpro" ;;
+	    *6f?:*:*:*) ax_gcc_arch="core2 pentium-m pentium3 pentiumpro" ;;
+	    *6??:*:*:*) ax_gcc_arch=pentiumpro ;;
+	    *f3[[347]]:*:*:*|*f4[1347]:*:*:*|*f6?:*:*:*)
+		case $host_cpu in
+	          x86_64*) ax_gcc_arch="nocona pentium4 pentiumpro" ;;
+	          *) ax_gcc_arch="prescott pentium4 pentiumpro" ;;
+	        esac ;;
+	    *f??:*:*:*) ax_gcc_arch="pentium4 pentiumpro";;
+          esac ;;
+       *:68747541:*:*) # AMD
+          case $ax_cv_gcc_x86_cpuid_1 in
+	    *5[[67]]?:*:*:*) ax_gcc_arch=k6 ;;
+	    *5[[8d]]?:*:*:*) ax_gcc_arch="k6-2 k6" ;;
+	    *5[[9]]?:*:*:*) ax_gcc_arch="k6-3 k6" ;;
+	    *60?:*:*:*) ax_gcc_arch=k7 ;;
+	    *6[[12]]?:*:*:*) ax_gcc_arch="athlon k7" ;;
+	    *6[[34]]?:*:*:*) ax_gcc_arch="athlon-tbird k7" ;;
+	    *67?:*:*:*) ax_gcc_arch="athlon-4 athlon k7" ;;
+	    *6[[68a]]?:*:*:*)
+	       AX_GCC_X86_CPUID(0x80000006) # L2 cache size
+	       case $ax_cv_gcc_x86_cpuid_0x80000006 in
+                 *:*:*[[1-9a-f]]??????:*) # (L2 = ecx >> 16) >= 256
+			ax_gcc_arch="athlon-xp athlon-4 athlon k7" ;;
+                 *) ax_gcc_arch="athlon-4 athlon k7" ;;
+	       esac ;;
+	    *5??f??:*:*:*) ax_gcc_arch="btver1 amdfam10 k8" ;;
+	    *6??f??:*:*:*) ax_gcc_arch="bdver1 amdfam10 k8" ;;
+	    *[[1-9a-f]]??f??:*:*:*) ax_gcc_arch="amdfam10 k8" ;;
+	    *f[[4cef8b]]?:*:*:*) ax_gcc_arch="athlon64 k8" ;;
+	    *f5?:*:*:*) ax_gcc_arch="opteron k8" ;;
+	    *f7?:*:*:*) ax_gcc_arch="athlon-fx opteron k8" ;;
+	    *f??:*:*:*) ax_gcc_arch="k8" ;;
+          esac ;;
+	*:746e6543:*:*) # IDT
+	   case $ax_cv_gcc_x86_cpuid_1 in
+	     *54?:*:*:*) ax_gcc_arch=winchip-c6 ;;
+	     *58?:*:*:*) ax_gcc_arch=winchip2 ;;
+	     *6[[78]]?:*:*:*) ax_gcc_arch=c3 ;;
+	     *69?:*:*:*) ax_gcc_arch="c3-2 c3" ;;
+	   esac ;;
+     esac
+     if test x"$ax_gcc_arch" = x; then # fallback
+	case $host_cpu in
+	  i586*) ax_gcc_arch=pentium ;;
+	  i686*) ax_gcc_arch=pentiumpro ;;
+        esac
+     fi
+     ;;
+
+  sparc*)
+     AC_PATH_PROG([PRTDIAG], [prtdiag], [prtdiag], [$PATH:/usr/platform/`uname -i`/sbin/:/usr/platform/`uname -m`/sbin/])
+     cputype=`(((grep cpu /proc/cpuinfo | cut -d: -f2) ; ($PRTDIAG -v |grep -i sparc) ; grep -i cpu /var/run/dmesg.boot ) | head -n 1) 2> /dev/null`
+     cputype=`echo "$cputype" | tr -d ' -' |tr $as_cr_LETTERS $as_cr_letters`
+     case $cputype in
+         *ultrasparciv*) ax_gcc_arch="ultrasparc4 ultrasparc3 ultrasparc v9" ;;
+         *ultrasparciii*) ax_gcc_arch="ultrasparc3 ultrasparc v9" ;;
+         *ultrasparc*) ax_gcc_arch="ultrasparc v9" ;;
+         *supersparc*|*tms390z5[[05]]*) ax_gcc_arch="supersparc v8" ;;
+         *hypersparc*|*rt62[[056]]*) ax_gcc_arch="hypersparc v8" ;;
+         *cypress*) ax_gcc_arch=cypress ;;
+     esac ;;
+
+  alphaev5) ax_gcc_arch=ev5 ;;
+  alphaev56) ax_gcc_arch=ev56 ;;
+  alphapca56) ax_gcc_arch="pca56 ev56" ;;
+  alphapca57) ax_gcc_arch="pca57 pca56 ev56" ;;
+  alphaev6) ax_gcc_arch=ev6 ;;
+  alphaev67) ax_gcc_arch=ev67 ;;
+  alphaev68) ax_gcc_arch="ev68 ev67" ;;
+  alphaev69) ax_gcc_arch="ev69 ev68 ev67" ;;
+  alphaev7) ax_gcc_arch="ev7 ev69 ev68 ev67" ;;
+  alphaev79) ax_gcc_arch="ev79 ev7 ev69 ev68 ev67" ;;
+
+  powerpc*)
+     cputype=`((grep cpu /proc/cpuinfo | head -n 1 | cut -d: -f2 | cut -d, -f1 | sed 's/ //g') ; /usr/bin/machine ; /bin/machine; grep CPU /var/run/dmesg.boot | head -n 1 | cut -d" " -f2) 2> /dev/null`
+     cputype=`echo $cputype | sed -e 's/ppc//g;s/ *//g'`
+     case $cputype in
+       *750*) ax_gcc_arch="750 G3" ;;
+       *740[[0-9]]*) ax_gcc_arch="$cputype 7400 G4" ;;
+       *74[[4-5]][[0-9]]*) ax_gcc_arch="$cputype 7450 G4" ;;
+       *74[[0-9]][[0-9]]*) ax_gcc_arch="$cputype G4" ;;
+       *970*) ax_gcc_arch="970 G5 power4";;
+       *POWER4*|*power4*|*gq*) ax_gcc_arch="power4 970";;
+       *POWER5*|*power5*|*gr*|*gs*) ax_gcc_arch="power5 power4 970";;
+       603ev|8240) ax_gcc_arch="$cputype 603e 603";;
+       *) ax_gcc_arch=$cputype ;;
+     esac
+     ax_gcc_arch="$ax_gcc_arch powerpc"
+     ;;
+esac
+fi # not cross-compiling
+fi # guess arch
+
+if test "x$ax_gcc_arch" != x -a "x$ax_gcc_arch" != xno; then
+for arch in $ax_gcc_arch; do
+  if test "x[]m4_default([$1],yes)" = xyes; then # if we require portable code
+    flags="-mtune=$arch"
+    # -mcpu=$arch and m$arch generate nonportable code on every arch except
+    # x86.  And some other arches (e.g. Alpha) don't accept -mtune.  Grrr.
+    case $host_cpu in i*86|x86_64*) flags="$flags -mcpu=$arch -m$arch";; esac
+  else
+    flags="-march=$arch -mcpu=$arch -m$arch"
+  fi
+  for flag in $flags; do
+    AX_CHECK_COMPILE_FLAG($flag, [ax_cv_gcc_archflag=$flag; break])
+  done
+  test "x$ax_cv_gcc_archflag" = xunknown || break
+done
+fi
+
+fi # $GCC=yes
+])
+AC_MSG_CHECKING([for gcc architecture flag])
+AC_MSG_RESULT($ax_cv_gcc_archflag)
+if test "x$ax_cv_gcc_archflag" = xunknown; then
+  m4_default([$3],:)
+else
+  m4_default([$2], [CFLAGS="$CFLAGS $ax_cv_gcc_archflag"])
+fi
+])
diff --git a/scripts/ax_gcc_x86_cpuid.m4 b/scripts/ax_gcc_x86_cpuid.m4
new file mode 100644
index 0000000..7d46fee
--- /dev/null
+++ b/scripts/ax_gcc_x86_cpuid.m4
@@ -0,0 +1,79 @@
+# ===========================================================================
+#     http://www.gnu.org/software/autoconf-archive/ax_gcc_x86_cpuid.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_GCC_X86_CPUID(OP)
+#
+# DESCRIPTION
+#
+#   On Pentium and later x86 processors, with gcc or a compiler that has a
+#   compatible syntax for inline assembly instructions, run a small program
+#   that executes the cpuid instruction with input OP. This can be used to
+#   detect the CPU type.
+#
+#   On output, the values of the eax, ebx, ecx, and edx registers are stored
+#   as hexadecimal strings as "eax:ebx:ecx:edx" in the cache variable
+#   ax_cv_gcc_x86_cpuid_OP.
+#
+#   If the cpuid instruction fails (because you are running a
+#   cross-compiler, or because you are not using gcc, or because you are on
+#   a processor that doesn't have this instruction), ax_cv_gcc_x86_cpuid_OP
+#   is set to the string "unknown".
+#
+#   This macro mainly exists to be used in AX_GCC_ARCHFLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Steven G. Johnson <stevenj at alum.mit.edu>
+#   Copyright (c) 2008 Matteo Frigo
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program is distributed in the hope that it will be useful, but
+#   WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+#   Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 7
+
+AC_DEFUN([AX_GCC_X86_CPUID],
+[AC_REQUIRE([AC_PROG_CC])
+AC_LANG_PUSH([C])
+AC_CACHE_CHECK(for x86 cpuid $1 output, ax_cv_gcc_x86_cpuid_$1,
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <stdio.h>], [
+     int op = $1, eax, ebx, ecx, edx;
+     FILE *f;
+      __asm__("cpuid"
+        : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
+        : "a" (op));
+     f = fopen("conftest_cpuid", "w"); if (!f) return 1;
+     fprintf(f, "%x:%x:%x:%x\n", eax, ebx, ecx, edx);
+     fclose(f);
+     return 0;
+])],
+     [ax_cv_gcc_x86_cpuid_$1=`cat conftest_cpuid`; rm -f conftest_cpuid],
+     [ax_cv_gcc_x86_cpuid_$1=unknown; rm -f conftest_cpuid],
+     [ax_cv_gcc_x86_cpuid_$1=unknown])])
+AC_LANG_POP([C])
+])
diff --git a/scripts/benchmark_compare.c b/scripts/benchmark_compare.c
new file mode 100644
index 0000000..dbb138a
--- /dev/null
+++ b/scripts/benchmark_compare.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// benchmark_compare.c
+// 
+// compare benchmark runs
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+// print usage/help message
+void usage()
+{
+    printf("benchmark_compare [old_benchmark] [new_benchmark]\n");
+}
+
+// define benchmark_t
+struct benchmark_t {
+    //unsigned int id;
+    char name[64];
+    //unsigned int name_len;
+    //unsigned int num_trials;
+    //float extime;
+    //float rate;
+    float cycles_per_trial;
+
+    // link to other benchmark
+    struct benchmark_t * link;
+};
+
+struct benchlist_s {
+    struct benchmark_t * benchmarks;
+    unsigned int num_benchmarks;
+};
+
+typedef struct benchlist_s * benchlist;
+
+// 
+benchlist benchlist_create();
+void benchlist_destroy(benchlist _q);
+void benchlist_print(benchlist _q);
+void benchlist_append(benchlist _q,
+                      char * _name,
+                      float _cycles_per_trial);
+
+void benchlist_link(benchlist _q0,
+                    benchlist _q1);
+
+// is line a comment?
+int parse_file(const char * _filename,
+               benchlist _benchmarks);
+
+// read line from file
+//  _fid    :   input file
+//  _buffer :   output buffer
+//  _n      :   buffer length
+void readline(FILE * _fid,
+              char * _buffer,
+              unsigned int _n);
+
+// is line a comment?
+int line_is_comment(char * _buffer);
+
+int main(int argc, char*argv[])
+{
+    if (argc != 3) {
+        usage();
+        exit(1);
+    }
+
+    // parse old benchmarks
+    benchlist benchmarks_old = benchlist_create();
+    parse_file(argv[1], benchmarks_old);
+    //benchlist_print(benchmarks_old);
+
+    // parse new benchmarks
+    benchlist benchmarks_new = benchlist_create();
+    parse_file(argv[2], benchmarks_new);
+    //benchlist_print(benchmarks_new);
+
+    // link benchmarks and print results
+    benchlist_link(benchmarks_old, benchmarks_new);
+    benchlist_print(benchmarks_old);
+
+    // destroy benchmark lists
+    benchlist_destroy(benchmarks_old);
+    benchlist_destroy(benchmarks_new);
+
+    printf("done.\n");
+    return 0;
+}
+
+// 
+benchlist benchlist_create()
+{
+    benchlist q = (benchlist) malloc(sizeof(struct benchlist_s));
+
+    // initialize internals
+    q->benchmarks = NULL;
+    q->num_benchmarks = 0;
+
+    // return main object
+    return q;
+}
+
+void benchlist_destroy(benchlist _q)
+{
+    // free internals
+    if (_q->benchmarks != NULL)
+        free(_q->benchmarks);
+
+    // free main object memory
+    free(_q);
+}
+
+#if 0
+void benchlist_print(benchlist _q)
+{
+    unsigned int i;
+    printf("benchlist [%u]:\n", _q->num_benchmarks);
+    for (i=0; i<_q->num_benchmarks; i++) {
+        printf("  - %-32s : %14.2f", _q->benchmarks[i].name,
+                                     _q->benchmarks[i].cycles_per_trial);
+        if (_q->benchmarks[i].link == NULL) {
+            printf("\n");
+        } else {
+            // print delta
+            float cycles_old = _q->benchmarks[i].cycles_per_trial;
+            float cycles_new = _q->benchmarks[i].link->cycles_per_trial;
+            float delta = (cycles_new - cycles_old)/cycles_old;
+
+            // print static value
+            printf(" : %12.3f   %8.2f %%\n", cycles_new, 100*delta);
+        }
+
+    }
+}
+#else
+void benchlist_print(benchlist _q)
+{
+    unsigned int i;
+    printf("benchlist [%u]:\n", _q->num_benchmarks);
+    for (i=0; i<_q->num_benchmarks; i++) {
+        if (_q->benchmarks[i].link == NULL)
+            continue;
+
+        printf("  - %-28s ", _q->benchmarks[i].name);
+            // print delta
+            float cycles_old = _q->benchmarks[i].cycles_per_trial;
+            float cycles_new = _q->benchmarks[i].link->cycles_per_trial;
+            float speedup    = (cycles_old + 1e-6f) / (cycles_new + 1e-6f);
+
+            // print bar graph style response
+            unsigned int num_spaces = 20;
+            unsigned int num_hashes = (unsigned int) (0.4*fabsf(log2f(speedup)) * num_spaces);
+            if (num_hashes > num_spaces) num_hashes = num_spaces;
+            unsigned int j;
+            if (speedup > 1.0) {
+                // newer is better
+                for (j=0; j<num_spaces; j++) printf(".");
+                printf("[%7.3f]", speedup);
+                for (j=0; j<num_hashes; j++) printf("#");
+                for (   ; j<num_spaces; j++) printf(".");
+                printf("\n");
+            } else {
+                // older is better
+                for (j=0; j<num_spaces-num_hashes; j++) printf(".");
+                for (   ; j<num_spaces; j++) printf("#");
+                printf("[%7.3f]", speedup);
+                for (j=0; j<num_spaces; j++) printf(".");
+                printf("\n");
+            }
+        }
+}
+#endif
+
+void benchlist_append(benchlist _q,
+                      char * _name,
+                      float _cycles_per_trial)
+{
+    // TODO : check for uniqueness
+    unsigned int i;
+    for (i=0; i<_q->num_benchmarks; i++) {
+        if ( strncmp(_q->benchmarks[i].name, _name, 64) == 0) {
+            fprintf(stderr,"warning: cannot append benchmark '%s'; benchmark already exists\n", _name);
+            return;
+        }
+    }
+
+    // re-allocate memory array
+    _q->num_benchmarks++;
+    _q->benchmarks = (struct benchmark_t*)realloc(_q->benchmarks, (_q->num_benchmarks)*sizeof(struct benchmark_t));
+
+    //printf("appending '%s' (%f)\n", _name, _cycles_per_trial);
+
+    // 
+    // append new element
+    //
+
+    // copy name
+    strncpy(_q->benchmarks[_q->num_benchmarks-1].name, _name, 64);
+
+    // copy properties
+    _q->benchmarks[_q->num_benchmarks-1].cycles_per_trial = _cycles_per_trial;
+
+    // set link to NULL
+    _q->benchmarks[_q->num_benchmarks-1].link = NULL;
+}
+
+void benchlist_link(benchlist _q0,
+                    benchlist _q1)
+{
+    // link nodes
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_q0->num_benchmarks; i++) {
+        for (j=0; j<_q1->num_benchmarks; j++) {
+            if ( strncmp(_q0->benchmarks[i].name, _q1->benchmarks[j].name, 64)==0 ) {
+                // match found; link nodes
+                _q0->benchmarks[i].link = &_q1->benchmarks[j];
+                _q1->benchmarks[j].link = &_q0->benchmarks[i];
+            }
+        }
+    }
+}
+
+
+// is line a comment?
+int parse_file(const char * _filename,
+               benchlist _benchmarks)
+{
+    //
+    FILE * fid = fopen(_filename,"r");
+    if (!fid) {
+        fprintf(stderr,"error: parse_file(), could not open '%s' for reading\n", _filename);
+        exit(1);
+    }
+
+    printf("parsing '%s'...\n", _filename);
+    char buffer[256];   // line buffer
+
+    int id;
+    char name[64];
+    unsigned long int num_trials;
+    float execution_time;
+    float rate;
+    float cycles_per_trial;
+
+    do {
+        // read line into buffer
+        readline(fid, buffer, 256);
+
+        // skip comment lines
+        if (line_is_comment(buffer))
+            continue;
+
+        // scan line for results
+        int results = sscanf(buffer,"%d %s %lu %f %f %f\n",
+                             &id,
+                             name,
+                             &num_trials,
+                             &execution_time,
+                             &rate,
+                             &cycles_per_trial);
+        if (results < 6) {
+            //fprintf(stderr,"warning: skipping line '%s'\n", buffer);
+            continue;
+        }
+
+        // append...
+        benchlist_append(_benchmarks, name, cycles_per_trial);
+
+    } while (!feof(fid));
+
+    // close the file
+    fclose(fid);
+
+    // return
+    return 0;
+}
+
+
+// read line from file
+//  _fid    :   input file
+//  _buffer :   output buffer
+//  _n      :   buffer length
+void readline(FILE * _fid,
+              char * _buffer,
+              unsigned int _n)
+{
+    unsigned int i=0;
+    char c;
+    while ( (c = fgetc(_fid)) != EOF && c != '\n' && i < _n-1) {
+        _buffer[i] = c;
+        i++;
+    }
+    _buffer[i] = '\0';
+}
+
+// is line a comment?
+int line_is_comment(char * _buffer)
+{
+    // read first non-whitespace character
+    unsigned int i=0;
+    char c;
+    do {
+        c = _buffer[i];
+        i++;
+    } while (c == ' ' || c == '\t');
+
+    if (c == '#' || c == '\0')
+        return 1;
+
+    return 0;
+}
+
diff --git a/scripts/config.guess b/scripts/config.guess
new file mode 100755
index 0000000..c2246a4
--- /dev/null
+++ b/scripts/config.guess
@@ -0,0 +1,1502 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+#   Free Software Foundation, Inc.
+
+timestamp='2009-12-30'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Originally written by Per Bothner.  Please send patches (context
+# diff format) to <config-patches at gnu.org> and include a ChangeLog
+# entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub.  If it succeeds, it prints the system name on stdout, and
+# exits with 0.  Otherwise, it exits with 1.
+#
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches at gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
+2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free
+Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,)    echo "int x;" > $dummy.c ;
+	for c in cc gcc c89 c99 ; do
+	  if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+	     CC_FOR_BUILD="$c"; break ;
+	  fi ;
+	done ;
+	if test x"$CC_FOR_BUILD" = x ; then
+	  CC_FOR_BUILD=no_compiler_found ;
+	fi
+	;;
+ ,,*)   CC_FOR_BUILD=$CC ;;
+ ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+esac ; set_cc_for_build= ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi at noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+	PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    *:NetBSD:*:*)
+	# NetBSD (nbsd) targets should (where applicable) match one or
+	# more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*,
+	# *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+	# switched to ELF, *-*-netbsd* would select the old
+	# object file format.  This provides both forward
+	# compatibility and a consistent mechanism for selecting the
+	# object file format.
+	#
+	# Note: NetBSD doesn't particularly care about the vendor
+	# portion of the name.  We always set it to "unknown".
+	sysctl="sysctl -n hw.machine_arch"
+	UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+	    /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+	case "${UNAME_MACHINE_ARCH}" in
+	    armeb) machine=armeb-unknown ;;
+	    arm*) machine=arm-unknown ;;
+	    sh3el) machine=shl-unknown ;;
+	    sh3eb) machine=sh-unknown ;;
+	    sh5el) machine=sh5le-unknown ;;
+	    *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+	esac
+	# The Operating System including object format, if it has switched
+	# to ELF recently, or will in the future.
+	case "${UNAME_MACHINE_ARCH}" in
+	    arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+		eval $set_cc_for_build
+		if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+			| grep -q __ELF__
+		then
+		    # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+		    # Return netbsd for either.  FIX?
+		    os=netbsd
+		else
+		    os=netbsdelf
+		fi
+		;;
+	    *)
+	        os=netbsd
+		;;
+	esac
+	# The OS release
+	# Debian GNU/NetBSD machines have a different userland, and
+	# thus, need a distinct triplet. However, they do not need
+	# kernel version information, so it can be replaced with a
+	# suitable tag, in the style of linux-gnu.
+	case "${UNAME_VERSION}" in
+	    Debian*)
+		release='-gnu'
+		;;
+	    *)
+		release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+		;;
+	esac
+	# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+	# contains redundant information, the shorter form:
+	# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+	echo "${machine}-${os}${release}"
+	exit ;;
+    *:OpenBSD:*:*)
+	UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+	echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
+	exit ;;
+    *:ekkoBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+	exit ;;
+    *:SolidBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
+	exit ;;
+    macppc:MirBSD:*:*)
+	echo powerpc-unknown-mirbsd${UNAME_RELEASE}
+	exit ;;
+    *:MirBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+	exit ;;
+    alpha:OSF1:*:*)
+	case $UNAME_RELEASE in
+	*4.0)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+		;;
+	*5.*)
+	        UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+		;;
+	esac
+	# According to Compaq, /usr/sbin/psrinfo has been available on
+	# OSF/1 and Tru64 systems produced since 1995.  I hope that
+	# covers most systems running today.  This code pipes the CPU
+	# types through head -n 1, so we only detect the type of CPU 0.
+	ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+	case "$ALPHA_CPU_TYPE" in
+	    "EV4 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV4.5 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "LCA4 (21066/21068)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV5 (21164)")
+		UNAME_MACHINE="alphaev5" ;;
+	    "EV5.6 (21164A)")
+		UNAME_MACHINE="alphaev56" ;;
+	    "EV5.6 (21164PC)")
+		UNAME_MACHINE="alphapca56" ;;
+	    "EV5.7 (21164PC)")
+		UNAME_MACHINE="alphapca57" ;;
+	    "EV6 (21264)")
+		UNAME_MACHINE="alphaev6" ;;
+	    "EV6.7 (21264A)")
+		UNAME_MACHINE="alphaev67" ;;
+	    "EV6.8CB (21264C)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8AL (21264B)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8CX (21264D)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.9A (21264/EV69A)")
+		UNAME_MACHINE="alphaev69" ;;
+	    "EV7 (21364)")
+		UNAME_MACHINE="alphaev7" ;;
+	    "EV7.9 (21364A)")
+		UNAME_MACHINE="alphaev79" ;;
+	esac
+	# A Pn.n version is a patched version.
+	# A Vn.n version is a released version.
+	# A Tn.n version is a released field test version.
+	# A Xn.n version is an unreleased experimental baselevel.
+	# 1.2 uses "1.2" for uname -r.
+	echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+	exit ;;
+    Alpha\ *:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# Should we change UNAME_MACHINE based on the output of uname instead
+	# of the specific Alpha model?
+	echo alpha-pc-interix
+	exit ;;
+    21064:Windows_NT:50:3)
+	echo alpha-dec-winnt3.5
+	exit ;;
+    Amiga*:UNIX_System_V:4.0:*)
+	echo m68k-unknown-sysv4
+	exit ;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-amigaos
+	exit ;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-morphos
+	exit ;;
+    *:OS/390:*:*)
+	echo i370-ibm-openedition
+	exit ;;
+    *:z/VM:*:*)
+	echo s390-ibm-zvmoe
+	exit ;;
+    *:OS400:*:*)
+        echo powerpc-ibm-os400
+	exit ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+	echo arm-acorn-riscix${UNAME_RELEASE}
+	exit ;;
+    arm:riscos:*:*|arm:RISCOS:*:*)
+	echo arm-unknown-riscos
+	exit ;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+	echo hppa1.1-hitachi-hiuxmpp
+	exit ;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+	# akee at wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+	if test "`(/bin/universe) 2>/dev/null`" = att ; then
+		echo pyramid-pyramid-sysv3
+	else
+		echo pyramid-pyramid-bsd
+	fi
+	exit ;;
+    NILE*:*:*:dcosx)
+	echo pyramid-pyramid-svr4
+	exit ;;
+    DRS?6000:unix:4.0:6*)
+	echo sparc-icl-nx6
+	exit ;;
+    DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+	case `/usr/bin/uname -p` in
+	    sparc) echo sparc-icl-nx7; exit ;;
+	esac ;;
+    s390x:SunOS:*:*)
+	echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4H:SunOS:5.*:*)
+	echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+	echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*)
+	echo i386-pc-auroraux${UNAME_RELEASE}
+	exit ;;
+    i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+	eval $set_cc_for_build
+	SUN_ARCH="i386"
+	# If there is a compiler, see if it is configured for 64-bit objects.
+	# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
+	# This test works for both compilers.
+	if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+	    if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
+		(CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+		grep IS_64BIT_ARCH >/dev/null
+	    then
+		SUN_ARCH="x86_64"
+	    fi
+	fi
+	echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:6*:*)
+	# According to config.sub, this is the proper way to canonicalize
+	# SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+	# it's likely to be more like Solaris than SunOS4.
+	echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    sun4*:SunOS:*:*)
+	case "`/usr/bin/arch -k`" in
+	    Series*|S4*)
+		UNAME_RELEASE=`uname -v`
+		;;
+	esac
+	# Japanese Language versions have a version number like `4.1.3-JL'.
+	echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+	exit ;;
+    sun3*:SunOS:*:*)
+	echo m68k-sun-sunos${UNAME_RELEASE}
+	exit ;;
+    sun*:*:4.2BSD:*)
+	UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+	test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+	case "`/bin/arch`" in
+	    sun3)
+		echo m68k-sun-sunos${UNAME_RELEASE}
+		;;
+	    sun4)
+		echo sparc-sun-sunos${UNAME_RELEASE}
+		;;
+	esac
+	exit ;;
+    aushp:SunOS:*:*)
+	echo sparc-auspex-sunos${UNAME_RELEASE}
+	exit ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+	exit ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+	echo m68k-atari-mint${UNAME_RELEASE}
+        exit ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+	exit ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+        echo m68k-milan-mint${UNAME_RELEASE}
+        exit ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+        echo m68k-hades-mint${UNAME_RELEASE}
+        exit ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+        echo m68k-unknown-mint${UNAME_RELEASE}
+        exit ;;
+    m68k:machten:*:*)
+	echo m68k-apple-machten${UNAME_RELEASE}
+	exit ;;
+    powerpc:machten:*:*)
+	echo powerpc-apple-machten${UNAME_RELEASE}
+	exit ;;
+    RISC*:Mach:*:*)
+	echo mips-dec-mach_bsd4.3
+	exit ;;
+    RISC*:ULTRIX:*:*)
+	echo mips-dec-ultrix${UNAME_RELEASE}
+	exit ;;
+    VAX*:ULTRIX*:*:*)
+	echo vax-dec-ultrix${UNAME_RELEASE}
+	exit ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+	echo clipper-intergraph-clix${UNAME_RELEASE}
+	exit ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+	int main (int argc, char *argv[]) {
+#else
+	int main (argc, argv) int argc; char *argv[]; {
+#endif
+	#if defined (host_mips) && defined (MIPSEB)
+	#if defined (SYSTYPE_SYSV)
+	  printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_SVR4)
+	  printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+	  printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+	#endif
+	#endif
+	  exit (-1);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c &&
+	  dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+	  SYSTEM_NAME=`$dummy $dummyarg` &&
+	    { echo "$SYSTEM_NAME"; exit; }
+	echo mips-mips-riscos${UNAME_RELEASE}
+	exit ;;
+    Motorola:PowerMAX_OS:*:*)
+	echo powerpc-motorola-powermax
+	exit ;;
+    Motorola:*:4.3:PL8-*)
+	echo powerpc-harris-powermax
+	exit ;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+	echo powerpc-harris-powermax
+	exit ;;
+    Night_Hawk:Power_UNIX:*:*)
+	echo powerpc-harris-powerunix
+	exit ;;
+    m88k:CX/UX:7*:*)
+	echo m88k-harris-cxux7
+	exit ;;
+    m88k:*:4*:R4*)
+	echo m88k-motorola-sysv4
+	exit ;;
+    m88k:*:3*:R3*)
+	echo m88k-motorola-sysv3
+	exit ;;
+    AViiON:dgux:*:*)
+        # DG/UX returns AViiON for all architectures
+        UNAME_PROCESSOR=`/usr/bin/uname -p`
+	if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+	then
+	    if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+	       [ ${TARGET_BINARY_INTERFACE}x = x ]
+	    then
+		echo m88k-dg-dgux${UNAME_RELEASE}
+	    else
+		echo m88k-dg-dguxbcs${UNAME_RELEASE}
+	    fi
+	else
+	    echo i586-dg-dgux${UNAME_RELEASE}
+	fi
+ 	exit ;;
+    M88*:DolphinOS:*:*)	# DolphinOS (SVR3)
+	echo m88k-dolphin-sysv3
+	exit ;;
+    M88*:*:R3*:*)
+	# Delta 88k system running SVR3
+	echo m88k-motorola-sysv3
+	exit ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+	echo m88k-tektronix-sysv3
+	exit ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+	echo m68k-tektronix-bsd
+	exit ;;
+    *:IRIX*:*:*)
+	echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+	exit ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+	echo romp-ibm-aix     # uname -m gives an 8 hex-code CPU id
+	exit ;;               # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+	echo i386-ibm-aix
+	exit ;;
+    ia64:AIX:*:*)
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+	exit ;;
+    *:AIX:2:3)
+	if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+		eval $set_cc_for_build
+		sed 's/^		//' << EOF >$dummy.c
+		#include <sys/systemcfg.h>
+
+		main()
+			{
+			if (!__power_pc())
+				exit(1);
+			puts("powerpc-ibm-aix3.2.5");
+			exit(0);
+			}
+EOF
+		if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+		then
+			echo "$SYSTEM_NAME"
+		else
+			echo rs6000-ibm-aix3.2.5
+		fi
+	elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+		echo rs6000-ibm-aix3.2.4
+	else
+		echo rs6000-ibm-aix3.2
+	fi
+	exit ;;
+    *:AIX:*:[456])
+	IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+	if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+		IBM_ARCH=rs6000
+	else
+		IBM_ARCH=powerpc
+	fi
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+	exit ;;
+    *:AIX:*:*)
+	echo rs6000-ibm-aix
+	exit ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+	echo romp-ibm-bsd4.4
+	exit ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+	echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+	exit ;;                             # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+	echo rs6000-bull-bosx
+	exit ;;
+    DPX/2?00:B.O.S.:*:*)
+	echo m68k-bull-sysv3
+	exit ;;
+    9000/[34]??:4.3bsd:1.*:*)
+	echo m68k-hp-bsd
+	exit ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+	echo m68k-hp-bsd4.4
+	exit ;;
+    9000/[34678]??:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	case "${UNAME_MACHINE}" in
+	    9000/31? )            HP_ARCH=m68000 ;;
+	    9000/[34]?? )         HP_ARCH=m68k ;;
+	    9000/[678][0-9][0-9])
+		if [ -x /usr/bin/getconf ]; then
+		    sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+                    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+                    case "${sc_cpu_version}" in
+                      523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+                      528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+                      532)                      # CPU_PA_RISC2_0
+                        case "${sc_kernel_bits}" in
+                          32) HP_ARCH="hppa2.0n" ;;
+                          64) HP_ARCH="hppa2.0w" ;;
+			  '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+                        esac ;;
+                    esac
+		fi
+		if [ "${HP_ARCH}" = "" ]; then
+		    eval $set_cc_for_build
+		    sed 's/^              //' << EOF >$dummy.c
+
+              #define _HPUX_SOURCE
+              #include <stdlib.h>
+              #include <unistd.h>
+
+              int main ()
+              {
+              #if defined(_SC_KERNEL_BITS)
+                  long bits = sysconf(_SC_KERNEL_BITS);
+              #endif
+                  long cpu  = sysconf (_SC_CPU_VERSION);
+
+                  switch (cpu)
+              	{
+              	case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+              	case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+              	case CPU_PA_RISC2_0:
+              #if defined(_SC_KERNEL_BITS)
+              	    switch (bits)
+              		{
+              		case 64: puts ("hppa2.0w"); break;
+              		case 32: puts ("hppa2.0n"); break;
+              		default: puts ("hppa2.0"); break;
+              		} break;
+              #else  /* !defined(_SC_KERNEL_BITS) */
+              	    puts ("hppa2.0"); break;
+              #endif
+              	default: puts ("hppa1.0"); break;
+              	}
+                  exit (0);
+              }
+EOF
+		    (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+		    test -z "$HP_ARCH" && HP_ARCH=hppa
+		fi ;;
+	esac
+	if [ ${HP_ARCH} = "hppa2.0w" ]
+	then
+	    eval $set_cc_for_build
+
+	    # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+	    # 32-bit code.  hppa64-hp-hpux* has the same kernel and a compiler
+	    # generating 64-bit code.  GNU and HP use different nomenclature:
+	    #
+	    # $ CC_FOR_BUILD=cc ./config.guess
+	    # => hppa2.0w-hp-hpux11.23
+	    # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+	    # => hppa64-hp-hpux11.23
+
+	    if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+		grep -q __LP64__
+	    then
+		HP_ARCH="hppa2.0w"
+	    else
+		HP_ARCH="hppa64"
+	    fi
+	fi
+	echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+	exit ;;
+    ia64:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	echo ia64-hp-hpux${HPUX_REV}
+	exit ;;
+    3050*:HI-UX:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#include <unistd.h>
+	int
+	main ()
+	{
+	  long cpu = sysconf (_SC_CPU_VERSION);
+	  /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+	     true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+	     results, however.  */
+	  if (CPU_IS_PA_RISC (cpu))
+	    {
+	      switch (cpu)
+		{
+		  case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+		  default: puts ("hppa-hitachi-hiuxwe2"); break;
+		}
+	    }
+	  else if (CPU_IS_HP_MC68K (cpu))
+	    puts ("m68k-hitachi-hiuxwe2");
+	  else puts ("unknown-hitachi-hiuxwe2");
+	  exit (0);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+		{ echo "$SYSTEM_NAME"; exit; }
+	echo unknown-hitachi-hiuxwe2
+	exit ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+	echo hppa1.1-hp-bsd
+	exit ;;
+    9000/8??:4.3bsd:*:*)
+	echo hppa1.0-hp-bsd
+	exit ;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+	echo hppa1.0-hp-mpeix
+	exit ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+	echo hppa1.1-hp-osf
+	exit ;;
+    hp8??:OSF1:*:*)
+	echo hppa1.0-hp-osf
+	exit ;;
+    i*86:OSF1:*:*)
+	if [ -x /usr/sbin/sysversion ] ; then
+	    echo ${UNAME_MACHINE}-unknown-osf1mk
+	else
+	    echo ${UNAME_MACHINE}-unknown-osf1
+	fi
+	exit ;;
+    parisc*:Lites*:*:*)
+	echo hppa1.1-hp-lites
+	exit ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+	echo c1-convex-bsd
+        exit ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+        exit ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+	echo c34-convex-bsd
+        exit ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+	echo c38-convex-bsd
+        exit ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+	echo c4-convex-bsd
+        exit ;;
+    CRAY*Y-MP:*:*:*)
+	echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*[A-Z]90:*:*:*)
+	echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+	| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+	      -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+	      -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*TS:*:*:*)
+	echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*T3E:*:*:*)
+	echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    CRAY*SV1:*:*:*)
+	echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    *:UNICOS/mp:*:*)
+	echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit ;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+	FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+        echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+        exit ;;
+    5000:UNIX_System_V:4.*:*)
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+        echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+	exit ;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+	echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+	exit ;;
+    sparc*:BSD/OS:*:*)
+	echo sparc-unknown-bsdi${UNAME_RELEASE}
+	exit ;;
+    *:BSD/OS:*:*)
+	echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+	exit ;;
+    *:FreeBSD:*:*)
+	case ${UNAME_MACHINE} in
+	    pc98)
+		echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+	    amd64)
+		echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+	    *)
+		echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+	esac
+	exit ;;
+    i*:CYGWIN*:*)
+	echo ${UNAME_MACHINE}-pc-cygwin
+	exit ;;
+    *:MINGW*:*)
+	echo ${UNAME_MACHINE}-pc-mingw32
+	exit ;;
+    i*:windows32*:*)
+    	# uname -m includes "-pc" on this system.
+    	echo ${UNAME_MACHINE}-mingw32
+	exit ;;
+    i*:PW*:*)
+	echo ${UNAME_MACHINE}-pc-pw32
+	exit ;;
+    *:Interix*:*)
+    	case ${UNAME_MACHINE} in
+	    x86)
+		echo i586-pc-interix${UNAME_RELEASE}
+		exit ;;
+	    authenticamd | genuineintel | EM64T)
+		echo x86_64-unknown-interix${UNAME_RELEASE}
+		exit ;;
+	    IA64)
+		echo ia64-unknown-interix${UNAME_RELEASE}
+		exit ;;
+	esac ;;
+    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+	echo i${UNAME_MACHINE}-pc-mks
+	exit ;;
+    8664:Windows_NT:*)
+	echo x86_64-pc-mks
+	exit ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+	# UNAME_MACHINE based on the output of uname instead of i386?
+	echo i586-pc-interix
+	exit ;;
+    i*:UWIN*:*)
+	echo ${UNAME_MACHINE}-pc-uwin
+	exit ;;
+    amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+	echo x86_64-unknown-cygwin
+	exit ;;
+    p*:CYGWIN*:*)
+	echo powerpcle-unknown-cygwin
+	exit ;;
+    prep*:SunOS:5.*:*)
+	echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit ;;
+    *:GNU:*:*)
+	# the GNU system
+	echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+	exit ;;
+    *:GNU/*:*:*)
+	# other systems with GNU libc and userland
+	echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu
+	exit ;;
+    i*86:Minix:*:*)
+	echo ${UNAME_MACHINE}-pc-minix
+	exit ;;
+    alpha:Linux:*:*)
+	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+	  EV5)   UNAME_MACHINE=alphaev5 ;;
+	  EV56)  UNAME_MACHINE=alphaev56 ;;
+	  PCA56) UNAME_MACHINE=alphapca56 ;;
+	  PCA57) UNAME_MACHINE=alphapca56 ;;
+	  EV6)   UNAME_MACHINE=alphaev6 ;;
+	  EV67)  UNAME_MACHINE=alphaev67 ;;
+	  EV68*) UNAME_MACHINE=alphaev68 ;;
+        esac
+	objdump --private-headers /bin/sh | grep -q ld.so.1
+	if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+	echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+	exit ;;
+    arm*:Linux:*:*)
+	eval $set_cc_for_build
+	if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+	    | grep -q __ARM_EABI__
+	then
+	    echo ${UNAME_MACHINE}-unknown-linux-gnu
+	else
+	    echo ${UNAME_MACHINE}-unknown-linux-gnueabi
+	fi
+	exit ;;
+    avr32*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    cris:Linux:*:*)
+	echo cris-axis-linux-gnu
+	exit ;;
+    crisv32:Linux:*:*)
+	echo crisv32-axis-linux-gnu
+	exit ;;
+    frv:Linux:*:*)
+    	echo frv-unknown-linux-gnu
+	exit ;;
+    i*86:Linux:*:*)
+	LIBC=gnu
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#ifdef __dietlibc__
+	LIBC=dietlibc
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'`
+	echo "${UNAME_MACHINE}-pc-linux-${LIBC}"
+	exit ;;
+    ia64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    m32r*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    m68*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    mips:Linux:*:* | mips64:Linux:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#undef CPU
+	#undef ${UNAME_MACHINE}
+	#undef ${UNAME_MACHINE}el
+	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+	CPU=${UNAME_MACHINE}el
+	#else
+	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+	CPU=${UNAME_MACHINE}
+	#else
+	CPU=
+	#endif
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
+	test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; }
+	;;
+    or32:Linux:*:*)
+	echo or32-unknown-linux-gnu
+	exit ;;
+    padre:Linux:*:*)
+	echo sparc-unknown-linux-gnu
+	exit ;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+	echo hppa64-unknown-linux-gnu
+	exit ;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+	# Look for CPU level
+	case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+	  PA7*) echo hppa1.1-unknown-linux-gnu ;;
+	  PA8*) echo hppa2.0-unknown-linux-gnu ;;
+	  *)    echo hppa-unknown-linux-gnu ;;
+	esac
+	exit ;;
+    ppc64:Linux:*:*)
+	echo powerpc64-unknown-linux-gnu
+	exit ;;
+    ppc:Linux:*:*)
+	echo powerpc-unknown-linux-gnu
+	exit ;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+	echo ${UNAME_MACHINE}-ibm-linux
+	exit ;;
+    sh64*:Linux:*:*)
+    	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    sh*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    vax:Linux:*:*)
+	echo ${UNAME_MACHINE}-dec-linux-gnu
+	exit ;;
+    x86_64:Linux:*:*)
+	echo x86_64-unknown-linux-gnu
+	exit ;;
+    xtensa*:Linux:*:*)
+    	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit ;;
+    i*86:DYNIX/ptx:4*:*)
+	# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+	# earlier versions are messed up and put the nodename in both
+	# sysname and nodename.
+	echo i386-sequent-sysv4
+	exit ;;
+    i*86:UNIX_SV:4.2MP:2.*)
+        # Unixware is an offshoot of SVR4, but it has its own version
+        # number series starting with 2...
+        # I am not positive that other SVR4 systems won't match this,
+	# I just have to hope.  -- rms.
+        # Use sysv4.2uw... so that sysv4* matches it.
+	echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+	exit ;;
+    i*86:OS/2:*:*)
+	# If we were able to find `uname', then EMX Unix compatibility
+	# is probably installed.
+	echo ${UNAME_MACHINE}-pc-os2-emx
+	exit ;;
+    i*86:XTS-300:*:STOP)
+	echo ${UNAME_MACHINE}-unknown-stop
+	exit ;;
+    i*86:atheos:*:*)
+	echo ${UNAME_MACHINE}-unknown-atheos
+	exit ;;
+    i*86:syllable:*:*)
+	echo ${UNAME_MACHINE}-pc-syllable
+	exit ;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*)
+	echo i386-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    i*86:*DOS:*:*)
+	echo ${UNAME_MACHINE}-pc-msdosdjgpp
+	exit ;;
+    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+	UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+	if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+		echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+	else
+		echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+	fi
+	exit ;;
+    i*86:*:5:[678]*)
+    	# UnixWare 7.x, OpenUNIX and OpenServer 6.
+	case `/bin/uname -X | grep "^Machine"` in
+	    *486*)	     UNAME_MACHINE=i486 ;;
+	    *Pentium)	     UNAME_MACHINE=i586 ;;
+	    *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+	esac
+	echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+	exit ;;
+    i*86:*:3.2:*)
+	if test -f /usr/options/cb.name; then
+		UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+		echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+	elif /bin/uname -X 2>/dev/null >/dev/null ; then
+		UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+		(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+		(/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+			&& UNAME_MACHINE=i586
+		(/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+	else
+		echo ${UNAME_MACHINE}-pc-sysv32
+	fi
+	exit ;;
+    pc:*:*:*)
+	# Left here for compatibility:
+        # uname -m prints for DJGPP always 'pc', but it prints nothing about
+        # the processor, so we play safe by assuming i586.
+	# Note: whatever this is, it MUST be the same as what config.sub
+	# prints for the "djgpp" host, or else GDB configury will decide that
+	# this is a cross-build.
+	echo i586-pc-msdosdjgpp
+        exit ;;
+    Intel:Mach:3*:*)
+	echo i386-pc-mach3
+	exit ;;
+    paragon:*:*:*)
+	echo i860-intel-osf1
+	exit ;;
+    i860:*:4.*:*) # i860-SVR4
+	if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+	  echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+	else # Add other i860-SVR4 vendors below as they are discovered.
+	  echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+	fi
+	exit ;;
+    mini*:CTIX:SYS*5:*)
+	# "miniframe"
+	echo m68010-convergent-sysv
+	exit ;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+	echo m68k-convergent-sysv
+	exit ;;
+    M680?0:D-NIX:5.3:*)
+	echo m68k-diab-dnix
+	exit ;;
+    M68*:*:R3V[5678]*:*)
+	test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+	OS_REL=''
+	test -r /etc/.relid \
+	&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	  && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+        /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+          && { echo i486-ncr-sysv4; exit; } ;;
+    NCR*:*:4.2:* | MPRAS*:*:4.2:*)
+	OS_REL='.3'
+	test -r /etc/.relid \
+	    && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	    && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; }
+	/bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \
+	    && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+	echo m68k-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    mc68030:UNIX_System_V:4.*:*)
+	echo m68k-atari-sysv4
+	exit ;;
+    TSUNAMI:LynxOS:2.*:*)
+	echo sparc-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    rs6000:LynxOS:2.*:*)
+	echo rs6000-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*)
+	echo powerpc-unknown-lynxos${UNAME_RELEASE}
+	exit ;;
+    SM[BE]S:UNIX_SV:*:*)
+	echo mips-dde-sysv${UNAME_RELEASE}
+	exit ;;
+    RM*:ReliantUNIX-*:*:*)
+	echo mips-sni-sysv4
+	exit ;;
+    RM*:SINIX-*:*:*)
+	echo mips-sni-sysv4
+	exit ;;
+    *:SINIX-*:*:*)
+	if uname -p 2>/dev/null >/dev/null ; then
+		UNAME_MACHINE=`(uname -p) 2>/dev/null`
+		echo ${UNAME_MACHINE}-sni-sysv4
+	else
+		echo ns32k-sni-sysv
+	fi
+	exit ;;
+    PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+                      # says <Richard.M.Bartel at ccMail.Census.GOV>
+        echo i586-unisys-sysv4
+        exit ;;
+    *:UNIX_System_V:4*:FTX*)
+	# From Gerald Hewes <hewes at openmarket.com>.
+	# How about differentiating between stratus architectures? -djm
+	echo hppa1.1-stratus-sysv4
+	exit ;;
+    *:*:*:FTX*)
+	# From seanf at swdc.stratus.com.
+	echo i860-stratus-sysv4
+	exit ;;
+    i*86:VOS:*:*)
+	# From Paul.Green at stratus.com.
+	echo ${UNAME_MACHINE}-stratus-vos
+	exit ;;
+    *:VOS:*:*)
+	# From Paul.Green at stratus.com.
+	echo hppa1.1-stratus-vos
+	exit ;;
+    mc68*:A/UX:*:*)
+	echo m68k-apple-aux${UNAME_RELEASE}
+	exit ;;
+    news*:NEWS-OS:6*:*)
+	echo mips-sony-newsos6
+	exit ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+	if [ -d /usr/nec ]; then
+	        echo mips-nec-sysv${UNAME_RELEASE}
+	else
+	        echo mips-unknown-sysv${UNAME_RELEASE}
+	fi
+        exit ;;
+    BeBox:BeOS:*:*)	# BeOS running on hardware made by Be, PPC only.
+	echo powerpc-be-beos
+	exit ;;
+    BeMac:BeOS:*:*)	# BeOS running on Mac or Mac clone, PPC only.
+	echo powerpc-apple-beos
+	exit ;;
+    BePC:BeOS:*:*)	# BeOS running on Intel PC compatible.
+	echo i586-pc-beos
+	exit ;;
+    BePC:Haiku:*:*)	# Haiku running on Intel PC compatible.
+	echo i586-pc-haiku
+	exit ;;
+    SX-4:SUPER-UX:*:*)
+	echo sx4-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-5:SUPER-UX:*:*)
+	echo sx5-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-6:SUPER-UX:*:*)
+	echo sx6-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-7:SUPER-UX:*:*)
+	echo sx7-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-8:SUPER-UX:*:*)
+	echo sx8-nec-superux${UNAME_RELEASE}
+	exit ;;
+    SX-8R:SUPER-UX:*:*)
+	echo sx8r-nec-superux${UNAME_RELEASE}
+	exit ;;
+    Power*:Rhapsody:*:*)
+	echo powerpc-apple-rhapsody${UNAME_RELEASE}
+	exit ;;
+    *:Rhapsody:*:*)
+	echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+	exit ;;
+    *:Darwin:*:*)
+	UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+	case $UNAME_PROCESSOR in
+	    i386)
+		eval $set_cc_for_build
+		if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+		  if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
+		      (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+		      grep IS_64BIT_ARCH >/dev/null
+		  then
+		      UNAME_PROCESSOR="x86_64"
+		  fi
+		fi ;;
+	    unknown) UNAME_PROCESSOR=powerpc ;;
+	esac
+	echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+	exit ;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+	UNAME_PROCESSOR=`uname -p`
+	if test "$UNAME_PROCESSOR" = "x86"; then
+		UNAME_PROCESSOR=i386
+		UNAME_MACHINE=pc
+	fi
+	echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+	exit ;;
+    *:QNX:*:4*)
+	echo i386-pc-qnx
+	exit ;;
+    NSE-?:NONSTOP_KERNEL:*:*)
+	echo nse-tandem-nsk${UNAME_RELEASE}
+	exit ;;
+    NSR-?:NONSTOP_KERNEL:*:*)
+	echo nsr-tandem-nsk${UNAME_RELEASE}
+	exit ;;
+    *:NonStop-UX:*:*)
+	echo mips-compaq-nonstopux
+	exit ;;
+    BS2000:POSIX*:*:*)
+	echo bs2000-siemens-sysv
+	exit ;;
+    DS/*:UNIX_System_V:*:*)
+	echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+	exit ;;
+    *:Plan9:*:*)
+	# "uname -m" is not consistent, so use $cputype instead. 386
+	# is converted to i386 for consistency with other x86
+	# operating systems.
+	if test "$cputype" = "386"; then
+	    UNAME_MACHINE=i386
+	else
+	    UNAME_MACHINE="$cputype"
+	fi
+	echo ${UNAME_MACHINE}-unknown-plan9
+	exit ;;
+    *:TOPS-10:*:*)
+	echo pdp10-unknown-tops10
+	exit ;;
+    *:TENEX:*:*)
+	echo pdp10-unknown-tenex
+	exit ;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+	echo pdp10-dec-tops20
+	exit ;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+	echo pdp10-xkl-tops20
+	exit ;;
+    *:TOPS-20:*:*)
+	echo pdp10-unknown-tops20
+	exit ;;
+    *:ITS:*:*)
+	echo pdp10-unknown-its
+	exit ;;
+    SEI:*:*:SEIUX)
+        echo mips-sei-seiux${UNAME_RELEASE}
+	exit ;;
+    *:DragonFly:*:*)
+	echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+	exit ;;
+    *:*VMS:*:*)
+    	UNAME_MACHINE=`(uname -p) 2>/dev/null`
+	case "${UNAME_MACHINE}" in
+	    A*) echo alpha-dec-vms ; exit ;;
+	    I*) echo ia64-dec-vms ; exit ;;
+	    V*) echo vax-dec-vms ; exit ;;
+	esac ;;
+    *:XENIX:*:SysV)
+	echo i386-pc-xenix
+	exit ;;
+    i*86:skyos:*:*)
+	echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
+	exit ;;
+    i*86:rdos:*:*)
+	echo ${UNAME_MACHINE}-pc-rdos
+	exit ;;
+    i*86:AROS:*:*)
+	echo ${UNAME_MACHINE}-pc-aros
+	exit ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+          "4"
+#else
+	  ""
+#endif
+         ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix\n"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+	printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+	printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+#  include <sys/param.h>
+#  if defined (BSD)
+#   if BSD == 43
+      printf ("vax-dec-bsd4.3\n"); exit (0);
+#   else
+#    if BSD == 199006
+      printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#    else
+      printf ("vax-dec-bsd\n"); exit (0);
+#    endif
+#   endif
+#  else
+    printf ("vax-dec-bsd\n"); exit (0);
+#  endif
+# else
+    printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` &&
+	{ echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+	echo c1-convex-bsd
+	exit ;;
+    c2*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+	exit ;;
+    c34*)
+	echo c34-convex-bsd
+	exit ;;
+    c38*)
+	echo c38-convex-bsd
+	exit ;;
+    c4*)
+	echo c4-convex-bsd
+	exit ;;
+    esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+and
+  http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches at gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM  = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/scripts/config.sub b/scripts/config.sub
new file mode 100755
index 0000000..c2d1257
--- /dev/null
+++ b/scripts/config.sub
@@ -0,0 +1,1714 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+#   Free Software Foundation, Inc.
+
+timestamp='2010-01-22'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine.  It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Please send patches to <config-patches at gnu.org>.  Submit a context
+# diff and a properly formatted GNU ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# You can get the latest version of this script from:
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#	CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#	CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+       $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches at gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000,
+2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free
+Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit ;;
+    --version | -v )
+       echo "$version" ; exit ;;
+    --help | --h* | -h )
+       echo "$usage"; exit ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help"
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo $1
+       exit ;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  nto-qnx* | linux-gnu* | linux-dietlibc | linux-newlib* | linux-uclibc* | \
+  uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | \
+  kopensolaris*-gnu* | \
+  storm-chaos* | os2-emx* | rtmk-nova*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+	-sun*os*)
+		# Prevent following clause from handling this invalid input.
+		;;
+	-dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+	-att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+	-unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+	-convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+	-c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+	-harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+	-apple | -axis | -knuth | -cray | -microblaze)
+		os=
+		basic_machine=$1
+		;;
+        -bluegene*)
+	        os=-cnk
+		;;
+	-sim | -cisco | -oki | -wec | -winbond)
+		os=
+		basic_machine=$1
+		;;
+	-scout)
+		;;
+	-wrs)
+		os=-vxworks
+		basic_machine=$1
+		;;
+	-chorusos*)
+		os=-chorusos
+		basic_machine=$1
+		;;
+ 	-chorusrdb)
+ 		os=-chorusrdb
+		basic_machine=$1
+ 		;;
+	-hiux*)
+		os=-hiuxwe2
+		;;
+	-sco6)
+		os=-sco5v6
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco5)
+		os=-sco3.2v5
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco4)
+		os=-sco3.2v4
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2.[4-9]*)
+		os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2v[4-9]*)
+		# Don't forget version if it is 3.2v4 or newer.
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco5v6*)
+		# Don't forget version if it is 3.2v4 or newer.
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco*)
+		os=-sco3.2v2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-udk*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-isc)
+		os=-isc2.2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-clix*)
+		basic_machine=clipper-intergraph
+		;;
+	-isc*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-lynx*)
+		os=-lynxos
+		;;
+	-ptx*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+		;;
+	-windowsnt*)
+		os=`echo $os | sed -e 's/windowsnt/winnt/'`
+		;;
+	-psos*)
+		os=-psos
+		;;
+	-mint | -mint[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+	# Recognize the basic CPU types without company name.
+	# Some are omitted here because they have special meanings below.
+	1750a | 580 \
+	| a29k \
+	| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+	| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+	| am33_2.0 \
+	| arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \
+	| bfin \
+	| c4x | clipper \
+	| d10v | d30v | dlx | dsp16xx \
+	| fido | fr30 | frv \
+	| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+	| i370 | i860 | i960 | ia64 \
+	| ip2k | iq2000 \
+	| lm32 \
+	| m32c | m32r | m32rle | m68000 | m68k | m88k \
+	| maxq | mb | microblaze | mcore | mep | metag \
+	| mips | mipsbe | mipseb | mipsel | mipsle \
+	| mips16 \
+	| mips64 | mips64el \
+	| mips64octeon | mips64octeonel \
+	| mips64orion | mips64orionel \
+	| mips64r5900 | mips64r5900el \
+	| mips64vr | mips64vrel \
+	| mips64vr4100 | mips64vr4100el \
+	| mips64vr4300 | mips64vr4300el \
+	| mips64vr5000 | mips64vr5000el \
+	| mips64vr5900 | mips64vr5900el \
+	| mipsisa32 | mipsisa32el \
+	| mipsisa32r2 | mipsisa32r2el \
+	| mipsisa64 | mipsisa64el \
+	| mipsisa64r2 | mipsisa64r2el \
+	| mipsisa64sb1 | mipsisa64sb1el \
+	| mipsisa64sr71k | mipsisa64sr71kel \
+	| mipstx39 | mipstx39el \
+	| mn10200 | mn10300 \
+	| moxie \
+	| mt \
+	| msp430 \
+	| nios | nios2 \
+	| ns16k | ns32k \
+	| or32 \
+	| pdp10 | pdp11 | pj | pjl \
+	| powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
+	| pyramid \
+	| rx \
+	| score \
+	| sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+	| sh64 | sh64le \
+	| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
+	| sparcv8 | sparcv9 | sparcv9b | sparcv9v \
+	| spu | strongarm \
+	| tahoe | thumb | tic4x | tic80 | tron \
+	| ubicom32 \
+	| v850 | v850e \
+	| we32k \
+	| x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \
+	| z8k | z80)
+		basic_machine=$basic_machine-unknown
+		;;
+	m6811 | m68hc11 | m6812 | m68hc12 | picochip)
+		# Motorola 68HC11/12.
+		basic_machine=$basic_machine-unknown
+		os=-none
+		;;
+	m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+		;;
+	ms1)
+		basic_machine=mt-unknown
+		;;
+
+	# We use `pc' rather than `unknown'
+	# because (1) that's what they normally are, and
+	# (2) the word "unknown" tends to confuse beginning users.
+	i*86 | x86_64)
+	  basic_machine=$basic_machine-pc
+	  ;;
+	# Object if more than one company name word.
+	*-*-*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+	# Recognize the basic CPU types with company name.
+	580-* \
+	| a29k-* \
+	| alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+	| alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+	| alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+	| arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
+	| avr-* | avr32-* \
+	| bfin-* | bs2000-* \
+	| c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
+	| clipper-* | craynv-* | cydra-* \
+	| d10v-* | d30v-* | dlx-* \
+	| elxsi-* \
+	| f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
+	| h8300-* | h8500-* \
+	| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+	| i*86-* | i860-* | i960-* | ia64-* \
+	| ip2k-* | iq2000-* \
+	| lm32-* \
+	| m32c-* | m32r-* | m32rle-* \
+	| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+	| m88110-* | m88k-* | maxq-* | mcore-* | metag-* | microblaze-* \
+	| mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+	| mips16-* \
+	| mips64-* | mips64el-* \
+	| mips64octeon-* | mips64octeonel-* \
+	| mips64orion-* | mips64orionel-* \
+	| mips64r5900-* | mips64r5900el-* \
+	| mips64vr-* | mips64vrel-* \
+	| mips64vr4100-* | mips64vr4100el-* \
+	| mips64vr4300-* | mips64vr4300el-* \
+	| mips64vr5000-* | mips64vr5000el-* \
+	| mips64vr5900-* | mips64vr5900el-* \
+	| mipsisa32-* | mipsisa32el-* \
+	| mipsisa32r2-* | mipsisa32r2el-* \
+	| mipsisa64-* | mipsisa64el-* \
+	| mipsisa64r2-* | mipsisa64r2el-* \
+	| mipsisa64sb1-* | mipsisa64sb1el-* \
+	| mipsisa64sr71k-* | mipsisa64sr71kel-* \
+	| mipstx39-* | mipstx39el-* \
+	| mmix-* \
+	| mt-* \
+	| msp430-* \
+	| nios-* | nios2-* \
+	| none-* | np1-* | ns16k-* | ns32k-* \
+	| orion-* \
+	| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+	| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
+	| pyramid-* \
+	| romp-* | rs6000-* | rx-* \
+	| sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
+	| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+	| sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
+	| sparclite-* \
+	| sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \
+	| tahoe-* | thumb-* \
+	| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+	| tile-* | tilegx-* \
+	| tron-* \
+	| ubicom32-* \
+	| v850-* | v850e-* | vax-* \
+	| we32k-* \
+	| x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \
+	| xstormy16-* | xtensa*-* \
+	| ymp-* \
+	| z8k-* | z80-*)
+		;;
+	# Recognize the basic CPU types without company name, with glob match.
+	xtensa*)
+		basic_machine=$basic_machine-unknown
+		;;
+	# Recognize the various machine names and aliases which stand
+	# for a CPU type and a company and sometimes even an OS.
+	386bsd)
+		basic_machine=i386-unknown
+		os=-bsd
+		;;
+	3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+		basic_machine=m68000-att
+		;;
+	3b*)
+		basic_machine=we32k-att
+		;;
+	a29khif)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+    	abacus)
+		basic_machine=abacus-unknown
+		;;
+	adobe68k)
+		basic_machine=m68010-adobe
+		os=-scout
+		;;
+	alliant | fx80)
+		basic_machine=fx80-alliant
+		;;
+	altos | altos3068)
+		basic_machine=m68k-altos
+		;;
+	am29k)
+		basic_machine=a29k-none
+		os=-bsd
+		;;
+	amd64)
+		basic_machine=x86_64-pc
+		;;
+	amd64-*)
+		basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	amdahl)
+		basic_machine=580-amdahl
+		os=-sysv
+		;;
+	amiga | amiga-*)
+		basic_machine=m68k-unknown
+		;;
+	amigaos | amigados)
+		basic_machine=m68k-unknown
+		os=-amigaos
+		;;
+	amigaunix | amix)
+		basic_machine=m68k-unknown
+		os=-sysv4
+		;;
+	apollo68)
+		basic_machine=m68k-apollo
+		os=-sysv
+		;;
+	apollo68bsd)
+		basic_machine=m68k-apollo
+		os=-bsd
+		;;
+	aros)
+		basic_machine=i386-pc
+		os=-aros
+		;;
+	aux)
+		basic_machine=m68k-apple
+		os=-aux
+		;;
+	balance)
+		basic_machine=ns32k-sequent
+		os=-dynix
+		;;
+	blackfin)
+		basic_machine=bfin-unknown
+		os=-linux
+		;;
+	blackfin-*)
+		basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	bluegene*)
+		basic_machine=powerpc-ibm
+		os=-cnk
+		;;
+	c90)
+		basic_machine=c90-cray
+		os=-unicos
+		;;
+        cegcc)
+		basic_machine=arm-unknown
+		os=-cegcc
+		;;
+	convex-c1)
+		basic_machine=c1-convex
+		os=-bsd
+		;;
+	convex-c2)
+		basic_machine=c2-convex
+		os=-bsd
+		;;
+	convex-c32)
+		basic_machine=c32-convex
+		os=-bsd
+		;;
+	convex-c34)
+		basic_machine=c34-convex
+		os=-bsd
+		;;
+	convex-c38)
+		basic_machine=c38-convex
+		os=-bsd
+		;;
+	cray | j90)
+		basic_machine=j90-cray
+		os=-unicos
+		;;
+	craynv)
+		basic_machine=craynv-cray
+		os=-unicosmp
+		;;
+	cr16)
+		basic_machine=cr16-unknown
+		os=-elf
+		;;
+	crds | unos)
+		basic_machine=m68k-crds
+		;;
+	crisv32 | crisv32-* | etraxfs*)
+		basic_machine=crisv32-axis
+		;;
+	cris | cris-* | etrax*)
+		basic_machine=cris-axis
+		;;
+	crx)
+		basic_machine=crx-unknown
+		os=-elf
+		;;
+	da30 | da30-*)
+		basic_machine=m68k-da30
+		;;
+	decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+		basic_machine=mips-dec
+		;;
+	decsystem10* | dec10*)
+		basic_machine=pdp10-dec
+		os=-tops10
+		;;
+	decsystem20* | dec20*)
+		basic_machine=pdp10-dec
+		os=-tops20
+		;;
+	delta | 3300 | motorola-3300 | motorola-delta \
+	      | 3300-motorola | delta-motorola)
+		basic_machine=m68k-motorola
+		;;
+	delta88)
+		basic_machine=m88k-motorola
+		os=-sysv3
+		;;
+	dicos)
+		basic_machine=i686-pc
+		os=-dicos
+		;;
+	djgpp)
+		basic_machine=i586-pc
+		os=-msdosdjgpp
+		;;
+	dpx20 | dpx20-*)
+		basic_machine=rs6000-bull
+		os=-bosx
+		;;
+	dpx2* | dpx2*-bull)
+		basic_machine=m68k-bull
+		os=-sysv3
+		;;
+	ebmon29k)
+		basic_machine=a29k-amd
+		os=-ebmon
+		;;
+	elxsi)
+		basic_machine=elxsi-elxsi
+		os=-bsd
+		;;
+	encore | umax | mmax)
+		basic_machine=ns32k-encore
+		;;
+	es1800 | OSE68k | ose68k | ose | OSE)
+		basic_machine=m68k-ericsson
+		os=-ose
+		;;
+	fx2800)
+		basic_machine=i860-alliant
+		;;
+	genix)
+		basic_machine=ns32k-ns
+		;;
+	gmicro)
+		basic_machine=tron-gmicro
+		os=-sysv
+		;;
+	go32)
+		basic_machine=i386-pc
+		os=-go32
+		;;
+	h3050r* | hiux*)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	h8300hms)
+		basic_machine=h8300-hitachi
+		os=-hms
+		;;
+	h8300xray)
+		basic_machine=h8300-hitachi
+		os=-xray
+		;;
+	h8500hms)
+		basic_machine=h8500-hitachi
+		os=-hms
+		;;
+	harris)
+		basic_machine=m88k-harris
+		os=-sysv3
+		;;
+	hp300-*)
+		basic_machine=m68k-hp
+		;;
+	hp300bsd)
+		basic_machine=m68k-hp
+		os=-bsd
+		;;
+	hp300hpux)
+		basic_machine=m68k-hp
+		os=-hpux
+		;;
+	hp3k9[0-9][0-9] | hp9[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k2[0-9][0-9] | hp9k31[0-9])
+		basic_machine=m68000-hp
+		;;
+	hp9k3[2-9][0-9])
+		basic_machine=m68k-hp
+		;;
+	hp9k6[0-9][0-9] | hp6[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k7[0-79][0-9] | hp7[0-79][0-9])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k78[0-9] | hp78[0-9])
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][13679] | hp8[0-9][13679])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][0-9] | hp8[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hppa-next)
+		os=-nextstep3
+		;;
+	hppaosf)
+		basic_machine=hppa1.1-hp
+		os=-osf
+		;;
+	hppro)
+		basic_machine=hppa1.1-hp
+		os=-proelf
+		;;
+	i370-ibm* | ibm*)
+		basic_machine=i370-ibm
+		;;
+# I'm not sure what "Sysv32" means.  Should this be sysv3.2?
+	i*86v32)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv32
+		;;
+	i*86v4*)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv4
+		;;
+	i*86v)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv
+		;;
+	i*86sol2)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-solaris2
+		;;
+	i386mach)
+		basic_machine=i386-mach
+		os=-mach
+		;;
+	i386-vsta | vsta)
+		basic_machine=i386-unknown
+		os=-vsta
+		;;
+	iris | iris4d)
+		basic_machine=mips-sgi
+		case $os in
+		    -irix*)
+			;;
+		    *)
+			os=-irix4
+			;;
+		esac
+		;;
+	isi68 | isi)
+		basic_machine=m68k-isi
+		os=-sysv
+		;;
+	m68knommu)
+		basic_machine=m68k-unknown
+		os=-linux
+		;;
+	m68knommu-*)
+		basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	m88k-omron*)
+		basic_machine=m88k-omron
+		;;
+	magnum | m3230)
+		basic_machine=mips-mips
+		os=-sysv
+		;;
+	merlin)
+		basic_machine=ns32k-utek
+		os=-sysv
+		;;
+        microblaze)
+		basic_machine=microblaze-xilinx
+		;;
+	mingw32)
+		basic_machine=i386-pc
+		os=-mingw32
+		;;
+	mingw32ce)
+		basic_machine=arm-unknown
+		os=-mingw32ce
+		;;
+	miniframe)
+		basic_machine=m68000-convergent
+		;;
+	*mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+	mips3*-*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+		;;
+	mips3*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+		;;
+	monitor)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	morphos)
+		basic_machine=powerpc-unknown
+		os=-morphos
+		;;
+	msdos)
+		basic_machine=i386-pc
+		os=-msdos
+		;;
+	ms1-*)
+		basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
+		;;
+	mvs)
+		basic_machine=i370-ibm
+		os=-mvs
+		;;
+	ncr3000)
+		basic_machine=i486-ncr
+		os=-sysv4
+		;;
+	netbsd386)
+		basic_machine=i386-unknown
+		os=-netbsd
+		;;
+	netwinder)
+		basic_machine=armv4l-rebel
+		os=-linux
+		;;
+	news | news700 | news800 | news900)
+		basic_machine=m68k-sony
+		os=-newsos
+		;;
+	news1000)
+		basic_machine=m68030-sony
+		os=-newsos
+		;;
+	news-3600 | risc-news)
+		basic_machine=mips-sony
+		os=-newsos
+		;;
+	necv70)
+		basic_machine=v70-nec
+		os=-sysv
+		;;
+	next | m*-next )
+		basic_machine=m68k-next
+		case $os in
+		    -nextstep* )
+			;;
+		    -ns2*)
+		      os=-nextstep2
+			;;
+		    *)
+		      os=-nextstep3
+			;;
+		esac
+		;;
+	nh3000)
+		basic_machine=m68k-harris
+		os=-cxux
+		;;
+	nh[45]000)
+		basic_machine=m88k-harris
+		os=-cxux
+		;;
+	nindy960)
+		basic_machine=i960-intel
+		os=-nindy
+		;;
+	mon960)
+		basic_machine=i960-intel
+		os=-mon960
+		;;
+	nonstopux)
+		basic_machine=mips-compaq
+		os=-nonstopux
+		;;
+	np1)
+		basic_machine=np1-gould
+		;;
+	nsr-tandem)
+		basic_machine=nsr-tandem
+		;;
+	op50n-* | op60c-*)
+		basic_machine=hppa1.1-oki
+		os=-proelf
+		;;
+	openrisc | openrisc-*)
+		basic_machine=or32-unknown
+		;;
+	os400)
+		basic_machine=powerpc-ibm
+		os=-os400
+		;;
+	OSE68000 | ose68000)
+		basic_machine=m68000-ericsson
+		os=-ose
+		;;
+	os68k)
+		basic_machine=m68k-none
+		os=-os68k
+		;;
+	pa-hitachi)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	paragon)
+		basic_machine=i860-intel
+		os=-osf
+		;;
+	parisc)
+		basic_machine=hppa-unknown
+		os=-linux
+		;;
+	parisc-*)
+		basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
+		os=-linux
+		;;
+	pbd)
+		basic_machine=sparc-tti
+		;;
+	pbb)
+		basic_machine=m68k-tti
+		;;
+	pc532 | pc532-*)
+		basic_machine=ns32k-pc532
+		;;
+	pc98)
+		basic_machine=i386-pc
+		;;
+	pc98-*)
+		basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentium | p5 | k5 | k6 | nexgen | viac3)
+		basic_machine=i586-pc
+		;;
+	pentiumpro | p6 | 6x86 | athlon | athlon_*)
+		basic_machine=i686-pc
+		;;
+	pentiumii | pentium2 | pentiumiii | pentium3)
+		basic_machine=i686-pc
+		;;
+	pentium4)
+		basic_machine=i786-pc
+		;;
+	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+		basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumpro-* | p6-* | 6x86-* | athlon-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentium4-*)
+		basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pn)
+		basic_machine=pn-gould
+		;;
+	power)	basic_machine=power-ibm
+		;;
+	ppc)	basic_machine=powerpc-unknown
+		;;
+	ppc-*)	basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppcle | powerpclittle | ppc-le | powerpc-little)
+		basic_machine=powerpcle-unknown
+		;;
+	ppcle-* | powerpclittle-*)
+		basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64)	basic_machine=powerpc64-unknown
+		;;
+	ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+		basic_machine=powerpc64le-unknown
+		;;
+	ppc64le-* | powerpc64little-*)
+		basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ps2)
+		basic_machine=i386-ibm
+		;;
+	pw32)
+		basic_machine=i586-unknown
+		os=-pw32
+		;;
+	rdos)
+		basic_machine=i386-pc
+		os=-rdos
+		;;
+	rom68k)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	rm[46]00)
+		basic_machine=mips-siemens
+		;;
+	rtpc | rtpc-*)
+		basic_machine=romp-ibm
+		;;
+	s390 | s390-*)
+		basic_machine=s390-ibm
+		;;
+	s390x | s390x-*)
+		basic_machine=s390x-ibm
+		;;
+	sa29200)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	sb1)
+		basic_machine=mipsisa64sb1-unknown
+		;;
+	sb1el)
+		basic_machine=mipsisa64sb1el-unknown
+		;;
+	sde)
+		basic_machine=mipsisa32-sde
+		os=-elf
+		;;
+	sei)
+		basic_machine=mips-sei
+		os=-seiux
+		;;
+	sequent)
+		basic_machine=i386-sequent
+		;;
+	sh)
+		basic_machine=sh-hitachi
+		os=-hms
+		;;
+	sh5el)
+		basic_machine=sh5le-unknown
+		;;
+	sh64)
+		basic_machine=sh64-unknown
+		;;
+	sparclite-wrs | simso-wrs)
+		basic_machine=sparclite-wrs
+		os=-vxworks
+		;;
+	sps7)
+		basic_machine=m68k-bull
+		os=-sysv2
+		;;
+	spur)
+		basic_machine=spur-unknown
+		;;
+	st2000)
+		basic_machine=m68k-tandem
+		;;
+	stratus)
+		basic_machine=i860-stratus
+		os=-sysv4
+		;;
+	sun2)
+		basic_machine=m68000-sun
+		;;
+	sun2os3)
+		basic_machine=m68000-sun
+		os=-sunos3
+		;;
+	sun2os4)
+		basic_machine=m68000-sun
+		os=-sunos4
+		;;
+	sun3os3)
+		basic_machine=m68k-sun
+		os=-sunos3
+		;;
+	sun3os4)
+		basic_machine=m68k-sun
+		os=-sunos4
+		;;
+	sun4os3)
+		basic_machine=sparc-sun
+		os=-sunos3
+		;;
+	sun4os4)
+		basic_machine=sparc-sun
+		os=-sunos4
+		;;
+	sun4sol2)
+		basic_machine=sparc-sun
+		os=-solaris2
+		;;
+	sun3 | sun3-*)
+		basic_machine=m68k-sun
+		;;
+	sun4)
+		basic_machine=sparc-sun
+		;;
+	sun386 | sun386i | roadrunner)
+		basic_machine=i386-sun
+		;;
+	sv1)
+		basic_machine=sv1-cray
+		os=-unicos
+		;;
+	symmetry)
+		basic_machine=i386-sequent
+		os=-dynix
+		;;
+	t3e)
+		basic_machine=alphaev5-cray
+		os=-unicos
+		;;
+	t90)
+		basic_machine=t90-cray
+		os=-unicos
+		;;
+	tic54x | c54x*)
+		basic_machine=tic54x-unknown
+		os=-coff
+		;;
+	tic55x | c55x*)
+		basic_machine=tic55x-unknown
+		os=-coff
+		;;
+	tic6x | c6x*)
+		basic_machine=tic6x-unknown
+		os=-coff
+		;;
+        # This must be matched before tile*.
+        tilegx*)
+		basic_machine=tilegx-unknown
+		os=-linux-gnu
+		;;
+	tile*)
+		basic_machine=tile-unknown
+		os=-linux-gnu
+		;;
+	tx39)
+		basic_machine=mipstx39-unknown
+		;;
+	tx39el)
+		basic_machine=mipstx39el-unknown
+		;;
+	toad1)
+		basic_machine=pdp10-xkl
+		os=-tops20
+		;;
+	tower | tower-32)
+		basic_machine=m68k-ncr
+		;;
+	tpf)
+		basic_machine=s390x-ibm
+		os=-tpf
+		;;
+	udi29k)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	ultra3)
+		basic_machine=a29k-nyu
+		os=-sym1
+		;;
+	v810 | necv810)
+		basic_machine=v810-nec
+		os=-none
+		;;
+	vaxv)
+		basic_machine=vax-dec
+		os=-sysv
+		;;
+	vms)
+		basic_machine=vax-dec
+		os=-vms
+		;;
+	vpp*|vx|vx-*)
+		basic_machine=f301-fujitsu
+		;;
+	vxworks960)
+		basic_machine=i960-wrs
+		os=-vxworks
+		;;
+	vxworks68)
+		basic_machine=m68k-wrs
+		os=-vxworks
+		;;
+	vxworks29k)
+		basic_machine=a29k-wrs
+		os=-vxworks
+		;;
+	w65*)
+		basic_machine=w65-wdc
+		os=-none
+		;;
+	w89k-*)
+		basic_machine=hppa1.1-winbond
+		os=-proelf
+		;;
+	xbox)
+		basic_machine=i686-pc
+		os=-mingw32
+		;;
+	xps | xps100)
+		basic_machine=xps100-honeywell
+		;;
+	ymp)
+		basic_machine=ymp-cray
+		os=-unicos
+		;;
+	z8k-*-coff)
+		basic_machine=z8k-unknown
+		os=-sim
+		;;
+	z80-*-coff)
+		basic_machine=z80-unknown
+		os=-sim
+		;;
+	none)
+		basic_machine=none-none
+		os=-none
+		;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+	w89k)
+		basic_machine=hppa1.1-winbond
+		;;
+	op50n)
+		basic_machine=hppa1.1-oki
+		;;
+	op60c)
+		basic_machine=hppa1.1-oki
+		;;
+	romp)
+		basic_machine=romp-ibm
+		;;
+	mmix)
+		basic_machine=mmix-knuth
+		;;
+	rs6000)
+		basic_machine=rs6000-ibm
+		;;
+	vax)
+		basic_machine=vax-dec
+		;;
+	pdp10)
+		# there are many clones, so DEC is not a safe bet
+		basic_machine=pdp10-unknown
+		;;
+	pdp11)
+		basic_machine=pdp11-dec
+		;;
+	we32k)
+		basic_machine=we32k-att
+		;;
+	sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele)
+		basic_machine=sh-unknown
+		;;
+	sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
+		basic_machine=sparc-sun
+		;;
+	cydra)
+		basic_machine=cydra-cydrome
+		;;
+	orion)
+		basic_machine=orion-highlevel
+		;;
+	orion105)
+		basic_machine=clipper-highlevel
+		;;
+	mac | mpw | mac-mpw)
+		basic_machine=m68k-apple
+		;;
+	pmac | pmac-mpw)
+		basic_machine=powerpc-apple
+		;;
+	*-unknown)
+		# Make sure to match an already-canonicalized machine name.
+		;;
+	*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+	*-digital*)
+		basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+		;;
+	*-commodore*)
+		basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+		;;
+	*)
+		;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+        # First match some system type aliases
+        # that might get confused with valid system types.
+	# -solaris* is a basic system type, with this one exception.
+        -auroraux)
+	        os=-auroraux
+		;;
+	-solaris1 | -solaris1.*)
+		os=`echo $os | sed -e 's|solaris1|sunos4|'`
+		;;
+	-solaris)
+		os=-solaris2
+		;;
+	-svr4*)
+		os=-sysv4
+		;;
+	-unixware*)
+		os=-sysv4.2uw
+		;;
+	-gnu/linux*)
+		os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+		;;
+	# First accept the basic system types.
+	# The portable systems comes first.
+	# Each alternative MUST END IN A *, to match a version number.
+	# -sysv* is not here because it comes later, after sysvr4.
+	-gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+	      | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\
+	      | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
+	      | -sym* | -kopensolaris* \
+	      | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+	      | -aos* | -aros* \
+	      | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+	      | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+	      | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
+	      | -openbsd* | -solidbsd* \
+	      | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+	      | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+	      | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+	      | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+	      | -chorusos* | -chorusrdb* | -cegcc* \
+	      | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+	      | -mingw32* | -linux-gnu* | -linux-newlib* | -linux-uclibc* \
+	      | -uxpv* | -beos* | -mpeix* | -udk* \
+	      | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+	      | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+	      | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+	      | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+	      | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+	      | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
+	      | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*)
+	# Remember, each alternative MUST END IN *, to match a version number.
+		;;
+	-qnx*)
+		case $basic_machine in
+		    x86-* | i*86-*)
+			;;
+		    *)
+			os=-nto$os
+			;;
+		esac
+		;;
+	-nto-qnx*)
+		;;
+	-nto*)
+		os=`echo $os | sed -e 's|nto|nto-qnx|'`
+		;;
+	-sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+	      | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
+	      | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+		;;
+	-mac*)
+		os=`echo $os | sed -e 's|mac|macos|'`
+		;;
+	-linux-dietlibc)
+		os=-linux-dietlibc
+		;;
+	-linux*)
+		os=`echo $os | sed -e 's|linux|linux-gnu|'`
+		;;
+	-sunos5*)
+		os=`echo $os | sed -e 's|sunos5|solaris2|'`
+		;;
+	-sunos6*)
+		os=`echo $os | sed -e 's|sunos6|solaris3|'`
+		;;
+	-opened*)
+		os=-openedition
+		;;
+        -os400*)
+		os=-os400
+		;;
+	-wince*)
+		os=-wince
+		;;
+	-osfrose*)
+		os=-osfrose
+		;;
+	-osf*)
+		os=-osf
+		;;
+	-utek*)
+		os=-bsd
+		;;
+	-dynix*)
+		os=-bsd
+		;;
+	-acis*)
+		os=-aos
+		;;
+	-atheos*)
+		os=-atheos
+		;;
+	-syllable*)
+		os=-syllable
+		;;
+	-386bsd)
+		os=-bsd
+		;;
+	-ctix* | -uts*)
+		os=-sysv
+		;;
+	-nova*)
+		os=-rtmk-nova
+		;;
+	-ns2 )
+		os=-nextstep2
+		;;
+	-nsk*)
+		os=-nsk
+		;;
+	# Preserve the version number of sinix5.
+	-sinix5.*)
+		os=`echo $os | sed -e 's|sinix|sysv|'`
+		;;
+	-sinix*)
+		os=-sysv4
+		;;
+        -tpf*)
+		os=-tpf
+		;;
+	-triton*)
+		os=-sysv3
+		;;
+	-oss*)
+		os=-sysv3
+		;;
+	-svr4)
+		os=-sysv4
+		;;
+	-svr3)
+		os=-sysv3
+		;;
+	-sysvr4)
+		os=-sysv4
+		;;
+	# This must come after -sysvr4.
+	-sysv*)
+		;;
+	-ose*)
+		os=-ose
+		;;
+	-es1800*)
+		os=-ose
+		;;
+	-xenix)
+		os=-xenix
+		;;
+	-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+		os=-mint
+		;;
+	-aros*)
+		os=-aros
+		;;
+	-kaos*)
+		os=-kaos
+		;;
+	-zvmoe)
+		os=-zvmoe
+		;;
+	-dicos*)
+		os=-dicos
+		;;
+        -nacl*)
+	        ;;
+	-none)
+		;;
+	*)
+		# Get rid of the `-' at the beginning of $os.
+		os=`echo $os | sed 's/[^-]*-//'`
+		echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+		exit 1
+		;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+        score-*)
+		os=-elf
+		;;
+        spu-*)
+		os=-elf
+		;;
+	*-acorn)
+		os=-riscix1.2
+		;;
+	arm*-rebel)
+		os=-linux
+		;;
+	arm*-semi)
+		os=-aout
+		;;
+        c4x-* | tic4x-*)
+        	os=-coff
+		;;
+	# This must come before the *-dec entry.
+	pdp10-*)
+		os=-tops20
+		;;
+	pdp11-*)
+		os=-none
+		;;
+	*-dec | vax-*)
+		os=-ultrix4.2
+		;;
+	m68*-apollo)
+		os=-domain
+		;;
+	i386-sun)
+		os=-sunos4.0.2
+		;;
+	m68000-sun)
+		os=-sunos3
+		# This also exists in the configure program, but was not the
+		# default.
+		# os=-sunos4
+		;;
+	m68*-cisco)
+		os=-aout
+		;;
+        mep-*)
+		os=-elf
+		;;
+	mips*-cisco)
+		os=-elf
+		;;
+	mips*-*)
+		os=-elf
+		;;
+	or32-*)
+		os=-coff
+		;;
+	*-tti)	# must be before sparc entry or we get the wrong os.
+		os=-sysv3
+		;;
+	sparc-* | *-sun)
+		os=-sunos4.1.1
+		;;
+	*-be)
+		os=-beos
+		;;
+	*-haiku)
+		os=-haiku
+		;;
+	*-ibm)
+		os=-aix
+		;;
+    	*-knuth)
+		os=-mmixware
+		;;
+	*-wec)
+		os=-proelf
+		;;
+	*-winbond)
+		os=-proelf
+		;;
+	*-oki)
+		os=-proelf
+		;;
+	*-hp)
+		os=-hpux
+		;;
+	*-hitachi)
+		os=-hiux
+		;;
+	i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+		os=-sysv
+		;;
+	*-cbm)
+		os=-amigaos
+		;;
+	*-dg)
+		os=-dgux
+		;;
+	*-dolphin)
+		os=-sysv3
+		;;
+	m68k-ccur)
+		os=-rtu
+		;;
+	m88k-omron*)
+		os=-luna
+		;;
+	*-next )
+		os=-nextstep
+		;;
+	*-sequent)
+		os=-ptx
+		;;
+	*-crds)
+		os=-unos
+		;;
+	*-ns)
+		os=-genix
+		;;
+	i370-*)
+		os=-mvs
+		;;
+	*-next)
+		os=-nextstep3
+		;;
+	*-gould)
+		os=-sysv
+		;;
+	*-highlevel)
+		os=-bsd
+		;;
+	*-encore)
+		os=-bsd
+		;;
+	*-sgi)
+		os=-irix
+		;;
+	*-siemens)
+		os=-sysv4
+		;;
+	*-masscomp)
+		os=-rtu
+		;;
+	f30[01]-fujitsu | f700-fujitsu)
+		os=-uxpv
+		;;
+	*-rom68k)
+		os=-coff
+		;;
+	*-*bug)
+		os=-coff
+		;;
+	*-apple)
+		os=-macos
+		;;
+	*-atari*)
+		os=-mint
+		;;
+	*)
+		os=-none
+		;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+	*-unknown)
+		case $os in
+			-riscix*)
+				vendor=acorn
+				;;
+			-sunos*)
+				vendor=sun
+				;;
+			-cnk*|-aix*)
+				vendor=ibm
+				;;
+			-beos*)
+				vendor=be
+				;;
+			-hpux*)
+				vendor=hp
+				;;
+			-mpeix*)
+				vendor=hp
+				;;
+			-hiux*)
+				vendor=hitachi
+				;;
+			-unos*)
+				vendor=crds
+				;;
+			-dgux*)
+				vendor=dg
+				;;
+			-luna*)
+				vendor=omron
+				;;
+			-genix*)
+				vendor=ns
+				;;
+			-mvs* | -opened*)
+				vendor=ibm
+				;;
+			-os400*)
+				vendor=ibm
+				;;
+			-ptx*)
+				vendor=sequent
+				;;
+			-tpf*)
+				vendor=ibm
+				;;
+			-vxsim* | -vxworks* | -windiss*)
+				vendor=wrs
+				;;
+			-aux*)
+				vendor=apple
+				;;
+			-hms*)
+				vendor=hitachi
+				;;
+			-mpw* | -macos*)
+				vendor=apple
+				;;
+			-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+				vendor=atari
+				;;
+			-vos*)
+				vendor=stratus
+				;;
+		esac
+		basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+		;;
+esac
+
+echo $basic_machine$os
+exit
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/scripts/example_autotest.h b/scripts/example_autotest.h
new file mode 100644
index 0000000..4af5c1a
--- /dev/null
+++ b/scripts/example_autotest.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Example of a benchmark header
+//
+
+#include <sys/resource.h>
+#include <math.h>
+
+void autotest_mytest()
+{
+    printf("this is my test\n");
+}
+
+
diff --git a/scripts/example_benchmark.h b/scripts/example_benchmark.h
new file mode 100644
index 0000000..3973a89
--- /dev/null
+++ b/scripts/example_benchmark.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Example of a benchmark header
+//
+
+#include <sys/resource.h>
+#include <math.h>
+
+// strings parsed by benchmarkgen.py
+const char * mybench_opts[3] = {
+    "opt1a opt1b",
+    "opt2a opt2b opt2c",
+    "opt3a opt3b opt3c"
+};
+
+
+void benchmark_mybench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+//    unsigned int argc,
+//    char *argv[])
+{
+    // DSP initiazation goes here
+
+    unsigned int i;
+    float x, y, theta;
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // DSP execution goes here
+        x = cosf(M_PI/2.0f);
+        y = sinf(M_PI/2.0f);
+        theta = atan2(y,x);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // DSP cleanup goes here
+}
+
+
diff --git a/scripts/install-sh b/scripts/install-sh
new file mode 100755
index 0000000..4fbbae7
--- /dev/null
+++ b/scripts/install-sh
@@ -0,0 +1,507 @@
+#!/bin/sh
+# install - install a program, script, or datafile
+
+scriptversion=2006-10-14.15
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
+#
+#
+# FSF changes to this file are in the public domain.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.
+
+nl='
+'
+IFS=" ""	$nl"
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+if test -z "$doit"; then
+  doit_exec=exec
+else
+  doit_exec=$doit
+fi
+
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+posix_glob=
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chmodcmd=$chmodprog
+chowncmd=
+chgrpcmd=
+stripcmd=
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=
+dst=
+dir_arg=
+dstarg=
+no_target_directory=
+
+usage="Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
+
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
+
+Options:
+-c         (ignored)
+-d         create directories instead of installing files.
+-g GROUP   $chgrpprog installed files to GROUP.
+-m MODE    $chmodprog installed files to MODE.
+-o USER    $chownprog installed files to USER.
+-s         $stripprog installed files.
+-t DIRECTORY  install into DIRECTORY.
+-T         report an error if DSTFILE is a directory.
+--help     display this help and exit.
+--version  display version info and exit.
+
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CPPROG MKDIRPROG MVPROG RMPROG STRIPPROG
+"
+
+while test $# -ne 0; do
+  case $1 in
+    -c) shift
+        continue;;
+
+    -d) dir_arg=true
+        shift
+        continue;;
+
+    -g) chgrpcmd="$chgrpprog $2"
+        shift
+        shift
+        continue;;
+
+    --help) echo "$usage"; exit $?;;
+
+    -m) mode=$2
+        shift
+        shift
+	case $mode in
+	  *' '* | *'	'* | *'
+'*	  | *'*'* | *'?'* | *'['*)
+	    echo "$0: invalid mode: $mode" >&2
+	    exit 1;;
+	esac
+        continue;;
+
+    -o) chowncmd="$chownprog $2"
+        shift
+        shift
+        continue;;
+
+    -s) stripcmd=$stripprog
+        shift
+        continue;;
+
+    -t) dstarg=$2
+	shift
+	shift
+	continue;;
+
+    -T) no_target_directory=true
+	shift
+	continue;;
+
+    --version) echo "$0 $scriptversion"; exit $?;;
+
+    --)	shift
+	break;;
+
+    -*)	echo "$0: invalid option: $1" >&2
+	exit 1;;
+
+    *)  break;;
+  esac
+done
+
+if test $# -ne 0 && test -z "$dir_arg$dstarg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dstarg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dstarg"
+      shift # fnord
+    fi
+    shift # arg
+    dstarg=$arg
+  done
+fi
+
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call `install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
+
+if test -z "$dir_arg"; then
+  trap '(exit $?); exit' 1 2 13 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
+
+for src
+do
+  # Protect names starting with `-'.
+  case $src in
+    -*) src=./$src ;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dstarg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+
+    dst=$dstarg
+    # Protect names starting with `-'.
+    case $dst in
+      -*) dst=./$dst ;;
+    esac
+
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+	echo "$0: $dstarg: Is a directory" >&2
+	exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+	(dirname "$dst") 2>/dev/null ||
+	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	     X"$dst" : 'X\(//\)[^/]' \| \
+	     X"$dst" : 'X\(//\)$' \| \
+	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+	echo X"$dst" |
+	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)[^/].*/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\).*/{
+		   s//\1/
+		   q
+		 }
+		 s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+	# Create intermediate dirs using mode 755 as modified by the umask.
+	# This is like FreeBSD 'install' as of 1997-10-28.
+	umask=`umask`
+	case $stripcmd.$umask in
+	  # Optimize common cases.
+	  *[2367][2367]) mkdir_umask=$umask;;
+	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+	  *[0-7])
+	    mkdir_umask=`expr $umask + 22 \
+	      - $umask % 100 % 40 + $umask % 20 \
+	      - $umask % 10 % 4 + $umask % 2
+	    `;;
+	  *) mkdir_umask=$umask,go-w;;
+	esac
+
+	# With -d, create the new directory with the user-specified mode.
+	# Otherwise, rely on $mkdir_umask.
+	if test -n "$dir_arg"; then
+	  mkdir_mode=-m$mode
+	else
+	  mkdir_mode=
+	fi
+
+	posix_mkdir=false
+	case $umask in
+	  *[123567][0-7][0-7])
+	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
+	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+	    ;;
+	  *)
+	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+	    if (umask $mkdir_umask &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+	    then
+	      if test -z "$dir_arg" || {
+		   # Check for POSIX incompatibilities with -m.
+		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+		   # other-writeable bit of parent directory when it shouldn't.
+		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   case $ls_ld_tmpdir in
+		     d????-?r-*) different_mode=700;;
+		     d????-?--*) different_mode=755;;
+		     *) false;;
+		   esac &&
+		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+		   }
+		 }
+	      then posix_mkdir=:
+	      fi
+	      rmdir "$tmpdir/d" "$tmpdir"
+	    else
+	      # Remove any dirs left behind by ancient mkdir implementations.
+	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	    fi
+	    trap '' 0;;
+	esac;;
+    esac
+
+    if
+      $posix_mkdir && (
+	umask $mkdir_umask &&
+	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+	/*) prefix=/ ;;
+	-*) prefix=./ ;;
+	*)  prefix= ;;
+      esac
+
+      case $posix_glob in
+        '')
+	  if (set -f) 2>/dev/null; then
+	    posix_glob=true
+	  else
+	    posix_glob=false
+	  fi ;;
+      esac
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob && set -f
+      set fnord $dstdir
+      shift
+      $posix_glob && set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+	test -z "$d" && continue
+
+	prefix=$prefix$d
+	if test -d "$prefix"; then
+	  prefixes=
+	else
+	  if $posix_mkdir; then
+	    (umask=$mkdir_umask &&
+	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+	    # Don't fail if two instances are running concurrently.
+	    test -d "$prefix" || exit 1
+	  else
+	    case $prefix in
+	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+	      *) qprefix=$prefix;;
+	    esac
+	    prefixes="$prefixes '$qprefix'"
+	  fi
+	fi
+	prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+	# Don't fail if two instances are running concurrently.
+	(umask $mkdir_umask &&
+	 eval "\$doit_exec \$mkdirprog $prefixes") ||
+	  test -d "$dstdir" || exit 1
+	obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } \
+      && { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } \
+      && { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } \
+      && { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # Now rename the file to the real destination.
+    { $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null \
+      || {
+	   # The rename failed, perhaps because mv can't rename something else
+	   # to itself, or perhaps because mv is so ancient that it does not
+	   # support -f.
+
+	   # Now remove or move aside any old file at destination location.
+	   # We try this two ways since rm can't unlink itself on some
+	   # systems and the destination file might be busy for other
+	   # reasons.  In this case, the final cleanup might fail but the new
+	   # file should still install successfully.
+	   {
+	     if test -f "$dst"; then
+	       $doit $rmcmd -f "$dst" 2>/dev/null \
+	       || { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null \
+		     && { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }; }\
+	       || {
+		 echo "$0: cannot unlink or rename $dst" >&2
+		 (exit 1); exit 1
+	       }
+	     else
+	       :
+	     fi
+	   } &&
+
+	   # Now rename the file to the real destination.
+	   $doit $mvcmd "$dsttmp" "$dst"
+	 }
+    } || exit 1
+
+    trap '' 0
+  fi
+done
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/scripts/main.c b/scripts/main.c
new file mode 100644
index 0000000..f1f0d3b
--- /dev/null
+++ b/scripts/main.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// scripts/main.c
+// 
+// Auto-script generator test program
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "autoscript.h"
+
+void usage() {
+    printf("Usage: autoscript_test DELIM TYPE [LIST OF FILES]\n");
+    printf("    DELIM   :   path separation delimiter (e.g. '/')\n");
+    printf("    TYPE    :   base type for parsing (e.g. \"benchmark\")\n");
+    printf("    [LIST]  :   list of auto-script files, named MYPACKAGE_TYPE.h\n");
+    printf("                with internal scripts \"void TYPE_MYSCRIPT(...)\"\n");
+}
+
+int main(int argc, char*argv[])
+{
+    //
+    if (argc < 3) {
+        // print help
+        fprintf(stderr,"error: %s, too few arguments\n", argv[0]);
+        usage();
+        exit(1);
+    } else {
+        //printf("//  delim :   '%c'\n", argv[1][0]);
+        //printf("//  type  :   \"%s\"\n", argv[2]);
+    }
+
+    // create autoscript object
+    //  first argument  :   type string (e.g. "benchmark")
+    //  second argument :   delimiter character (file system)
+    autoscript q = autoscript_create(argv[2], argv[1][0]);
+
+    // parse files
+    int i;
+    for (i=3; i<argc; i++) {
+        autoscript_parse(q,argv[i]);
+    }
+
+    // print results
+    autoscript_print(q);
+
+    // destroy autoscript object
+    autoscript_destroy(q);
+
+    return 0;
+}
+
diff --git a/scripts/update_copyright.py b/scripts/update_copyright.py
new file mode 100755
index 0000000..7354dab
--- /dev/null
+++ b/scripts/update_copyright.py
@@ -0,0 +1,168 @@
+#!/usr/bin/python
+
+'''
+This script updates the copyright license lines for all files within a
+particular directory.
+
+usage:
+  $ python scripts/update_copyright.py <dir>
+'''
+
+import os
+import sys
+import re
+
+def usage():
+    print __doc__
+
+if len(sys.argv) != 2:
+    sys.stderr.write("error: please provide an input directory\n")
+    usage()
+    sys.exit(-1)
+elif sys.argv[1] in ('-h','--help'):
+    usage()
+    sys.exit(0)
+
+# new copyright
+newcopy = '''\
+Copyright (c) 2007 - 2015 Joseph Gaeddert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.'''.splitlines()
+
+# add new line character to end of each line
+for i in xrange(len(newcopy)):
+    newcopy[i] += '\n'
+
+rootdir = sys.argv[1]
+
+# specific files to ignore
+ignore_files = ['update_copyright.py',]
+
+# directories to ignore
+ignore_directories = ['.git',]
+
+# only look at these extensions
+include_extensions = ['.h', '.c', '.md', '.tex', '.ac', '.in', '.m',]
+
+# print alignment for status
+align = 56
+
+#
+def update_copyright(filename=""):
+    """
+    Update copyright on file.
+    """
+    # try to open file
+    contents = []
+    with open( filename, 'r' ) as f:
+        # read lines from file (removing newline character at end)
+        for line in f:
+            #contents.append(line.strip('\n\r'))
+            contents.append(line)
+
+    # parse contents
+    index_start = -1
+    index_stop  = -1
+
+    # search for copyright; starts at top of file
+    for i in range(min(10, len(contents))):
+        if re.search(r'Copyright .* Joseph Gaeddert',contents[i]):
+            index_start = i
+            break
+
+    if index_start == -1:
+        print "  " + filename + ":" + " "*(align-len(filename)) + "no copyright found"
+        return
+
+    # look for end of copyright
+    #for i in range(index_start+15,index_start+15+min(10, len(contents))):
+    i = index_start + 15
+    if re.search(r'along with liquid.  If not, see',contents[i]):
+        index_stop = i
+    elif re.search(r'AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM',contents[i]):
+        print "  " + filename + ":" + " "*(align-len(filename)) + "copyright already updated"
+        return
+    else:
+        print "  " + filename + ":" + " "*(align-len(filename)) + "could not find end of copyright"
+        return
+
+    # check comment type
+    m = re.search(r'^( \*|#+) +Copyright',contents[index_start])
+    if m:
+        comment = m.group(1)
+    else:
+        raise Exception('unexpected error')
+
+    # delete items in list
+    contents.__delslice__(index_start, index_stop+1)
+
+    # insert new copyright
+    for i in range(len(newcopy)):
+        # only add space after comment characters if string is not empty
+        # (e.g. print ' *' instead of ' * ')
+        space = ' ' if len(newcopy[i].strip()) > 0 else ''
+
+        # insert new comment
+        contents.insert(index_start+i, comment + space + newcopy[i])
+
+    # open original file for writing
+    with open( filename, 'w' ) as f:
+        for line in contents:
+            f.write(line)
+            
+    print "  " + filename + ":" + " "*(align-len(filename)) + "updated"
+
+#
+for root, subFolders, files in os.walk(rootdir):
+
+    # strip off leading './' if it exists
+    if root.startswith('./'):
+        root = root[2:]
+
+    # print root directory
+    print root
+    
+    # ignore certain directories
+    if root in ignore_directories:
+        print "(skipping directory)"
+        continue
+
+    # print subfolders
+    #for folder in subFolders:
+    #    print "%s has subdirectory %s" % (root, folder)
+
+    # parse each filename in directory
+    for filename in files:
+        filePath = os.path.join(root, filename)
+
+        # check filename
+        if filePath in ignore_files:
+            print "  " + filePath + ":" + " "*(align-len(filePath)) + "ignoring this specific file"
+            continue;
+
+        # check filename extension
+        baseName, extension = os.path.splitext(filename)
+        if extension not in include_extensions:
+            print "  " + filePath + ":" + " "*(align-len(filePath)) + "improper extension; ignoring file"
+            continue;
+
+        # continue on with this file
+        update_copyright(filePath)
+
+
diff --git a/src/agc/bench/agc_crcf_benchmark.c b/src/agc/bench/agc_crcf_benchmark.c
new file mode 100644
index 0000000..34a616d
--- /dev/null
+++ b/src/agc/bench/agc_crcf_benchmark.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+
+#include "liquid.h"
+
+// helper function to keep code base small
+void benchmark_agc_crcf(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations)
+{
+    unsigned int i;
+
+    // initialize AGC object
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q,0.05f);
+
+    float complex x = 1e-6f;    // input sample
+    float complex y;            // output sample
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+        agc_crcf_execute(q, x, &y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    *_num_iterations *= 8;
+
+    // destroy object
+    agc_crcf_destroy(q);
+}
+
diff --git a/src/agc/src/agc.c b/src/agc/src/agc.c
new file mode 100644
index 0000000..70a2c20
--- /dev/null
+++ b/src/agc/src/agc.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Automatic gain control
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// default AGC loop bandwidth
+#define AGC_DEFAULT_BW   (1e-2f)
+
+// agc structure object
+struct AGC(_s) {
+    // gain variables
+    T g;            // current gain value
+
+    // gain control loop filter parameters
+    float bandwidth;// bandwidth-time constant
+    T alpha;        // feed-back gain
+
+    // signal level estimate
+    T y2_prime;     // filtered output signal energy estimate
+
+    // AGC locked flag
+    int is_locked;
+};
+
+// create agc object
+AGC() AGC(_create)(void)
+{
+    // create object and initialize to default parameters
+    AGC() _q = (AGC()) malloc(sizeof(struct AGC(_s)));
+
+    // initialize bandwidth
+    AGC(_set_bandwidth)(_q, AGC_DEFAULT_BW);
+
+    // reset object
+    AGC(_reset)(_q);
+
+    // return object
+    return _q;
+}
+
+// destroy agc object, freeing all internally-allocated memory
+void AGC(_destroy)(AGC() _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+// print agc object internals
+void AGC(_print)(AGC() _q)
+{
+    printf("agc [rssi: %12.4fdB]:\n", AGC(_get_rssi)(_q));
+}
+
+// reset agc object's internal state
+void AGC(_reset)(AGC() _q)
+{
+    // reset gain estimate
+    _q->g = 1.0f;
+
+    // reset signal level estimate
+    _q->y2_prime = 1.0f;
+
+    // unlock gain control
+    AGC(_unlock)(_q);
+}
+
+// execute automatic gain control loop
+//  _q      :   agc object
+//  _x      :   input sample
+//  _y      :   output sample
+void AGC(_execute)(AGC() _q,
+                   TC    _x,
+                   TC *  _y)
+{
+    // apply gain to input sample
+    *_y = _x * _q->g;
+
+    // compute output signal energy
+    T y2 = crealf( (*_y)*conjf(*_y) );
+
+    // smooth energy estimate using single-pole low-pass filter
+    _q->y2_prime = (1.0-_q->alpha)*_q->y2_prime + _q->alpha*y2;
+
+    // return if locked
+    if (_q->is_locked)
+        return;
+
+    // update gain according to output energy
+    if (_q->y2_prime > 1e-6f)
+        _q->g *= expf( -0.5f*_q->alpha*logf(_q->y2_prime) );
+
+    // clamp to 120 dB gain
+    if (_q->g > 1e6f)
+        _q->g = 1e6f;
+}
+
+// execute automatic gain control on block of samples
+//  _q      : automatic gain control object
+//  _x      : input data array, [size: _n x 1]
+//  _n      : number of input, output samples
+//  _y      : output data array, [size: _n x 1]
+void AGC(_execute_block)(AGC()        _q,
+                         TC *         _x,
+                         unsigned int _n,
+                         TC *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        AGC(_execute)(_q, _x[i], &_y[i]);
+}
+
+// lock agc
+void AGC(_lock)(AGC() _q)
+{
+    _q->is_locked = 1;
+}
+
+// unlock agc
+void AGC(_unlock)(AGC() _q)
+{
+    _q->is_locked = 0;
+}
+
+// get agc loop bandwidth
+float AGC(_get_bandwidth)(AGC() _q)
+{
+    return _q->bandwidth;
+}
+
+// set agc loop bandwidth
+//  _q      :   agc object
+//  _BT     :   bandwidth
+void AGC(_set_bandwidth)(AGC() _q,
+                         float _bt)
+{
+    // check to ensure bandwidth is reasonable
+    if ( _bt < 0 ) {
+        fprintf(stderr,"error: agc_%s_set_bandwidth(), bandwidth must be positive\n", EXTENSION_FULL);
+        exit(-1);
+    } else if ( _bt > 1.0f ) {
+        fprintf(stderr,"error: agc_%s_set_bandwidth(), bandwidth must less than 1.0\n", EXTENSION_FULL);
+        exit(-1);
+    }
+
+    // set internal bandwidth
+    _q->bandwidth = _bt;
+
+    // compute filter coefficient based on bandwidth
+    _q->alpha = _q->bandwidth;
+}
+
+// get estimated signal level (linear)
+float AGC(_get_signal_level)(AGC() _q)
+{
+    return 1.0f / _q->g;
+}
+
+// set estimated signal level (linear)
+void AGC(_set_signal_level)(AGC() _q,
+                            float _signal_level)
+{
+    // check to ensure signal level is reasonable
+    if ( _signal_level <= 0 ) {
+        fprintf(stderr,"error: agc_%s_set_signal_level(), bandwidth must be greater than zero\n", EXTENSION_FULL);
+        exit(-1);
+    }
+
+    // set internal gain appropriately
+    _q->g = 1.0f / _signal_level;
+
+    // reset internal output signal level
+    _q->y2_prime = 1.0f;
+}
+
+// get estimated signal level (dB)
+float AGC(_get_rssi)(AGC() _q)
+{
+    return -20*log10(_q->g);
+}
+
+// set estimated signal level (dB)
+void AGC(_set_rssi)(AGC() _q,
+                    float _rssi)
+{
+    // set internal gain appropriately
+    _q->g = powf(10.0f, -_rssi/20.0f);
+
+    // ensure resulting gain is not arbitrarily low
+    if (_q->g < 1e-16f)
+        _q->g = 1e-16f;
+
+    // reset internal output signal level
+    _q->y2_prime = 1.0f;
+}
+
+// get internal gain
+float AGC(_get_gain)(AGC() _q)
+{
+    return _q->g;
+}
+
+// set internal gain
+void AGC(_set_gain)(AGC() _q,
+                    float _gain)
+{
+    // check to ensure gain is reasonable
+    if ( _gain <= 0 ) {
+        fprintf(stderr,"error: agc_%s_set_gain(), gain must be greater than zero\n", EXTENSION_FULL);
+        exit(-1);
+    }
+
+    // set internal gain appropriately
+    _q->g = _gain;
+}
+
+// initialize internal gain on input array
+//  _q      : automatic gain control object
+//  _x      : input data array, [size: _n x 1]
+//  _n      : number of input, output samples
+void AGC(_init)(AGC()        _q,
+                TC *         _x,
+                unsigned int _n)
+{
+    // ensure number of samples is greater than zero
+    if ( _n == 0 ) {
+        fprintf(stderr,"error: agc_%s_init(), number of samples must be greater than zero\n", EXTENSION_FULL);
+        exit(-1);
+    }
+
+    // compute sum squares on input
+    // TODO: use vector methods for this
+    unsigned int i;
+    T x2 = 0;
+    for (i=0; i<_n; i++)
+        x2 += crealf( _x[i]*conjf(_x[i]) );
+
+    // compute RMS level and ensure result is positive
+    x2 = sqrtf( x2 / (float) _n ) + 1e-16f;
+
+    // set internal gain based on estimated signal level
+    AGC(_set_signal_level)(_q, x2);
+}
+
diff --git a/src/agc/src/agc_crcf.c b/src/agc/src/agc_crcf.c
new file mode 100644
index 0000000..8bdef67
--- /dev/null
+++ b/src/agc/src/agc_crcf.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// AGC API: floating-point [complex]
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "crcf"
+
+// macros
+#define AGC(name)           LIQUID_CONCAT(agc_crcf,name)
+
+#define T                   float           // general
+#define TC                  float complex   // input/output
+
+#define TC_COMPLEX          1
+
+// source files
+#include "agc.c"
diff --git a/src/agc/src/agc_rrrf.c b/src/agc/src/agc_rrrf.c
new file mode 100644
index 0000000..e4421ae
--- /dev/null
+++ b/src/agc/src/agc_rrrf.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// AGC API: floating-point [real]
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "rrrf"
+
+// macros
+#define AGC(name)           LIQUID_CONCAT(agc_rrrf,name)
+
+#define T                   float           // general
+#define TC                  float           // input/output
+
+#define TC_COMPLEX          0
+
+// source files
+#include "agc.c"
diff --git a/src/agc/tests/agc_crcf_autotest.c b/src/agc/tests/agc_crcf_autotest.c
new file mode 100644
index 0000000..0101416
--- /dev/null
+++ b/src/agc/tests/agc_crcf_autotest.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// Test DC gain control
+//
+void autotest_agc_crcf_dc_gain_control()
+{
+    // set paramaters
+    float gamma = 0.1f;     // nominal signal level
+    float bt    = 0.1f;     // bandwidth-time product
+    float tol   = 0.001f;   // error tolerance
+
+    // create AGC object and initialize
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q, bt);
+
+    unsigned int i;
+    float complex x = gamma;    // input sample
+    float complex y;            // output sample
+    for (i=0; i<256; i++)
+        agc_crcf_execute(q, x, &y);
+    
+    // Check results
+    CONTEND_DELTA( crealf(y), 1.0f, tol );
+    CONTEND_DELTA( cimagf(y), 0.0f, tol );
+    CONTEND_DELTA( agc_crcf_get_gain(q), 1.0f/gamma, tol );
+
+    // destroy AGC object
+    agc_crcf_destroy(q);
+}
+
+// 
+// Test AC gain control
+//
+void autotest_agc_crcf_ac_gain_control()
+{
+    // set paramaters
+    float gamma = 0.1f;             // nominal signal level
+    float bt    = 0.1f;             // bandwidth-time product
+    float tol   = 0.001f;           // error tolerance
+    float dphi  = 0.1f;             // NCO frequency
+
+    // create AGC object and initialize
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q, bt);
+
+    unsigned int i;
+    float complex x;
+    float complex y;
+    for (i=0; i<256; i++) {
+        x = gamma * cexpf(_Complex_I*i*dphi);
+        agc_crcf_execute(q, x, &y);
+    }
+
+    if (liquid_autotest_verbose)
+        printf("gamma : %12.8f, rssi : %12.8f\n", gamma, agc_crcf_get_signal_level(q));
+
+    // Check results
+    CONTEND_DELTA( agc_crcf_get_gain(q), 1.0f/gamma, tol);
+
+    // destroy AGC object
+    agc_crcf_destroy(q);
+}
+
+
+
+// 
+// Test RSSI on sinusoidal input
+//
+void autotest_agc_crcf_rssi_sinusoid()
+{
+    // set paramaters
+    float gamma = 0.3f;         // nominal signal level
+    float bt    = 0.05f;        // agc bandwidth
+    float tol   = 0.001f;       // error tolerance
+
+    // signal properties
+    float dphi = 0.1f;          // signal frequency
+
+    // create AGC object and initialize
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q, bt);
+
+    unsigned int i;
+    float complex x, y;
+    for (i=0; i<512; i++) {
+        // generate sample (complex sinusoid)
+        x = gamma * cexpf(_Complex_I*dphi*i);
+
+        // execute agc
+        agc_crcf_execute(q, x, &y);
+    }
+
+    // get received signal strength indication
+    float rssi = agc_crcf_get_signal_level(q);
+
+    if (liquid_autotest_verbose)
+        printf("gamma : %12.8f, rssi : %12.8f\n", gamma, rssi);
+
+    // Check results
+    CONTEND_DELTA( rssi, gamma, tol );
+
+    // destroy agc object
+    agc_crcf_destroy(q);
+}
+
+
+// 
+// Test RSSI on noise input
+//
+void autotest_agc_crcf_rssi_noise()
+{
+    // set paramaters
+    float gamma = -30.0f;   // nominal signal level [dB]
+    float bt    =  0.01f;   // agc bandwidth
+    float tol   =  0.2f;    // error tolerance [dB]
+
+    // signal properties
+    float nstd = powf(10.0f, gamma/20);
+
+    // create AGC object and initialize
+    agc_crcf q = agc_crcf_create();
+    agc_crcf_set_bandwidth(q, bt);
+
+    unsigned int i;
+    float complex x, y;
+    for (i=0; i<3000; i++) {
+        // generate sample (circular complex noise)
+        x = nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+        // execute agc
+        agc_crcf_execute(q, x, &y);
+    }
+
+    // get received signal strength indication
+    float rssi = agc_crcf_get_rssi(q);
+
+    if (liquid_autotest_verbose)
+        printf("gamma : %12.8f, rssi : %12.8f\n", gamma, rssi);
+
+    // Check results
+    CONTEND_DELTA( rssi, gamma, tol );
+
+    // destroy agc object
+    agc_crcf_destroy(q);
+}
+
+
+
diff --git a/src/audio/bench/cvsd_benchmark.c b/src/audio/bench/cvsd_benchmark.c
new file mode 100644
index 0000000..439d3ab
--- /dev/null
+++ b/src/audio/bench/cvsd_benchmark.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// benchmark CVSD encoder
+void benchmark_cvsd_encode(struct rusage *_start,
+                           struct rusage *_finish,
+                           unsigned long int *_num_iterations)
+{
+    unsigned long int i;
+
+    // options
+    unsigned int nbits=4;   // number of adjacent bits to observe
+    float zeta=1.5f;        // slope adjustment multiplier
+    float alpha = 0.95;     // pre-/post-filter coefficient
+    char bit    = 0;        // output bit
+
+    // create cvsd encoder
+    cvsd encoder = cvsd_create(nbits, zeta, alpha);
+
+    // input time series (random)
+    float x[8] = {
+       1.19403f,  -0.76765f,  -1.08415f,   0.65095f,
+       0.11647f,  -0.80130f,  -0.87540f,  -0.14888f};
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        bit ^= cvsd_encode(encoder, x[0]);
+        bit ^= cvsd_encode(encoder, x[2]);
+        bit ^= cvsd_encode(encoder, x[3]);
+        bit ^= cvsd_encode(encoder, x[3]);
+        bit ^= cvsd_encode(encoder, x[4]);
+        bit ^= cvsd_encode(encoder, x[5]);
+        bit ^= cvsd_encode(encoder, x[6]);
+        bit ^= cvsd_encode(encoder, x[7]);
+
+        // randomize input
+        x[i%8] += bit ? 0.1f : -0.1f;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 8;
+
+    // destroy cvsd encoder
+    cvsd_destroy(encoder);
+}
+
+// benchmark CVSD decoder
+void benchmark_cvsd_decode(struct rusage *_start,
+                           struct rusage *_finish,
+                           unsigned long int *_num_iterations)
+{
+    unsigned long int i;
+
+    // options
+    unsigned int nbits=4;   // number of adjacent bits to observe
+    float zeta=1.5f;        // slope adjustment multiplier
+    float alpha = 0.95;     // pre-/post-filter coefficient
+    float x     = 0.0f;
+
+    // create cvsd decoder
+    cvsd decoder = cvsd_create(nbits, zeta, alpha);
+
+    // input bit sequence (random)
+    unsigned char b[8] = {1, 1, 1, 0, 1, 0, 0, 0};
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += cvsd_decode(decoder, b[0]);
+        x += cvsd_decode(decoder, b[2]);
+        x += cvsd_decode(decoder, b[3]);
+        x += cvsd_decode(decoder, b[3]);
+        x += cvsd_decode(decoder, b[4]);
+        x += cvsd_decode(decoder, b[5]);
+        x += cvsd_decode(decoder, b[6]);
+        x += cvsd_decode(decoder, b[7]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 8;
+
+    // destroy cvsd decoder
+    cvsd_destroy(decoder);
+}
+
diff --git a/src/audio/src/cvsd.c b/src/audio/src/cvsd.c
new file mode 100644
index 0000000..12127ac
--- /dev/null
+++ b/src/audio/src/cvsd.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// CVSD: continuously variable slope delta
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// enable use of the pre-emphasis and post-emphasis filters
+#define CVSD_ENABLE_SIGNAL_CONDITIONING (1)
+
+struct cvsd_s {
+    unsigned int num_bits;
+    unsigned char bitref;   // historical bit reference
+    unsigned char bitmask;  // historical bit reference mask
+    float ref;              // internal reference
+
+    float zeta;             // delta step factor
+    float delta;            // current step size
+    float delta_min;        // minimum delta
+    float delta_max;        // maximum delta
+
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    float alpha;            // pre-/de-emphasis filter coefficient
+    float beta;             // DC-blocking coefficient (decoder)
+    iirfilt_rrrf prefilt;   // pre-emphasis filter (encoder)
+    iirfilt_rrrf postfilt;  // e-emphasis filter (decoder)
+#endif
+};
+
+// create cvsd object
+//  _num_bits   :   number of adjacent bits to observe
+//  _zeta       :   slope adjustment multiplier
+//  _alpha      :   pre-/post-emphasis filter coefficient (0.9 recommended)
+// NOTE: _alpha must be in [0,1]
+cvsd cvsd_create(unsigned int _num_bits,
+                 float _zeta,
+                 float _alpha)
+{
+    if (_num_bits == 0) {
+        fprintf(stderr, "error: cvsd_create(), _num_bits must be positive\n");
+        exit(1);
+    } else if (_zeta <= 1.0f) {
+        fprintf(stderr, "error: cvsd_create(), zeta must be greater than 1\n");
+        exit(1);
+    } else if (_alpha < 0.0f || _alpha > 1.0f) {
+        fprintf(stderr, "error: cvsd_create(), alpha must be in [0,1]\n");
+        exit(1);
+    }
+
+    cvsd q = (cvsd) malloc(sizeof(struct cvsd_s));
+    q->num_bits = _num_bits;
+    q->bitref = 0;
+    q->bitmask = (1<<(q->num_bits)) - 1;
+
+    q->ref = 0.0f;
+    q->zeta = _zeta;
+    q->delta = 0.01f;
+    q->delta_min = 0.01f;
+    q->delta_max = 1.0f;
+
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    // design pre-emphasis filter
+    q->alpha = _alpha;
+    float b_pre[2] = {1.0f, -q->alpha};
+    float a_pre[2] = {1.0f, 0.0f};
+    q->prefilt  = iirfilt_rrrf_create(b_pre,2,a_pre,2);
+
+    // design post-emphasis filter
+    q->beta = 0.99f;    // DC-blocking parameter
+    float b_post[3] = {1.0f, -1.0f, 0.0f};
+    float a_post[3] = {1.0f, -(q->alpha + q->beta), q->alpha*q->beta};
+    q->postfilt = iirfilt_rrrf_create(b_post,3,a_post,3);
+#endif
+
+    return q;
+}
+
+// destroy cvsd object
+void cvsd_destroy(cvsd _q)
+{
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    // destroy filters
+    iirfilt_rrrf_destroy(_q->prefilt);
+    iirfilt_rrrf_destroy(_q->postfilt);
+#endif
+
+    // free main object memory
+    free(_q);
+}
+
+// print cvsd object parameters
+void cvsd_print(cvsd _q)
+{
+    printf("cvsd codec:\n");
+    printf("    num bits: %u\n", _q->num_bits);
+    printf("    zeta    : %8.4f\n", _q->zeta);
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    printf("    alpha   : %8.4f\n", _q->alpha);
+#endif
+}
+
+// encode single sample
+unsigned char cvsd_encode(cvsd _q,
+                          float _audio_sample)
+{
+    // push audio sample through pre-filter
+    float y;
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    iirfilt_rrrf_execute(_q->prefilt, _audio_sample, &y);
+#else
+    y = _audio_sample;
+#endif
+
+    // determine output value
+    unsigned char bit = (_q->ref > y) ? 0 : 1;
+
+    // shift last value into buffer
+    _q->bitref <<= 1;
+    _q->bitref |= bit;
+    _q->bitref &= _q->bitmask;
+
+    // update delta
+    if (_q->bitref == 0 || _q->bitref == _q->bitmask)
+        _q->delta *= _q->zeta;  // increase delta
+    else
+        _q->delta /= _q->zeta;  // decrease delta
+
+    // limit delta
+    _q->delta = (_q->delta > _q->delta_max) ? _q->delta_max : _q->delta;
+    _q->delta = (_q->delta < _q->delta_min) ? _q->delta_min : _q->delta;
+
+    // update reference
+    _q->ref += (bit) ? _q->delta : -_q->delta;
+
+    // limit reference
+    _q->ref = (_q->ref >  1.0f) ?  1.0f : _q->ref;
+    _q->ref = (_q->ref < -1.0f) ? -1.0f : _q->ref;
+
+    return bit;
+}
+
+// decode single sample
+float cvsd_decode(cvsd _q,
+                  unsigned char _bit)
+{
+    // append bit into register
+    _q->bitref <<= 1;
+    _q->bitref |= (_bit & 0x01);
+    _q->bitref &= _q->bitmask;
+
+    // update delta
+    if (_q->bitref == 0 || _q->bitref == _q->bitmask)
+        _q->delta *= _q->zeta;  // increase delta
+    else
+        _q->delta /= _q->zeta;  // decrease delta
+
+    // limit delta
+    _q->delta = (_q->delta > _q->delta_max) ? _q->delta_max : _q->delta;
+    _q->delta = (_q->delta < _q->delta_min) ? _q->delta_min : _q->delta;
+
+    // update reference
+    _q->ref += (_bit&0x01) ? _q->delta : -_q->delta;
+
+    // limit reference
+    _q->ref = (_q->ref >  1.0f) ?  1.0f : _q->ref;
+    _q->ref = (_q->ref < -1.0f) ? -1.0f : _q->ref;
+
+    // push reference value through post-filter
+    float y;
+#if CVSD_ENABLE_SIGNAL_CONDITIONING
+    iirfilt_rrrf_execute(_q->postfilt, _q->ref, &y);
+#else
+    y = _q->ref;
+#endif
+
+    return y;
+}
+
+// encode 8 samples
+void cvsd_encode8(cvsd _q,
+                  float * _audio,
+                  unsigned char * _data)
+{
+    unsigned char data=0x00;
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        data <<= 1;
+        data |= cvsd_encode(_q, _audio[i]);
+    }
+
+    // set return value
+    *_data = data;
+}
+
+// decode 8 samples
+void cvsd_decode8(cvsd _q,
+                  unsigned char _data,
+                  float * _audio)
+{
+    unsigned char bit;
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        bit = (_data >> (8-i-1)) & 0x01;
+        _audio[i] = cvsd_decode(_q, bit);
+    }
+}
+
diff --git a/src/audio/tests/cvsd_autotest.c b/src/audio/tests/cvsd_autotest.c
new file mode 100644
index 0000000..5268d9b
--- /dev/null
+++ b/src/audio/tests/cvsd_autotest.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: check RMSE for CVSD
+//
+void autotest_cvsd_rmse_sine() {
+    unsigned int n=256;
+    unsigned int nbits=3;
+    float zeta=1.5f;
+    float alpha=0.90f;
+
+    // create cvsd codecs
+    cvsd cvsd_encoder = cvsd_create(nbits,zeta,alpha);
+    cvsd cvsd_decoder = cvsd_create(nbits,zeta,alpha);
+
+    float phi=0.0f;
+    float dphi=0.1f;
+    unsigned int i;
+    unsigned char b;
+    float x,y;
+    float rmse=0.0f;
+    for (i=0; i<n; i++) {
+        x = 0.5f*sinf(phi);
+        b = cvsd_encode(cvsd_encoder, x); 
+        y = cvsd_decode(cvsd_decoder, b); 
+
+        rmse += (x-y)*(x-y);
+        phi += dphi;
+    }   
+
+    rmse = 10*log10f(rmse/n);
+    if (liquid_autotest_verbose)
+        printf("cvsd rmse : %8.2f dB\n", rmse);
+    CONTEND_LESS_THAN(rmse, -20.0f);
+
+    // destroy cvsd codecs
+    cvsd_destroy(cvsd_encoder);
+    cvsd_destroy(cvsd_decoder);
+}
+
diff --git a/src/buffer/bench/cbuffercf_benchmark.c b/src/buffer/bench/cbuffercf_benchmark.c
new file mode 100644
index 0000000..b729eae
--- /dev/null
+++ b/src/buffer/bench/cbuffercf_benchmark.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define CBUFFERCF_BENCH_API(N, W, R)        \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ cbuffercf_bench(_start, _finish, _num_iterations, N, W, R); }
+
+// Helper function to keep code base small
+void cbuffercf_bench(struct rusage *     _start,
+                     struct rusage *     _finish,
+                     unsigned long int * _num_iterations,
+                     unsigned int        _n,
+                     unsigned int        _write_size,
+                     unsigned int        _read_size)
+{
+    // validate input
+    if (_n < 2) {
+        fprintf(stderr,"error: cbuffercf_bench(), number of elements must be at least 2\n");
+        exit(1);
+    } else if (_write_size > _n-1) {
+        fprintf(stderr,"error: cbuffercf_bench(), write size must be in (0,n)\n");
+        exit(1);
+    } else if (_read_size > _n-1) {
+        fprintf(stderr,"error: cbuffercf_bench(), read size must be in (0,n)\n");
+        exit(1);
+    }
+
+    // normalize number of iterations
+    *_num_iterations *= _n;
+
+    // create object
+    cbuffercf q = cbuffercf_create(_n);
+
+    // 
+    float complex   v[_write_size]; // array for writing
+    float complex * r;              // read pointer
+    unsigned int num_requested;     // number of elements requested
+    unsigned int num_read;          // number of elements read
+
+    // initialize array for writing
+    unsigned int i;
+    for (i=0; i<_write_size; i++)
+        v[i] = 0.0f;
+
+    // accumulate total number of elements
+    unsigned long int num_total_elements = 0;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    while (num_total_elements < *_num_iterations) {
+        // write elements to buffer if space is available
+        if (_n - cbuffercf_size(q) > _write_size)
+            cbuffercf_write(q, v, _write_size);
+
+        // read up to '_read_size' elements
+        num_requested = _read_size;
+        cbuffercf_read(q, num_requested, &r, &num_read);
+
+        // release elements that were read
+        cbuffercf_release(q, num_read);
+
+        // increment counter by number of elements passing through
+        num_total_elements += num_read;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // total number of iterations equal to to total number of elements
+    // that have passed through the buffer
+    *_num_iterations = num_total_elements;
+
+    // clean up allocated memory
+    cbuffercf_destroy(q);
+}
+
+// 
+void benchmark_cbuffercf_n16     CBUFFERCF_BENCH_API(  16,  12,  11);
+void benchmark_cbuffercf_n32     CBUFFERCF_BENCH_API(  32,  24,  23);
+void benchmark_cbuffercf_n64     CBUFFERCF_BENCH_API(  64,  48,  47);
+void benchmark_cbuffercf_n128    CBUFFERCF_BENCH_API( 128,  96,  95);
+void benchmark_cbuffercf_n256    CBUFFERCF_BENCH_API( 256, 192, 191);
+void benchmark_cbuffercf_n512    CBUFFERCF_BENCH_API( 512, 384, 383);
+void benchmark_cbuffercf_n1024   CBUFFERCF_BENCH_API(1024, 768, 767);
+
diff --git a/src/buffer/bench/window_push_benchmark.c b/src/buffer/bench/window_push_benchmark.c
new file mode 100644
index 0000000..3893e36
--- /dev/null
+++ b/src/buffer/bench/window_push_benchmark.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define WINDOW_PUSH_BENCH_API(N)        \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ window_push_bench(_start, _finish, _num_iterations, N); }
+
+// Helper function to keep code base small
+void window_push_bench(struct rusage *_start,
+                       struct rusage *_finish,
+                       unsigned long int *_num_iterations,
+                       unsigned int _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 8;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // initialize port
+    windowcf w = windowcf_create(_n);
+
+    unsigned long int i;
+
+    // start trials:
+    //   write to port, read from port
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        windowcf_push(w, 1.0f);
+        windowcf_push(w, 1.0f);
+        windowcf_push(w, 1.0f);
+        windowcf_push(w, 1.0f);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    windowcf_destroy(w);
+}
+
+// 
+void benchmark_windowcf_push_n16     WINDOW_PUSH_BENCH_API(16)
+void benchmark_windowcf_push_n32     WINDOW_PUSH_BENCH_API(32)
+void benchmark_windowcf_push_n64     WINDOW_PUSH_BENCH_API(64)
+void benchmark_windowcf_push_n128    WINDOW_PUSH_BENCH_API(128)
+void benchmark_windowcf_push_n256    WINDOW_PUSH_BENCH_API(256)
+
diff --git a/src/buffer/bench/window_read_benchmark.c b/src/buffer/bench/window_read_benchmark.c
new file mode 100644
index 0000000..2a94365
--- /dev/null
+++ b/src/buffer/bench/window_read_benchmark.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define WINDOW_READ_BENCH_API(N)        \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ window_read_bench(_start, _finish, _num_iterations, N); }
+
+// Helper function to keep code base small
+void window_read_bench(struct rusage *_start,
+                       struct rusage *_finish,
+                       unsigned long int *_num_iterations,
+                       unsigned int _n)
+{
+    // normalize number of iterations
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // initialize port
+    windowcf w = windowcf_create(_n);
+
+    unsigned long int i;
+
+    float complex * r;
+
+    // start trials:
+    //   write to buffer, read from buffer
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        windowcf_push(w,1.0f);  windowcf_read(w, &r);
+        windowcf_push(w,1.0f);  windowcf_read(w, &r);
+        windowcf_push(w,1.0f);  windowcf_read(w, &r);
+        windowcf_push(w,1.0f);  windowcf_read(w, &r);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    windowcf_destroy(w);
+}
+
+// 
+void benchmark_windowcf_read_n16    WINDOW_READ_BENCH_API(16)
+void benchmark_windowcf_read_n32    WINDOW_READ_BENCH_API(32)
+void benchmark_windowcf_read_n64    WINDOW_READ_BENCH_API(64)
+void benchmark_windowcf_read_n128   WINDOW_READ_BENCH_API(128)
+void benchmark_windowcf_read_n256   WINDOW_READ_BENCH_API(256)
+
diff --git a/src/buffer/src/buffer.c b/src/buffer/src/buffer.c
new file mode 100644
index 0000000..853e095
--- /dev/null
+++ b/src/buffer/src/buffer.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Buffers, defined by macro
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+struct BUFFER(_s) {
+    T * v;
+    unsigned int len;           // length of buffer
+    unsigned int N;             // number of elements allocated
+                                // in memory
+    unsigned int num_elements;  // number of elements currently
+                                // in the buffer
+
+    unsigned int read_index;
+    unsigned int write_index;
+
+    buffer_type type;
+
+    // mutex/semaphore
+};
+
+BUFFER() BUFFER(_create)(buffer_type _type, unsigned int _n)
+{
+    BUFFER() b = (BUFFER()) malloc(sizeof(struct BUFFER(_s)));
+    b->type = _type;
+    b->len = _n;
+
+    if (b->type == CIRCULAR)
+        b->N = 2*(b->len) - 1;
+    else
+        b->N = b->len;
+
+    b->v = (T*) malloc((b->N)*sizeof(T));
+    b->num_elements = 0;
+    b->read_index = 0;
+    b->write_index = 0;
+
+    return b;
+}
+
+void BUFFER(_destroy)(BUFFER() _b)
+{
+    free(_b->v);
+    free(_b);
+}
+
+void BUFFER(_print)(BUFFER() _b)
+{
+    if (_b->type == CIRCULAR)
+        printf("circular ");
+    else
+        printf("static ");
+    printf("buffer [%u elements] :\n", _b->num_elements);
+    unsigned int i;
+    for (i=0; i<_b->num_elements; i++) {
+        printf("%u", i);
+        BUFFER_PRINT_LINE(_b,(_b->read_index+i)%(_b->len))
+        printf("\n");
+    }
+}
+
+void BUFFER(_debug_print)(BUFFER() _b)
+{
+    if (_b->type == CIRCULAR)
+        printf("circular ");
+    else
+        printf("static ");
+    printf("buffer [%u elements] :\n", _b->num_elements);
+    unsigned int i;
+    for (i=0; i<_b->len; i++) {
+        // print read index pointer
+        if (i==_b->read_index)
+            printf("<r>");
+
+        // print write index pointer
+        if (i==_b->write_index)
+            printf("<w>");
+
+        // print buffer value
+        BUFFER_PRINT_LINE(_b,i)
+        printf("\n");
+    }
+    printf("----------------------------------\n");
+
+    // print excess buffer memory
+    for (i=_b->len; i<_b->N; i++) {
+        BUFFER_PRINT_LINE(_b,i)
+        printf("\n");
+    }
+}
+
+void BUFFER(_clear)(BUFFER() _b)
+{
+    _b->read_index = 0;
+    _b->write_index = 0;
+    _b->num_elements = 0;
+}
+
+void BUFFER(_zero)(BUFFER() _b)
+{
+    _b->read_index = 0;
+    _b->write_index = 0;
+    _b->num_elements = _b->len;
+    memset(_b->v, 0, (_b->num_elements)*sizeof(T));
+}
+
+void BUFFER(_read)(BUFFER() _b, T ** _v, unsigned int *_n)
+{
+    if (_b->type == CIRCULAR)
+        BUFFER(_c_read)(_b, _v, _n);
+    else
+        BUFFER(_s_read)(_b, _v, _n);
+}
+
+void BUFFER(_c_read)(BUFFER() _b, T ** _v, unsigned int *_n)
+{
+    //printf("buffer_read() trying to read %u elements (%u available)\n", *_n, _b->num_elements);
+#if 0
+    if (*_n > _b->num_elements) {
+        printf("error: buffer_read(), cannot read more elements than are available\n");
+        *_v = NULL;
+        *_n = 0;
+        return;
+    } else
+#endif
+    if (*_n > (_b->len - _b->read_index)) {
+        //
+        BUFFER(_linearize)(_b);
+    }
+    *_v = _b->v + _b->read_index;
+    *_n = _b->num_elements;
+}
+
+void BUFFER(_s_read)(BUFFER() _b, T ** _v, unsigned int *_n)
+{
+    //printf("buffer_s_read() reading %u elements\n", _b->num_elements);
+    *_v = _b->v;
+    *_n = _b->num_elements;
+}
+
+void BUFFER(_release)(BUFFER() _b, unsigned int _n)
+{
+    if (_b->type == CIRCULAR)
+        BUFFER(_c_release)(_b, _n);
+    else
+        BUFFER(_s_release)(_b, _n);
+}
+
+
+void BUFFER(_c_release)(BUFFER() _b, unsigned int _n)
+{
+    // advance read_index by _n making sure not to step on write_index
+    if (_n > _b->num_elements) {
+        printf("error: buffer_c_release(), cannot release more elements in buffer than exist\n");
+        return;
+    }
+
+    _b->read_index = (_b->read_index + _n) % _b->len;
+    _b->num_elements -= _n;
+}
+
+
+void BUFFER(_s_release)(BUFFER() _b, unsigned int _n)
+{
+    BUFFER(_clear)(_b);
+}
+
+void BUFFER(_write)(BUFFER() _b, T * _v, unsigned int _n)
+{
+    if (_b->type == CIRCULAR)
+        BUFFER(_c_write)(_b, _v, _n);
+    else
+        BUFFER(_s_write)(_b, _v, _n);
+}
+
+void BUFFER(_c_write)(BUFFER() _b, T * _v, unsigned int _n)
+{
+    //
+    if (_n > (_b->len - _b->num_elements)) {
+        printf("error: buffer_write(), cannot write more elements than are available\n");
+        return;
+    }
+
+    _b->num_elements += _n;
+    // space available at end of buffer
+    unsigned int k = _b->len - _b->write_index;
+    //printf("n : %u, k : %u\n", _n, k);
+
+    // check for condition where we need to wrap around
+    if (_n > k) {
+        memcpy(_b->v + _b->write_index, _v, k*sizeof(T));
+        memcpy(_b->v, &_v[k], (_n-k)*sizeof(T));
+        _b->write_index = _n - k;
+    } else {
+        memcpy(_b->v + _b->write_index, _v, _n*sizeof(T));
+        _b->write_index += _n;
+    }
+}
+
+void BUFFER(_s_write)(BUFFER() _b, T * _v, unsigned int _n)
+{
+    if (_n > (_b->len - _b->num_elements)) {
+        printf("error: buffer_s_write(), cannot write more elements than are available\n");
+        return;
+    }
+
+    memcpy(_b->v + _b->num_elements, _v, _n*sizeof(T));
+    _b->num_elements += _n;
+}
+
+//void BUFFER(_force_write)(BUFFER() _b, T * _v, unsigned int _n)
+
+void BUFFER(_push)(BUFFER() _b, T _v)
+{
+    // push value (force write)
+    if (_b->type == CIRCULAR)
+        BUFFER(_c_push)(_b, _v);
+    else
+        BUFFER(_s_push)(_b, _v);
+}
+
+void BUFFER(_c_push)(BUFFER() _b, T _v)
+{
+    _b->v[_b->write_index] = _v;
+    if (_b->num_elements < _b->len) {
+        _b->num_elements++;
+    } else {
+        _b->read_index = (_b->read_index+1) % _b->len;
+    }
+    _b->write_index = (_b->write_index+1) % _b->len;
+}
+
+void BUFFER(_s_push)(BUFFER() _b, T _v)
+{
+
+}
+
+void BUFFER(_linearize)(BUFFER() _b)
+{
+    // check to see if anything needs to be done
+    if ( (_b->len - _b->read_index) > _b->num_elements)
+        return;
+
+    // perform memory copy
+    memcpy(_b->v + _b->len, _b->v, (_b->write_index)*sizeof(T));
+}
+
diff --git a/src/buffer/src/buffercf.c b/src/buffer/src/buffercf.c
new file mode 100644
index 0000000..65c45f0
--- /dev/null
+++ b/src/buffer/src/buffercf.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex Float buffer
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION       "cf"
+
+#define BUFFER_TYPE_CFLOAT
+
+#define CBUFFER(name)   LIQUID_CONCAT(cbuffercf, name)
+//#define SBUFFER(name)   LIQUID_CONCAT(sbuffercf, name)
+#define WDELAY(name)    LIQUID_CONCAT(wdelaycf,  name)
+#define WINDOW(name)    LIQUID_CONCAT(windowcf,  name)
+
+#define T float complex
+#define BUFFER_PRINT_LINE(B,I) \
+    printf("  : %12.8f + %12.8f", crealf(B->v[I]), cimagf(B->v[I]));
+#define BUFFER_PRINT_VALUE(V) \
+    printf("  : %12.4e + %12.4e", crealf(V), cimagf(V));
+
+#include "cbuffer.c"
+//#include "sbuffer.c"
+#include "window.c"
+#include "wdelay.c"
+
diff --git a/src/buffer/src/bufferf.c b/src/buffer/src/bufferf.c
new file mode 100644
index 0000000..2133ad1
--- /dev/null
+++ b/src/buffer/src/bufferf.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Float buffer
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION       "f"
+
+#define BUFFER_TYPE_FLOAT
+
+#define CBUFFER(name)   LIQUID_CONCAT(cbufferf, name)
+//#define SBUFFER(name)   LIQUID_CONCAT(sbufferf, name)
+#define WDELAY(name)    LIQUID_CONCAT(wdelayf,  name)
+#define WINDOW(name)    LIQUID_CONCAT(windowf,  name)
+
+#define T float
+#define BUFFER_PRINT_LINE(B,I) \
+    printf("  : %12.8f", B->v[I]);
+#define BUFFER_PRINT_VALUE(V) \
+    printf("  : %12.4e", V);
+
+#include "cbuffer.c"
+//#include "sbuffer.c"
+#include "wdelay.c"
+#include "window.c"
+
diff --git a/src/buffer/src/cbuffer.c b/src/buffer/src/cbuffer.c
new file mode 100644
index 0000000..16eed2b
--- /dev/null
+++ b/src/buffer/src/cbuffer.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// circular buffer
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// linearize buffer (if necessary)
+void CBUFFER(_linearize)(CBUFFER() _q);
+
+// cbuffer object
+struct CBUFFER(_s) {
+    // allocated memory array
+    T * v;
+
+    // length of buffer
+    unsigned int max_size;
+
+    // maximum number of elements that can be read at any given time
+    unsigned int max_read;
+
+    // number of elements allocated in memory
+    unsigned int num_allocated;
+    
+    // number of elements currently in buffer
+    unsigned int num_elements;
+    
+    // index to read
+    unsigned int read_index;
+    
+    // index to write
+    unsigned int write_index;
+};
+
+// create circular buffer object of a particular size
+CBUFFER() CBUFFER(_create)(unsigned int _max_size)
+{
+    // create main object
+    CBUFFER() q = CBUFFER(_create_max)(_max_size, _max_size);
+
+    // return main object
+    return q;
+}
+
+// create circular buffer object of a particular size and
+// specify the maximum number of elements that can be read
+// at any given time.
+CBUFFER() CBUFFER(_create_max)(unsigned int _max_size,
+                               unsigned int _max_read)
+{
+    // create main object
+    CBUFFER() q = (CBUFFER()) malloc(sizeof(struct CBUFFER(_s)));
+
+    // set internal properties
+    q->max_size = _max_size;
+    q->max_read = _max_read;
+
+    // internal memory allocation
+    q->num_allocated = q->max_size + q->max_read - 1;
+
+    // allocate internal memory array
+    q->v = (T*) malloc((q->num_allocated)*sizeof(T));
+
+    // reset object
+    CBUFFER(_clear)(q);
+
+    // return main object
+    return q;
+}
+
+// destroy cbuffer object, freeing all internal memory
+void CBUFFER(_destroy)(CBUFFER() _q)
+{
+    // free internal memory
+    free(_q->v);
+
+    // free main object
+    free(_q);
+}
+
+// print cbuffer object properties
+void CBUFFER(_print)(CBUFFER() _q)
+{
+    printf("cbuffer%s [max size: %u, max read: %u, elements: %u]\n",
+            EXTENSION,
+            _q->max_size,
+            _q->max_read,
+            _q->num_elements);
+
+    unsigned int i;
+    for (i=0; i<_q->num_elements; i++) {
+        printf("%u", i);
+        BUFFER_PRINT_LINE(_q,(_q->read_index+i)%(_q->max_size))
+        printf("\n");
+    }
+}
+
+// print cbuffer object properties and internal state
+void CBUFFER(_debug_print)(CBUFFER() _q)
+{
+    printf("cbuffer%s [max size: %u, max read: %u, elements: %u]\n",
+            EXTENSION,
+            _q->max_size,
+            _q->max_read,
+            _q->num_elements);
+
+    unsigned int i;
+    for (i=0; i<_q->max_size; i++) {
+        // print read index pointer
+        if (i==_q->read_index)
+            printf("<r>");
+        else
+            printf("   ");
+
+        // print write index pointer
+        if (i==_q->write_index)
+            printf("<w>");
+        else
+            printf("   ");
+
+        // print buffer value
+        BUFFER_PRINT_LINE(_q,i)
+        printf("\n");
+    }
+    printf("----------------------------------\n");
+
+    // print excess buffer memory
+    for (i=_q->max_size; i<_q->num_allocated; i++) {
+        printf("      ");
+        BUFFER_PRINT_LINE(_q,i)
+        printf("\n");
+    }
+}
+
+// clear internal buffer
+void CBUFFER(_clear)(CBUFFER() _q)
+{
+    _q->read_index   = 0;
+    _q->write_index  = 0;
+    _q->num_elements = 0;
+}
+
+// get the number of elements currently in the buffer
+unsigned int CBUFFER(_size)(CBUFFER() _q)
+{
+    return _q->num_elements;
+}
+
+// get the maximum number of elements the buffer can hold
+unsigned int CBUFFER(_max_size)(CBUFFER() _q)
+{
+    return _q->max_size;
+}
+
+// get the maximum number of elements that can be read from
+// the buffer at any given time.
+unsigned int CBUFFER(_max_read)(CBUFFER() _q)
+{
+    return _q->max_read;
+}
+
+// return number of elements available for writing
+unsigned int CBUFFER(_space_available)(CBUFFER() _q)
+{
+    return _q->max_size - _q->num_elements;
+}
+
+// is buffer full?
+int CBUFFER(_is_full)(CBUFFER() _q)
+{
+    return (_q->num_elements == _q->max_size ? 1 : 0);
+}
+
+// write a single sample into the buffer
+//  _q  : circular buffer object
+//  _v  : input sample
+void CBUFFER(_push)(CBUFFER() _q,
+                    T         _v)
+{
+    // ensure buffer isn't already full
+    if (_q->num_elements == _q->max_size) {
+        fprintf(stderr,"warning: cbuffer%s_push(), no space available\n",
+                EXTENSION);
+        return;
+    }
+
+    // add sample at write index
+    _q->v[_q->write_index] = _v;
+
+    // update write index
+    _q->write_index = (_q->write_index+1) % _q->max_size;
+
+    // increment number of elements
+    _q->num_elements++;
+}
+
+// write samples to the buffer
+//  _q  : circular buffer object
+//  _v  : output array
+//  _n  : number of samples to write
+void CBUFFER(_write)(CBUFFER()    _q,
+                     T *          _v,
+                     unsigned int _n)
+{
+    // ensure number of samples to write doesn't exceed space available
+    if (_n > (_q->max_size - _q->num_elements)) {
+        printf("warning: cbuffer%s_write(), cannot write more elements than are available\n", EXTENSION);
+        return;
+    }
+
+    _q->num_elements += _n;
+    // space available at end of buffer
+    unsigned int k = _q->max_size - _q->write_index;
+    //printf("n : %u, k : %u\n", _n, k);
+
+    // check for condition where we need to wrap around
+    if (_n > k) {
+        memmove(_q->v + _q->write_index, _v, k*sizeof(T));
+        memmove(_q->v, &_v[k], (_n-k)*sizeof(T));
+        _q->write_index = _n - k;
+    } else {
+        memmove(_q->v + _q->write_index, _v, _n*sizeof(T));
+        _q->write_index += _n;
+    }
+}
+
+// remove and return a single element from the buffer
+//  _q  : circular buffer object
+//  _v  : pointer to sample output
+void CBUFFER(_pop)(CBUFFER()    _q,
+                   T *          _v)
+{
+    // ensure there is at least one element
+    if (_q->num_elements == 0) {
+        fprintf(stderr,"warning: cbuffer%s_pop(), no elements available\n",
+                EXTENSION);
+        return;
+    }
+
+    // set return value
+    if (_v != NULL)
+        *_v = _q->v[ _q->read_index ];
+
+    // increment read index
+    _q->read_index = (_q->read_index + 1) % _q->max_size;
+
+    // decrement number of elements in the buffer
+    _q->num_elements--;
+}
+
+// read buffer contents
+//  _q              : circular buffer object
+//  _num_requested  : number of elements requested
+//  _v              : output pointer
+//  _nr             : number of elements referenced by _v
+void CBUFFER(_read)(CBUFFER()      _q,
+                    unsigned int   _num_requested,
+                    T **           _v,
+                    unsigned int * _num_read)
+{
+    // adjust number requested depending upon availability
+    if (_num_requested > _q->num_elements)
+        _num_requested = _q->num_elements;
+    
+    // restrict maximum number of elements to originally specified value
+    if (_num_requested > _q->max_read)
+        _num_requested = _q->max_read;
+
+    // linearize tail end of buffer if necessary
+    if (_num_requested > (_q->max_size - _q->read_index))
+        CBUFFER(_linearize)(_q);
+    
+    // set output pointer appropriately
+    *_v        = _q->v + _q->read_index;
+    *_num_read = _num_requested;
+}
+
+// release _n samples in the buffer
+void CBUFFER(_release)(CBUFFER()    _q,
+                       unsigned int _n)
+{
+    // advance read_index by _n making sure not to step on write_index
+    if (_n > _q->num_elements) {
+        printf("error: cbuffer%s_release(), cannot release more elements in buffer than exist\n", EXTENSION);
+        return;
+    }
+
+    _q->read_index = (_q->read_index + _n) % _q->max_size;
+    _q->num_elements -= _n;
+}
+
+
+//
+// internal methods
+//
+
+// internal linearization
+void CBUFFER(_linearize)(CBUFFER() _q)
+{
+#if 0
+    // check to see if anything needs to be done
+    if ( (_q->max_size - _q->read_index) > _q->num_elements)
+        return;
+#endif
+
+    //printf("cbuffer linearize: [%6u : %6u], num elements: %6u, read index: %6u, write index: %6u\n",
+    //        _q->max_size, _q->max_read-1, _q->num_elements, _q->read_index, _q->write_index);
+
+    // move maximum amount
+    memmove(_q->v + _q->max_size, _q->v, (_q->max_read-1)*sizeof(T));
+}
+
diff --git a/src/buffer/src/wdelay.c b/src/buffer/src/wdelay.c
new file mode 100644
index 0000000..950c63b
--- /dev/null
+++ b/src/buffer/src/wdelay.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// windowed delay, defined by macro
+//
+
+#include "liquid.internal.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct WDELAY(_s) {
+    T * v;                      // allocated array pointer
+    unsigned int delay;         // length of window
+    unsigned int read_index;
+};
+
+// create delay buffer object with '_delay' samples
+WDELAY() WDELAY(_create)(unsigned int _delay)
+{
+    // create main object
+    WDELAY() q = (WDELAY()) malloc(sizeof(struct WDELAY(_s)));
+
+    // set internal values
+    q->delay = _delay;
+
+    // allocte memory
+    q->v = (T*) malloc((q->delay)*sizeof(T));
+    q->read_index = 0;
+
+    // clear window
+    WDELAY(_clear)(q);
+
+    return q;
+}
+
+// re-create delay buffer object with '_delay' samples
+//  _q      :   old delay buffer object
+//  _delay  :   delay for new object
+WDELAY() WDELAY(_recreate)(WDELAY()     _q,
+                           unsigned int _delay)
+{
+    // copy internal buffer, re-aligned
+    unsigned int ktmp = _q->delay;
+    T * vtmp = (T*) malloc(_q->delay * sizeof(T));
+    unsigned int i;
+    for (i=0; i<_q->delay; i++)
+        vtmp[i] = _q->v[ (i + _q->read_index) % _q->delay ];
+    
+    // destroy object and re-create it
+    WDELAY(_destroy)(_q);
+    _q = WDELAY(_create)(_delay);
+
+    // push old values
+    for (i=0; i<ktmp; i++)
+        WDELAY(_push)(_q, vtmp[i]);
+
+    // free temporary array
+    free(vtmp);
+
+    // return object
+    return _q;
+}
+
+// destroy delay buffer object, freeing internal memory
+void WDELAY(_destroy)(WDELAY() _q)
+{
+    // free internal array buffer
+    free(_q->v);
+
+    // free main object memory
+    free(_q);
+}
+
+// print delay buffer object's state to stdout
+void WDELAY(_print)(WDELAY() _q)
+{
+    printf("wdelay [%u elements] :\n", _q->delay);
+    unsigned int i, j;
+    for (i=0; i<_q->delay; i++) {
+        j = (i + _q->read_index) % _q->delay;
+        printf("%4u", i);
+        BUFFER_PRINT_VALUE(_q->v[j]);
+        printf("\n");
+    }
+}
+
+// clear/reset state of object
+void WDELAY(_clear)(WDELAY() _q)
+{
+    _q->read_index = 0;
+    memset(_q->v, 0, (_q->delay)*sizeof(T));
+}
+
+// read delayed sample from delay buffer object
+//  _q  :   delay buffer object
+//  _v  :   value of delayed element
+void WDELAY(_read)(WDELAY() _q,
+                   T *      _v)
+{
+    // return value at end of buffer
+    *_v = _q->v[_q->read_index];
+}
+
+// push new sample into delay buffer object
+//  _q  :   delay buffer object
+//  _v  :   new value to be added to buffer
+void WDELAY(_push)(WDELAY() _q,
+                   T        _v)
+{
+    // append value to end of buffer
+    _q->v[_q->read_index] = _v;
+
+    // increment index
+    _q->read_index++;
+
+    // wrap around pointer
+    _q->read_index %= _q->delay;
+}
+
diff --git a/src/buffer/src/window.c b/src/buffer/src/window.c
new file mode 100644
index 0000000..ecc05b6
--- /dev/null
+++ b/src/buffer/src/window.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Windows, defined by macro
+//
+
+#include "liquid.internal.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct WINDOW(_s) {
+    T * v;                      // allocated array pointer
+    unsigned int len;           // length of window
+    unsigned int m;             // floor(log2(len)) + 1
+    unsigned int n;             // 2^m
+    unsigned int mask;          // n-1
+    unsigned int num_allocated; // number of elements allocated
+                                // in memory
+    unsigned int read_index;
+};
+
+// create window buffer object of length _n
+WINDOW() WINDOW(_create)(unsigned int _n)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: window%s_create(), window size must be greater than zero\n",
+                EXTENSION);
+        exit(1);
+    }
+
+    // create initial object
+    WINDOW() q = (WINDOW()) malloc(sizeof(struct WINDOW(_s)));
+
+    // set internal parameters
+    q->len  = _n;                   // nominal window size
+    q->m    = liquid_msb_index(_n); // effectively floor(log2(len))+1
+    q->n    = 1<<(q->m);            // 2^m
+    q->mask = q->n - 1;             // bit mask
+
+    // number of elements to allocate to memory
+    q->num_allocated = q->n + q->len - 1;
+
+    // allocte memory
+    q->v = (T*) malloc((q->num_allocated)*sizeof(T));
+    q->read_index = 0;
+
+    // clear window
+    WINDOW(_clear)(q);
+
+    // return object
+    return q;
+}
+
+// recreate window buffer object with new length
+//  _q      : old window object
+//  _n      : new window length
+WINDOW() WINDOW(_recreate)(WINDOW() _q, unsigned int _n)
+{
+    // TODO: only create new window if old is too small
+
+    if (_n == _q->len)
+        return _q;
+
+    // create new window
+    WINDOW() w = WINDOW(_create)(_n);
+
+    // copy old values
+    T* r;
+    WINDOW(_read)(_q, &r);
+    //memmove(q->v, ...);
+    unsigned int i;
+    if (_n > _q->len) {
+        // new buffer is larger; push zeros, then old values
+        for (i=0; i<(_n-_q->len); i++)
+            WINDOW(_push)(w, 0);
+        for (i=0; i<_q->len; i++)
+            WINDOW(_push)(w, r[i]);
+    } else {
+        // new buffer is shorter; push latest old values
+        for (i=(_q->len-_n); i<_q->len; i++)
+            WINDOW(_push)(w, r[i]);
+    }
+
+    // destroy old window
+    WINDOW(_destroy)(_q);
+
+    return w;
+}
+
+// destroy window object, freeing all internally memory
+void WINDOW(_destroy)(WINDOW() _q)
+{
+    // free internal memory array
+    free(_q->v);
+
+    // free main object memory
+    free(_q);
+}
+
+// print window object to stdout
+void WINDOW(_print)(WINDOW() _q)
+{
+    printf("window [%u elements] :\n", _q->len);
+    unsigned int i;
+    T * r;
+    WINDOW(_read)(_q, &r);
+    for (i=0; i<_q->len; i++) {
+        printf("%4u", i);
+        BUFFER_PRINT_VALUE(r[i]);
+        printf("\n");
+    }
+}
+
+// print window object to stdout (with extra information)
+void WINDOW(_debug_print)(WINDOW() _q)
+{
+    printf("window [%u elements] :\n", _q->len);
+    unsigned int i;
+    for (i=0; i<_q->len; i++) {
+        // print read index pointer
+        if (i==_q->read_index)
+            printf("<r>");
+
+        // print window value
+        BUFFER_PRINT_LINE(_q,i)
+        printf("\n");
+    }
+    printf("----------------------------------\n");
+
+    // print excess window memory
+    for (i=_q->len; i<_q->num_allocated; i++) {
+        BUFFER_PRINT_LINE(_q,i)
+        printf("\n");
+    }
+}
+
+// clear/reset window object (initialize to zeros)
+void WINDOW(_clear)(WINDOW() _q)
+{
+    // reset read index
+    _q->read_index = 0;
+
+    // clear all allocated memory
+    memset(_q->v, 0, (_q->num_allocated)*sizeof(T));
+}
+
+// read window buffer contents
+//  _q      : window object
+//  _v      : output pointer (set to internal array)
+void WINDOW(_read)(WINDOW() _q, T ** _v)
+{
+    // return pointer to buffer
+    *_v = _q->v + _q->read_index;
+}
+
+// index single element in buffer at a particular index
+//  _q      : window object
+//  _i      : index of element to read
+//  _v      : output value pointer
+void WINDOW(_index)(WINDOW()     _q,
+                    unsigned int _i,
+                    T *          _v)
+{
+    // validate input
+    if (_i >= _q->len) {
+        fprintf(stderr,"error: window_index(), index value out of range\n");
+        exit(1);
+    }
+
+    // return value at index
+    *_v = _q->v[_q->read_index + _i];
+}
+
+// push single element onto window buffer
+//  _q      : window object
+//  _v      : single input element
+void WINDOW(_push)(WINDOW() _q, T _v)
+{
+    // increment index
+    _q->read_index++;
+
+    // wrap around pointer
+    _q->read_index &= _q->mask;
+
+    // if pointer wraps around, copy excess memory
+    if (_q->read_index == 0)
+        memmove(_q->v, _q->v + _q->n, (_q->len-1)*sizeof(T));
+
+    // append value to end of buffer
+    _q->v[_q->read_index + _q->len - 1] = _v;
+}
+
+// write array of elements onto window buffer
+//  _q      : window object
+//  _v      : input array of values to write
+//  _n      : number of input values to write
+void WINDOW(_write)(WINDOW()     _q,
+                    T *          _v,
+                    unsigned int _n)
+{
+    // TODO make this more efficient
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        WINDOW(_push)(_q, _v[i]);
+}
+
diff --git a/src/buffer/tests/cbuffer_autotest.c b/src/buffer/tests/cbuffer_autotest.c
new file mode 100644
index 0000000..73342c3
--- /dev/null
+++ b/src/buffer/tests/cbuffer_autotest.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// circular buffer autotest
+//
+
+#include <stdlib.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// floating point
+void autotest_cbufferf()
+{
+    // input array of values
+    float v[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+    // output test arrays
+    float test1[] = {1, 2, 3, 4};
+    float test2[] = {3, 4, 1, 2, 3, 4, 5, 6, 7, 8};
+    float test3[] = {3, 4, 5, 6, 7, 8};
+    float test4[] = {3, 4, 5, 6, 7, 8, 1, 2, 3};
+    float *r;                   // output read pointer
+    unsigned int num_requested; // number of samples requested
+    unsigned int num_read;      // number of samples read
+
+    // create new circular buffer with 10 elements
+    cbufferf q = cbufferf_create(10);
+    // cbuffer: { <empty> }
+
+    // part 1: write 4 elements to the buffer
+    cbufferf_write(q, v, 4);
+    // cbuffer: {1 2 3 4}
+
+    // part 2: try to read 4 elements
+    num_requested = 4;
+    cbufferf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,4);
+    CONTEND_SAME_DATA(r,test1,4*sizeof(float));
+
+    // part 3: release two elements, write 8 more, read 10
+    cbufferf_release(q, 2);
+    // cbuffer: {3 4}
+    cbufferf_write(q, v, 8);
+    // cbuffer: {3 4 1 2 3 4 5 6 7 8}
+    num_requested = 10;
+    cbufferf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,10);
+    CONTEND_SAME_DATA(r,test2,10*sizeof(float));
+
+    // part 4: pop single element from buffer
+    CONTEND_EQUALITY( cbufferf_size(q), 10    );
+    cbufferf_pop(q, r);
+    // cbuffer: {4 1 2 3 4 5 6 7 8}
+    CONTEND_EQUALITY( cbufferf_size(q),  9    );
+    CONTEND_EQUALITY( *r,                3.0f );
+
+    // part 5: release three elements, and try reading 10
+    cbufferf_release(q, 3);
+    // cbuffer: {3 4 5 6 7 8}
+    num_requested = 10;
+    cbufferf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,6);
+    CONTEND_SAME_DATA(r,test3,6*sizeof(float));
+
+    // part 6: test pushing multiple elements
+    cbufferf_push(q, 1);
+    cbufferf_push(q, 2);
+    cbufferf_push(q, 3);
+    // cbuffer: {3 4 5 6 7 8 1 2 3}
+    num_requested = 10;
+    cbufferf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,9);
+    CONTEND_SAME_DATA(r,test4,9*sizeof(float));
+
+    // part 7: add one more element; buffer should be full
+    CONTEND_EXPRESSION( cbufferf_is_full(q)==0 );
+    cbufferf_push(q, 1);
+    // cbuffer: {3 4 5 6 7 8 1 2 3 1}
+    CONTEND_EXPRESSION( cbufferf_is_full(q)==1 );
+
+    // memory leaks are evil
+    cbufferf_destroy(q);
+}
+
+
+// complex float complexing point
+void autotest_cbuffercf()
+{
+    // input array of values
+    float complex v[] = {1.0 - 1.0 * _Complex_I,
+                         2.0 + 2.0 * _Complex_I,
+                         3.0 - 3.0 * _Complex_I,
+                         4.0 + 4.0 * _Complex_I,
+                         5.0 - 5.0 * _Complex_I,
+                         6.0 + 6.0 * _Complex_I,
+                         7.0 - 7.0 * _Complex_I,
+                         8.0 + 8.0 * _Complex_I};
+
+    // output test arrays
+    float complex test1[] = {1.0 - 1.0 * _Complex_I,
+                             2.0 + 2.0 * _Complex_I,
+                             3.0 - 3.0 * _Complex_I,
+                             4.0 + 4.0 * _Complex_I};
+    float complex test2[] = {3.0 - 3.0 * _Complex_I,
+                             4.0 + 4.0 * _Complex_I,
+                             1.0 - 1.0 * _Complex_I,
+                             2.0 + 2.0 * _Complex_I,
+                             3.0 - 3.0 * _Complex_I,
+                             4.0 + 4.0 * _Complex_I,
+                             5.0 - 5.0 * _Complex_I,
+                             6.0 + 6.0 * _Complex_I,
+                             7.0 - 7.0 * _Complex_I,
+                             8.0 + 8.0 * _Complex_I};
+    float complex test3[] = {3.0 - 3.0 * _Complex_I,
+                             4.0 + 4.0 * _Complex_I,
+                             5.0 - 5.0 * _Complex_I,
+                             6.0 + 6.0 * _Complex_I,
+                             7.0 - 7.0 * _Complex_I,
+                             8.0 + 8.0 * _Complex_I};
+    float complex test4[] = {3.0 - 3.0 * _Complex_I,
+                             4.0 + 4.0 * _Complex_I,
+                             5.0 - 5.0 * _Complex_I,
+                             6.0 + 6.0 * _Complex_I,
+                             7.0 - 7.0 * _Complex_I,
+                             8.0 + 8.0 * _Complex_I,
+                             1.0 - 1.0 * _Complex_I,
+                             2.0 + 2.0 * _Complex_I,
+                             3.0 - 3.0 * _Complex_I};
+    float complex *r;           // output read pointer
+    unsigned int num_requested; // number of samples requested
+    unsigned int num_read;      // number of samples read
+
+    // create new circular buffer with 10 elements
+    cbuffercf q = cbuffercf_create(10);
+    // cbuffer: { <empty> }
+
+    // part 1: write 4 elements to the buffer
+    cbuffercf_write(q, v, 4);
+    // cbuffer: {1 2 3 4}
+
+    // part 2: try to read 4 elements
+    num_requested = 4;
+    cbuffercf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,4);
+    CONTEND_SAME_DATA(r,test1,4*sizeof(float complex));
+
+    // part 3: release two elements, write 8 more, read 10
+    cbuffercf_release(q, 2);
+    // cbuffer: {3 4}
+    cbuffercf_write(q, v, 8);
+    // cbuffer: {3 4 1 2 3 4 5 6 7 8}
+    num_requested = 10;
+    cbuffercf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,10);
+    CONTEND_SAME_DATA(r,test2,10*sizeof(float complex));
+
+    // part 4: pop single element from buffer
+    CONTEND_EQUALITY( cbuffercf_size(q), 10    );
+    cbuffercf_pop(q, r);
+    // cbuffer: {4 1 2 3 4 5 6 7 8}
+    CONTEND_EQUALITY( cbuffercf_size(q),  9    );
+    CONTEND_EQUALITY( crealf(*r),         3.0f );
+    CONTEND_EQUALITY( cimagf(*r),        -3.0f );
+
+    // part 5: release three elements, and try reading 10
+    cbuffercf_release(q, 3);
+    // cbuffer: {3 4 5 6 7 8}
+    num_requested = 10;
+    cbuffercf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,6);
+    CONTEND_SAME_DATA(r,test3,6*sizeof(float complex));
+
+    // part 6: test pushing multiple elements
+    cbuffercf_push(q, 1.0 - 1.0 * _Complex_I);
+    cbuffercf_push(q, 2.0 + 2.0 * _Complex_I);
+    cbuffercf_push(q, 3.0 - 3.0 * _Complex_I);
+    // cbuffer: {3 4 5 6 7 8 1 2 3}
+    num_requested = 10;
+    cbuffercf_read(q, num_requested, &r, &num_read);
+    CONTEND_EQUALITY(num_read,9);
+    CONTEND_SAME_DATA(r,test4,9*sizeof(float complex));
+
+    // part 7: add one more element; buffer should be full
+    CONTEND_EXPRESSION( cbuffercf_is_full(q)==0 );
+    cbuffercf_push(q, 1.0 - 1.0 * _Complex_I);
+    // cbuffer: {3 4 5 6 7 8 1 2 3 1}
+    CONTEND_EXPRESSION( cbuffercf_is_full(q)==1 );
+
+    // memory leaks are evil
+    cbuffercf_destroy(q);
+}
+
+// test general flow
+void autotest_cbufferf_flow()
+{
+    // options
+    unsigned int max_size     =   48; // maximum number of elements in buffer
+    unsigned int max_read     =   17; // maximum number of elements to read
+    unsigned int num_elements = 1200; // total number of elements for run
+
+    // flag to indicate if test was successful
+    int success = 1;
+
+    // temporary buffer to write samples before sending to cbuffer
+    float write_buffer[max_size];
+
+    // create new circular buffer
+    cbufferf q = cbufferf_create_max(max_size, max_read);
+
+    //
+    unsigned i;
+    unsigned write_id = 0;  // running total number of values written
+    unsigned read_id  = 0;  // running total number of values read
+
+    // continue running until
+    while (1) {
+        // write some values
+        unsigned int num_available_to_write = cbufferf_space_available(q);
+
+        // write samples if space is available
+        if (num_available_to_write > 0) {
+            // number of elements to write
+            unsigned int num_to_write = (rand() % num_available_to_write) + 1;
+
+            // generate samples to write
+            for (i=0; i<num_to_write; i++) {
+                write_buffer[i] = (float)(write_id);
+                write_id++;
+            }
+
+            // write samples
+            cbufferf_write(q, write_buffer, num_to_write);
+        }
+
+        // read some values
+        unsigned int num_available_to_read = cbufferf_size(q);
+        
+        // read samples if available
+        if (num_available_to_read > 0) {
+            // number of elements to read
+            unsigned int num_to_read = rand() % num_available_to_read;
+
+            // read samples
+            float *r;               // output read pointer
+            unsigned int num_read;  // number of samples read
+            cbufferf_read(q, num_to_read, &r, &num_read);
+
+            // compare results
+            for (i=0; i<num_read; i++) {
+                if (liquid_autotest_verbose)
+                    printf(" %s read %12.0f, expected %12u\n", r[i] == (float)read_id ? " " : "*", r[i], read_id);
+
+                if (r[i] != (float)read_id)
+                    success = 0;
+                read_id++;
+            }
+
+            // release all the samples that were read
+            cbufferf_release(q, num_read);
+        }
+
+        // stop on fail or upon completion
+        if (!success || read_id >= num_elements)
+            break;
+    }
+    
+    // ensure test was successful
+    CONTEND_EXPRESSION(success == 1);
+
+    // destroy object
+    cbufferf_destroy(q);
+}
+
+
diff --git a/src/buffer/tests/sbuffer_autotest.c b/src/buffer/tests/sbuffer_autotest.c
new file mode 100644
index 0000000..78f4baa
--- /dev/null
+++ b/src/buffer/tests/sbuffer_autotest.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// buffer autotest (static)
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+#define SBUFFER_AUTOTEST_DEFINE_API(X,T)        \
+    T v[] = {1, 2, 3, 4, 5, 6, 7, 8};           \
+    T test1[] = {1, 2, 3, 4};                   \
+    T test2[] = {1, 2, 3, 4, 5, 6, 7, 8};       \
+    T test3[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; \
+    T *r;                                       \
+    unsigned int n;                             \
+                                                \
+    X() cb = X(_create)(STATIC,10);             \
+                                                \
+    X(_write)(cb, v, 4);                        \
+    n = 4;                                      \
+    X(_read)(cb, &r, &n);                       \
+                                                \
+    CONTEND_EQUALITY(n,4);                      \
+    CONTEND_SAME_DATA(r,test1,4*sizeof(T));     \
+                                                \
+    X(_release)(cb, 0);                         \
+    X(_write)(cb, v, 8);                        \
+    n = 8;                                      \
+    X(_read)(cb, &r, &n);                       \
+    CONTEND_EQUALITY(n,8);                      \
+    CONTEND_SAME_DATA(r,test2,8*sizeof(T));     \
+                                                \
+    X(_zero)(cb);                               \
+    n = 10;                                     \
+    X(_read)(cb, &r, &n);                       \
+    CONTEND_EQUALITY(n,10);                     \
+    CONTEND_SAME_DATA(r,test3,10*sizeof(T));    \
+                                                \
+    X(_destroy)(cb);
+
+
+//
+// AUTOTEST: static float buffer
+//
+void autotest_fbuffer_static()
+{
+    SBUFFER_AUTOTEST_DEFINE_API(BUFFER_MANGLE_FLOAT, float)
+}
+
+
+//
+// AUTOTEST: static complex float buffer
+//
+void autotest_cfbuffer_static()
+{
+    SBUFFER_AUTOTEST_DEFINE_API(BUFFER_MANGLE_CFLOAT, float complex)
+}
+
+
+#if 0
+//
+// AUTOTEST: static unsigned int buffer
+//
+void xautotest_uibuffer_static()
+{
+    SBUFFER_AUTOTEST_DEFINE_API(BUFFER_MANGLE_UINT, unsigned int)
+}
+#endif
+
diff --git a/src/buffer/tests/wdelay_autotest.c b/src/buffer/tests/wdelay_autotest.c
new file mode 100644
index 0000000..356ef36
--- /dev/null
+++ b/src/buffer/tests/wdelay_autotest.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: wdelayf
+//
+void autotest_wdelayf()
+{
+    float v;    // reader
+    unsigned int i;
+
+    // create wdelay
+    // wdelay: 0 0 0 0
+    wdelayf w = wdelayf_create(4);
+
+    wdelayf_read(w, &v);
+    CONTEND_EQUALITY(v, 0);
+
+    float x0[10]      = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    float y0_test[10] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6};
+    float y0[10];
+
+    for (i=0; i<10; i++) {
+        wdelayf_read(w, &y0[i]);
+        wdelayf_push(w,  x0[i]);
+        //printf("%3u : %6.2f (%6.2f)\n", i, y0[i], y0_test[i]);
+    }
+    // 7 8 9 10
+    CONTEND_SAME_DATA(y0, y0_test, 10*sizeof(float));
+
+    // re-create wdelay object
+    // wdelay: 0 0 7 8 9 10
+    w = wdelayf_recreate(w,6);
+
+    float x1[10]     = {3, 4, 5, 6, 7, 8, 9, 2, 2, 2};
+    float y1_test[10]= {0, 0, 7, 8, 9, 10,3, 4, 5, 6};
+    float y1[10];
+    for (i=0; i<10; i++) {
+        wdelayf_read(w, &y1[i]);
+        wdelayf_push(w,  x1[i]);
+        //printf("%3u : %6.2f (%6.2f)\n", i, y1[i], y1_test[i]);
+    }
+    // wdelay: 7 8 9 2 2 2
+    CONTEND_SAME_DATA(y1, y1_test, 10*sizeof(float));
+
+    // re-create wdelay object
+    // wdelay: 8 9 2 2 2
+    w = wdelayf_recreate(w,5);
+
+    float x2[10]     = {1, 1, 1, 1, 1, 1, 1, 2, 3, 4};
+    float y2_test[10]= {8, 9, 2, 2, 2, 1, 1, 1, 1, 1};
+    float y2[10];
+
+    for (i=0; i<10; i++) {
+        wdelayf_read(w, &y2[i]);
+        wdelayf_push(w,  x2[i]);
+        //printf("%3u : %6.2f (%6.2f)\n", i, y1[i], y1_test[i]);
+    }
+    // wdelay: 1 1 2 3 4
+    CONTEND_SAME_DATA(y2, y2_test, 10*sizeof(float));
+
+    // destroy object
+    wdelayf_destroy(w);
+}
+
diff --git a/src/buffer/tests/window_autotest.c b/src/buffer/tests/window_autotest.c
new file mode 100644
index 0000000..f0dddaf
--- /dev/null
+++ b/src/buffer/tests/window_autotest.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: windowf
+//
+void autotest_windowf()
+{
+    float v[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+    float *r;   // reader pointer
+    float x;    // temporary value holder
+    unsigned int i;
+
+    float test0[10] = {0,0,0,0,0,0,0,0,0,0};
+    float test1[10] = {0,0,0,0,0,0,1,1,1,1};
+    float test2[10] = {0,0,1,1,1,1,9,8,7,6};
+    float test3[10] = {1,1,9,8,7,6,3,3,3,3};
+    float test4[10] = {7,6,3,3,3,3,5,5,5,5};
+    float test5[6]  = {3,3,5,5,5,5};
+    float test6[6]  = {5,5,5,5,6,7};
+    float test7[10] = {0,0,0,0,5,5,5,5,6,7};
+    float test8[10] = {0,0,0,0,0,0,0,0,0,0};
+
+    // create window
+    // 0 0 0 0 0 0 0 0 0 0
+    windowf w = windowf_create(10);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test0,10*sizeof(float));
+
+    // push 4 elements
+    // 0 0 0 0 0 0 1 1 1 1
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+    windowf_push(w, 1);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test1,10*sizeof(float));
+
+    // push 4 more elements
+    // 0 0 1 1 1 1 9 8 7 6
+    windowf_write(w, v, 4);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test2,10*sizeof(float));
+
+    // push 4 more elements
+    // 1 1 9 8 7 6 3 3 3 3
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+    windowf_push(w, 3);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test3,10*sizeof(float));
+
+    // test indexing operation
+    windowf_index(w, 0, &x);    CONTEND_EQUALITY(x, 1);
+    windowf_index(w, 1, &x);    CONTEND_EQUALITY(x, 1);
+    windowf_index(w, 2, &x);    CONTEND_EQUALITY(x, 9);
+    windowf_index(w, 3, &x);    CONTEND_EQUALITY(x, 8);
+    windowf_index(w, 4, &x);    CONTEND_EQUALITY(x, 7);
+    windowf_index(w, 5, &x);    CONTEND_EQUALITY(x, 6);
+    windowf_index(w, 6, &x);    CONTEND_EQUALITY(x, 3);
+    windowf_index(w, 7, &x);    CONTEND_EQUALITY(x, 3);
+    windowf_index(w, 8, &x);    CONTEND_EQUALITY(x, 3);
+    windowf_index(w, 9, &x);    CONTEND_EQUALITY(x, 3);
+
+    // push 4 more elements
+    // 7 6 3 3 3 3 5 5 5 5
+    windowf_push(w, 5);
+    windowf_push(w, 5);
+    windowf_push(w, 5);
+    windowf_push(w, 5);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test4,10*sizeof(float));
+    if (liquid_autotest_verbose)
+        windowf_debug_print(w);
+
+    // recreate window (truncate to last 6 elements)
+    // 3 3 5 5 5 5
+    w = windowf_recreate(w,6);
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test5,6*sizeof(float));
+
+    // push 2 more elements
+    // 5 5 5 5 6 7
+    windowf_push(w, 6);
+    windowf_push(w, 7);
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test6,6*sizeof(float));
+
+    // recreate window (extend to 10 elements)
+    // 0 0 0 0 5 5 5 5 6 7
+    w = windowf_recreate(w,10);
+    windowf_read(w,&r);
+    CONTEND_SAME_DATA(r,test7,10*sizeof(float));
+
+    // clear
+    // 0 0 0 0 0 0 0 0 0 0
+    windowf_clear(w);
+
+    windowf_read(w, &r);
+    CONTEND_SAME_DATA(r,test8,10*sizeof(float));
+
+    if (liquid_autotest_verbose) {
+        // manual print
+        printf("manual output:\n");
+        for (i=0; i<10; i++)
+            printf("%6u : %f\n", i, r[i]);
+
+        windowf_debug_print(w);
+    }
+
+    windowf_destroy(w);
+
+    printf("done.\n");
+}
+
diff --git a/src/buffer/window.readme.txt b/src/buffer/window.readme.txt
new file mode 100644
index 0000000..5e21ab4
--- /dev/null
+++ b/src/buffer/window.readme.txt
@@ -0,0 +1,45 @@
+##
+## Window methods
+##
+
+window window_create(unsigned int _n)
+    Creates a window (circular/ring buffer) with _n accessable
+    elements.  By default the window is filled with zeros.
+
+window window_recreate(window _w, unsigned int _n)
+    Re-creates a window from a previously instantiated one with a new
+    length _n.  If the length of the window increases, the old values
+    are preserved and zeros are pushed into the beginning (oldest).
+    If the length of the window decreases, the oldest values are
+    truncated.  Similar to the standard C realloc() function.
+
+void window_destroy(window _w)
+    Destroys a window object (de-allocates memory).
+
+void window_print(window _w)
+    Prints elements in a window object to the file stdout in a
+    well-formatted manner.
+
+void window_debug_print(window _w)
+    Prints all internal elements in a window object to the file stdout
+    in a well-formatted manner.
+
+void window_clear(window _w)
+    Clears all elements in a window object by setting them to zero.
+
+void window_read(window _w, T ** _v)
+    Returns a pointer to the beginning of the buffer.
+    NOTE: the intention is to read values from the window buffer and
+    not to change them.  The result of changing values in the returned
+    array pointer _v is undefined due to the internal mechanism the
+    window methods use for efficient bookkeeping.
+
+void window_push(window _w, T _v)
+    Push a single value to the end of the window.  Oldest value is
+    removed from the buffer and all values are logically shifted left
+    by one index.
+
+void window_write(window _w, T * _v, unsigned int _n)
+    Writes _n elements in the array _v to the window object.  This is
+    equivalent to iterating window_push() _n times over the array.
+
diff --git a/src/channel/src/channel.c b/src/channel/src/channel.c
new file mode 100644
index 0000000..8dc602b
--- /dev/null
+++ b/src/channel/src/channel.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic channel
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+// portable structured channel object
+struct CHANNEL(_s) {
+    // sample rate
+    int             enabled_resamp;     // resampler enabled?
+    unsigned int    resamp_m;           // resampling filter semi-length
+    float           resamp_rate;        // resampling rate
+    TO              resamp_buf[8];      // resampling buffer
+    RESAMP()        resamp;             // resampling filter
+
+    // additive white Gauss noise
+    int             enabled_awgn;       // AWGN enabled?
+    T               gamma;              // channel gain
+    T               nstd;               // noise standard deviation
+    float           noise_floor_dB;     // noise floor
+    float           SNRdB;              // signal-to-noise ratio [dB]
+
+    // carrier offset
+    int             enabled_carrier;    // carrier offset enabled?
+    float           dphi;               // channel gain
+    float           phi;                // noise standard deviation
+    NCO()           nco;                // oscillator
+
+    // multi-path channel
+    int             enabled_multipath;  // enable multi-path channel filter?
+    FIRFILT()       channel_filter;     // multi-path channel filter object
+    TC *            h;                  // multi-path channel filter coefficients
+    unsigned int    h_len;              // multi-path channel filter length
+
+    // shadowing channel
+    int             enabled_shadowing;  // enable shadowing?
+    IIRFILT()       shadowing_filter;   // shadowing filter object
+    float           shadowing_std;      // shadowing standard deviation
+    float           shadowing_fd;       // shadowing Doppler frequency
+};
+
+// create structured channel object with default parameters
+CHANNEL() CHANNEL(_create)(void)
+{
+    // return object
+    return CHANNEL(_create_delay)(7);
+}
+
+// create structured channel object with a particular resampling filter delay
+CHANNEL() CHANNEL(_create_delay)(unsigned int _m)
+{
+    // validate input
+    if (_m < 2 || _m > 30) {
+        fprintf(stderr,"error: channel_%s_create_delay(m), delay out of range\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    CHANNEL() q = (CHANNEL()) malloc(sizeof(struct CHANNEL(_s)));
+
+    // initialize all options as off
+    q->enabled_resamp    = 0;
+    q->enabled_awgn      = 0;
+    q->enabled_carrier   = 0;
+    q->enabled_multipath = 0;
+    q->enabled_shadowing = 0;
+
+    // create internal objects
+    q->resamp_rate      = 1.0f;
+    q->resamp_m         = _m;
+    q->resamp           = RESAMP(_create)(q->resamp_rate, q->resamp_m, 0.45f, 50.0f, 64);
+    q->nco              = NCO(_create)(LIQUID_VCO);
+    q->h_len            = 1;
+    q->h                = (TC*) malloc(q->h_len*sizeof(TC));
+    q->h[0]             = 1.0f;
+    q->channel_filter   = FIRFILT(_create)(q->h, q->h_len);
+    q->shadowing_filter = NULL;
+
+    // return object
+    return q;
+}
+
+// destroy channel object
+void CHANNEL(_destroy)(CHANNEL() _q)
+{
+    // destroy internal objects
+    NCO(_destroy)(_q->nco);
+    FIRFILT(_destroy)(_q->channel_filter);
+    if (_q->shadowing_filter != NULL)
+        IIRFILT(_destroy)(_q->shadowing_filter);
+    free(_q->h);
+
+    // free main object memory
+    free(_q);
+}
+
+// print channel object
+void CHANNEL(_print)(CHANNEL() _q)
+{
+    printf("channel\n");
+    if (_q->enabled_resamp)     printf("  resamp:    m=%u, rate=%.6f\n", _q->resamp_m, _q->resamp_rate);
+    if (_q->enabled_awgn)       printf("  AWGN:      SNR=%.3f dB, gamma=%.3f, std=%.6f\n", _q->SNRdB, _q->gamma, _q->nstd);
+    if (_q->enabled_carrier)    printf("  carrier:   dphi=%.3f, phi=%.3f\n", _q->dphi, _q->phi);
+    if (_q->enabled_multipath)  printf("  multipath: h_len=%u\n", _q->h_len);
+    if (_q->enabled_shadowing)  printf("  shadowing: std=%.3fdB, fd=%.3f\n", _q->shadowing_std, _q->shadowing_fd);
+}
+
+// apply additive white Gausss noise impairment
+//  _q              : channel object
+//  _noise_floor_dB : noise floor power spectral density
+//  _SNR_dB         : signal-to-noise ratio [dB]
+void CHANNEL(_add_awgn)(CHANNEL() _q,
+                        float     _noise_floor_dB,
+                        float     _SNRdB)
+{
+    // enable module
+    _q->enabled_awgn = 1;
+
+    //
+    _q->noise_floor_dB = _noise_floor_dB;
+    _q->SNRdB          = _SNRdB;
+
+    // set values appropriately
+    _q->nstd  = powf(10.0f, _noise_floor_dB/20.0f);
+    _q->gamma = powf(10.0f, (_q->SNRdB+_q->noise_floor_dB)/20.0f);
+}
+
+// apply additive white Gausss noise impairment
+//  _q              : channel object
+//  _delay          : resampling delay
+//  _rate           : resampling rate
+void CHANNEL(_add_resamp)(CHANNEL() _q,
+                          float     _delay,
+                          float     _rate)
+{
+    if (_delay < -0.5f || _delay > 0.5f) {
+        fprintf(stderr,"warning: channel_%s_add_resamp(), delay must be in [-0.5,0.5]; ignoring\n", EXTENSION_FULL);
+        return;
+    } else if (_rate < 0.95f || _rate > 1.05f) {
+        fprintf(stderr,"warning: channel_%s_add_resamp(), rate must be in [0.95,1.05]; ignoring\n", EXTENSION_FULL);
+        return;
+    }
+
+    // enable module
+    _q->enabled_resamp = 1;
+
+    // set parameters
+    _q->resamp_rate = _rate;
+
+    // TODO: set delay appropriately
+    
+    // set resampling rate
+    RESAMP(_set_rate)(_q->resamp, _q->resamp_rate);
+}
+
+// apply carrier offset impairment
+//  _q          : channel object
+//  _frequency  : carrier frequency offse [radians/sample
+//  _phase      : carrier phase offset    [radians]
+void CHANNEL(_add_carrier_offset)(CHANNEL() _q,
+                                  float     _frequency,
+                                  float     _phase)
+{
+    // enable module
+    _q->enabled_carrier = 1;
+
+    // carrier frequency/phase offsets
+    _q->dphi = _frequency;
+    _q->phi  = _phase;
+
+    // set values appropriately
+    NCO(_set_frequency)(_q->nco, _q->dphi);
+    NCO(_set_phase)    (_q->nco, _q->phi);
+}
+
+// apply multi-path channel impairment
+//  _q          : channel object
+//  _h          : channel coefficients (NULL for random)
+//  _h_len      : number of channel coefficients
+void CHANNEL(_add_multipath)(CHANNEL()    _q,
+                             TC *         _h,
+                             unsigned int _h_len)
+{
+    if (_h_len == 0) {
+        fprintf(stderr,"warning: channel_%s_add_multipath(), filter length is zero (ignoring)\n", EXTENSION_FULL);
+        return;
+    } else if (_h_len > 1000) {
+        fprintf(stderr,"warning: channel_%s_add_multipath(), filter length exceeds maximum\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // enable module
+    _q->enabled_multipath = 1;
+
+    // set values appropriately
+    // TODO: test for types other than float complex
+    if (_q->h_len != _h_len)
+        _q->h = (TC*) realloc(_q->h, _h_len*sizeof(TC));
+
+    // update length
+    _q->h_len = _h_len;
+    
+    // copy coefficients internally
+    if (_h == NULL) {
+        // generate random coefficients using m-sequence generator
+        // TODO: support types other than float
+        _q->h[0] = 1.0f;
+        unsigned int i;
+        msequence ms = msequence_create_default(14);
+        for (i=1; i<_q->h_len; i++) {
+            float vi = msequence_generate_symbol(ms, 8) / 256.0f - 0.5f;
+            float vq = msequence_generate_symbol(ms, 8) / 256.0f - 0.5f;
+            _q->h[i] = (vi + _Complex_I*vq) * 0.5f;
+        }
+        msequence_destroy(ms);
+    } else {
+        // copy coefficients internally
+        memmove(_q->h, _h, _q->h_len*sizeof(TC));
+    }
+
+    // re-create channel filter
+    _q->channel_filter = FIRFILT(_recreate)(_q->channel_filter, _q->h, _q->h_len);
+}
+
+// apply slowly-varying shadowing impairment
+//  _q          : channel object
+//  _sigma      : std. deviation for log-normal shadowing
+//  _fd         : Doppler frequency, _fd in (0,0.5)
+void CHANNEL(_add_shadowing)(CHANNEL() _q,
+                             float     _sigma,
+                             float     _fd)
+{
+    if (_q->enabled_shadowing) {
+        fprintf(stderr,"warning: channel_%s_add_shadowing(), shadowing already enabled\n", EXTENSION_FULL);
+        return;
+    } else if (_sigma <= 0) {
+        fprintf(stderr,"warning: channel_%s_add_shadowing(), standard deviation less than or equal to zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fd <= 0 || _fd >= 0.5) {
+        fprintf(stderr,"warning: channel_%s_add_shadowing(), Doppler frequency must be in (0,0.5)\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // enable module
+    _q->enabled_shadowing = 1;
+
+    // TODO: set values appropriately
+    _q->shadowing_std = _sigma;
+    _q->shadowing_fd  = _fd;
+
+    // re-create channel filter
+    // TODO: adjust gain
+    //_q->shadowing_filter = IIRFILT(_create_lowpass)(11, _q->shadowing_fd);
+    float alpha = _q->shadowing_fd;
+    float a[2] = {1.0f, alpha-1.0f};
+    float b[2] = {alpha, 0};
+    _q->shadowing_filter = IIRFILT(_create)(b,2,a,2);
+}
+
+// get nominal channel delay [samples]
+unsigned int CHANNEL(_get_delay)(CHANNEL() _q)
+{
+    return 2*RESAMP(_get_delay)(_q->resamp) + 1;
+}
+
+// apply channel impairments on input array
+//  _q      : channel object
+//  _x      : input array [size: _nx x 1]
+//  _nx     : input array length
+//  _y      : output array
+//  _ny     : output array length
+void CHANNEL(_execute)(CHANNEL()      _q,
+                       TI *           _x,
+                       unsigned int   _nx,
+                       TO *           _y,
+                       unsigned int * _ny)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int num_resamp;    // resampler output length
+    unsigned int n=0;           // number of output samples
+
+    // apply channel effects on each input sample
+    for (i=0; i<_nx; i++) {
+
+        // apply resampler (always push through resampling filter)
+        RESAMP(_execute)(_q->resamp, _x[i], _q->resamp_buf, &num_resamp);
+
+        // run resulting resampled result through remaining channel objects
+        for (j=0; j<num_resamp; j++) {
+            // apply filter
+            if (_q->enabled_multipath) {
+                FIRFILT(_push)(   _q->channel_filter,  _q->resamp_buf[j]);
+                FIRFILT(_execute)(_q->channel_filter, &_y[n]);
+            } else
+                _y[n] = _q->resamp_buf[j];
+
+            // apply shadowing if enabled
+            if (_q->enabled_shadowing) {
+                // TODO: use type-specific value other than float
+                float g = 0;
+                IIRFILT(_execute)(_q->shadowing_filter, randnf()*_q->shadowing_std, &g);
+                g /= _q->shadowing_fd * 6.9f;
+                g = powf(10.0f, g/20.0f);
+                _y[n] *= g;
+            }
+
+            // apply carrier if enabled
+            if (_q->enabled_carrier) {
+                NCO(_mix_up)(_q->nco, _y[n], &_y[n]);
+                NCO(_step)  (_q->nco);
+            }
+
+            // apply AWGN if enabled
+            if (_q->enabled_awgn) {
+                _y[n] *= _q->gamma;
+                _y[n] += _q->nstd * ( randnf() + _Complex_I*randnf() ) * M_SQRT1_2;
+            }
+
+            // increment output sample counter
+            n++;
+        }
+    }
+
+    // set output sample length
+    if (_ny != NULL)
+        *_ny = n;
+}
+
diff --git a/src/channel/src/channel_cccf.c b/src/channel/src/channel_cccf.c
new file mode 100644
index 0000000..69888ba
--- /dev/null
+++ b/src/channel/src/channel_cccf.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Complex floating-point channel
+//
+
+#include <complex.h>
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT "f"
+#define EXTENSION_FULL  "cccf"
+
+#define CHANNEL(name)   LIQUID_CONCAT(channel_cccf,name)
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_cccf,name)
+#define FIRFILT(name)   LIQUID_CONCAT(firfilt_cccf,name)
+#define IIRFILT(name)   LIQUID_CONCAT(iirfilt_rrrf,name)
+#define NCO(name)       LIQUID_CONCAT(nco_crcf,name)
+#define RESAMP(name)    LIQUID_CONCAT(resamp_crcf,name)
+#define TVMPCH(name)    LIQUID_CONCAT(tvmpch_cccf,name)
+#define WINDOW(name)    LIQUID_CONCAT(windowcf,name)
+
+#define TO              float complex   // output type
+#define TC              float complex   // coefficients type
+#define TI              float complex   // input type
+#define T               float           // primitive type
+
+#define PRINTVAL_TC(X,F) PRINTVAL_CFLOAT(X,F)
+
+#include "channel.c"
+#include "tvmpch.c"
+
diff --git a/src/channel/src/tvmpch.c b/src/channel/src/tvmpch.c
new file mode 100644
index 0000000..d14d9ca
--- /dev/null
+++ b/src/channel/src/tvmpch.c
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// tvmpch : finite impulse response (FIR) filter
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// tvmpch object structure
+struct TVMPCH(_s) {
+    TC * h;             // filter coefficients array [size; h_len x 1]
+    unsigned int h_len; // filter length
+
+    // use window object for internal buffer
+    WINDOW() w;
+
+    //
+    float std;
+    float alpha;
+    float beta;
+};
+
+// create time-varying multi-path channel emulator object
+//  _n      :   number of coefficients, _n > 0
+//  _std    :   standard deviation
+//  _tau    :   coherence time
+TVMPCH() TVMPCH(_create)(unsigned int _n,
+                         float        _std,
+                         float        _tau)
+{
+    // validate input
+    if (_n < 1) {
+        fprintf(stderr,"error: tvmpch_%s_create(), filter length must be greater than one\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_std < 0.0f) {
+        fprintf(stderr,"error: tvmpch_%s_create(), standard deviation must be positive\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_tau < 0.0f || _tau > 1.0f) {
+        fprintf(stderr,"error: tvmpch_%s_create(), coherence time must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create filter object and initialize
+    TVMPCH() q = (TVMPCH()) malloc(sizeof(struct TVMPCH(_s)));
+    q->h_len = _n;
+    q->h     = (TC *) malloc((q->h_len)*sizeof(TC));
+    q->beta  = _tau;
+    q->std   = 2.0f * _std / sqrtf(q->beta);
+    q->alpha = 1.0f - q->beta;
+
+    // time-reverse coefficients
+    unsigned int i;
+    q->h[q->h_len-1] = 1.0f;
+    for (i=0; i<q->h_len-1; i++)
+        q->h[i] = 0.0f;
+
+    // create window (internal buffer)
+    q->w = WINDOW(_create)(q->h_len);
+
+    // reset filter state (clear buffer)
+    TVMPCH(_reset)(q);
+
+    //
+    return q;
+}
+
+// destroy tvmpch object
+void TVMPCH(_destroy)(TVMPCH() _q)
+{
+    WINDOW(_destroy)(_q->w);
+    free(_q->h);
+    free(_q);
+}
+
+// reset internal state of filter object
+void TVMPCH(_reset)(TVMPCH() _q)
+{
+    WINDOW(_clear)(_q->w);
+}
+
+// print filter object internals (taps, buffer)
+void TVMPCH(_print)(TVMPCH() _q)
+{
+    printf("tvmpch_%s:\n", EXTENSION_FULL);
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++) {
+        printf("  h(%3u) = ", i+1);
+        PRINTVAL_TC(_q->h[n-i-1],%12.8f);
+        printf(";\n");
+    }
+    //WINDOW(_print)(_q->w);
+}
+
+// push sample into filter object's internal buffer
+//  _q      :   filter object
+//  _x      :   input sample
+void TVMPCH(_push)(TVMPCH() _q,
+                   TI       _x)
+{
+    // update coefficients
+    unsigned int i;
+    for (i=0; i<_q->h_len-1; i++)
+        _q->h[i] = _q->alpha*_q->h[i] + _q->beta*(randnf() + _Complex_I*randnf()) * _q->std * M_SQRT1_2;
+
+    // push sample into window buffer
+    WINDOW(_push)(_q->w, _x);
+}
+
+// compute output sample (dot product between internal
+// filter coefficients and internal buffer)
+//  _q      :   filter object
+//  _y      :   output sample pointer
+void TVMPCH(_execute)(TVMPCH() _q,
+                      TO *     _y)
+{
+    // read buffer (retrieve pointer to aligned memory array)
+    TI *r;
+    WINDOW(_read)(_q->w, &r);
+
+    // execute dot product
+    DOTPROD(_run4)(r, _q->h, _q->h_len, _y);
+}
+
+// execute the filter on a block of input samples; the
+// input and output buffers may be the same
+//  _q      : filter object
+//  _x      : pointer to input array [size: _n x 1]
+//  _n      : number of input, output samples
+//  _y      : pointer to output array [size: _n x 1]
+void TVMPCH(_execute_block)(TVMPCH()     _q,
+                            TI *         _x,
+                            unsigned int _n,
+                            TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // push sample into filter
+        TVMPCH(_push)(_q, _x[i]);
+
+        // compute output sample
+        TVMPCH(_execute)(_q, &_y[i]);
+    }
+}
+
+#if 0
+// get filter length
+unsigned int TVMPCH(_get_length)(TVMPCH() _q)
+{
+    return _q->h_len;
+}
+
+// compute complex frequency response
+//  _q      :   filter object
+//  _fc     :   frequency
+//  _H      :   output frequency response
+void TVMPCH(_freqresponse)(TVMPCH()        _q,
+                           float           _fc,
+                           float complex * _H)
+{
+    unsigned int i;
+    float complex H = 0.0f;
+
+    // compute dot product between coefficients and exp{ 2 pi fc {0..n-1} }
+    for (i=0; i<_q->h_len; i++)
+        H += _q->h[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+
+    // apply scaling
+    H *= _q->scale;
+
+    // set return value
+    *_H = H;
+}
+
+
+// compute group delay in samples
+//  _q      :   filter object
+//  _fc     :   frequency
+float TVMPCH(_groupdelay)(TVMPCH() _q,
+                          float    _fc)
+{
+    // copy coefficients to be in correct order
+    float h[_q->h_len];
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++)
+        h[i] = crealf(_q->h[n-i-1]);
+
+    return fir_group_delay(h, n, _fc);
+}
+#endif
diff --git a/src/dotprod/bench/dotprod_cccf_benchmark.c b/src/dotprod/bench/dotprod_cccf_benchmark.c
new file mode 100644
index 0000000..54e327f
--- /dev/null
+++ b/src/dotprod/bench/dotprod_cccf_benchmark.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void dotprod_cccf_bench(struct rusage *_start,
+                        struct rusage *_finish,
+                        unsigned long int *_num_iterations,
+                        unsigned int _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 100;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float complex x[_n];
+    float complex h[_n];
+    float complex y[8];
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        x[i] = randnf() + _Complex_I*randnf();
+        h[i] = randnf() + _Complex_I*randnf();
+    }
+
+    // create dotprod structure;
+    dotprod_cccf dp = dotprod_cccf_create(h,_n);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        dotprod_cccf_execute(dp, x, &y[0]);
+        dotprod_cccf_execute(dp, x, &y[1]);
+        dotprod_cccf_execute(dp, x, &y[2]);
+        dotprod_cccf_execute(dp, x, &y[3]);
+        dotprod_cccf_execute(dp, x, &y[4]);
+        dotprod_cccf_execute(dp, x, &y[5]);
+        dotprod_cccf_execute(dp, x, &y[6]);
+        dotprod_cccf_execute(dp, x, &y[7]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 8;
+
+    // clean up objects
+    dotprod_cccf_destroy(dp);
+}
+
+#define DOTPROD_CCCF_BENCHMARK_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ dotprod_cccf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_dotprod_cccf_4      DOTPROD_CCCF_BENCHMARK_API(4)
+void benchmark_dotprod_cccf_16     DOTPROD_CCCF_BENCHMARK_API(16)
+void benchmark_dotprod_cccf_64     DOTPROD_CCCF_BENCHMARK_API(64)
+void benchmark_dotprod_cccf_256    DOTPROD_CCCF_BENCHMARK_API(256)
+
diff --git a/src/dotprod/bench/dotprod_crcf_benchmark.c b/src/dotprod/bench/dotprod_crcf_benchmark.c
new file mode 100644
index 0000000..2734246
--- /dev/null
+++ b/src/dotprod/bench/dotprod_crcf_benchmark.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void dotprod_crcf_bench(struct rusage *_start,
+                        struct rusage *_finish,
+                        unsigned long int *_num_iterations,
+                        unsigned int _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 100;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float complex x[_n];
+    float h[_n];
+    float complex y[8];
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        x[i] = randnf() + _Complex_I*randnf();
+        h[i] = randnf();
+    }
+
+    // create dotprod structure;
+    dotprod_crcf dp = dotprod_crcf_create(h,_n);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        dotprod_crcf_execute(dp, x, &y[0]);
+        dotprod_crcf_execute(dp, x, &y[1]);
+        dotprod_crcf_execute(dp, x, &y[2]);
+        dotprod_crcf_execute(dp, x, &y[3]);
+        dotprod_crcf_execute(dp, x, &y[4]);
+        dotprod_crcf_execute(dp, x, &y[5]);
+        dotprod_crcf_execute(dp, x, &y[6]);
+        dotprod_crcf_execute(dp, x, &y[7]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 8;
+
+    // clean up objects
+    dotprod_crcf_destroy(dp);
+}
+
+#define DOTPROD_CRCF_BENCHMARK_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ dotprod_crcf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_dotprod_crcf_4      DOTPROD_CRCF_BENCHMARK_API(4)
+void benchmark_dotprod_crcf_16     DOTPROD_CRCF_BENCHMARK_API(16)
+void benchmark_dotprod_crcf_64     DOTPROD_CRCF_BENCHMARK_API(64)
+void benchmark_dotprod_crcf_256    DOTPROD_CRCF_BENCHMARK_API(256)
+
diff --git a/src/dotprod/bench/dotprod_rrrf_benchmark.c b/src/dotprod/bench/dotprod_rrrf_benchmark.c
new file mode 100644
index 0000000..b2724af
--- /dev/null
+++ b/src/dotprod/bench/dotprod_rrrf_benchmark.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void dotprod_rrrf_bench(struct rusage *_start,
+                        struct rusage *_finish,
+                        unsigned long int *_num_iterations,
+                        unsigned int _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 128;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float x[_n], h[_n], y;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        x[i] = 1.0f;
+        h[i] = 1.0f;
+    }
+
+    // create dotprod structure;
+    dotprod_rrrf dp = dotprod_rrrf_create(h,_n);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        dotprod_rrrf_execute(dp,x,&y);
+        dotprod_rrrf_execute(dp,x,&y);
+        dotprod_rrrf_execute(dp,x,&y);
+        dotprod_rrrf_execute(dp,x,&y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up objects
+    dotprod_rrrf_destroy(dp);
+}
+
+#define DOTPROD_RRRF_BENCHMARK_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ dotprod_rrrf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_dotprod_rrrf_4       DOTPROD_RRRF_BENCHMARK_API(4)
+void benchmark_dotprod_rrrf_16      DOTPROD_RRRF_BENCHMARK_API(16)
+void benchmark_dotprod_rrrf_64      DOTPROD_RRRF_BENCHMARK_API(64)
+void benchmark_dotprod_rrrf_256     DOTPROD_RRRF_BENCHMARK_API(256)
+
diff --git a/src/dotprod/bench/sumsqcf_benchmark.c b/src/dotprod/bench/sumsqcf_benchmark.c
new file mode 100644
index 0000000..e2cb98d
--- /dev/null
+++ b/src/dotprod/bench/sumsqcf_benchmark.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void sumsqcf_bench(struct rusage *     _start,
+                   struct rusage *     _finish,
+                   unsigned long int * _num_iterations,
+                   unsigned int        _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 128;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float complex x[_n];
+    float complex y = 0.0f;
+    unsigned int long i;
+    for (i=0; i<_n; i++)
+        x[i] = 0.2f + 0.2f*_Complex_I;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        y += liquid_sumsqcf(x, _n);
+        y -= liquid_sumsqcf(x, _n);
+        y += liquid_sumsqcf(x, _n);
+        y -= liquid_sumsqcf(x, _n);
+
+        // change input
+        x[i%_n] = y;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define SUMSQCF_BENCHMARK_API(N)        \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ sumsqcf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_sumsqcf_4        SUMSQCF_BENCHMARK_API(4)
+void benchmark_sumsqcf_16       SUMSQCF_BENCHMARK_API(16)
+void benchmark_sumsqcf_64       SUMSQCF_BENCHMARK_API(64)
+void benchmark_sumsqcf_256      SUMSQCF_BENCHMARK_API(256)
+
diff --git a/src/dotprod/bench/sumsqf_benchmark.c b/src/dotprod/bench/sumsqf_benchmark.c
new file mode 100644
index 0000000..4cfab6c
--- /dev/null
+++ b/src/dotprod/bench/sumsqf_benchmark.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void sumsqf_bench(struct rusage *     _start,
+                  struct rusage *     _finish,
+                  unsigned long int * _num_iterations,
+                  unsigned int        _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 128;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float x[_n];
+    float y = 0.0f;
+    unsigned int long i;
+    for (i=0; i<_n; i++)
+        x[i] = 1.0f;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        y += liquid_sumsqf(x, _n);
+        y -= liquid_sumsqf(x, _n);
+        y += liquid_sumsqf(x, _n);
+        y -= liquid_sumsqf(x, _n);
+        
+        // change input
+        x[i%_n] = y;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define SUMSQF_BENCHMARK_API(N)         \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ sumsqf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_sumsqf_4         SUMSQF_BENCHMARK_API(4)
+void benchmark_sumsqf_16        SUMSQF_BENCHMARK_API(16)
+void benchmark_sumsqf_64        SUMSQF_BENCHMARK_API(64)
+void benchmark_sumsqf_256       SUMSQF_BENCHMARK_API(256)
+
diff --git a/src/dotprod/src/dotprod.c b/src/dotprod/src/dotprod.c
new file mode 100644
index 0000000..bd078f1
--- /dev/null
+++ b/src/dotprod/src/dotprod.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic dot product
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+// portable structured dot product object
+struct DOTPROD(_s) {
+    TC * h;             // coefficients array
+    unsigned int n;     // length
+};
+
+// basic dot product
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void DOTPROD(_run)(TC *         _h,
+                   TI *         _x,
+                   unsigned int _n,
+                   TO *         _y)
+{
+    // initialize accumulator
+    TO r=0;
+
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    // return result
+    *_y = r;
+}
+
+// basic dotproduct, unrolling loop
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void DOTPROD(_run4)(TC *         _h,
+                    TI *         _x,
+                    unsigned int _n,
+                    TO *         _y)
+{
+    // initialize accumulator
+    TO r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    // return result
+    *_y = r;
+}
+
+//
+// structured dot product
+//
+
+// create structured dot product object
+//  _h      :   coefficients array [size: 1 x _n]
+//  _n      :   dot product length
+DOTPROD() DOTPROD(_create)(TC *         _h,
+                           unsigned int _n)
+{
+    DOTPROD() q = (DOTPROD()) malloc(sizeof(struct DOTPROD(_s)));
+    q->n = _n;
+
+    // allocate memory for coefficients
+    q->h = (TC*) malloc((q->n)*sizeof(TC));
+
+    // move coefficients
+    memmove(q->h, _h, (q->n)*sizeof(TC));
+
+    // return object
+    return q;
+}
+
+// re-create dot product object
+//  _q      :   old dot dot product object
+//  _h      :   new coefficients [size: 1 x _n]
+//  _n      :   new dot product size
+DOTPROD() DOTPROD(_recreate)(DOTPROD()    _q,
+                             TC *         _h,
+                             unsigned int _n)
+{
+    // check to see if length has changed
+    if (_q->n != _n) {
+        // set new length
+        _q->n = _n;
+
+        // re-allocate memory
+        _q->h = (TC*) realloc(_q->h, (_q->n)*sizeof(TC));
+    }
+
+    // move new coefficients
+    memmove(_q->h, _h, (_q->n)*sizeof(TC));
+
+    // return re-structured object
+    return _q;
+}
+
+// destroy dot product object
+void DOTPROD(_destroy)(DOTPROD() _q)
+{
+    free(_q->h);    // free coefficients memory
+    free(_q);       // free main object memory
+}
+
+// print dot product object
+void DOTPROD(_print)(DOTPROD() _q)
+{
+    printf("dotprod [portable, %u coefficients]:\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++) {
+        printf("  %4u: %12.8f + j*%12.8f\n", i,
+                                             crealf(_q->h[i]),
+                                             cimagf(_q->h[i]));
+    }
+}
+
+// execute structured dot product
+//  _q      :   dot product object
+//  _x      :   input array [size: 1 x _n]
+//  _y      :   output dot product
+void DOTPROD(_execute)(DOTPROD() _q,
+                       TI *      _x,
+                       TO *      _y)
+{
+    // run basic dot product with unrolled loops
+    DOTPROD(_run4)(_q->h, _x, _q->n, _y);
+}
+
diff --git a/src/dotprod/src/dotprod_cccf.c b/src/dotprod/src/dotprod_cccf.c
new file mode 100644
index 0000000..e981460
--- /dev/null
+++ b/src/dotprod/src/dotprod_cccf.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Complex floating-point dot product
+//
+
+#include <complex.h>
+#include "liquid.internal.h"
+
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_cccf,name)
+#define TO              float complex
+#define TC              float complex
+#define TI              float complex
+
+#include "dotprod.c"
diff --git a/src/dotprod/src/dotprod_cccf.mmx.c b/src/dotprod/src/dotprod_cccf.mmx.c
new file mode 100644
index 0000000..dc389d7
--- /dev/null
+++ b/src/dotprod/src/dotprod_cccf.mmx.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (MMX)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for x86 platforms
+// NOTE: these pre-processor macros are defined in config.h
+
+#if HAVE_MMINTRIN_H
+#include <mmintrin.h>   // MMX
+#endif
+
+#if HAVE_XMMINTRIN_H
+#include <xmmintrin.h>  // SSE
+#endif
+
+#if HAVE_EMMINTRIN_H
+#include <emmintrin.h>  // SSE2
+#endif
+
+#if HAVE_PMMINTRIN_H
+#include <pmmintrin.h>  // SSE3
+#endif
+
+#define DEBUG_DOTPROD_CCCF_MMX   0
+
+// forward declaration of internal methods
+void dotprod_cccf_execute_mmx(dotprod_cccf    _q,
+                              float complex * _x,
+                              float complex * _y);
+
+void dotprod_cccf_execute_mmx4(dotprod_cccf    _q,
+                               float complex * _x,
+                               float complex * _y);
+
+// basic dot product (ordinal calculation)
+void dotprod_cccf_run(float complex * _h,
+                      float complex * _x,
+                      unsigned int    _n,
+                      float complex * _y)
+{
+    float complex r = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_cccf_run4(float complex * _h,
+                       float complex * _x,
+                       unsigned int    _n,
+                       float complex * _y)
+{
+    float complex r = 0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured MMX dot product
+//
+
+struct dotprod_cccf_s {
+    unsigned int n;     // length
+    float * hi;         // in-phase
+    float * hq;         // quadrature
+};
+
+dotprod_cccf dotprod_cccf_create(float complex * _h,
+                                 unsigned int    _n)
+{
+    dotprod_cccf q = (dotprod_cccf)malloc(sizeof(struct dotprod_cccf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients, 16-byte aligned
+    q->hi = (float*) _mm_malloc( 2*q->n*sizeof(float), 16 );
+    q->hq = (float*) _mm_malloc( 2*q->n*sizeof(float), 16 );
+
+    // set coefficients, repeated
+    //  hi = { crealf(_h[0]), crealf(_h[0]), ... crealf(_h[n-1]), crealf(_h[n-1])}
+    //  hq = { cimagf(_h[0]), cimagf(_h[0]), ... cimagf(_h[n-1]), cimagf(_h[n-1])}
+    unsigned int i;
+    for (i=0; i<q->n; i++) {
+        q->hi[2*i+0] = crealf(_h[i]);
+        q->hi[2*i+1] = crealf(_h[i]);
+
+        q->hq[2*i+0] = cimagf(_h[i]);
+        q->hq[2*i+1] = cimagf(_h[i]);
+    }
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_cccf dotprod_cccf_recreate(dotprod_cccf    _q,
+                                   float complex * _h,
+                                   unsigned int    _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_cccf_destroy(_q);
+    return dotprod_cccf_create(_h,_n);
+}
+
+
+void dotprod_cccf_destroy(dotprod_cccf _q)
+{
+    _mm_free(_q->hi);
+    _mm_free(_q->hq);
+    free(_q);
+}
+
+void dotprod_cccf_print(dotprod_cccf _q)
+{
+    printf("dotprod_cccf [mmx, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f +j%12.9f\n", i, _q->hi[i], _q->hq[i]);
+}
+
+// execute structured dot product
+//  _q      :   dotprod object
+//  _x      :   input array
+//  _y      :   output sample
+void dotprod_cccf_execute(dotprod_cccf    _q,
+                          float complex * _x,
+                          float complex * _y)
+{
+    // switch based on size
+    if (_q->n < 32) {
+        dotprod_cccf_execute_mmx(_q, _x, _y);
+    } else {
+        dotprod_cccf_execute_mmx4(_q, _x, _y);
+    }
+}
+
+// use MMX/SSE extensions
+//
+// (a + jb)(c + jd) = (ac - bd) + j(ad + bc)
+//
+// mm_x  = { x[0].real, x[0].imag, x[1].real, x[1].imag }
+// mm_hi = { h[0].real, h[0].real, h[1].real, h[1].real }
+// mm_hq = { h[0].imag, h[0].imag, h[1].imag, h[1].imag }
+//
+// mm_y0 = mm_x * mm_hi
+//       = { x[0].real * h[0].real,
+//           x[0].imag * h[0].real,
+//           x[1].real * h[1].real,
+//           x[1].imag * h[1].real };
+//
+// mm_y1 = mm_x * mm_hq
+//       = { x[0].real * h[0].imag,
+//           x[0].imag * h[0].imag,
+//           x[1].real * h[1].imag,
+//           x[1].imag * h[1].imag };
+//
+void dotprod_cccf_execute_mmx(dotprod_cccf    _q,
+                              float complex * _x,
+                              float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // temporary buffers
+    __m128 v;   // input vector
+    __m128 hi;  // coefficients vector (real)
+    __m128 hq;  // coefficients vector (imag)
+    __m128 ci;  // output multiplication (v * hi)
+    __m128 cq;  // output multiplication (v * hq)
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16))) = {0,0,0,0};
+
+#if HAVE_PMMINTRIN_H
+    // SSE3
+    __m128 s;   // dot product
+    __m128 sum = _mm_setzero_ps(); // load zeros into sum register
+#else
+    // no SSE3
+    float wi[4] __attribute__((aligned(16)));
+    float wq[4] __attribute__((aligned(16)));
+#endif
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        // {x[0].real, x[0].imag, x[1].real, x[1].imag}
+        v = _mm_loadu_ps(&x[i]);
+
+        // load coefficients into register (aligned)
+        hi = _mm_load_ps(&_q->hi[i]);
+        hq = _mm_load_ps(&_q->hq[i]);
+
+        // compute parallel multiplications
+        ci = _mm_mul_ps(v, hi);
+        cq = _mm_mul_ps(v, hq);
+
+        // shuffle values
+        cq = _mm_shuffle_ps( cq, cq, _MM_SHUFFLE(2,3,0,1) );
+        
+#if HAVE_PMMINTRIN_H
+        // SSE3: combine using addsub_ps()
+        s = _mm_addsub_ps( ci, cq );
+
+        // accumulate
+        sum = _mm_add_ps(sum, s);
+#else
+        // no SSE3: combine using slow method
+        // FIXME: implement slow method
+        // unload values
+        _mm_store_ps(wi, ci);
+        _mm_store_ps(wq, cq);
+
+        // accumulate
+        w[0] += wi[0] - wq[0];
+        w[1] += wi[1] + wq[1];
+        w[2] += wi[2] - wq[2];
+        w[3] += wi[3] + wq[3];
+#endif
+    }
+
+#if HAVE_PMMINTRIN_H
+    // unload packed array
+    _mm_store_ps(w, sum);
+#endif
+
+    // add in-phase and quadrature components
+    w[0] += w[2];   // I
+    w[1] += w[3];   // Q
+
+    //float complex total = *((float complex*)w);
+    float complex total = w[0] + w[1] * _Complex_I;
+
+    // cleanup
+    for (i=t/2; i<_q->n; i++)
+        total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I );
+
+    // set return value
+    *_y = total;
+}
+
+// use MMX/SSE extensions
+void dotprod_cccf_execute_mmx4(dotprod_cccf    _q,
+                               float complex * _x,
+                               float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    __m128 v0,  v1,  v2,  v3;   // input vectors
+    __m128 hi0, hi1, hi2, hi3;  // coefficients vectors (real)
+    __m128 hq0, hq1, hq2, hq3;  // coefficients vectors (imag)
+    __m128 ci0, ci1, ci2, ci3;  // output multiplications (v * hi)
+    __m128 cq0, cq1, cq2, cq3;  // output multiplications (v * hq)
+
+    // load zeros into sum registers
+    __m128 sumi = _mm_setzero_ps();
+    __m128 sumq = _mm_setzero_ps();
+
+    // r = 4*floor(n/16)
+    unsigned int r = (n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = _mm_loadu_ps(&x[4*i+0]);
+        v1 = _mm_loadu_ps(&x[4*i+4]);
+        v2 = _mm_loadu_ps(&x[4*i+8]);
+        v3 = _mm_loadu_ps(&x[4*i+12]);
+
+        // load real coefficients into registers (aligned)
+        hi0 = _mm_load_ps(&_q->hi[4*i+0]);
+        hi1 = _mm_load_ps(&_q->hi[4*i+4]);
+        hi2 = _mm_load_ps(&_q->hi[4*i+8]);
+        hi3 = _mm_load_ps(&_q->hi[4*i+12]);
+
+        // load real coefficients into registers (aligned)
+        hq0 = _mm_load_ps(&_q->hq[4*i+0]);
+        hq1 = _mm_load_ps(&_q->hq[4*i+4]);
+        hq2 = _mm_load_ps(&_q->hq[4*i+8]);
+        hq3 = _mm_load_ps(&_q->hq[4*i+12]);
+        
+        // compute parallel multiplications (real)
+        ci0 = _mm_mul_ps(v0, hi0);
+        ci1 = _mm_mul_ps(v1, hi1);
+        ci2 = _mm_mul_ps(v2, hi2);
+        ci3 = _mm_mul_ps(v3, hi3);
+
+        // compute parallel multiplications (imag)
+        cq0 = _mm_mul_ps(v0, hq0);
+        cq1 = _mm_mul_ps(v1, hq1);
+        cq2 = _mm_mul_ps(v2, hq2);
+        cq3 = _mm_mul_ps(v3, hq3);
+
+        // accumulate
+        sumi = _mm_add_ps(sumi, ci0);   sumq = _mm_add_ps(sumq, cq0);
+        sumi = _mm_add_ps(sumi, ci1);   sumq = _mm_add_ps(sumq, cq1);
+        sumi = _mm_add_ps(sumi, ci2);   sumq = _mm_add_ps(sumq, cq2);
+        sumi = _mm_add_ps(sumi, ci3);   sumq = _mm_add_ps(sumq, cq3);
+    }
+
+    // shuffle values
+    sumq = _mm_shuffle_ps( sumq, sumq, _MM_SHUFFLE(2,3,0,1) );
+
+    // unload
+    float wi[4] __attribute__((aligned(16)));
+    float wq[4] __attribute__((aligned(16)));
+    _mm_store_ps(wi, sumi);
+    _mm_store_ps(wq, sumq);
+
+    // fold down (add/sub)
+    float complex total = 
+        ((wi[0] - wq[0]) + (wi[2] - wq[2])) +
+        ((wi[1] + wq[1]) + (wi[3] + wq[3])) * _Complex_I;
+
+    // cleanup (note: n _must_ be even)
+    // TODO : clean this method up
+    for (i=2*r; i<_q->n; i++) {
+        total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I );
+    }
+
+    // set return value
+    *_y = total;
+}
+
diff --git a/src/dotprod/src/dotprod_cccf.neon.c b/src/dotprod/src/dotprod_cccf.neon.c
new file mode 100644
index 0000000..c12df32
--- /dev/null
+++ b/src/dotprod/src/dotprod_cccf.neon.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (ARM Neon)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for ARM Neon
+#include <arm_neon.h>
+
+#define DEBUG_DOTPROD_CCCF_NEON   0
+
+// forward declaration of internal methods
+void dotprod_cccf_execute_neon(dotprod_cccf    _q,
+                               float complex * _x,
+                               float complex * _y);
+
+void dotprod_cccf_execute_neon4(dotprod_cccf    _q,
+                                float complex * _x,
+                                float complex * _y);
+
+// basic dot product (ordinal calculation)
+void dotprod_cccf_run(float complex * _h,
+                      float complex * _x,
+                      unsigned int    _n,
+                      float complex * _y)
+{
+    float complex r = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_cccf_run4(float complex * _h,
+                       float complex * _x,
+                       unsigned int    _n,
+                       float complex * _y)
+{
+    float complex r = 0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured ARM Neon dot product
+//
+
+struct dotprod_cccf_s {
+    unsigned int n;     // length
+    float * hi;         // in-phase
+    float * hq;         // quadrature
+};
+
+dotprod_cccf dotprod_cccf_create(float complex * _h,
+                                 unsigned int    _n)
+{
+    dotprod_cccf q = (dotprod_cccf)malloc(sizeof(struct dotprod_cccf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients
+    q->hi = (float*) malloc( 2*q->n*sizeof(float) );
+    q->hq = (float*) malloc( 2*q->n*sizeof(float) );
+
+    // set coefficients, repeated
+    //  hi = { crealf(_h[0]), crealf(_h[0]), ... crealf(_h[n-1]), crealf(_h[n-1])}
+    //  hq = { cimagf(_h[0]), cimagf(_h[0]), ... cimagf(_h[n-1]), cimagf(_h[n-1])}
+    unsigned int i;
+    for (i=0; i<q->n; i++) {
+        q->hi[2*i+0] = crealf(_h[i]);
+        q->hi[2*i+1] = crealf(_h[i]);
+
+        q->hq[2*i+0] = cimagf(_h[i]);
+        q->hq[2*i+1] = cimagf(_h[i]);
+    }
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_cccf dotprod_cccf_recreate(dotprod_cccf    _q,
+                                   float complex * _h,
+                                   unsigned int    _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_cccf_destroy(_q);
+    return dotprod_cccf_create(_h,_n);
+}
+
+
+void dotprod_cccf_destroy(dotprod_cccf _q)
+{
+    // free coefficients arrays
+    free(_q->hi);
+    free(_q->hq);
+
+    // free main memory
+    free(_q);
+}
+
+void dotprod_cccf_print(dotprod_cccf _q)
+{
+    printf("dotprod_cccf [arm-neon, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f +j%12.9f\n", i, _q->hi[i], _q->hq[i]);
+}
+
+// execute structured dot product
+//  _q      :   dotprod object
+//  _x      :   input array
+//  _y      :   output sample
+void dotprod_cccf_execute(dotprod_cccf    _q,
+                          float complex * _x,
+                          float complex * _y)
+{
+    // switch based on size
+    if (_q->n < 32) {
+        dotprod_cccf_execute_neon(_q, _x, _y);
+    } else {
+        dotprod_cccf_execute_neon4(_q, _x, _y);
+    }
+}
+
+// use ARM Neon extensions
+//
+// (a + jb)(c + jd) = (ac - bd) + j(ad + bc)
+//
+// mm_x  = { x[0].real, x[0].imag, x[1].real, x[1].imag }
+// mm_hi = { h[0].real, h[0].real, h[1].real, h[1].real }
+// mm_hq = { h[0].imag, h[0].imag, h[1].imag, h[1].imag }
+//
+// mm_y0 = mm_x * mm_hi
+//       = { x[0].real * h[0].real,
+//           x[0].imag * h[0].real,
+//           x[1].real * h[1].real,
+//           x[1].imag * h[1].real };
+//
+// mm_y1 = mm_x * mm_hq
+//       = { x[0].real * h[0].imag,
+//           x[0].imag * h[0].imag,
+//           x[1].real * h[1].imag,
+//           x[1].imag * h[1].imag };
+//
+void dotprod_cccf_execute_neon(dotprod_cccf    _q,
+                               float complex * _x,
+                               float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // temporary buffers
+    float32x4_t v;   // input vector
+    float32x4_t hi;  // coefficients vector (real)
+    float32x4_t hq;  // coefficients vector (imag)
+    float32x4_t ci;  // output multiplication (v * hi)
+    float32x4_t cq;  // output multiplication (v * hq)
+
+    // output accumulators
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sumi = vld1q_f32(zeros);
+    float32x4_t sumq = vld1q_f32(zeros);
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        // {x[0].real, x[0].imag, x[1].real, x[1].imag}
+        v = vld1q_f32(&x[i]);
+
+        // load coefficients into register (aligned)
+        // {hi[0].real, hi[0].imag, hi[1].real, hi[1].imag}
+        // {hq[0].real, hq[0].imag, hq[1].real, hq[1].imag}
+        hi = vld1q_f32(&_q->hi[i]);
+        hq = vld1q_f32(&_q->hq[i]);
+
+        // compute parallel multiplications
+        ci = vmulq_f32(v, hi);
+        cq = vmulq_f32(v, hq);
+
+        // parallel addition
+        sumi = vaddq_f32(sumi, ci);
+        sumq = vaddq_f32(sumq, cq);
+    }
+
+    // unload and combine
+    float wi[4];
+    float wq[4];
+    vst1q_f32(wi, sumi);
+    vst1q_f32(wq, sumq);
+
+    // fold down (add/sub)
+    float complex total = 
+        ((wi[0] - wq[1]) + (wi[2] - wq[3])) +
+        ((wi[1] + wq[0]) + (wi[3] + wq[2])) * _Complex_I;
+
+    // cleanup
+    for (i=t/2; i<_q->n; i++)
+        total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I );
+
+    // set return value
+    *_y = total;
+}
+
+// use ARM Neon extensions (unrolled loop)
+// NOTE: unrolling doesn't show any appreciable performance difference
+void dotprod_cccf_execute_neon4(dotprod_cccf    _q,
+                                float complex * _x,
+                                float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    float32x4_t v0,  v1,  v2,  v3;   // input vectors
+    float32x4_t hi0, hi1, hi2, hi3;  // coefficients vectors (real)
+    float32x4_t hq0, hq1, hq2, hq3;  // coefficients vectors (imag)
+    float32x4_t ci0, ci1, ci2, ci3;  // output multiplications (v * hi)
+    float32x4_t cq0, cq1, cq2, cq3;  // output multiplications (v * hq)
+
+    // load zeros into sum registers
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sumi = vld1q_f32(zeros);
+    float32x4_t sumq = vld1q_f32(zeros);
+
+    // r = 4*floor(n/16)
+    unsigned int r = (n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = vld1q_f32(&x[4*i+0]);
+        v1 = vld1q_f32(&x[4*i+4]);
+        v2 = vld1q_f32(&x[4*i+8]);
+        v3 = vld1q_f32(&x[4*i+12]);
+
+        // load real coefficients into registers (aligned)
+        hi0 = vld1q_f32(&_q->hi[4*i+0]);
+        hi1 = vld1q_f32(&_q->hi[4*i+4]);
+        hi2 = vld1q_f32(&_q->hi[4*i+8]);
+        hi3 = vld1q_f32(&_q->hi[4*i+12]);
+
+        // load real coefficients into registers (aligned)
+        hq0 = vld1q_f32(&_q->hq[4*i+0]);
+        hq1 = vld1q_f32(&_q->hq[4*i+4]);
+        hq2 = vld1q_f32(&_q->hq[4*i+8]);
+        hq3 = vld1q_f32(&_q->hq[4*i+12]);
+        
+        // compute parallel multiplications (real)
+        ci0 = vmulq_f32(v0, hi0);
+        ci1 = vmulq_f32(v1, hi1);
+        ci2 = vmulq_f32(v2, hi2);
+        ci3 = vmulq_f32(v3, hi3);
+
+        // compute parallel multiplications (imag)
+        cq0 = vmulq_f32(v0, hq0);
+        cq1 = vmulq_f32(v1, hq1);
+        cq2 = vmulq_f32(v2, hq2);
+        cq3 = vmulq_f32(v3, hq3);
+
+        // accumulate
+        sumi = vaddq_f32(sumi, ci0);    sumq = vaddq_f32(sumq, cq0);
+        sumi = vaddq_f32(sumi, ci1);    sumq = vaddq_f32(sumq, cq1);
+        sumi = vaddq_f32(sumi, ci2);    sumq = vaddq_f32(sumq, cq2);
+        sumi = vaddq_f32(sumi, ci3);    sumq = vaddq_f32(sumq, cq3);
+    }
+
+    // unload
+    float wi[4];
+    float wq[4];
+    vst1q_f32(wi, sumi);
+    vst1q_f32(wq, sumq);
+
+    // fold down (add/sub)
+    float complex total = 
+        ((wi[0] - wq[1]) + (wi[2] - wq[3])) +
+        ((wi[1] + wq[0]) + (wi[3] + wq[2])) * _Complex_I;
+
+    // cleanup (note: n _must_ be even)
+    // TODO : clean this method up
+    for (i=2*r; i<_q->n; i++) {
+        total += _x[i] * ( _q->hi[2*i] + _q->hq[2*i]*_Complex_I );
+    }
+
+    // set return value
+    *_y = total;
+}
+
diff --git a/src/dotprod/src/dotprod_crcf.av.c b/src/dotprod/src/dotprod_crcf.av.c
new file mode 100644
index 0000000..ba40595
--- /dev/null
+++ b/src/dotprod/src/dotprod_crcf.av.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Complex floating-point dot product (altivec velocity engine)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_DOTPROD_CRCF_AV   0
+
+// basic dot product
+
+// basic dot product
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void dotprod_crcf_run(float *         _h,
+                      float complex * _x,
+                      unsigned int    _n,
+                      float complex * _y)
+{
+    float complex r=0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product, unrolling loop
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void dotprod_crcf_run4(float *         _h,
+                       float complex * _x,
+                       unsigned int    _n,
+                       float complex * _y)
+{
+    float complex r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured dot product
+//
+
+struct dotprod_crcf_s {
+    // dotprod length (number of coefficients)
+    unsigned int n;
+
+    // coefficients arrays: the altivec velocity engine operates
+    // on 128-bit registers which can hold four 32-bit floating-
+    // point values.  We need to hold 4 copies of the coefficients
+    // to meet all possible alignments to the input data.
+    float *h[4];
+};
+
+// create the structured dotprod object
+dotprod_crcf dotprod_crcf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_crcf dp = (dotprod_crcf)malloc(sizeof(struct dotprod_crcf_s));
+    dp->n = _n;
+
+    // create 4 copies of the input coefficients (one for each
+    // data alignment).  For example: _h[4] = {1,2,3,4,5,6}
+    //  dp->h[0] = {1,1,2,2,3,3,4,4,5,5,6,6}
+    //  dp->h[1] = {. 1,1,2,2,3,3,4,4,5,5,6,6}
+    //  dp->h[2] = {. . 1,1,2,2,3,3,4,4,5,5,6,6}
+    //  dp->h[3] = {. . . 1,1,2,2,3,3,4,4,5,5,6,6}
+    // NOTE: double allocation size; coefficients are real, but
+    //       need to be multiplied by real and complex components
+    //       of input.
+    unsigned int i,j;
+    for (i=0; i<4; i++) {
+        dp->h[i] = calloc(1+(2*dp->n+i-1)/4,2*sizeof(vector float));
+        for (j=0; j<dp->n; j++) {
+            dp->h[i][2*j+0+i] = _h[j];
+            dp->h[i][2*j+1+i] = _h[j];
+        }
+    }
+
+    return dp;
+}
+
+// re-create the structured dotprod object
+dotprod_crcf dotprod_crcf_recreate(dotprod_crcf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_crcf_destroy(_q);
+    return dotprod_crcf_create(_h,_n);
+}
+
+// destroy the structured dotprod object
+void dotprod_crcf_destroy(dotprod_crcf _q)
+{
+    // clean up coefficients arrays
+    unsigned int i;
+    for (i=0; i<4; i++)
+        free(_q->h[i]);
+
+    // free allocated object memory
+    free(_q);
+}
+
+// print the dotprod object
+void dotprod_crcf_print(dotprod_crcf _q)
+{
+    printf("dotprod_crcf [altivec, %u coefficients]:\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f\n", i, _q->h[0][2*i]);
+}
+
+// exectue vectorized structured inner dot product
+void dotprod_crcf_execute(dotprod_crcf    _q,
+                          float complex * _x,
+                          float complex * _r)
+{
+    int al; // input data alignment
+
+    vector float *ar,*d;
+    vector float s0,s1,s2,s3;
+    union { vector float v; float w[4];} s;
+    unsigned int nblocks;
+
+    ar = (vector float*)( (int)_x & ~15);
+    al = ((int)_x & 15)/sizeof(float);
+
+    d = (vector float*)_q->h[al];
+
+    // number of blocks doubles because of complex type
+    nblocks = (2*_q->n + al - 1)/4 + 1;
+
+    // split into four vectors each with four 32-bit
+    // partial sums.  Effectively each loop iteration
+    // operates on 16 input samples at a time.
+    s0 = s1 = s2 = s3 = (vector float)(0);
+    while (nblocks >= 4) {
+        s0 = vec_madd(ar[nblocks-1],d[nblocks-1],s0);
+        s1 = vec_madd(ar[nblocks-2],d[nblocks-2],s1);
+        s2 = vec_madd(ar[nblocks-3],d[nblocks-3],s2);
+        s3 = vec_madd(ar[nblocks-4],d[nblocks-4],s3);
+        nblocks -= 4;
+    }
+
+    // fold the resulting partial sums into vector s0
+    s0 = vec_add(s0,s1);    // s0 = s0+s1
+    s2 = vec_add(s2,s3);    // s2 = s2+s3
+    s0 = vec_add(s0,s2);    // s0 = s0+s2
+
+    // finish partial summing operations
+    while (nblocks-- > 0)
+        s0 = vec_madd(ar[nblocks],d[nblocks],s0);
+
+    // move the result into the union s (effetively,
+    // this loads the four 32-bit values in s0 into
+    // the array w).
+    s.v = vec_add(s0,(vector float)(0));
+
+    // sum the resulting array
+    //*_r = s.w[0] + s.w[1] + s.w[2] + s.w[3];
+    *_r = (s.w[0] + s.w[2]) + (s.w[1] + s.w[3]) * _Complex_I;
+}
+
diff --git a/src/dotprod/src/dotprod_crcf.c b/src/dotprod/src/dotprod_crcf.c
new file mode 100644
index 0000000..6e58e9d
--- /dev/null
+++ b/src/dotprod/src/dotprod_crcf.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Complex floating-point dot product
+//
+
+#include <complex.h>
+#include "liquid.internal.h"
+
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_crcf,name)
+#define TO              float complex
+#define TC              float
+#define TI              float complex
+
+#include "dotprod.c"
diff --git a/src/dotprod/src/dotprod_crcf.mmx.c b/src/dotprod/src/dotprod_crcf.mmx.c
new file mode 100644
index 0000000..deabd0c
--- /dev/null
+++ b/src/dotprod/src/dotprod_crcf.mmx.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (MMX)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xmmintrin.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_DOTPROD_CRCF_MMX   0
+
+// forward declaration of internal methods
+void dotprod_crcf_execute_mmx(dotprod_crcf    _q,
+                              float complex * _x,
+                              float complex * _y);
+void dotprod_crcf_execute_mmx4(dotprod_crcf    _q,
+                               float complex * _x,
+                               float complex * _y);
+
+// basic dot product (ordinal calculation)
+void dotprod_crcf_run(float *         _h,
+                      float complex * _x,
+                      unsigned int    _n,
+                      float complex * _y)
+{
+    float complex r = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_crcf_run4(float *         _h,
+                       float complex * _x,
+                       unsigned int    _n,
+                       float complex * _y)
+{
+    float complex r = 0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured MMX dot product
+//
+
+struct dotprod_crcf_s {
+    unsigned int n;     // length
+    float * h;          // coefficients array
+};
+
+dotprod_crcf dotprod_crcf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_crcf q = (dotprod_crcf)malloc(sizeof(struct dotprod_crcf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients, 16-byte aligned
+    q->h = (float*) _mm_malloc( 2*q->n*sizeof(float), 16 );
+
+    // set coefficients, repeated
+    //  h = { _h[0], _h[0], _h[1], _h[1], ... _h[n-1], _h[n-1]}
+    unsigned int i;
+    for (i=0; i<q->n; i++) {
+        q->h[2*i+0] = _h[i];
+        q->h[2*i+1] = _h[i];
+    }
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_crcf dotprod_crcf_recreate(dotprod_crcf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_crcf_destroy(_q);
+    return dotprod_crcf_create(_h,_n);
+}
+
+
+void dotprod_crcf_destroy(dotprod_crcf _q)
+{
+    _mm_free(_q->h);
+    free(_q);
+}
+
+void dotprod_crcf_print(dotprod_crcf _q)
+{
+    // print coefficients to screen, skipping odd entries (due
+    // to repeated coefficients)
+    printf("dotprod_crcf [mmx, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f\n", i, _q->h[2*i]);
+}
+
+// 
+void dotprod_crcf_execute(dotprod_crcf    _q,
+                          float complex * _x,
+                          float complex * _y)
+{
+    // switch based on size
+    if (_q->n < 32) {
+        dotprod_crcf_execute_mmx(_q, _x, _y);
+    } else {
+        dotprod_crcf_execute_mmx4(_q, _x, _y);
+    }
+}
+
+// use MMX/SSE extensions
+void dotprod_crcf_execute_mmx(dotprod_crcf    _q,
+                              float complex * _x,
+                              float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    __m128 v;   // input vector
+    __m128 h;   // coefficients vector
+    __m128 s;   // dot product
+    __m128 sum = _mm_setzero_ps();  // load zeros into sum register
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = _mm_loadu_ps(&x[i]);
+
+        // load coefficients into register (aligned)
+        h = _mm_load_ps(&_q->h[i]);
+
+        // compute multiplication
+        s = _mm_mul_ps(v, h);
+
+        // accumulate
+        sum = _mm_add_ps(sum, s);
+    }
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+    // unload packed array
+    _mm_store_ps(w, sum);
+
+    // add in-phase and quadrature components
+    w[0] += w[2];
+    w[1] += w[3];
+
+    // cleanup (note: n _must_ be even)
+    for (; i<n; i+=2) {
+        w[0] += x[i  ] * _q->h[i  ];
+        w[1] += x[i+1] * _q->h[i+1];
+    }
+
+    // set return value
+    *_y = w[0] + _Complex_I*w[1];
+}
+
+// use MMX/SSE extensions
+void dotprod_crcf_execute_mmx4(dotprod_crcf    _q,
+                               float complex * _x,
+                               float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    __m128 v0, v1, v2, v3;  // input vectors
+    __m128 h0, h1, h2, h3;  // coefficients vectors
+    __m128 s0, s1, s2, s3;  // dot products [re, im, re, im]
+
+    // load zeros into sum registers
+    __m128 sum0 = _mm_setzero_ps();
+    __m128 sum1 = _mm_setzero_ps();
+    __m128 sum2 = _mm_setzero_ps();
+    __m128 sum3 = _mm_setzero_ps();
+
+    // r = 4*floor(n/16)
+    unsigned int r = (n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = _mm_loadu_ps(&x[4*i+0]);
+        v1 = _mm_loadu_ps(&x[4*i+4]);
+        v2 = _mm_loadu_ps(&x[4*i+8]);
+        v3 = _mm_loadu_ps(&x[4*i+12]);
+
+        // load coefficients into register (aligned)
+        h0 = _mm_load_ps(&_q->h[4*i+0]);
+        h1 = _mm_load_ps(&_q->h[4*i+4]);
+        h2 = _mm_load_ps(&_q->h[4*i+8]);
+        h3 = _mm_load_ps(&_q->h[4*i+12]);
+
+        // compute multiplication
+        s0 = _mm_mul_ps(v0, h0);
+        s1 = _mm_mul_ps(v1, h1);
+        s2 = _mm_mul_ps(v2, h2);
+        s3 = _mm_mul_ps(v3, h3);
+        
+        // parallel addition
+        sum0 = _mm_add_ps( sum0, s0 );
+        sum1 = _mm_add_ps( sum1, s1 );
+        sum2 = _mm_add_ps( sum2, s2 );
+        sum3 = _mm_add_ps( sum3, s3 );
+    }
+
+    // fold down
+    sum0 = _mm_add_ps( sum0, sum1 );
+    sum2 = _mm_add_ps( sum2, sum3 );
+    sum0 = _mm_add_ps( sum0, sum2 );
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+    // unload packed array and perform manual sum
+    _mm_store_ps(w, sum0);
+    w[0] += w[2];
+    w[1] += w[3];
+
+    // cleanup (note: n _must_ be even)
+    for (i=4*r; i<n; i+=2) {
+        w[0] += x[i  ] * _q->h[i  ];
+        w[1] += x[i+1] * _q->h[i+1];
+    }
+
+    // set return value
+    *_y = w[0] + w[1]*_Complex_I;
+}
+
diff --git a/src/dotprod/src/dotprod_crcf.neon.c b/src/dotprod/src/dotprod_crcf.neon.c
new file mode 100644
index 0000000..a89c0d5
--- /dev/null
+++ b/src/dotprod/src/dotprod_crcf.neon.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (ARM Neon)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for ARM Neon
+#include <arm_neon.h>
+
+#define DEBUG_DOTPROD_CRCF_NEON   0
+
+// forward declaration of internal methods
+void dotprod_crcf_execute_neon(dotprod_crcf    _q,
+                               float complex * _x,
+                               float complex * _y);
+void dotprod_crcf_execute_neon4(dotprod_crcf    _q,
+                                float complex * _x,
+                                float complex * _y);
+
+// basic dot product (ordinal calculation) using neon extensions
+void dotprod_crcf_run(float *         _h,
+                      float complex * _x,
+                      unsigned int    _n,
+                      float complex * _y)
+{
+    // initialize accumulator
+    float complex r=0;
+
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    // return result
+    *_y = r;
+}
+
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_crcf_run4(float *         _h,
+                       float complex * _x,
+                       unsigned int    _n,
+                       float complex * _y)
+{
+    float complex r = 0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured ARM Neon dot product
+//
+
+struct dotprod_crcf_s {
+    unsigned int n;     // length
+    float * h;          // coefficients array
+};
+
+dotprod_crcf dotprod_crcf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_crcf q = (dotprod_crcf)malloc(sizeof(struct dotprod_crcf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients (double size)
+    q->h = (float*) malloc( 2*q->n*sizeof(float) );
+
+    // set coefficients, repeated
+    //  h = { _h[0], _h[0], _h[1], _h[1], ... _h[n-1], _h[n-1]}
+    unsigned int i;
+    for (i=0; i<q->n; i++) {
+        q->h[2*i+0] = _h[i];
+        q->h[2*i+1] = _h[i];
+    }
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_crcf dotprod_crcf_recreate(dotprod_crcf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_crcf_destroy(_q);
+    return dotprod_crcf_create(_h,_n);
+}
+
+
+void dotprod_crcf_destroy(dotprod_crcf _q)
+{
+    // free coefficients array
+    free(_q->h);
+
+    // free main memory
+    free(_q);
+}
+
+void dotprod_crcf_print(dotprod_crcf _q)
+{
+    // print coefficients to screen, skipping odd entries (due
+    // to repeated coefficients)
+    printf("dotprod_crcf [arm-neon, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f\n", i, _q->h[2*i]);
+}
+
+// 
+void dotprod_crcf_execute(dotprod_crcf    _q,
+                          float complex * _x,
+                          float complex * _y)
+{
+    // switch based on size
+    if (_q->n < 32) {
+        dotprod_crcf_execute_neon(_q, _x, _y);
+    } else {
+        dotprod_crcf_execute_neon4(_q, _x, _y);
+    }
+}
+
+// use ARM Neon extensions
+void dotprod_crcf_execute_neon(dotprod_crcf    _q,
+                               float complex * _x,
+                               float complex * _y)
+{
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    float32x4_t v;   // input vector
+    float32x4_t h;   // coefficients vector
+    float32x4_t s;   // dot product
+    
+    // load zeros into sum register
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sum = vld1q_f32(zeros);
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = vld1q_f32(&x[i]);
+
+        // load coefficients into register (aligned)
+        h = vld1q_f32(&_q->h[i]);
+
+        // compute multiplication
+        s = vmulq_f32(h,v);
+
+        // accumulate
+        sum = vaddq_f32(sum, s);
+    }
+
+    // unload packed array
+    float w[4];
+    vst1q_f32(w, sum);
+
+    // add in-phase and quadrature components
+    w[0] += w[2];
+    w[1] += w[3];
+
+    // cleanup (note: n _must_ be even)
+    for (; i<n; i+=2) {
+        w[0] += x[i  ] * _q->h[i  ];
+        w[1] += x[i+1] * _q->h[i+1];
+    }
+
+    // set return value
+    *_y = w[0] + _Complex_I*w[1];
+}
+
+// use ARM Neon extensions
+void dotprod_crcf_execute_neon4(dotprod_crcf    _q,
+                                float complex * _x,
+                                float complex * _y)
+{
+#if 1
+    // type cast input as floating point array
+    float * x = (float*) _x;
+
+    // double effective length
+    unsigned int n = 2*_q->n;
+
+    // first cut: ...
+    float32x4_t v0, v1, v2, v3;  // input vectors
+    float32x4_t h0, h1, h2, h3;  // coefficients vectors
+    float32x4_t s0, s1, s2, s3;  // dot products [re, im, re, im]
+
+    // load zeros into sum registers
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sum0 = vld1q_f32(zeros);
+    float32x4_t sum1 = vld1q_f32(zeros);
+    float32x4_t sum2 = vld1q_f32(zeros);
+    float32x4_t sum3 = vld1q_f32(zeros);
+
+    // r = 4*floor(n/16)
+    unsigned int r = (n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = vld1q_f32(&x[4*i+0]);
+        v1 = vld1q_f32(&x[4*i+4]);
+        v2 = vld1q_f32(&x[4*i+8]);
+        v3 = vld1q_f32(&x[4*i+12]);
+
+        // load coefficients into register (aligned)
+        h0 = vld1q_f32(&_q->h[4*i+0]);
+        h1 = vld1q_f32(&_q->h[4*i+4]);
+        h2 = vld1q_f32(&_q->h[4*i+8]);
+        h3 = vld1q_f32(&_q->h[4*i+12]);
+
+        // compute multiplication
+        s0 = vmulq_f32(v0, h0);
+        s1 = vmulq_f32(v1, h1);
+        s2 = vmulq_f32(v2, h2);
+        s3 = vmulq_f32(v3, h3);
+        
+        // parallel addition
+        sum0 = vaddq_f32( sum0, s0 );
+        sum1 = vaddq_f32( sum1, s1 );
+        sum2 = vaddq_f32( sum2, s2 );
+        sum3 = vaddq_f32( sum3, s3 );
+    }
+
+    // fold down into sum0
+    sum0 = vaddq_f32( sum0, sum1 );
+    sum2 = vaddq_f32( sum2, sum3 );
+    sum0 = vaddq_f32( sum0, sum2 );
+
+    // unload packed array
+    float w[4];
+    vst1q_f32(w, sum0);
+
+    // add in-phase and quadrature components
+    w[0] += w[2];
+    w[1] += w[3];
+
+    // cleanup (note: n _must_ be even)
+    for (i=4*r; i<n; i+=2) {
+        w[0] += x[i  ] * _q->h[i  ];
+        w[1] += x[i+1] * _q->h[i+1];
+    }
+
+    // set return value
+    *_y = w[0] + w[1]*_Complex_I;
+#else
+    dotprod_crcf_execute_neon(_q, _x, _y);
+#endif
+}
+
diff --git a/src/dotprod/src/dotprod_rrrf.av.c b/src/dotprod/src/dotprod_rrrf.av.c
new file mode 100644
index 0000000..b500c6b
--- /dev/null
+++ b/src/dotprod/src/dotprod_rrrf.av.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (altivec velocity engine)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_DOTPROD_RRRF_AV   0
+
+// basic dot product
+
+// basic dot product
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void dotprod_rrrf_run(float *      _h,
+                      float *      _x,
+                      unsigned int _n,
+                      float *      _y)
+{
+    float r=0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product, unrolling loop
+//  _h      :   coefficients array [size: 1 x _n]
+//  _x      :   input array [size: 1 x _n]
+//  _n      :   input lengths
+//  _y      :   output dot product
+void dotprod_rrrf_run4(float *      _h,
+                       float *      _x,
+                       unsigned int _n,
+                       float *      _y)
+{
+    float r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured dot product
+//
+
+struct dotprod_rrrf_s {
+    // dotprod length (number of coefficients)
+    unsigned int n;
+
+    // coefficients arrays: the altivec velocity engine operates
+    // on 128-bit registers which can hold four 32-bit floating-
+    // point values.  We need to hold 4 copies of the coefficients
+    // to meet all possible alignments to the input data.
+    float *h[4];
+};
+
+// create the structured dotprod object
+dotprod_rrrf dotprod_rrrf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_rrrf dp = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s));
+    dp->n = _n;
+
+    // create 4 copies of the input coefficients (one for each
+    // data alignment).  For example: _h[4] = {1,2,3,4,5,6}
+    //  dp->h[0] = {1,2,3,4,5,6}
+    //  dp->h[1] = {. 1,2,3,4,5,6}
+    //  dp->h[2] = {. . 1,2,3,4,5,6}
+    //  dp->h[3] = {. . . 1,2,3,4,5,6}
+    unsigned int i,j;
+    for (i=0; i<4; i++) {
+        dp->h[i] = calloc(1+(dp->n+i-1)/4,sizeof(vector float));
+        for (j=0; j<dp->n; j++)
+            dp->h[i][j+i] = _h[j];
+    }
+
+    return dp;
+}
+
+// re-create the structured dotprod object
+dotprod_rrrf dotprod_rrrf_recreate(dotprod_rrrf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_rrrf_destroy(_q);
+    return dotprod_rrrf_create(_h,_n);
+}
+
+// destroy the structured dotprod object
+void dotprod_rrrf_destroy(dotprod_rrrf _q)
+{
+    // clean up coefficients arrays
+    unsigned int i;
+    for (i=0; i<4; i++)
+        free(_q->h[i]);
+
+    // free allocated object memory
+    free(_q);
+}
+
+// print the dotprod object
+void dotprod_rrrf_print(dotprod_rrrf _q)
+{
+    printf("dotprod_rrrf [altivec, %u coefficients]:\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("  %3u : %12.9f\n", i, _q->h[0][i]);
+}
+
+// exectue vectorized structured inner dot product
+void dotprod_rrrf_execute(dotprod_rrrf _q,
+                          float *      _x,
+                          float *      _r)
+{
+    int al; // input data alignment
+
+    vector float *ar,*d;
+    vector float s0,s1,s2,s3;
+    union { vector float v; float w[4];} s;
+    unsigned int nblocks;
+
+    ar = (vector float*)( (int)_x & ~15);
+    al = ((int)_x & 15)/sizeof(float);
+
+    d = (vector float*)_q->h[al];
+
+    nblocks = (_q->n + al - 1)/4 + 1;
+
+    // split into four vectors each with four 32-bit
+    // partial sums.  Effectively each loop iteration
+    // operates on 16 input samples at a time.
+    s0 = s1 = s2 = s3 = (vector float)(0);
+    while (nblocks >= 4) {
+        s0 = vec_madd(ar[nblocks-1],d[nblocks-1],s0);
+        s1 = vec_madd(ar[nblocks-2],d[nblocks-2],s1);
+        s2 = vec_madd(ar[nblocks-3],d[nblocks-3],s2);
+        s3 = vec_madd(ar[nblocks-4],d[nblocks-4],s3);
+        nblocks -= 4;
+    }
+
+    // fold the resulting partial sums into vector s0
+    s0 = vec_add(s0,s1);    // s0 = s0+s1
+    s2 = vec_add(s2,s3);    // s2 = s2+s3
+    s0 = vec_add(s0,s2);    // s0 = s0+s2
+
+    // finish partial summing operations
+    while (nblocks-- > 0)
+        s0 = vec_madd(ar[nblocks],d[nblocks],s0);
+
+    // move the result into the union s (effetively,
+    // this loads the four 32-bit values in s0 into
+    // the array w).
+    s.v = vec_add(s0,(vector float)(0));
+
+    // sum the resulting array
+    *_r = s.w[0] + s.w[1] + s.w[2] + s.w[3];
+}
+
diff --git a/src/dotprod/src/dotprod_rrrf.c b/src/dotprod/src/dotprod_rrrf.c
new file mode 100644
index 0000000..b8c1b83
--- /dev/null
+++ b/src/dotprod/src/dotprod_rrrf.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product
+//
+
+#include "liquid.internal.h"
+
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_rrrf,name)
+#define TO              float
+#define TC              float
+#define TI              float
+
+#include "dotprod.c"
diff --git a/src/dotprod/src/dotprod_rrrf.mmx.c b/src/dotprod/src/dotprod_rrrf.mmx.c
new file mode 100644
index 0000000..321efc4
--- /dev/null
+++ b/src/dotprod/src/dotprod_rrrf.mmx.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (MMX)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for x86 platforms
+// NOTE: these pre-processor macros are defined in config.h
+
+#if HAVE_MMINTRIN_H
+#include <mmintrin.h>   // MMX
+#endif
+
+#if HAVE_XMMINTRIN_H
+#include <xmmintrin.h>  // SSE
+#endif
+
+#if HAVE_EMMINTRIN_H
+#include <emmintrin.h>  // SSE2
+#endif
+
+#if HAVE_PMMINTRIN_H
+#include <pmmintrin.h>  // SSE3
+#endif
+
+#define DEBUG_DOTPROD_RRRF_MMX   0
+
+// internal methods
+void dotprod_rrrf_execute_mmx(dotprod_rrrf _q,
+                              float *      _x,
+                              float *      _y);
+void dotprod_rrrf_execute_mmx4(dotprod_rrrf _q,
+                               float *      _x,
+                               float *      _y);
+
+// basic dot product (ordinal calculation)
+void dotprod_rrrf_run(float *      _h,
+                      float *      _x,
+                      unsigned int _n,
+                      float *      _y)
+{
+    float r=0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_rrrf_run4(float *      _h,
+                       float *      _x,
+                       unsigned int _n,
+                       float *      _y)
+{
+    float r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured MMX dot product
+//
+
+struct dotprod_rrrf_s {
+    unsigned int n;     // length
+    float * h;          // coefficients array
+};
+
+dotprod_rrrf dotprod_rrrf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_rrrf q = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients, 16-byte aligned
+    q->h = (float*) _mm_malloc( q->n*sizeof(float), 16);
+
+    // set coefficients
+    memmove(q->h, _h, _n*sizeof(float));
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_rrrf dotprod_rrrf_recreate(dotprod_rrrf _q,
+                                   float * _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_rrrf_destroy(_q);
+    return dotprod_rrrf_create(_h,_n);
+}
+
+
+void dotprod_rrrf_destroy(dotprod_rrrf _q)
+{
+    _mm_free(_q->h);
+    free(_q);
+}
+
+void dotprod_rrrf_print(dotprod_rrrf _q)
+{
+    printf("dotprod_rrrf [mmx, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("%3u : %12.9f\n", i, _q->h[i]);
+}
+
+// 
+void dotprod_rrrf_execute(dotprod_rrrf _q,
+                          float *      _x,
+                          float *      _y)
+{
+    // switch based on size
+    if (_q->n < 16) {
+        dotprod_rrrf_execute_mmx(_q, _x, _y);
+    } else {
+        dotprod_rrrf_execute_mmx4(_q, _x, _y);
+    }
+}
+
+// use MMX/SSE extensions
+void dotprod_rrrf_execute_mmx(dotprod_rrrf _q,
+                              float *      _x,
+                              float *      _y)
+{
+    // first cut: ...
+    __m128 v;   // input vector
+    __m128 h;   // coefficients vector
+    __m128 s;   // dot product
+    __m128 sum = _mm_setzero_ps(); // load zeros into sum register
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (_q->n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = _mm_loadu_ps(&_x[i]);
+
+        // load coefficients into register (aligned)
+        h = _mm_load_ps(&_q->h[i]);
+
+        // compute multiplication
+        s = _mm_mul_ps(v, h);
+       
+        // parallel addition
+        sum = _mm_add_ps( sum, s );
+    }
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+#if HAVE_PMMINTRIN_H
+    // fold down into single value
+    __m128 z = _mm_setzero_ps();
+    sum = _mm_hadd_ps(sum, z);
+    sum = _mm_hadd_ps(sum, z);
+   
+    // unload single (lower value)
+    _mm_store_ss(w, sum);
+    float total = w[0];
+#else
+    // unload packed array
+    _mm_store_ps(w, sum);
+    float total = w[0] + w[1] + w[2] + w[3];
+#endif
+
+    // cleanup
+    for (; i<_q->n; i++)
+        total += _x[i] * _q->h[i];
+
+    // set return value
+    *_y = total;
+}
+
+// use MMX/SSE extensions, unrolled loop
+void dotprod_rrrf_execute_mmx4(dotprod_rrrf _q,
+                               float *      _x,
+                               float *      _y)
+{
+    // first cut: ...
+    __m128 v0, v1, v2, v3;
+    __m128 h0, h1, h2, h3;
+    __m128 s0, s1, s2, s3;
+
+    // load zeros into sum registers
+    __m128 sum0 = _mm_setzero_ps();
+    __m128 sum1 = _mm_setzero_ps();
+    __m128 sum2 = _mm_setzero_ps();
+    __m128 sum3 = _mm_setzero_ps();
+
+    // r = 4*floor(n/16)
+    unsigned int r = (_q->n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = _mm_loadu_ps(&_x[4*i+0]);
+        v1 = _mm_loadu_ps(&_x[4*i+4]);
+        v2 = _mm_loadu_ps(&_x[4*i+8]);
+        v3 = _mm_loadu_ps(&_x[4*i+12]);
+
+        // load coefficients into register (aligned)
+        h0 = _mm_load_ps(&_q->h[4*i+0]);
+        h1 = _mm_load_ps(&_q->h[4*i+4]);
+        h2 = _mm_load_ps(&_q->h[4*i+8]);
+        h3 = _mm_load_ps(&_q->h[4*i+12]);
+
+        // compute multiplication
+        s0 = _mm_mul_ps(v0, h0);
+        s1 = _mm_mul_ps(v1, h1);
+        s2 = _mm_mul_ps(v2, h2);
+        s3 = _mm_mul_ps(v3, h3);
+        
+        // parallel addition
+        sum0 = _mm_add_ps( sum0, s0 );
+        sum1 = _mm_add_ps( sum1, s1 );
+        sum2 = _mm_add_ps( sum2, s2 );
+        sum3 = _mm_add_ps( sum3, s3 );
+    }
+
+    // fold down into single 4-element register
+    sum0 = _mm_add_ps( sum0, sum1 );
+    sum2 = _mm_add_ps( sum2, sum3 );
+    sum0 = _mm_add_ps( sum0, sum2);
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+#if HAVE_PMMINTRIN_H
+    // SSE3: fold down to single value using _mm_hadd_ps()
+    __m128 z = _mm_setzero_ps();
+    sum0 = _mm_hadd_ps(sum0, z);
+    sum0 = _mm_hadd_ps(sum0, z);
+   
+    // unload single (lower value)
+    _mm_store_ss(w, sum0);
+    float total = w[0];
+#else
+    // SSE2 and below: unload packed array and perform manual sum
+    _mm_store_ps(w, sum0);
+    float total = w[0] + w[1] + w[2] + w[3];
+#endif
+
+    // cleanup
+    // TODO : use intrinsics here as well
+    for (i=4*r; i<_q->n; i++)
+        total += _x[i] * _q->h[i];
+
+    // set return value
+    *_y = total;
+}
+
diff --git a/src/dotprod/src/dotprod_rrrf.neon.c b/src/dotprod/src/dotprod_rrrf.neon.c
new file mode 100644
index 0000000..ac2d2e3
--- /dev/null
+++ b/src/dotprod/src/dotprod_rrrf.neon.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (ARM Neon)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for ARM Neon
+#include <arm_neon.h>
+
+#define DEBUG_DOTPROD_RRRF_NEON   0
+
+// basic dot product (ordinal calculation) using neon extensions
+void dotprod_rrrf_run(float *      _h,
+                      float *      _x,
+                      unsigned int _n,
+                      float *      _y)
+{
+    float32x4_t v;   // input vector
+    float32x4_t h;   // coefficients vector
+    float32x4_t s;   // dot product
+
+    // load zeros into sum register
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sum = vld1q_f32(zeros);
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (_n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = vld1q_f32(&_x[i]);
+
+        // load coefficients into register (aligned)
+        h = vld1q_f32(&_h[i]);
+
+        // compute multiplication
+        s = vmulq_f32(h,v);
+       
+        // parallel addition
+        sum = vaddq_f32(sum, s);
+    }
+
+    // unload packed array
+    float w[4];
+    vst1q_f32(w, sum);
+    float total = w[0] + w[1] + w[2] + w[3];
+
+    // cleanup
+    for (; i<_n; i++)
+        total += _x[i] * _h[i];
+
+    // set return value
+    *_y = total;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled, neon extensions
+void dotprod_rrrf_run4(float *      _h,
+                       float *      _x,
+                       unsigned int _n,
+                       float *      _y)
+{
+    float32x4_t v0, v1, v2, v3;
+    float32x4_t h0, h1, h2, h3;
+    float32x4_t s0, s1, s2, s3;
+
+    // load zeros into sum registers
+    float zeros[4] = {0,0,0,0};
+    float32x4_t sum0 = vld1q_f32(zeros);
+    float32x4_t sum1 = vld1q_f32(zeros);
+    float32x4_t sum2 = vld1q_f32(zeros);
+    float32x4_t sum3 = vld1q_f32(zeros);
+
+    // r = 4*floor(n/16)
+    unsigned int r = (_n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = vld1q_f32(&_x[4*i+0]);
+        v1 = vld1q_f32(&_x[4*i+4]);
+        v2 = vld1q_f32(&_x[4*i+8]);
+        v3 = vld1q_f32(&_x[4*i+12]);
+
+        // load coefficients into register (aligned)
+        h0 = vld1q_f32(&_h[4*i+0]);
+        h1 = vld1q_f32(&_h[4*i+4]);
+        h2 = vld1q_f32(&_h[4*i+8]);
+        h3 = vld1q_f32(&_h[4*i+12]);
+
+        // compute multiplication
+        s0 = vmulq_f32(v0, h0);
+        s1 = vmulq_f32(v1, h1);
+        s2 = vmulq_f32(v2, h2);
+        s3 = vmulq_f32(v3, h3);
+        
+        // parallel addition
+        sum0 = vaddq_f32( sum0, s0 );
+        sum1 = vaddq_f32( sum1, s1 );
+        sum2 = vaddq_f32( sum2, s2 );
+        sum3 = vaddq_f32( sum3, s3 );
+    }
+
+    // fold down into single 4-element register
+    sum0 = vaddq_f32( sum0, sum1 );
+    sum2 = vaddq_f32( sum2, sum3 );
+    sum0 = vaddq_f32( sum0, sum2);
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+    // unload packed array and perform manual sum
+    vst1q_f32(w, sum0);
+    float total = w[0] + w[1] + w[2] + w[3];
+
+    // cleanup
+    // TODO : use intrinsics here as well
+    for (i=4*r; i<_n; i++)
+        total += _x[i] * _h[i];
+
+    // set return value
+    *_y = total;
+}
+
+
+//
+// structured dot product
+//
+
+struct dotprod_rrrf_s {
+    unsigned int n;     // length
+    float * h;          // coefficients array
+};
+
+// create dotprod object
+dotprod_rrrf dotprod_rrrf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_rrrf q = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients
+    q->h = (float*) malloc( q->n*sizeof(float) );
+
+    // set coefficients
+    memmove(q->h, _h, _n*sizeof(float));
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_rrrf dotprod_rrrf_recreate(dotprod_rrrf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_rrrf_destroy(_q);
+    return dotprod_rrrf_create(_h,_n);
+}
+
+// destroy dotprod object, freeing internal memory
+void dotprod_rrrf_destroy(dotprod_rrrf _q)
+{
+    // free coefficients
+    free(_q->h);
+
+    // free main object
+    free(_q);
+}
+
+// print dotprod internal state
+void dotprod_rrrf_print(dotprod_rrrf _q)
+{
+    printf("dotprod_rrrf [arm-neon, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("%3u : %12.9f\n", i, _q->h[i]);
+}
+
+// execute dot product on input vector
+void dotprod_rrrf_execute(dotprod_rrrf _q,
+                          float *      _x,
+                          float *      _y)
+{
+    // switch based on size
+    if (_q->n < 16) {
+        dotprod_rrrf_run(_q->h, _x, _q->n, _y);
+    } else {
+        dotprod_rrrf_run4(_q->h, _x, _q->n, _y);
+    }
+}
+
diff --git a/src/dotprod/src/dotprod_rrrf.sse4.c b/src/dotprod/src/dotprod_rrrf.sse4.c
new file mode 100644
index 0000000..f29b73d
--- /dev/null
+++ b/src/dotprod/src/dotprod_rrrf.sse4.c
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point dot product (SSE4.1/2)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for x86 platforms
+// NOTE: these pre-processor macros are defined in config.h
+
+#if 0
+#include <mmintrin.h>   // MMX
+#include <xmmintrin.h>  // SSE
+#include <emmintrin.h>  // SSE2
+#include <pmmintrin.h>  // SSE3
+#endif
+#include <smmintrin.h>  // SSE4.1/2
+
+#define DEBUG_DOTPROD_RRRF_SSE4     0
+
+// internal methods
+void dotprod_rrrf_execute_sse4(dotprod_rrrf _q,
+                               float *      _x,
+                               float *      _y);
+void dotprod_rrrf_execute_sse4u(dotprod_rrrf _q,
+                                float *      _x,
+                                float *      _y);
+
+// basic dot product (ordinal calculation)
+void dotprod_rrrf_run(float *      _h,
+                      float *      _x,
+                      unsigned int _n,
+                      float *      _y)
+{
+    float r=0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        r += _h[i] * _x[i];
+    *_y = r;
+}
+
+// basic dot product (ordinal calculation) with loop unrolled
+void dotprod_rrrf_run4(float *      _h,
+                       float *      _x,
+                       unsigned int _n,
+                       float *      _y)
+{
+    float r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute dotprod in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _h[i]   * _x[i];
+        r += _h[i+1] * _x[i+1];
+        r += _h[i+2] * _x[i+2];
+        r += _h[i+3] * _x[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _h[i] * _x[i];
+
+    *_y = r;
+}
+
+
+//
+// structured MMX dot product
+//
+
+struct dotprod_rrrf_s {
+    unsigned int n;     // length
+    float * h;          // coefficients array
+};
+
+dotprod_rrrf dotprod_rrrf_create(float *      _h,
+                                 unsigned int _n)
+{
+    dotprod_rrrf q = (dotprod_rrrf)malloc(sizeof(struct dotprod_rrrf_s));
+    q->n = _n;
+
+    // allocate memory for coefficients, 16-byte aligned
+    q->h = (float*) _mm_malloc( q->n*sizeof(float), 16);
+
+    // set coefficients
+    memmove(q->h, _h, _n*sizeof(float));
+
+    // return object
+    return q;
+}
+
+// re-create the structured dotprod object
+dotprod_rrrf dotprod_rrrf_recreate(dotprod_rrrf _q,
+                                   float *      _h,
+                                   unsigned int _n)
+{
+    // completely destroy and re-create dotprod object
+    dotprod_rrrf_destroy(_q);
+    return dotprod_rrrf_create(_h,_n);
+}
+
+
+void dotprod_rrrf_destroy(dotprod_rrrf _q)
+{
+    _mm_free(_q->h);
+    free(_q);
+}
+
+void dotprod_rrrf_print(dotprod_rrrf _q)
+{
+    printf("dotprod_rrrf [sse4.1/4.2, %u coefficients]\n", _q->n);
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        printf("%3u : %12.9f\n", i, _q->h[i]);
+}
+
+// 
+void dotprod_rrrf_execute(dotprod_rrrf _q,
+                          float *      _x,
+                          float *      _y)
+{
+    // switch based on size
+    if (_q->n < 16) {
+        dotprod_rrrf_execute_sse4(_q, _x, _y);
+    } else {
+        dotprod_rrrf_execute_sse4u(_q, _x, _y);
+    }
+}
+
+// use MMX/SSE extensions
+void dotprod_rrrf_execute_sse4(dotprod_rrrf _q,
+                               float *      _x,
+                               float *      _y)
+{
+    __m128 v;   // input vector
+    __m128 h;   // coefficients vector
+    __m128 s;   // dot product
+    __m128 sum = _mm_setzero_ps(); // load zeros into sum register
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (_q->n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = _mm_loadu_ps(&_x[i]);
+
+        // load coefficients into register (aligned)
+        h = _mm_load_ps(&_q->h[i]);
+
+        // compute dot product
+        s = _mm_dp_ps(v, h, 0xffffffff);
+        
+        // parallel addition
+        sum = _mm_add_ps( sum, s );
+    }
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+    // unload packed array
+    _mm_store_ps(w, sum);
+    float total = w[0];
+
+    // cleanup
+    for (; i<_q->n; i++)
+        total += _x[i] * _q->h[i];
+
+    // set return value
+    *_y = total;
+}
+
+// use MMX/SSE extensions (unrolled)
+void dotprod_rrrf_execute_sse4u(dotprod_rrrf _q,
+                                float *      _x,
+                                float *      _y)
+{
+    __m128 v0, v1, v2, v3;
+    __m128 h0, h1, h2, h3;
+    __m128 s0, s1, s2, s3;
+    __m128 sum = _mm_setzero_ps(); // load zeros into sum register
+
+    // t = 4*(floor(_n/16))
+    unsigned int r = (_q->n >> 4) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<r; i+=4) {
+        // load inputs into register (unaligned)
+        v0 = _mm_loadu_ps(&_x[4*i+ 0]);
+        v1 = _mm_loadu_ps(&_x[4*i+ 4]);
+        v2 = _mm_loadu_ps(&_x[4*i+ 8]);
+        v3 = _mm_loadu_ps(&_x[4*i+12]);
+
+        // load coefficients into register (aligned)
+        h0 = _mm_load_ps(&_q->h[4*i+ 0]);
+        h1 = _mm_load_ps(&_q->h[4*i+ 4]);
+        h2 = _mm_load_ps(&_q->h[4*i+ 8]);
+        h3 = _mm_load_ps(&_q->h[4*i+12]);
+
+        // compute dot products
+        s0 = _mm_dp_ps(v0, h0, 0xffffffff);
+        s1 = _mm_dp_ps(v1, h1, 0xffffffff);
+        s2 = _mm_dp_ps(v2, h2, 0xffffffff);
+        s3 = _mm_dp_ps(v3, h3, 0xffffffff);
+        
+        // parallel addition
+        // FIXME: these additions are by far the limiting factor
+        sum = _mm_add_ps( sum, s0 );
+        sum = _mm_add_ps( sum, s1 );
+        sum = _mm_add_ps( sum, s2 );
+        sum = _mm_add_ps( sum, s3 );
+    }
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+    // unload packed array
+    _mm_store_ps(w, sum);
+    float total = w[0];
+
+    // cleanup
+    for (i=4*r; i<_q->n; i++)
+        total += _x[i] * _q->h[i];
+
+    // set return value
+    *_y = total;
+}
+
diff --git a/src/dotprod/src/sumsq.c b/src/dotprod/src/sumsq.c
new file mode 100644
index 0000000..e89bb3e
--- /dev/null
+++ b/src/dotprod/src/sumsq.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sumsq.c : sum of squares
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "liquid.internal.h"
+
+// sum squares, basic loop
+//  _v      :   input array [size: 1 x _n]
+//  _n      :   input length
+float liquid_sumsqf(float *      _v,
+                    unsigned int _n)
+{
+    // initialize accumulator
+    float r=0;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // run computation in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        r += _v[i  ] * _v[i  ];
+        r += _v[i+1] * _v[i+1];
+        r += _v[i+2] * _v[i+2];
+        r += _v[i+3] * _v[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        r += _v[i] * _v[i];
+
+    // return result
+    return r;
+}
+
+// sum squares, basic loop
+//  _v      :   input array [size: 1 x _n]
+//  _n      :   input length
+float liquid_sumsqcf(float complex * _v,
+                     unsigned int    _n)
+{
+    // simple method: type cast input as real pointer, run double
+    // length sumsqf method
+    float * v = (float*) _v;
+    return liquid_sumsqf(v, 2*_n);
+}
+
diff --git a/src/dotprod/src/sumsq.mmx.c b/src/dotprod/src/sumsq.mmx.c
new file mode 100644
index 0000000..79bdada
--- /dev/null
+++ b/src/dotprod/src/sumsq.mmx.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sumsq.mmx.c : floating-point sum of squares (MMX)
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "liquid.internal.h"
+
+// include proper SIMD extensions for x86 platforms
+// NOTE: these pre-processor macros are defined in config.h
+
+#if HAVE_MMINTRIN_H
+#include <mmintrin.h>   // MMX
+#endif
+
+#if HAVE_XMMINTRIN_H
+#include <xmmintrin.h>  // SSE
+#endif
+
+#if HAVE_EMMINTRIN_H
+#include <emmintrin.h>  // SSE2
+#endif
+
+#if HAVE_PMMINTRIN_H
+#include <pmmintrin.h>  // SSE3
+#endif
+
+// sum squares, basic loop
+//  _v      :   input array [size: 1 x _n]
+//  _n      :   input length
+float liquid_sumsqf(float *      _v,
+                    unsigned int _n)
+{
+    // first cut: ...
+    __m128 v;   // input vector
+    __m128 s;   // dot product
+    __m128 sum = _mm_setzero_ps(); // load zeros into sum register
+
+    // t = 4*(floor(_n/4))
+    unsigned int t = (_n >> 2) << 2;
+
+    //
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        // load inputs into register (unaligned)
+        v = _mm_loadu_ps(&_v[i]);
+
+        // compute multiplication
+        s = _mm_mul_ps(v, v);
+       
+        // parallel addition
+        sum = _mm_add_ps( sum, s );
+    }
+
+    // aligned output array
+    float w[4] __attribute__((aligned(16)));
+
+#if HAVE_PMMINTRIN_H
+    // fold down into single value
+    __m128 z = _mm_setzero_ps();
+    sum = _mm_hadd_ps(sum, z);
+    sum = _mm_hadd_ps(sum, z);
+   
+    // unload single (lower value)
+    _mm_store_ss(w, sum);
+    float total = w[0];
+#else
+    // unload packed array
+    _mm_store_ps(w, sum);
+    float total = w[0] + w[1] + w[2] + w[3];
+#endif
+
+    // cleanup
+    for (; i<_n; i++)
+        total += _v[i] * _v[i];
+
+    // set return value
+    return total;
+}
+
+// sum squares, basic loop
+//  _v      :   input array [size: 1 x _n]
+//  _n      :   input length
+float liquid_sumsqcf(float complex * _v,
+                     unsigned int    _n)
+{
+    // simple method: type cast input as real pointer, run double
+    // length sumsqf method
+    float * v = (float*) _v;
+    return liquid_sumsqf(v, 2*_n);
+}
diff --git a/src/dotprod/tests/dotprod_cccf_autotest.c b/src/dotprod/tests/dotprod_cccf_autotest.c
new file mode 100644
index 0000000..e84dd59
--- /dev/null
+++ b/src/dotprod/tests/dotprod_cccf_autotest.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST: compare structured result to oridinal computation
+//
+
+// 
+// AUTOTEST: dot product with floating-point data
+//
+void autotest_dotprod_cccf_rand16()
+{
+    float complex h[16] = {
+      0.17702709 +   1.38978455*_Complex_I,  0.91294148 +   0.39217381*_Complex_I,
+     -0.80607338 +   0.76477512*_Complex_I,  0.05099755 +  -0.87350051*_Complex_I,
+      0.44513826 +  -0.49490569*_Complex_I,  0.14754967 +   2.04349962*_Complex_I,
+      1.07246623 +   1.08146290*_Complex_I, -1.14028088 +   1.83380899*_Complex_I,
+      0.38105361 +  -0.45591846*_Complex_I,  0.32605401 +   0.34440081*_Complex_I,
+     -0.05477144 +   0.60832595*_Complex_I,  1.81667523 +  -1.12238075*_Complex_I,
+     -0.87190497 +   1.10743858*_Complex_I,  1.30921403 +   1.24438643*_Complex_I,
+      0.55524695 +  -1.94931519*_Complex_I, -0.87191170 +   0.91693119*_Complex_I,
+    };
+
+    float complex x[16] = {
+     -2.19591953 +  -0.93229692*_Complex_I,  0.17150376 +   0.56165114*_Complex_I,
+      1.58354529 +  -0.50696037*_Complex_I,  1.40929619 +   0.87868803*_Complex_I,
+     -0.75505072 +  -0.30867372*_Complex_I, -0.09821367 +  -0.73949106*_Complex_I,
+      0.03785571 +   0.72763665*_Complex_I, -1.20262636 +  -0.88838102*_Complex_I,
+      0.23323685 +   0.12456235*_Complex_I,  0.34593736 +   0.02529594*_Complex_I,
+      0.33669564 +   0.39064649*_Complex_I, -2.45003867 +  -0.54862205*_Complex_I,
+     -2.64870707 +   2.33444473*_Complex_I, -0.92284477 +  -2.45121397*_Complex_I,
+      0.24852918 +  -0.62409860*_Complex_I, -0.87039907 +   0.90921212*_Complex_I,
+    };
+
+    float complex y;
+    float complex test = -0.604285042605890 - 12.390925785344704 * _Complex_I;
+
+    float tol = 1e-3f;
+
+    dotprod_cccf_run(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    dotprod_cccf_run4(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    // test object
+    dotprod_cccf q = dotprod_cccf_create(h,16);
+    dotprod_cccf_execute(q,x,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+    dotprod_cccf_destroy(q);
+}
+
+// 
+// AUTOTEST: structured dot product, odd lengths
+//
+void autotest_dotprod_cccf_struct_lengths()
+{
+    float tol = 2e-6;
+    float complex y;
+
+    float complex h[35] = {
+      1.11555653 +   2.30658043*_Complex_I, -0.36133676 +  -0.10917327*_Complex_I,
+      0.17714505 +  -2.14631440*_Complex_I,  2.20424609 +   0.59063608*_Complex_I,
+     -0.44699194 +   0.23369318*_Complex_I,  0.60613931 +   0.21868288*_Complex_I,
+     -1.18746289 +  -0.52159563*_Complex_I, -0.46277775 +   0.75010157*_Complex_I,
+      0.93796307 +   0.28608151*_Complex_I, -2.18699829 +   0.38029319*_Complex_I,
+      0.16145611 +   0.18343353*_Complex_I, -0.62653631 +  -1.79037656*_Complex_I,
+     -0.67042462 +   0.11044084*_Complex_I,  0.70333438 +   1.78729174*_Complex_I,
+     -0.32923580 +   0.78514690*_Complex_I,  0.27534332 +  -0.56377431*_Complex_I,
+      0.41492559 +   1.37176526*_Complex_I,  3.25368958 +   2.70495218*_Complex_I,
+      1.63002035 +  -0.14193750*_Complex_I,  2.22057186 +   0.55056461*_Complex_I,
+      1.40896777 +   0.80722903*_Complex_I, -0.22334033 +  -0.14227395*_Complex_I,
+     -1.48631186 +   0.53610531*_Complex_I, -1.91632185 +   0.88755083*_Complex_I,
+     -0.52054895 +  -0.35572001*_Complex_I, -1.56515607 +  -0.41448794*_Complex_I,
+     -0.91107117 +   0.17059659*_Complex_I, -0.77007659 +   2.73381816*_Complex_I,
+     -0.46645585 +   0.38994666*_Complex_I,  0.80317663 +  -0.41756968*_Complex_I,
+      0.26992512 +   0.41828145*_Complex_I, -0.72456446 +   1.25002030*_Complex_I,
+      1.19573306 +   0.98449546*_Complex_I,  1.42491943 +  -0.55426305*_Complex_I,
+      1.08243614 +   0.35774368*_Complex_I, };
+
+    float complex x[35] = {
+     -0.82466736 +  -1.39329228*_Complex_I, -1.46176052 +  -1.96218827*_Complex_I,
+     -1.28388174 +  -0.07152934*_Complex_I, -0.51910014 +  -0.37915971*_Complex_I,
+     -0.65964708 +  -0.98417534*_Complex_I, -1.40213479 +  -0.82198463*_Complex_I,
+      0.86051446 +   0.97926463*_Complex_I,  0.26257342 +   0.76586696*_Complex_I,
+      0.72174183 +  -1.89884636*_Complex_I, -0.26018863 +   1.06920599*_Complex_I,
+      0.57949117 +  -0.77431546*_Complex_I,  0.84635184 +  -0.81123009*_Complex_I,
+     -1.12637629 +  -0.42027412*_Complex_I, -1.04214881 +   0.90519721*_Complex_I,
+      0.54458433 +  -1.03487314*_Complex_I, -0.17847893 +   2.20358978*_Complex_I,
+      0.19642532 +  -0.07449796*_Complex_I, -1.84958229 +   0.13218920*_Complex_I,
+     -1.49042886 +   0.81610408*_Complex_I, -0.27466940 +  -1.48438409*_Complex_I,
+      0.29239375 +   0.72443343*_Complex_I, -1.20243456 +  -2.77032750*_Complex_I,
+     -0.41784260 +   0.77455254*_Complex_I,  0.37737465 +  -0.52426993*_Complex_I,
+     -1.25500377 +   1.76270122*_Complex_I,  1.55976056 +  -1.18189171*_Complex_I,
+     -0.05111343 +  -1.18849396*_Complex_I, -1.92966664 +   0.66504899*_Complex_I,
+     -2.82387897 +   1.41128242*_Complex_I, -1.48171326 +  -0.03347470*_Complex_I,
+      0.38047273 +  -1.40969799*_Complex_I,  1.71995272 +   0.00298203*_Complex_I,
+      0.56040910 +  -0.12713027*_Complex_I, -0.46653022 +  -0.65450499*_Complex_I,
+      0.15515755 +   1.58944030*_Complex_I, };
+
+    float complex v32 = -11.5100903519506 - 15.3575526884014*_Complex_I;
+    float complex v33 = -10.7148314918614 - 14.9578463360225*_Complex_I;
+    float complex v34 = -11.7423673921916 - 15.6318827515320*_Complex_I;
+    float complex v35 = -12.1430314741466 - 13.8559085000689*_Complex_I;
+
+    // 
+    dotprod_cccf dp;
+
+    // n = 32
+    dp = dotprod_cccf_create(h,32);
+    dotprod_cccf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v32, tol);
+    dotprod_cccf_destroy(dp);
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-cccf-32 : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                crealf(y), cimagf(y), crealf(v32), cimagf(v32));
+    }
+
+    // n = 33
+    dp = dotprod_cccf_create(h,33);
+    dotprod_cccf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v33, tol);
+    dotprod_cccf_destroy(dp);
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-cccf-33 : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                crealf(y), cimagf(y), crealf(v33), cimagf(v33));
+    }
+
+    // n = 34
+    dp = dotprod_cccf_create(h,34);
+    dotprod_cccf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v34, tol);
+    dotprod_cccf_destroy(dp);
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-cccf-34 : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                crealf(y), cimagf(y), crealf(v34), cimagf(v34));
+    }
+
+    // n = 35
+    dp = dotprod_cccf_create(h,35);
+    dotprod_cccf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v35, tol);
+    dotprod_cccf_destroy(dp);
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-cccf-35 : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                crealf(y), cimagf(y), crealf(v35), cimagf(v35));
+    }
+}
+
+
+
+// helper function (compare structured object to ordinal computation)
+void runtest_dotprod_cccf(unsigned int _n)
+{
+    float tol = 1e-3;
+    float complex h[_n];
+    float complex x[_n];
+
+    // generate random coefficients
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        h[i] = randnf() + randnf() * _Complex_I;
+        x[i] = randnf() + randnf() * _Complex_I;
+    }
+    
+    // compute expected value (ordinal computation)
+    float complex y_test=0;
+    for (i=0; i<_n; i++)
+        y_test += h[i] * x[i];
+
+    // create and run dot product object
+    float complex y;
+    dotprod_cccf dp;
+    dp = dotprod_cccf_create(h,_n);
+    dotprod_cccf_execute(dp, x, &y);
+    dotprod_cccf_destroy(dp);
+
+    // print results
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-cccf-%-4u : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                _n, crealf(y), cimagf(y), crealf(y_test), cimagf(y_test));
+    }
+
+    // validate result
+    CONTEND_DELTA(crealf(y), crealf(y_test), tol);
+    CONTEND_DELTA(cimagf(y), cimagf(y_test), tol);
+}
+
+// compare structured object to ordinal computation
+void autotest_dotprod_cccf_struct_vs_ordinal()
+{
+    // run many, many tests
+    unsigned int i;
+    for (i=1; i<=512; i++)
+        runtest_dotprod_cccf(i);
+}
+
diff --git a/src/dotprod/tests/dotprod_crcf_autotest.c b/src/dotprod/tests/dotprod_crcf_autotest.c
new file mode 100644
index 0000000..2da0527
--- /dev/null
+++ b/src/dotprod/tests/dotprod_crcf_autotest.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST: dot product with floating-point data
+//
+void autotest_dotprod_crcf_rand01()
+{
+    float h[16] = {
+      5.5375e-02,  -6.5857e-01,  -1.7657e+00,   7.7444e-01, 
+      8.0730e-01,  -5.1340e-01,  -9.3437e-02,  -5.6301e-01, 
+     -6.6480e-01,  -2.1673e+00,   9.0269e-01,   3.5284e+00, 
+     -9.7835e-01,  -6.9512e-01,  -1.2958e+00,   1.1628e+00
+    };
+
+    float complex x[16] = {
+      1.3164e+00+  5.4161e-01*_Complex_I,   1.8295e-01+ -9.0284e-02*_Complex_I, 
+      1.3487e+00+ -1.8148e+00*_Complex_I,  -7.4696e-01+ -4.1792e-01*_Complex_I, 
+     -9.0551e-01+ -4.4294e-01*_Complex_I,   6.0591e-01+ -1.5383e+00*_Complex_I, 
+     -7.5393e-01+ -3.5691e-01*_Complex_I,  -4.5733e-01+  1.1926e-01*_Complex_I, 
+     -1.4744e-01+ -4.7676e-02*_Complex_I,  -1.2422e+00+ -2.0213e+00*_Complex_I, 
+      3.3208e-02+ -1.3756e+00*_Complex_I,  -4.8573e-01+  1.0977e+00*_Complex_I, 
+      1.5053e+00+  2.1141e-01*_Complex_I,  -8.4062e-01+ -1.0211e+00*_Complex_I, 
+     -1.3932e+00+ -4.8491e-01*_Complex_I,  -1.4234e+00+  2.0333e-01*_Complex_I
+    };
+
+    float complex y;
+    float complex test = -3.35346556487224 + 11.78023318618137*_Complex_I;
+    float tol = 1e-3f;
+
+    dotprod_crcf_run(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    dotprod_crcf_run4(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    // test object
+    dotprod_crcf q = dotprod_crcf_create(h,16);
+    dotprod_crcf_execute(q,x,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+    dotprod_crcf_destroy(q);
+}
+
+
+
+// 
+// AUTOTEST: dot product with floating-point data
+//
+void autotest_dotprod_crcf_rand02()
+{
+    float h[16] = {
+      4.7622e-01,   7.1453e-01,  -7.1370e-01,  -1.6457e-01, 
+     -1.1573e-01,   6.4114e-01,  -1.0688e+00,  -1.6761e+00, 
+     -1.0376e+00,  -1.0991e+00,  -2.4161e-01,   4.6065e-01, 
+     -1.0403e+00,  -1.1424e-01,  -1.2371e+00,  -7.9723e-01
+    };
+
+    float complex x[16] = {
+     -8.3558e-01+  3.0504e-01*_Complex_I,  -6.3004e-01+  2.4680e-01*_Complex_I, 
+      9.6908e-01+  1.2978e+00*_Complex_I,  -2.0587e+00+  9.5385e-01*_Complex_I, 
+      2.5692e-01+ -1.7314e+00*_Complex_I,  -1.2237e+00+ -6.2139e-02*_Complex_I, 
+      5.0300e-02+ -9.2092e-01*_Complex_I,  -1.8816e-01+  7.0746e-02*_Complex_I, 
+     -2.4177e+00+  8.3177e-01*_Complex_I,   1.6871e-01+ -8.5129e-02*_Complex_I, 
+      6.5203e-01+  2.0739e-02*_Complex_I,  -1.2331e-01+ -9.7920e-01*_Complex_I, 
+      8.2352e-01+  9.1093e-01*_Complex_I,   1.5161e+00+ -9.1865e-01*_Complex_I, 
+     -2.0892e+00+  2.7759e-02*_Complex_I,  -2.5188e-01+  2.5568e-01*_Complex_I
+    };
+
+    float complex y;
+    float complex test = 2.11053363855085 - 2.04167493441477*_Complex_I;
+    float tol = 1e-3f;
+
+    dotprod_crcf_run(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    dotprod_crcf_run4(h,x,16,&y);
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+
+    // test object
+    dotprod_crcf q = dotprod_crcf_create(h,16);
+    dotprod_crcf_execute(q,x,&y);
+    if (liquid_autotest_verbose) {
+        printf("  dotprod : %12.8f + j%12.8f (expected: %12.8f + j%12.8f)\n",
+                crealf(y), cimagf(y), crealf(test), cimagf(test));
+    }
+    CONTEND_DELTA( crealf(y), crealf(test), tol);
+    CONTEND_DELTA( cimagf(y), cimagf(test), tol);
+    dotprod_crcf_destroy(q);
+}
+
+// 
+// AUTOTEST: compare structured result to oridinal computation
+//
+
+// helper function (compare structured object to ordinal computation)
+void runtest_dotprod_crcf(unsigned int _n)
+{
+    float tol = 1e-4;
+    float h[_n];
+    float complex x[_n];
+
+    // generate random coefficients
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        h[i] = randnf();
+        x[i] = randnf() + randnf() * _Complex_I;
+    }
+    
+    // compute expected value (ordinal computation)
+    float complex y_test=0;
+    for (i=0; i<_n; i++)
+        y_test += h[i] * x[i];
+
+    // create and run dot product object
+    float complex y;
+    dotprod_crcf dp;
+    dp = dotprod_crcf_create(h,_n);
+    dotprod_crcf_execute(dp, x, &y);
+    dotprod_crcf_destroy(dp);
+
+    // print results
+    if (liquid_autotest_verbose) {
+        printf("  dotprod-crcf-%-4u : %12.8f + j%12.8f (expected %12.8f + j%12.8f)\n",
+                _n, crealf(y), cimagf(y), crealf(y_test), cimagf(y_test));
+    }
+
+    // validate result
+    CONTEND_DELTA(crealf(y), crealf(y_test), tol);
+    CONTEND_DELTA(cimagf(y), cimagf(y_test), tol);
+}
+
+// compare structured object to ordinal computation
+void autotest_dotprod_crcf_struct_vs_ordinal()
+{
+    // run many, many tests
+    unsigned int i;
+    for (i=1; i<=512; i++)
+        runtest_dotprod_crcf(i);
+}
+
diff --git a/src/dotprod/tests/dotprod_rrrf_autotest.c b/src/dotprod/tests/dotprod_rrrf_autotest.c
new file mode 100644
index 0000000..1a14a69
--- /dev/null
+++ b/src/dotprod/tests/dotprod_rrrf_autotest.c
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST: basic dot product
+//
+void autotest_dotprod_rrrf_basic()
+{
+    float tol = 1e-6;   // error tolerance
+    float y;            // return value
+
+    float h[16] = {
+        1, -1, 1, -1, 1, -1, 1, -1,
+        1, -1, 1, -1, 1, -1, 1, -1};
+
+    float x0[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+    float test0 = 0;
+    dotprod_rrrf_run(h,x0,16,&y);
+    CONTEND_DELTA(y,  test0, tol);
+    dotprod_rrrf_run4(h,x0,16,&y);
+    CONTEND_DELTA(y,  test0, tol);
+
+    float x1[16] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
+    float test1 = 0;
+    dotprod_rrrf_run(h,x1,16,&y);
+    CONTEND_DELTA(y,  test1, tol);
+    dotprod_rrrf_run4(h,x1,16,&y);
+    CONTEND_DELTA(y,  test1, tol);
+
+    float x2[16] = {0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1};
+    float test2 = -8;
+    dotprod_rrrf_run(h,x2,16,&y);
+    CONTEND_DELTA(y,  test2, tol);
+    dotprod_rrrf_run4(h,x2,16,&y);
+    CONTEND_DELTA(y,  test2, tol);
+
+    float x3[16] = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0};
+    float test3 = 8;
+    dotprod_rrrf_run(h,x3,16,&y);
+    CONTEND_DELTA(y,  test3, tol);
+    dotprod_rrrf_run4(h,x3,16,&y);
+    CONTEND_DELTA(y,  test3, tol);
+
+    float test4 = 16;
+    dotprod_rrrf_run(h,h,16,&y);
+    CONTEND_DELTA(y,  test4, tol);
+    dotprod_rrrf_run4(h,h,16,&y);
+    CONTEND_DELTA(y,  test4, tol);
+
+}
+
+// 
+// AUTOTEST: uneven dot product
+//
+void autotest_dotprod_rrrf_uneven()
+{
+    float tol = 1e-6;
+    float y;
+
+    float h[16] = {
+        1, -1, 1, -1, 1, -1, 1, -1,
+        1, -1, 1, -1, 1, -1, 1, -1};
+
+    float x[16] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
+
+    float test1 = 1;
+    dotprod_rrrf_run(h,x,1,&y);
+    CONTEND_DELTA(y,  test1, tol);
+
+    float test2 = 0;
+    dotprod_rrrf_run(h,x,2,&y);
+    CONTEND_DELTA(y, test2, tol);
+
+    float test3 = 1;
+    dotprod_rrrf_run(h,x,3,&y);
+    CONTEND_DELTA(y, test3, tol);
+
+    float test11 = 1;
+    dotprod_rrrf_run(h,x,11,&y);
+    CONTEND_DELTA(y, test11, tol);
+
+    float test13 = 1;
+    dotprod_rrrf_run(h,x,13,&y);
+    CONTEND_DELTA(y, test13, tol);
+
+    float test15 = 1;
+    dotprod_rrrf_run(h,x,15,&y);
+    CONTEND_DELTA(y, test15, tol);
+
+}
+
+// 
+// AUTOTEST: structured dot product
+//
+void autotest_dotprod_rrrf_struct()
+{
+    float tol = 1e-6;
+    float y;
+
+    float h[16] = {
+        1, -1, 1, -1, 1, -1, 1, -1,
+        1, -1, 1, -1, 1, -1, 1, -1};
+
+    // create object
+    dotprod_rrrf dp = dotprod_rrrf_create(h,16);
+
+    float x0[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+    float test0 = 0;
+    dotprod_rrrf_execute(dp,x0,&y);
+    CONTEND_DELTA(y,  test0, tol);
+
+    float x1[16] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
+    float test1 = 0;
+    dotprod_rrrf_execute(dp,x1,&y);
+    CONTEND_DELTA(y,  test1, tol);
+
+    float x2[16] = {0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1};
+    float test2 = -8;
+    dotprod_rrrf_execute(dp,x2,&y);
+    CONTEND_DELTA(y,  test2, tol);
+
+    float *x3 = h;
+    float test3 = 16;
+    dotprod_rrrf_execute(dp,x3,&y);
+    CONTEND_DELTA(y,  test3, tol);
+
+    // clean-up
+    dotprod_rrrf_destroy(dp);
+}
+
+// 
+// AUTOTEST: structured dot product with floating-point data
+//
+void autotest_dotprod_rrrf_struct_align()
+{
+    float h[16] = {
+    -0.050565, -0.952580,  0.274320,  1.232400, 
+     1.268200,  0.565770,  0.800830,  0.923970, 
+     0.517060, -0.530340, -0.378550, -1.127100, 
+     1.123100, -1.006000, -1.483800, -0.062007
+    };
+
+    float x[16] = {
+    -0.384280, -0.812030,  0.156930,  1.919500, 
+     0.564580, -0.123610, -0.138640,  0.004984, 
+    -1.100200, -0.497620,  0.089977, -1.745500, 
+     0.463640,  0.592100,  1.150000, -1.225400
+    };
+
+    float test = 3.66411513609863;
+    float tol = 1e-3f;
+    float y;
+
+    // create dotprod object
+    dotprod_rrrf dp = dotprod_rrrf_create(h,16);
+
+    // test data mis-alignment conditions
+    float x_buffer[20];
+    float * x_hat;
+    unsigned int i;
+    for (i=0; i<4; i++) {
+        // set pointer to array aligned with counter
+        x_hat = &x_buffer[i];
+
+        // copy input data to buffer
+        memmove(x_hat, x, 16*sizeof(float));
+        
+        // execute dotprod
+        dotprod_rrrf_execute(dp,x_hat,&y);
+        CONTEND_DELTA(y,test,tol);
+    }
+
+    // destroy dotprod object
+    dotprod_rrrf_destroy(dp);
+}
+
+
+// 
+// AUTOTEST: dot product with floating-point data
+//
+void autotest_dotprod_rrrf_rand01()
+{
+    float h[16] = {
+    -0.050565, -0.952580,  0.274320,  1.232400, 
+     1.268200,  0.565770,  0.800830,  0.923970, 
+     0.517060, -0.530340, -0.378550, -1.127100, 
+     1.123100, -1.006000, -1.483800, -0.062007
+    };
+
+    float x[16] = {
+    -0.384280, -0.812030,  0.156930,  1.919500, 
+     0.564580, -0.123610, -0.138640,  0.004984, 
+    -1.100200, -0.497620,  0.089977, -1.745500, 
+     0.463640,  0.592100,  1.150000, -1.225400
+    };
+
+    float test = 3.66411513609863;
+    float tol = 1e-3f;
+    float y;
+
+    dotprod_rrrf_run(h,x,16,&y);
+    CONTEND_DELTA(y,test,tol);
+}
+
+// 
+// AUTOTEST: dot product with floating-point data
+//
+void autotest_dotprod_rrrf_rand02()
+{
+    float h[16] = {
+     2.595300,  1.243600, -0.818550, -1.439800, 
+     0.055795, -1.476000,  0.445900,  0.325460, 
+    -3.451200,  0.058528, -0.246990,  0.476290, 
+    -0.598780, -0.885250,  0.464660, -0.610140
+    };
+
+    float x[16] = {
+    -0.917010, -1.278200, -0.533190,  2.309200, 
+     0.592980,  0.964820,  0.183220, -0.082864, 
+     0.057171, -1.186500, -0.738260,  0.356960, 
+    -0.144000, -1.435200, -0.893420,  1.657800
+    };
+
+    float test = -8.17832326680587;
+    float tol = 1e-3f;
+    float y;
+
+    dotprod_rrrf_run(h,x,16,&y);
+    CONTEND_DELTA(y,test,tol);
+}
+
+// 
+// AUTOTEST: structured dot product, odd lengths
+//
+void autotest_dotprod_rrrf_struct_lengths()
+{
+    float tol = 2e-6;
+    float y;
+
+    float x[35] = {
+         0.03117498,  -1.54311769,  -0.58759073,  -0.73882202, 
+         0.86592259,  -0.26669417,  -0.70153724,  -1.24555787, 
+        -1.09272288,  -1.41984975,  -1.40299260,   0.95861481, 
+        -0.67361246,   2.05305710,   1.26576873,  -0.77474848, 
+        -0.93143252,  -1.05724660,   0.21455006,   1.07554168, 
+        -0.46703810,   0.68878404,  -1.11900266,  -0.52016966, 
+         0.61400744,  -0.46506142,  -0.16801031,   0.48237303, 
+         0.51286055,  -0.57239385,  -0.64462740,  -0.75596668, 
+         1.95612355,  -0.47917908,   0.52384983, };
+    float h[35] = {
+        -0.12380948,   0.88417134,   2.27373797,  -2.61506417, 
+         0.35022002,   0.07481393,   0.52984228,  -0.65542307, 
+        -2.14893606,   0.62466395,   0.07330391,  -1.28014856, 
+         0.16347776,   0.21238151,   0.05462232,  -0.60290942, 
+        -1.27658956,   3.05114996,   1.34789601,  -1.22098592, 
+         1.70899633,  -0.41002037,   3.08009931,  -1.39895771, 
+        -0.50875066,   0.25817865,   1.08668549,   0.05494174, 
+        -1.05337166,   1.26772604,   1.00369204,  -0.55129338, 
+         1.01828299,   0.76014664,  -0.15605569, };
+
+    float v32 = -7.99577847f;
+    float v33 = -6.00389114f;
+    float v34 = -6.36813751f;
+    float v35 = -6.44988725f;
+
+    // 
+    dotprod_rrrf dp;
+
+    // n = 32
+    dp = dotprod_rrrf_create(h,32);
+    dotprod_rrrf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v32, tol);
+    dotprod_rrrf_destroy(dp);
+    if (liquid_autotest_verbose)
+        printf("  dotprod-rrrf-32 : %12.8f (expected %12.8f)\n", y, v32);
+
+    // n = 33
+    dp = dotprod_rrrf_create(h,33);
+    dotprod_rrrf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v33, tol);
+    dotprod_rrrf_destroy(dp);
+    if (liquid_autotest_verbose)
+        printf("  dotprod-rrrf-33 : %12.8f (expected %12.8f)\n", y, v33);
+
+    // n = 34
+    dp = dotprod_rrrf_create(h,34);
+    dotprod_rrrf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v34, tol);
+    dotprod_rrrf_destroy(dp);
+    if (liquid_autotest_verbose)
+        printf("  dotprod-rrrf-34 : %12.8f (expected %12.8f)\n", y, v34);
+
+    // n = 35
+    dp = dotprod_rrrf_create(h,35);
+    dotprod_rrrf_execute(dp, x, &y);
+    CONTEND_DELTA(y, v35, tol);
+    dotprod_rrrf_destroy(dp);
+    if (liquid_autotest_verbose)
+        printf("  dotprod-rrrf-35 : %12.8f (expected %12.8f)\n", y, v35);
+}
+
+// 
+// AUTOTEST: compare structured result to oridinal computation
+//
+
+// helper function (compare structured object to ordinal computation)
+void runtest_dotprod_rrrf(unsigned int _n)
+{
+    float tol = 1e-4;
+    float h[_n];
+    float x[_n];
+
+    // generate random coefficients
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        h[i] = randnf();
+        x[i] = randnf();
+    }
+    
+    // compute expected value (ordinal computation)
+    float y_test=0;
+    for (i=0; i<_n; i++)
+        y_test += h[i] * x[i];
+
+    // create and run dot product object
+    float y;
+    dotprod_rrrf dp;
+    dp = dotprod_rrrf_create(h,_n);
+    dotprod_rrrf_execute(dp, x, &y);
+    dotprod_rrrf_destroy(dp);
+
+    // print results
+    if (liquid_autotest_verbose)
+        printf("  dotprod-rrrf-%-4u : %12.8f (expected %12.8f)\n", _n, y, y_test);
+
+    // validate result
+    CONTEND_DELTA(y, y_test, tol);
+}
+
+// compare structured object to ordinal computation
+void autotest_dotprod_rrrf_struct_vs_ordinal()
+{
+    // run many, many tests
+    unsigned int i;
+    for (i=1; i<=512; i++)
+        runtest_dotprod_rrrf(i);
+}
+
diff --git a/src/dotprod/tests/sumsqcf_autotest.c b/src/dotprod/tests/sumsqcf_autotest.c
new file mode 100644
index 0000000..b2c5589
--- /dev/null
+++ b/src/dotprod/tests/sumsqcf_autotest.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// test data
+float complex sumsqcf_test_x3[3];   float sumsqcf_test_y3;
+float complex sumsqcf_test_x4[4];   float sumsqcf_test_y4;
+float complex sumsqcf_test_x7[7];   float sumsqcf_test_y7;
+float complex sumsqcf_test_x8[8];   float sumsqcf_test_y8;
+float complex sumsqcf_test_x15[15]; float sumsqcf_test_y15;
+float complex sumsqcf_test_x16[16]; float sumsqcf_test_y16;
+
+// helper function
+void sumsqcf_runtest(float complex * _x,
+                     unsigned int    _n,
+                     float           _y)
+{
+    float tol = 1e-6;   // error tolerance
+
+    // run test
+    float y = liquid_sumsqcf(_x, _n);
+
+    CONTEND_DELTA( y, _y, tol );
+}
+
+// 
+// AUTOTESTS : run test with pre-determined data sets
+//
+void autotest_sumsqcf_3()   {   sumsqcf_runtest( sumsqcf_test_x3,  3,  sumsqcf_test_y3  );  }
+void autotest_sumsqcf_4()   {   sumsqcf_runtest( sumsqcf_test_x4,  4,  sumsqcf_test_y4  );  }
+void autotest_sumsqcf_7()   {   sumsqcf_runtest( sumsqcf_test_x7,  7,  sumsqcf_test_y7  );  }
+void autotest_sumsqcf_8()   {   sumsqcf_runtest( sumsqcf_test_x8,  8,  sumsqcf_test_y8  );  }
+void autotest_sumsqcf_15()  {   sumsqcf_runtest( sumsqcf_test_x15, 15, sumsqcf_test_y15 );  }
+void autotest_sumsqcf_16()  {   sumsqcf_runtest( sumsqcf_test_x16, 16, sumsqcf_test_y16 );  }
+
+float complex sumsqcf_test_x3[3] = {
+  -0.143606511525 +  -0.137405158308*_Complex_I,
+  -0.155077565599 +  -0.128712786230*_Complex_I,
+   0.259257309730 +  -0.354313982924*_Complex_I};
+float sumsqcf_test_y3 = 0.272871791516851;
+
+float complex sumsqcf_test_x4[4] = {
+  -0.027688113439 +   0.014257850202*_Complex_I,
+   0.135913101830 +  -0.193497844930*_Complex_I,
+  -0.184688262513 +  -0.018367564232*_Complex_I,
+   0.033677897260 +  -0.365996497668*_Complex_I};
+float sumsqcf_test_y4 = 0.226418463954813;
+
+float complex sumsqcf_test_x7[7] = {
+  -0.052790293375 +   0.173778162166*_Complex_I,
+   0.026113336498 +  -0.228399854303*_Complex_I,
+   0.060259677552 +  -0.064704230326*_Complex_I,
+  -0.085637350173 +  -0.140391580928*_Complex_I,
+   0.137662823620 +  -0.049602389650*_Complex_I,
+   0.081078554377 +   0.103320097893*_Complex_I,
+  -0.140068020211 +  -0.028552894932*_Complex_I};
+float sumsqcf_test_y7 = 0.179790025178960;
+
+float complex sumsqcf_test_x8[8] = {
+  -0.114842287937 +  -0.044108491804*_Complex_I,
+  -0.027032488500 +  -0.098073597323*_Complex_I,
+  -0.248865158871 +  -0.058431293594*_Complex_I,
+   0.152349654138 +   0.011146740847*_Complex_I,
+   0.100890388238 +   0.037191727983*_Complex_I,
+  -0.173317554621 +  -0.287191794305*_Complex_I,
+   0.159045702603 +  -0.097006888823*_Complex_I,
+  -0.048463564653 +  -0.123659611524*_Complex_I};
+float sumsqcf_test_y8 = 0.290592731534459;
+
+float complex sumsqcf_test_x15[15] = {
+  -0.233166865552 +  -0.325575589001*_Complex_I,
+  -0.062157314569 +  -0.052675113778*_Complex_I,
+  -0.184924733094 +  -0.037448582846*_Complex_I,
+  -0.019336799407 +  -0.146627815330*_Complex_I,
+   0.014671587594 +  -0.040490423681*_Complex_I,
+  -0.070920638099 +   0.353056761369*_Complex_I,
+   0.342121380549 +   0.016365636789*_Complex_I,
+   0.407809024847 +  -0.067677610212*_Complex_I,
+   0.166345037028 +  -0.070618449000*_Complex_I,
+  -0.151572833379 +  -0.241061531174*_Complex_I,
+  -0.295395183108 +   0.107933512849*_Complex_I,
+   0.214887288420 +   0.158211288996*_Complex_I,
+   0.089528110626 +   0.534731503540*_Complex_I,
+  -0.387245894254 +   0.127860010582*_Complex_I,
+  -0.123711595377 +   0.212526707755*_Complex_I};
+float sumsqcf_test_y15 = 1.44880523546855;
+
+float complex sumsqcf_test_x16[16] = {
+  -0.065168142317 +   0.069453199546*_Complex_I,
+   0.175268433034 +  -0.227486860237*_Complex_I,
+  -0.190532229460 +   0.079975095234*_Complex_I,
+   0.119309235855 +  -0.238114343006*_Complex_I,
+   0.125737810036 +   0.045214179459*_Complex_I,
+  -0.197170380197 +  -0.159688600627*_Complex_I,
+   0.075166226059 +   0.148949236785*_Complex_I,
+  -0.290229918639 +   0.019293769432*_Complex_I,
+  -0.145299853755 +  -0.083512058709*_Complex_I,
+  -0.256618190275 +  -0.450932031739*_Complex_I,
+  -0.169487127499 +   0.187004249967*_Complex_I,
+   0.203885942759 +   0.121347578873*_Complex_I,
+  -0.176280563451 +  -0.304717971490*_Complex_I,
+   0.240587060249 +  -0.055540407201*_Complex_I,
+   0.022889112723 +   0.027170265053*_Complex_I,
+   0.265769617236 +  -0.023686695049*_Complex_I};
+float sumsqcf_test_y16 = 1.07446555417927;
diff --git a/src/dotprod/tests/sumsqf_autotest.c b/src/dotprod/tests/sumsqf_autotest.c
new file mode 100644
index 0000000..d671e5d
--- /dev/null
+++ b/src/dotprod/tests/sumsqf_autotest.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// test data
+float sumsqf_test_x3[3];    float sumsqf_test_y3;
+float sumsqf_test_x4[4];    float sumsqf_test_y4;
+float sumsqf_test_x7[7];    float sumsqf_test_y7;
+float sumsqf_test_x8[8];    float sumsqf_test_y8;
+float sumsqf_test_x15[15];  float sumsqf_test_y15;
+float sumsqf_test_x16[16];  float sumsqf_test_y16;
+
+// helper function
+void sumsqf_runtest(float *      _x,
+                    unsigned int _n,
+                    float        _y)
+{
+    float tol = 1e-6;   // error tolerance
+
+    // run test
+    float y = liquid_sumsqf(_x, _n);
+
+    CONTEND_DELTA( y, _y, tol );
+}
+
+// 
+// AUTOTESTS : run test with pre-determined data sets
+//
+void autotest_sumsqf_3()    {   sumsqf_runtest( sumsqf_test_x3,  3,  sumsqf_test_y3  ); }
+void autotest_sumsqf_4()    {   sumsqf_runtest( sumsqf_test_x4,  4,  sumsqf_test_y4  ); }
+void autotest_sumsqf_7()    {   sumsqf_runtest( sumsqf_test_x7,  7,  sumsqf_test_y7  ); }
+void autotest_sumsqf_8()    {   sumsqf_runtest( sumsqf_test_x8,  8,  sumsqf_test_y8  ); }
+void autotest_sumsqf_15()   {   sumsqf_runtest( sumsqf_test_x15, 15, sumsqf_test_y15 ); }
+void autotest_sumsqf_16()   {   sumsqf_runtest( sumsqf_test_x16, 16, sumsqf_test_y16 ); }
+
+float sumsqf_test_x3[3] = {
+  -0.4546496371984978f,
+   0.4451201395218938f,
+   0.0138788690209525f};
+float sumsqf_test_y3 = 0.405030854218017;
+
+float sumsqf_test_x4[4] = {
+   0.1322698385026883f,
+  -0.0569081631536912f,
+  -0.3244384492417431f,
+  -0.2872733941910143f};
+float sumsqf_test_y4 = 0.208520159567467;
+
+float sumsqf_test_x7[7] = {
+  -0.221079351597278f,
+  -0.227902662215897f,
+   0.382941891419158f,
+   0.246800053933030f,
+  -0.190152017725480f,
+   0.395758452636014f,
+   0.211220685416265f};
+float sumsqf_test_y7 = 0.545767182598435;
+
+float sumsqf_test_x8[8] = {
+  -0.3405090291337944f,
+   0.5568858414046379f,
+  -0.0870643704340343f,
+   0.1724369367547939f,
+  -0.7379946538182081f,
+  -0.3514326419380984f,
+   0.2782541955998314f,
+   0.4354875172406391f};
+float sumsqf_test_y8 = 1.39859872696022;
+
+float sumsqf_test_x15[15] = {
+  -0.4630291295549499f,
+  -0.2776019612369674f,
+  -0.4933486186123937f,
+  -0.0850997992116534f,
+   0.0117036410972943f,
+   0.0215560948199280f,
+   0.1203298759952301f,
+   0.5866344749815807f,
+   0.3791165816771581f,
+  -0.4070288299889871f,
+  -0.4971431800502791f,
+  -0.2142770391709351f,
+   0.3330589842198580f,
+  -0.0150675851612766f,
+  -0.3947266044391958f};
+float sumsqf_test_y15 = 1.77074683901981f;
+
+float sumsqf_test_x16[16] = {
+  -0.2975264216819841f,
+   0.5642827287388987f,
+  -0.7956166087428503f,
+  -0.1931368701566655f,
+  -0.0287212417958668f,
+   0.3697266870899014f,
+   0.0791822603183984f,
+   0.1668276194302965f,
+   0.2048176237333448f,
+  -0.0617609549162579f,
+   0.5317006403634014f,
+  -0.3964290790294236f,
+   0.5404940967800361f,
+   0.1755457122664283f,
+   0.1585602895144933f,
+   0.0791731424937176f};
+float sumsqf_test_y16 = 2.08885480396333f;
+
diff --git a/src/equalization/bench/eqlms_cccf_benchmark.c b/src/equalization/bench/eqlms_cccf_benchmark.c
new file mode 100644
index 0000000..22b7979
--- /dev/null
+++ b/src/equalization/bench/eqlms_cccf_benchmark.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define EQLMS_CCCF_TRAIN_BENCH_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ eqlms_cccf_train_bench(_start, _finish, _num_iterations, N); }
+
+// Helper function to keep code base small
+void eqlms_cccf_train_bench(struct rusage *_start,
+                            struct rusage *_finish,
+                            unsigned long int *_num_iterations,
+                            unsigned int _h_len)
+{
+    // scale number of iterations appropriately
+    // log(cycles/trial) ~ 5.63 + 0.767*log(_h_len)
+    *_num_iterations *= 3200;
+    *_num_iterations /= (unsigned int) expf(5.63f + 0.767f*logf(_h_len));
+    *_num_iterations = (*_num_iterations < 4) ? 4 : *_num_iterations;
+
+    eqlms_cccf eq = eqlms_cccf_create(NULL,_h_len);
+    
+    unsigned long int i;
+
+    // set up initial arrays to 'randomize' inputs/outputs
+    float complex y[11];
+    for (i=0; i<11; i++)
+        y[i] = randnf() + _Complex_I*randnf();
+
+    float complex d[13];
+    for (i=0; i<13; i++)
+        d[i] = randnf() + _Complex_I*randnf();
+
+    unsigned int iy=0;
+    unsigned int id=0;
+
+    float complex z;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        eqlms_cccf_push(eq, y[iy]);     // push input into equalizer
+        eqlms_cccf_execute(eq, &z);     // compute equalizer output
+        eqlms_cccf_step(eq, d[id], z);  // step equalizer internals
+
+        // update counters
+        iy = (iy+1)%11;
+        id = (id+1)%13;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    eqlms_cccf_destroy(eq);
+}
+
+// 
+void benchmark_eqlms_cccf_n4    EQLMS_CCCF_TRAIN_BENCH_API(4)
+void benchmark_eqlms_cccf_n8    EQLMS_CCCF_TRAIN_BENCH_API(8)
+void benchmark_eqlms_cccf_n16   EQLMS_CCCF_TRAIN_BENCH_API(16)
+void benchmark_eqlms_cccf_n32   EQLMS_CCCF_TRAIN_BENCH_API(32)
+void benchmark_eqlms_cccf_n64   EQLMS_CCCF_TRAIN_BENCH_API(64)
+
diff --git a/src/equalization/bench/eqrls_cccf_benchmark.c b/src/equalization/bench/eqrls_cccf_benchmark.c
new file mode 100644
index 0000000..f41b2be
--- /dev/null
+++ b/src/equalization/bench/eqrls_cccf_benchmark.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.h"
+
+#define EQRLS_CCCF_TRAIN_BENCH_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ eqrls_cccf_train_bench(_start, _finish, _num_iterations, N); }
+
+// Helper function to keep code base small
+void eqrls_cccf_train_bench(struct rusage *_start,
+                            struct rusage *_finish,
+                            unsigned long int *_num_iterations,
+                            unsigned int _h_len)
+{
+    // scale number of iterations appropriately
+    // log(cycles/trial) ~ 5.57 + 2.74*log(_h_len)
+    *_num_iterations *= 2400;
+    *_num_iterations /= (unsigned int) expf(5.57f + 2.64f*logf(_h_len));
+    *_num_iterations = (*_num_iterations < 4) ? 4 : *_num_iterations;
+
+    eqrls_cccf eq = eqrls_cccf_create(NULL,_h_len);
+    
+    unsigned long int i;
+
+    // set up initial arrays to 'randomize' inputs/outputs
+    float complex y[11];
+    for (i=0; i<11; i++)
+        y[i] = randnf() + _Complex_I*randnf();
+
+    float complex d[13];
+    for (i=0; i<13; i++)
+        d[i] = randnf() + _Complex_I*randnf();
+
+    unsigned int iy=0;
+    unsigned int id=0;
+
+    float complex z;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        eqrls_cccf_push(eq, y[iy]);     // push input into equalizer
+        eqrls_cccf_execute(eq, &z);     // compute equalizer output
+        eqrls_cccf_step(eq, d[id], z);  // step equalizer internals
+
+        // update counters
+        iy = (iy+1)%11;
+        id = (id+1)%13;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    eqrls_cccf_destroy(eq);
+}
+
+// 
+void benchmark_eqrls_cccf_n4    EQRLS_CCCF_TRAIN_BENCH_API(4)
+void benchmark_eqrls_cccf_n8    EQRLS_CCCF_TRAIN_BENCH_API(8)
+void benchmark_eqrls_cccf_n16   EQRLS_CCCF_TRAIN_BENCH_API(16)
+void benchmark_eqrls_cccf_n32   EQRLS_CCCF_TRAIN_BENCH_API(32)
+void benchmark_eqrls_cccf_n64   EQRLS_CCCF_TRAIN_BENCH_API(64)
+
diff --git a/src/equalization/src/eqlms.c b/src/equalization/src/eqlms.c
new file mode 100644
index 0000000..c08c8e0
--- /dev/null
+++ b/src/equalization/src/eqlms.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Least mean-squares (LMS) equalizer
+//
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+//#define DEBUG
+
+struct EQLMS(_s) {
+    unsigned int h_len;     // filter length
+    float        mu;        // LMS step size
+
+    // internal matrices
+    T *          h0;        // initial coefficients
+    T *          w0;        // weights [px1]
+    T *          w1;        // weights [px1]
+
+    unsigned int count;     // input sample count
+    int          buf_full;  // input buffer full flag
+    WINDOW()     buffer;    // input buffer
+    wdelayf      x2;        // buffer of |x|^2 values
+    float        x2_sum;    // sum{ |x|^2 }
+};
+
+// update sum{|x|^2}
+void EQLMS(_update_sumsq)(EQLMS() _q, T _x);
+
+// create least mean-squares (LMS) equalizer object
+//  _h      :   initial coefficients [size: _h_len x 1], default if NULL
+//  _p      :   equalizer length (number of taps)
+EQLMS() EQLMS(_create)(T *          _h,
+                       unsigned int _h_len)
+{
+    EQLMS() q = (EQLMS()) malloc(sizeof(struct EQLMS(_s)));
+
+    // set filter order, other params
+    q->h_len = _h_len;
+    q->mu    = 0.5f;
+
+    q->h0 = (T*) malloc((q->h_len)*sizeof(T));
+    q->w0 = (T*) malloc((q->h_len)*sizeof(T));
+    q->w1 = (T*) malloc((q->h_len)*sizeof(T));
+    q->buffer = WINDOW(_create)(q->h_len);
+    q->x2     = wdelayf_create(q->h_len);
+
+    // copy coefficients (if not NULL)
+    if (_h == NULL) {
+        // initial coefficients with delta at first index
+        unsigned int i;
+        for (i=0; i<q->h_len; i++)
+            q->h0[i] = (i==0) ? 1.0 : 0.0;
+    } else {
+        // copy user-defined initial coefficients
+        memmove(q->h0, _h, (q->h_len)*sizeof(T));
+    }
+
+    // reset equalizer object
+    EQLMS(_reset)(q);
+
+    // return main object
+    return q;
+}
+
+// create square-root Nyquist interpolator
+//  _type   :   filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _k      :   samples/symbol _k > 1
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   excess bandwidth factor, 0 < _beta < 1
+//  _dt     :   fractional sample delay, 0 <= _dt < 1
+EQLMS() EQLMS(_create_rnyquist)(int          _type,
+                                unsigned int _k,
+                                unsigned int _m,
+                                float        _beta,
+                                float        _dt)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: eqlms_%s_create_rnyquist(), samples/symbol must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: eqlms_%s_create_rnyquist(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: eqlms_%s_create_rnyquist(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: eqlms_%s_create_rnyquist(), filter fractional sample delay must be in [-1,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int h_len = 2*_k*_m + 1;
+    float h[h_len];
+    liquid_firdes_prototype(_type,_k,_m,_beta,_dt,h);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    // and scale by samples/symbol
+    unsigned int i;
+    T hc[h_len];
+    for (i=0; i<h_len; i++)
+        hc[i] = h[i] / (float)_k;
+
+    // return equalizer object
+    return EQLMS(_create)(hc, h_len);
+}
+
+// create LMS EQ initialized with low-pass filter
+//  _h_len  : filter length
+//  _fc     : filter cut-off, _fc in (0,0.5]
+EQLMS() EQLMS(_create_lowpass)(unsigned int _h_len,
+                               float        _fc)
+{
+    // validate input
+    if (_h_len == 0) {
+        fprintf(stderr,"error: eqlms_%s_create_lowpass(), filter length must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fc <= 0.0f || _fc > 0.5f) {
+        fprintf(stderr,"error: eqlms_%s_create_rnyquist(), filter cutoff must be in (0,0.5]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate low-pass filter prototype
+    float h[_h_len];
+    liquid_firdes_kaiser(_h_len, _fc, 40.0f, 0.0f, h);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    T hc[_h_len];
+    for (i=0; i<_h_len; i++)
+        hc[i] = h[i];
+
+    // return equalizer object
+    return EQLMS(_create)(hc, _h_len);
+}
+
+// re-create least mean-squares (LMS) equalizer object
+//  _q      :   old equalizer object
+//  _h      :   initial coefficients [size: _p x 1], default if NULL
+//  _p      :   equalizer length (number of taps)
+EQLMS() EQLMS(_recreate)(EQLMS()      _q,
+                         T *          _h,
+                         unsigned int _p)
+{
+    if (_q->h_len == _p) {
+        // length hasn't changed; copy default coefficients
+        // and return object
+        unsigned int i;
+        for (i=0; i<_q->h_len; i++)
+            _q->h0[i] = _h[i];
+        return _q;
+    }
+
+    // completely destroy old equalizer object
+    EQLMS(_destroy)(_q);
+
+    // create new one and return
+    return EQLMS(_create)(_h,_p);
+}
+
+
+// destroy eqlms object
+void EQLMS(_destroy)(EQLMS() _q)
+{
+    free(_q->h0);
+    free(_q->w0);
+    free(_q->w1);
+
+    WINDOW(_destroy)(_q->buffer);
+    wdelayf_destroy(_q->x2);
+    free(_q);
+}
+
+// reset equalizer
+void EQLMS(_reset)(EQLMS() _q)
+{
+    // copy default coefficients
+    memmove(_q->w0, _q->h0, (_q->h_len)*sizeof(T));
+
+    WINDOW(_clear)(_q->buffer);
+    wdelayf_clear(_q->x2);
+
+    // reset input count
+    _q->count = 0;
+    _q->buf_full = 0;
+
+    // reset squared magnitude sum
+    _q->x2_sum = 0;
+}
+
+// print eqlms object internals
+void EQLMS(_print)(EQLMS() _q)
+{
+    printf("equalizer (LMS):\n");
+    printf("    order:      %u\n", _q->h_len);
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        printf("  h(%3u) = %12.4e + j*%12.4e;\n", i+1, creal(_q->w0[i]), cimag(_q->w0[i]));
+}
+
+// get learning rate of equalizer
+float EQLMS(_get_bw)(EQLMS() _q)
+{
+    return _q->mu;
+}
+
+// set learning rate of equalizer
+//  _q      :   equalizer object
+//  _mu     :   LMS learning rate (should be near 0), 0 < _mu < 1
+void EQLMS(_set_bw)(EQLMS() _q,
+                    float   _mu)
+{
+    if (_mu < 0.0f) {
+        fprintf(stderr,"error: eqlms_%s_set_bw(), learning rate cannot be less than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    _q->mu = _mu;
+}
+
+// push sample into equalizer internal buffer
+//  _q      :   equalizer object
+//  _x      :   received sample
+void EQLMS(_push)(EQLMS() _q,
+                  T _x)
+{
+    // push value into buffer
+    WINDOW(_push)(_q->buffer, _x);
+
+    // update sum{|x|^2}
+    EQLMS(_update_sumsq)(_q, _x);
+
+    // increment count
+    _q->count++;
+}
+
+// push sample into equalizer internal buffer as block
+//  _q      :   equalizer object
+//  _x      :   input sample array
+//  _n      :   input sample array length
+void EQLMS(_push_block)(EQLMS()      _q,
+                        T *          _x,
+                        unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        EQLMS(_push)(_q, _x[i]);
+}
+
+// execute internal dot product
+//  _q      :   equalizer object
+//  _y      :   output sample
+void EQLMS(_execute)(EQLMS() _q,
+                     T *     _y)
+{
+    T y = 0;    // temporary accumulator
+    T * r;      // read buffer
+    WINDOW(_read)(_q->buffer, &r);
+
+    // compute conjugate vector dot product
+    //DOTPROD(_run)(_q->w0, r, _q->h_len, &y);
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++) {
+        T sum = conj(_q->w0[i])*r[i];
+        y += sum;
+    }
+
+    // set output
+    *_y = y;
+}
+
+// execute equalizer with block of samples using constant
+// modulus algorithm, operating on a decimation rate of _k
+// samples.
+//  _q      :   equalizer object
+//  _k      :   down-sampling rate
+//  _x      :   input sample array [size: _n x 1]
+//  _n      :   input sample array length
+//  _y      :   output sample array [size: _n x 1]
+void EQLMS(_execute_block)(EQLMS()      _q,
+                           unsigned int _k,
+                           T *          _x,
+                           unsigned int _n,
+                           T *          _y)
+{
+    if (_k == 0) {
+        fprintf(stderr,"error: eqlms_%s_execute_block(), down-sampling rate 'k' must be greater than 0\n", EXTENSION_FULL);
+        exit(-1);
+    }
+
+    unsigned int i;
+    T d_hat;
+    for (i=0; i<_n; i++) {
+        // push input sample
+        EQLMS(_push)(_q, _x[i]);
+
+        // compute output sample
+        EQLMS(_execute)(_q, &d_hat);
+
+        // store result in output
+        _y[i] = d_hat;
+
+        // decimate by _k
+        if ( ((_q->count+_k-1) % _k) == 0 ) {
+            // update equalizer independent of the signal: estimate error
+            // assuming constant modulus signal
+            EQLMS(_step_blind)(_q, d_hat);
+        }
+    }
+}
+
+// step through one cycle of equalizer training
+//  _q      :   equalizer object
+//  _d      :   desired output
+//  _d_hat  :   filtered output
+void EQLMS(_step)(EQLMS() _q,
+                  T       _d,
+                  T       _d_hat)
+{
+    // check count; only run step when buffer is full
+    if (!_q->buf_full) {
+        if (_q->count < _q->h_len)
+            return;
+        else
+            _q->buf_full = 1;
+    }
+
+    unsigned int i;
+
+    // compute error (a priori)
+    T alpha = _d - _d_hat;
+
+    // read buffer
+    T * r;      // read buffer
+    WINDOW(_read)(_q->buffer, &r);
+
+    // update weighting vector
+    // w[n+1] = w[n] + mu*conj(d-d_hat)*x[n]/(x[n]' * conj(x[n]))
+    for (i=0; i<_q->h_len; i++)
+        _q->w1[i] = _q->w0[i] + (_q->mu)*conj(alpha)*r[i]/_q->x2_sum;
+
+#ifdef DEBUG
+    printf("w0: \n");
+    for (i=0; i<_q->h_len; i++) {
+        PRINTVAL(_q->w0[i]);
+        printf("\n");
+    }
+    printf("w1: \n");
+    for (i=0; i<_q->h_len; i++) {
+        PRINTVAL(_q->w1[i]);
+        printf("\n");
+    }
+#endif
+
+    // copy old values
+    memmove(_q->w0, _q->w1, _q->h_len*sizeof(T));
+}
+
+// step through one cycle of equalizer training
+//  _q      :   equalizer object
+//  _d_hat  :   filtered output
+void EQLMS(_step_blind)(EQLMS() _q,
+                        T       _d_hat)
+{
+    // update equalizer using constant modulus method
+#if T_COMPLEX
+    T d = _d_hat / cabsf(_d_hat);
+#else
+    T d = _d_hat > 0 ? 1 : -1;
+#endif
+    EQLMS(_step)(_q, d, _d_hat);
+}
+
+// retrieve internal filter coefficients
+void EQLMS(_get_weights)(EQLMS() _q, T * _w)
+{
+    // copy output weight vector
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        _w[i] = conj(_q->w0[_q->h_len-i-1]);
+}
+
+// train equalizer object
+//  _q      :   equalizer object
+//  _w      :   initial weights / output weights
+//  _x      :   received sample vector
+//  _d      :   desired output vector
+//  _n      :   vector length
+void EQLMS(_train)(EQLMS()      _q,
+                   T *          _w,
+                   T *          _x,
+                   T *          _d,
+                   unsigned int _n)
+{
+    unsigned int p=_q->h_len;
+    if (_n < _q->h_len) {
+        fprintf(stderr,"warning: eqlms_%s_train(), traning sequence less than filter order\n", EXTENSION_FULL);
+    }
+
+    unsigned int i;
+
+    // reset equalizer state
+    EQLMS(_reset)(_q);
+
+    // copy initial weights into buffer
+    for (i=0; i<p; i++)
+        _q->w0[i] = _w[p - i - 1];
+
+    T d_hat;
+    for (i=0; i<_n; i++) {
+        // push sample into internal buffer
+        EQLMS(_push)(_q, _x[i]);
+
+        // execute vector dot product
+        EQLMS(_execute)(_q, &d_hat);
+
+        // step through training cycle
+        EQLMS(_step)(_q, _d[i], d_hat);
+    }
+
+    // copy output weight vector
+    EQLMS(_get_weights)(_q, _w);
+}
+
+// 
+// internal methods
+//
+
+// update sum{|x|^2}
+void EQLMS(_update_sumsq)(EQLMS() _q, T _x)
+{
+    // update estimate of signal magnitude squared
+    // |x[n-1]|^2 (input sample)
+    float x2_n = crealf(_x * conjf(_x));
+
+    // |x[0]  |^2 (oldest sample)
+    float x2_0;
+
+    // read oldest sample
+    wdelayf_read(_q->x2, &x2_0);
+
+    // push newest sample
+    wdelayf_push(_q->x2, x2_n);
+
+    // update sum( |x|^2 ) of last 'n' input samples
+    _q->x2_sum = _q->x2_sum + x2_n - x2_0;
+}
+
diff --git a/src/equalization/src/eqrls.c b/src/equalization/src/eqrls.c
new file mode 100644
index 0000000..58d7a05
--- /dev/null
+++ b/src/equalization/src/eqrls.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Recursive least-squares (RLS) equalizer
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+//#define DEBUG
+
+struct EQRLS(_s) {
+    unsigned int p;     // filter order
+    float lambda;       // RLS forgetting factor
+    float delta;        // RLS initialization factor
+
+    // internal matrices
+    T * h0;             // initial coefficients
+    T * w0, * w1;       // weights [px1]
+    T * P0, * P1;       // recursion matrix [pxp]
+    T * g;              // gain vector [px1]
+
+    // temporary matrices
+    T * xP0;            // [1xp]
+    T zeta;             // constant
+
+    T * gxl;            // [pxp]
+    T * gxlP0;          // [pxp]
+
+    unsigned int n;     // input counter
+    WINDOW() buffer;    // input buffer
+};
+
+
+// create recursive least-squares (RLS) equalizer object
+//  _h      :   initial coefficients [size: _p x 1], default if NULL
+//  _p      :   equalizer length (number of taps)
+EQRLS() EQRLS(_create)(T *          _h,
+                       unsigned int _p)
+{
+    EQRLS() q = (EQRLS()) malloc(sizeof(struct EQRLS(_s)));
+
+    // set filter order, other parameters
+    q->p      = _p;     // filter order
+    q->lambda = 0.99f;  // learning rate
+    q->delta  = 0.1f;   // initialization factor
+
+    // allocate memory for matrices
+    q->h0 = (T*) malloc((q->p)*sizeof(T));
+    q->w0 = (T*) malloc((q->p)*sizeof(T));
+    q->w1 = (T*) malloc((q->p)*sizeof(T));
+    q->P0 = (T*) malloc((q->p)*(q->p)*sizeof(T));
+    q->P1 = (T*) malloc((q->p)*(q->p)*sizeof(T));
+    q->g  = (T*) malloc((q->p)*sizeof(T));
+
+    q->xP0 =   (T*) malloc((q->p)*sizeof(T));
+    q->gxl =   (T*) malloc((q->p)*(q->p)*sizeof(T));
+    q->gxlP0 = (T*) malloc((q->p)*(q->p)*sizeof(T));
+
+    q->buffer = WINDOW(_create)(q->p);
+
+    // copy coefficients (if not NULL)
+    if (_h == NULL) {
+        // initial coefficients with delta at first index
+        unsigned int i;
+        for (i=0; i<q->p; i++)
+            q->h0[i] = (i==0) ? 1.0 : 0.0;
+    } else {
+        // copy user-defined initial coefficients
+        memmove(q->h0, _h, (q->p)*sizeof(T));
+    }
+
+    // reset equalizer
+    EQRLS(_reset)(q);
+
+    // return object
+    return q;
+}
+
+
+// re-create recursive least-squares (RLS) equalizer object
+//  _q  : old equalizer object
+//  _h  : filter coefficients (NULL for {1,0,0...})
+//  _p  : equalizer length (number of taps)
+EQRLS() EQRLS(_recreate)(EQRLS()      _q,
+                         T *          _h,
+                         unsigned int _p)
+{
+    if (_q->p == _p) {
+        // length hasn't changed; copy default coefficients
+        // and return object
+        unsigned int i;
+        for (i=0; i<_q->p; i++)
+            _q->h0[i] = _h[i];
+        return _q;
+    }
+
+    // completely destroy old equalizer object
+    EQRLS(_destroy)(_q);
+
+    // create new one and return
+    return EQRLS(_create)(_h,_p);
+}
+
+// destroy eqrls object
+void EQRLS(_destroy)(EQRLS() _q)
+{
+    // free vectors and matrices
+    free(_q->h0);
+    free(_q->w0);
+    free(_q->w1);
+    free(_q->P0);
+    free(_q->P1);
+    free(_q->g);
+
+    free(_q->xP0);
+    free(_q->gxl);
+    free(_q->gxlP0);
+
+    // destroy window buffer
+    WINDOW(_destroy)(_q->buffer);
+
+    // free main object memory
+    free(_q);
+}
+
+// print eqrls object internals
+void EQRLS(_print)(EQRLS() _q)
+{
+    printf("equalizer (RLS):\n");
+    printf("    order:      %u\n", _q->p);
+
+#ifdef DEBUG
+    unsigned int r,c,p=_q->p;
+    printf("P0:\n");
+    for (r=0; r<p; r++) {
+        for (c=0; c<p; c++) {
+            PRINTVAL(matrix_access(_q->P0,p,p,r,c));
+        }
+        printf("\n");
+    }
+
+    printf("P1:\n");
+    for (r=0; r<p; r++) {
+        for (c=0; c<p; c++) {
+            PRINTVAL(matrix_access(_q->P1,p,p,r,c));
+        }
+        printf("\n");
+    }
+
+    printf("gxl:\n");
+    for (r=0; r<p; r++) {
+        for (c=0; c<p; c++) {
+            PRINTVAL(matrix_access(_q->gxl,p,p,r,c));
+        }
+        printf("\n");
+    }
+#endif
+}
+
+// reset equalizer
+void EQRLS(_reset)(EQRLS() _q)
+{
+    // reset input counter
+    _q->n = 0;
+
+    unsigned int i, j;
+    // initialize...
+    for (i=0; i<_q->p; i++) {
+        for (j=0; j<_q->p; j++) {
+            if (i==j)   _q->P0[(_q->p)*i + j] = 1 / (_q->delta);
+            else        _q->P0[(_q->p)*i + j] = 0;
+        }
+    }
+
+    // copy default coefficients
+    memmove(_q->w0, _q->h0, (_q->p)*sizeof(T));
+
+    // clear window object
+    WINDOW(_clear)(_q->buffer);
+}
+
+// get learning rate of equalizer
+float EQRLS(_get_bw)(EQRLS() _q)
+{
+    return _q->lambda;
+}
+
+// set learning rate of equalizer
+//  _q     :   equalizer object
+//  _lambda :   RLS learning rate (should be close to 1.0), 0 < _lambda < 1
+void EQRLS(_set_bw)(EQRLS() _q,
+                    float   _lambda)
+{
+    if (_lambda < 0.0f || _lambda > 1.0f) {
+        printf("error: eqrls_%s_set_bw(), learning rate must be in (0,1)\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // set internal value
+    _q->lambda = _lambda;
+}
+
+// push sample into equalizer internal buffer
+//  _q  :   equalizer object
+//  _x  :   received sample
+void EQRLS(_push)(EQRLS() _q,
+                  T _x)
+{
+    // push value into buffer
+    WINDOW(_push)(_q->buffer, _x);
+}
+
+// execute internal dot product
+//  _q      :   equalizer object
+//  _y      :   output sample
+void EQRLS(_execute)(EQRLS() _q,
+                     T *     _y)
+{
+    // compute vector dot product
+    T * r;      // read buffer
+    WINDOW(_read)(_q->buffer, &r);
+    DOTPROD(_run)(_q->w0, r, _q->p, _y);
+}
+
+// execute cycle of equalizer, filtering output
+//  _q      :   equalizer object
+//  _x      :   received sample
+//  _d      :   desired output
+//  _d_hat  :   filtered output
+void EQRLS(_step)(EQRLS() _q,
+                  T       _d,
+                  T       _d_hat)
+{
+    unsigned int i,r,c;
+    unsigned int p=_q->p;
+
+    // compute error (a priori)
+    T alpha = _d - _d_hat;
+
+    // read buffer
+    T * x;
+    WINDOW(_read)(_q->buffer, &x);
+
+    // compute gain vector
+    for (c=0; c<p; c++) {
+        _q->xP0[c] = 0;
+        for (r=0; r<p; r++) {
+            _q->xP0[c] += x[r] * matrix_access(_q->P0,p,p,r,c);
+        }
+    }
+
+#ifdef DEBUG
+    printf("x: ");
+    for (i=0; i<p; i++)
+        PRINTVAL(x[i]);
+    printf("\n");
+
+    DEBUG_PRINTF_CFLOAT(stdout,"    d",0,_d);
+    DEBUG_PRINTF_CFLOAT(stdout,"_d_hat",0,_d_hat);
+    DEBUG_PRINTF_CFLOAT(stdout,"error",0,alpha);
+
+    printf("xP0: ");
+    for (c=0; c<p; c++)
+        PRINTVAL(_q->xP0[c]);
+    printf("\n");
+#endif
+    // zeta = lambda + [x.']*[P0]*[conj(x)]
+    _q->zeta = 0;
+    for (c=0; c<p; c++) {
+        T sum = _q->xP0[c] * conj(x[c]);
+        _q->zeta += sum;
+    }
+    _q->zeta += _q->lambda;
+#ifdef DEBUG
+    printf("zeta : ");
+    PRINTVAL(_q->zeta);
+    printf("\n");
+#endif
+    for (r=0; r<p; r++) {
+        _q->g[r] = 0;
+        for (c=0; c<p; c++) {
+            T sum = matrix_access(_q->P0,p,p,r,c) * conj(x[c]);
+            _q->g[r] += sum;
+        }
+        _q->g[r] /= _q->zeta;
+    }
+#ifdef DEBUG
+    printf("g: ");
+    for (i=0; i<p; i++)
+        PRINTVAL(_q->g[i]);
+        //printf("%6.3f ", _q->g[i]);
+    printf("\n");
+#endif
+
+    // update recursion matrix
+    for (r=0; r<p; r++) {
+        for (c=0; c<p; c++) {
+            // gxl = [g] * [x.'] / lambda
+            matrix_access(_q->gxl,p,p,r,c) = _q->g[r] * x[c] / _q->lambda;
+        }
+    }
+    // multiply two [pxp] matrices: gxlP0 = gxl * P0
+    MATRIX(_mul)(_q->gxl,  p,p,
+                 _q->P0,   p,p,
+                 _q->gxlP0,p,p);
+
+    for (i=0; i<p*p; i++)
+        _q->P1[i] = _q->P0[i] / _q->lambda - _q->gxlP0[i];
+
+    // update weighting vector
+    for (i=0; i<p; i++)
+        _q->w1[i] = _q->w0[i] + alpha*(_q->g[i]);
+
+#ifdef DEBUG
+    printf("w0: \n");
+    for (i=0; i<p; i++) {
+        PRINTVAL(_q->w0[i]);
+        printf("\n");
+    }
+    printf("w1: \n");
+    for (i=0; i<p; i++) {
+        PRINTVAL(_q->w1[i]);
+        printf("\n");
+    EQRLS(_print)(_q);
+    }
+    //if (_q->n == 7)
+    //    exit(0);
+
+#endif
+
+    // copy old values
+    memmove(_q->w0, _q->w1,   p*sizeof(T));
+    memmove(_q->P0, _q->P1, p*p*sizeof(T));
+
+}
+
+// retrieve internal filter coefficients
+//  _q      :   equalizer object
+//  _w      :   weights [size: _p x 1]
+void EQRLS(_get_weights)(EQRLS() _q,
+                         T *     _w)
+{
+    // copy output weight vector, reversing order
+    unsigned int i;
+    for (i=0; i<_q->p; i++)
+        _w[i] = _q->w1[_q->p-i-1];
+}
+
+// train equalizer object
+//  _q      :   equalizer object
+//  _w      :   initial weights / output weights
+//  _x      :   received sample vector
+//  _d      :   desired output vector
+//  _n      :   vector length
+void EQRLS(_train)(EQRLS()      _q,
+                   T *          _w,
+                   T *          _x,
+                   T *          _d,
+                   unsigned int _n)
+{
+    unsigned int i;
+    if (_n < _q->p) {
+        printf("warning: eqrls_%s_train(), traning sequence less than filter order\n",
+                EXTENSION_FULL);
+        return;
+    }
+
+    // reset equalizer state
+    EQRLS(_reset)(_q);
+
+    // copy initial weights into buffer
+    for (i=0; i<_q->p; i++)
+        _q->w0[i] = _w[_q->p - i - 1];
+
+    T d_hat;
+    for (i=0; i<_n; i++) {
+        // push sample into internal buffer
+        EQRLS(_push)(_q, _x[i]);
+
+        // execute vector dot product
+        EQRLS(_execute)(_q, &d_hat);
+
+        // step through training cycle
+        EQRLS(_step)(_q, _d[i], d_hat);
+    }
+
+    // copy output weight vector
+    EQRLS(_get_weights)(_q, _w);
+}
diff --git a/src/equalization/src/equalizer_cccf.c b/src/equalization/src/equalizer_cccf.c
new file mode 100644
index 0000000..f94e9d0
--- /dev/null
+++ b/src/equalization/src/equalizer_cccf.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point equalizers
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT "f"
+#define EXTENSION_FULL  "cccf"
+
+#define EQLMS(name)     LIQUID_CONCAT(eqlms_cccf,name)
+#define EQRLS(name)     LIQUID_CONCAT(eqrls_cccf,name)
+
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_cccf,name)
+#define WINDOW(name)    LIQUID_CONCAT(windowcf,name)
+#define MATRIX(name)    LIQUID_CONCAT(matrixcf,name)
+
+#define T_COMPLEX       1
+#define T               float complex
+
+#define PRINTVAL(V)     printf("%5.2f+j%5.2f ", crealf(V), cimagf(V));
+
+#include "eqlms.c"
+#include "eqrls.c"
diff --git a/src/equalization/src/equalizer_rrrf.c b/src/equalization/src/equalizer_rrrf.c
new file mode 100644
index 0000000..95a0bde
--- /dev/null
+++ b/src/equalization/src/equalizer_rrrf.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Floating-point equalizers
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT "f"
+#define EXTENSION_FULL  "rrrf"
+
+#define EQLMS(name)     LIQUID_CONCAT(eqlms_rrrf,name)
+#define EQRLS(name)     LIQUID_CONCAT(eqrls_rrrf,name)
+
+#define DOTPROD(name)   LIQUID_CONCAT(dotprod_rrrf,name)
+#define WINDOW(name)    LIQUID_CONCAT(windowf,name)
+#define MATRIX(name)    LIQUID_CONCAT(matrixf,name)
+
+#define T_COMPLEX       0
+#define T               float
+
+#define PRINTVAL(V)     printf("%5.2f ", V);
+
+#include "eqlms.c"
+#include "eqrls.c"
diff --git a/src/equalization/tests/eqlms_cccf_autotest.c b/src/equalization/tests/eqlms_cccf_autotest.c
new file mode 100644
index 0000000..598e05c
--- /dev/null
+++ b/src/equalization/tests/eqlms_cccf_autotest.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: static channel filter, blind equalization on QPSK symbols
+//
+void autotest_eqlms_cccf_blind()
+{
+    // parameters
+    float           tol         = 2e-2f;    // error tolerance
+    unsigned int    k           =  2;       // samples/symbol
+    unsigned int    m           =  7;       // filter delay
+    float           beta        =  0.3f;    // excess bandwidth factor
+    unsigned int    p           =  7;       // equalizer order
+    float           mu          =  0.7f;    // equalizer bandwidth
+    unsigned int    num_symbols = 400;      // number of symbols to observe
+
+    // create sequence generator for repeatability
+    msequence ms = msequence_create_default(12);
+
+    // create interpolating filter
+    firinterp_crcf interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_ARKAISER,k,m,beta,0);
+
+    // create equalizer
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_ARKAISER,k,p,beta,0);
+    eqlms_cccf_set_bw(eq, mu);
+
+    // create channel filter
+    float complex h[5] = {
+        { 1.00f,  0.00f},
+        { 0.00f, -0.01f},
+        {-0.11f,  0.02f},
+        { 0.02f,  0.01f},
+        {-0.09f, -0.04f} };
+    firfilt_cccf fchannel = firfilt_cccf_create(h,5);
+
+    // arrays
+    float complex buf[k];               // filter buffer
+    float complex sym_in [num_symbols]; // input symbols
+    float complex sym_out[num_symbols]; // equalized symbols
+
+    // run equalization
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<num_symbols; i++) {
+        // generate input symbol
+        sym_in[i] = ( msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2 ) +
+                    ( msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2 ) * _Complex_I;
+
+        // interpolate
+        firinterp_crcf_execute(interp, sym_in[i], buf);
+
+        // apply channel filter (in place)
+        firfilt_cccf_execute_block(fchannel, buf, k, buf);
+
+        // apply equalizer as filter
+        for (j=0; j<k; j++) {
+            eqlms_cccf_push(eq, buf[j]);
+
+            // decimate by k
+            if ( (j%k) != 0 ) continue;
+
+            eqlms_cccf_execute(eq, &sym_out[i]);
+
+            // update equalization (blind operation)
+            if (i > m + p)
+                eqlms_cccf_step(eq, sym_out[i]/cabsf(sym_out[i]), sym_out[i]);
+        }
+    }
+
+    // compare input, output
+    unsigned int settling_delay = 285;
+    for (i=m+p; i<num_symbols; i++) {
+        // compensate for delay
+        j = i-m-p;
+
+        // absolute error
+        float error = cabsf(sym_in[j]-sym_out[i]);
+
+        if (liquid_autotest_verbose) {
+            printf("x[%3u] = {%12.8f,%12.8f}, y[%3u] = {%12.8f,%12.8f}, error=%12.8f %s\n",
+                    j, crealf(sym_in [j]), cimagf(sym_in [j]),
+                    i, crealf(sym_out[i]), cimagf(sym_out[i]),
+                    error, error > tol ? "*" : "");
+            if (i == settling_delay + m + p)
+                printf("--- start of test ---\n");
+        }
+
+        // check error
+        if (i > settling_delay + m + p)
+            CONTEND_DELTA(error, 0.0f, tol);
+    }
+
+    // clean up objects
+    firfilt_cccf_destroy(fchannel);
+    eqlms_cccf_destroy(eq);
+    msequence_destroy(ms);
+}
+
+// 
+// AUTOTEST: static channel filter, decision-directed on QPSK symbols
+//
+void autotest_eqlms_cccf_decisiondirected()
+{
+    // parameters
+    float           tol         = 1e-2f;    // error tolerance
+    unsigned int    k           =  2;       // samples/symbol
+    unsigned int    m           =  7;       // filter delay
+    float           beta        =  0.3f;    // excess bandwidth factor
+    unsigned int    p           =  7;       // equalizer order
+    unsigned int    num_symbols = 400;      // number of symbols to observe
+
+    // create sequence generator for repeatability
+    msequence ms = msequence_create_default(12);
+
+    // create interpolating filter
+    firinterp_crcf interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_ARKAISER,k,m,beta,0);
+
+    // create equalizer
+    eqlms_cccf eq = eqlms_cccf_create_rnyquist(LIQUID_FIRFILT_ARKAISER,k,p,beta,0);
+    eqlms_cccf_set_bw(eq, 0.5f);
+
+    // create channel filter
+    unsigned int h_len = 5; // channel filter length
+    float complex h[5] = {1.0f, 0.0f, -0.1f, 0.02f, -0.1f};
+    firfilt_cccf fchannel = firfilt_cccf_create(h,h_len);
+
+    // arrays
+    float complex buf[k];               // filter buffer
+    float complex sym_in [num_symbols]; // input symbols
+    float complex sym_out[num_symbols]; // equalized symbols
+
+    // run equalization
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<num_symbols; i++) {
+        // generate input symbol
+        sym_in[i] = ( msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2 ) +
+                    ( msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2 ) * _Complex_I;
+
+        // interpolate
+        firinterp_crcf_execute(interp, sym_in[i], buf);
+
+        // apply channel filter (in place)
+        firfilt_cccf_execute_block(fchannel, buf, k, buf);
+
+        // apply equalizer as filter
+        for (j=0; j<k; j++) {
+            eqlms_cccf_push(eq, buf[j]);
+
+            // decimate by k
+            if ( (j%k) != 0 ) continue;
+
+            eqlms_cccf_execute(eq, &sym_out[i]);
+
+            // update equalization (blind operation)
+            if (i > m + p)
+                eqlms_cccf_step(eq, sym_out[i]/cabsf(sym_out[i]), sym_out[i]);
+        }
+    }
+
+    // compare input, output
+    unsigned int settling_delay = 250;
+    for (i=m+p; i<num_symbols; i++) {
+        // compensate for delay
+        j = i-m-p;
+
+        // absolute error
+        float error = cabsf(sym_in[j]-sym_out[i]);
+
+        if (liquid_autotest_verbose) {
+            printf("x[%3u] = {%12.8f,%12.8f}, y[%3u] = {%12.8f,%12.8f}, error=%12.8f %s\n",
+                    j, crealf(sym_in [j]), cimagf(sym_in [j]),
+                    i, crealf(sym_out[i]), cimagf(sym_out[i]),
+                    error, error > tol ? "*" : "");
+            if (i == settling_delay + m + p)
+                printf("--- start of test ---\n");
+        }
+
+        // check error
+        if (i > settling_delay + m + p)
+            CONTEND_DELTA(error, 0.0f, tol);
+    }
+
+    // clean up objects
+    firfilt_cccf_destroy(fchannel);
+    eqlms_cccf_destroy(eq);
+    msequence_destroy(ms);
+}
+
diff --git a/src/equalization/tests/eqrls_rrrf_autotest.c b/src/equalization/tests/eqrls_rrrf_autotest.c
new file mode 100644
index 0000000..c15254f
--- /dev/null
+++ b/src/equalization/tests/eqrls_rrrf_autotest.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// constant data sequence
+const float eqrls_rrrf_autotest_data_sequence[64] = {
+    -1.0, -1.0,  1.0, -1.0,  1.0, -1.0,  1.0, -1.0, 
+    -1.0,  1.0,  1.0, -1.0, -1.0,  1.0, -1.0,  1.0, 
+     1.0, -1.0, -1.0, -1.0,  1.0,  1.0, -1.0,  1.0, 
+    -1.0,  1.0,  1.0,  1.0,  1.0,  1.0, -1.0, -1.0,
+     1.0,  1.0, -1.0, -1.0,  1.0, -1.0,  1.0, -1.0, 
+    -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 
+     1.0, -1.0, -1.0, -1.0,  1.0,  1.0, -1.0,  1.0, 
+    -1.0,  1.0,  1.0, -1.0,  1.0, -1.0,  1.0, -1.0
+};
+
+// 
+// AUTOTEST: channel filter: delta with zero delay
+//
+void autotest_eqrls_rrrf_01()
+{
+    float tol=1e-2f;        // error tolerance
+
+    // fixed parameters (do not change)
+    unsigned int h_len=4;   // channel filter length
+    unsigned int p=6;       // equalizer order
+    unsigned int n=64;      // number of symbols to observe
+
+    // bookkeeping variables
+    float y[n];         // received data sequence (filtered by channel)
+    //float d_hat[n];   // recovered data sequence
+    float h[h_len];     // channel filter coefficients
+    float w[p];         // equalizer filter coefficients
+    unsigned int i;
+
+    // create equalizer
+    eqrls_rrrf eq = eqrls_rrrf_create(NULL, p);
+
+    // create channel filter
+    h[0] = 1.0f;
+    for (i=1; i<h_len; i++)
+        h[i] = 0.0f;
+    firfilt_rrrf f = firfilt_rrrf_create(h,h_len);
+
+    // data sequence
+    float *d = (float*) eqrls_rrrf_autotest_data_sequence;
+
+    // filter data signal through channel
+    for (i=0; i<n; i++) {
+        firfilt_rrrf_push(f,d[i]);
+        firfilt_rrrf_execute(f,&y[i]);
+    }
+
+    // initialize weights, train equalizer
+    for (i=0; i<p; i++)
+        w[i] = 0;
+    eqrls_rrrf_train(eq, w, y, d, n);
+
+    // compare filter taps
+    CONTEND_DELTA(w[0], 1.0f, tol);
+    for (i=1; i<p; i++)
+        CONTEND_DELTA(w[i], 0.0f, tol);
+
+    // clean up objects
+    firfilt_rrrf_destroy(f);
+    eqrls_rrrf_destroy(eq);
+}
+
diff --git a/src/fec/bench/crc_benchmark.c b/src/fec/bench/crc_benchmark.c
new file mode 100644
index 0000000..043d017
--- /dev/null
+++ b/src/fec/bench/crc_benchmark.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define CRC_BENCH_API(CRC,N)                \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ crc_bench(_start, _finish, _num_iterations, CRC, N); }
+
+// Helper function to keep code base small
+void crc_bench(struct rusage *_start,
+               struct rusage *_finish,
+               unsigned long int *_num_iterations,
+               crc_scheme _crc,
+               unsigned int _n)
+{
+    // normalize number of iterations
+    if (_crc != LIQUID_CRC_CHECKSUM)
+        *_num_iterations /= 8;
+
+    unsigned long int i;
+
+    // create arrays
+    unsigned char msg[_n];
+    unsigned int key = 0;
+
+    // initialze message
+    for (i=0; i<_n; i++)
+        msg[i] = rand() & 0xff;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        key ^= crc_generate_key(_crc, msg, _n);
+        key ^= crc_generate_key(_crc, msg, _n);
+        key ^= crc_generate_key(_crc, msg, _n);
+        key ^= crc_generate_key(_crc, msg, _n);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+//
+// BENCHMARKS
+//
+void benchmark_crc_checksum_n256    CRC_BENCH_API(LIQUID_CRC_CHECKSUM,  256)
+
+void benchmark_crc_crc8_n256        CRC_BENCH_API(LIQUID_CRC_8,         256)
+void benchmark_crc_crc16_n256       CRC_BENCH_API(LIQUID_CRC_16,        256)
+void benchmark_crc_crc24_n256       CRC_BENCH_API(LIQUID_CRC_24,        256)
+void benchmark_crc_crc32_n256       CRC_BENCH_API(LIQUID_CRC_32,        256)
+
diff --git a/src/fec/bench/fec_decode_benchmark.c b/src/fec/bench/fec_decode_benchmark.c
new file mode 100644
index 0000000..2e5fb0f
--- /dev/null
+++ b/src/fec/bench/fec_decode_benchmark.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define FEC_DECODE_BENCH_API(FS,N, OPT) \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ fec_decode_bench(_start, _finish, _num_iterations, FS, N, OPT); }
+
+// Helper function to keep code base small
+void fec_decode_bench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations,
+    fec_scheme _fs,
+    unsigned int _n,
+    void * _opts)
+{
+#if !LIBFEC_ENABLED
+    if ( _fs == LIQUID_FEC_CONV_V27    ||
+         _fs == LIQUID_FEC_CONV_V29    ||
+         _fs == LIQUID_FEC_CONV_V39    ||
+         _fs == LIQUID_FEC_CONV_V615   ||
+         _fs == LIQUID_FEC_CONV_V27P23 ||
+         _fs == LIQUID_FEC_CONV_V27P34 ||
+         _fs == LIQUID_FEC_CONV_V27P45 ||
+         _fs == LIQUID_FEC_CONV_V27P56 ||
+         _fs == LIQUID_FEC_CONV_V27P67 ||
+         _fs == LIQUID_FEC_CONV_V27P78 ||
+         _fs == LIQUID_FEC_CONV_V29P23 ||
+         _fs == LIQUID_FEC_CONV_V29P34 ||
+         _fs == LIQUID_FEC_CONV_V29P45 ||
+         _fs == LIQUID_FEC_CONV_V29P56 ||
+         _fs == LIQUID_FEC_CONV_V29P67 ||
+         _fs == LIQUID_FEC_CONV_V29P78 ||
+         _fs == LIQUID_FEC_RS_M8)
+    {
+        fprintf(stderr,"warning: convolutional, Reed-Solomon codes unavailable (install libfec)\n");
+        getrusage(RUSAGE_SELF, _start);
+        memmove((void*)_finish,(void*)_start,sizeof(struct rusage));
+        return;
+    }
+#endif
+
+    // normalize number of iterations
+    *_num_iterations /= _n;
+
+    switch (_fs) {
+    case LIQUID_FEC_NONE:          *_num_iterations *= 500;    break;
+    case LIQUID_FEC_REP3:          *_num_iterations *= 200;    break;
+    case LIQUID_FEC_REP5:          *_num_iterations *= 100;    break;
+    case LIQUID_FEC_HAMMING74:     *_num_iterations *= 10;     break;
+    case LIQUID_FEC_HAMMING84:     *_num_iterations *= 10;     break;
+    case LIQUID_FEC_HAMMING128:    *_num_iterations *= 10;     break;
+    case LIQUID_FEC_SECDED2216:    *_num_iterations *= 10;     break;
+    case LIQUID_FEC_SECDED3932:    *_num_iterations *= 10;     break;
+    case LIQUID_FEC_SECDED7264:    *_num_iterations *= 10;     break;
+    case LIQUID_FEC_GOLAY2412:     *_num_iterations *= 2;      break;
+    case LIQUID_FEC_CONV_V27:      *_num_iterations /= 5;      break;
+    case LIQUID_FEC_CONV_V29:      *_num_iterations /= 10;     break;
+    case LIQUID_FEC_CONV_V39:      *_num_iterations /= 100;    break;
+    case LIQUID_FEC_CONV_V615:     *_num_iterations /= 200;    break;
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+        *_num_iterations /= 20;
+        break;
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        *_num_iterations /= 50;
+        break;
+    case LIQUID_FEC_RS_M8:
+        *_num_iterations *= 1;
+        break;
+    default:;
+    }
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // generate fec object
+    fec q = fec_create(_fs,_opts);
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(_fs,_n);
+    unsigned char msg[_n];          // original message
+    unsigned char msg_enc[n_enc];   // decoded message
+    unsigned char msg_dec[_n];      // decoded message
+
+    // initialze message
+    unsigned long int i;
+    for (i=0; i<_n; i++)
+        msg[i] = rand() & 0xff;
+
+    // encode message
+    fec_encode(q,_n,msg,msg_enc);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fec_decode(q,_n,msg_enc,msg_dec);
+        fec_decode(q,_n,msg_enc,msg_dec);
+        fec_decode(q,_n,msg_enc,msg_dec);
+        fec_decode(q,_n,msg_enc,msg_dec);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+//
+// BENCHMARKS
+//
+void benchmark_fec_dec_none_n64         FEC_DECODE_BENCH_API(LIQUID_FEC_NONE,      64,  NULL)
+
+void benchmark_fec_dec_rep3_n64         FEC_DECODE_BENCH_API(LIQUID_FEC_REP3,      64,  NULL)
+void benchmark_fec_dec_rep5_n64         FEC_DECODE_BENCH_API(LIQUID_FEC_REP5,      64,  NULL)
+void benchmark_fec_dec_hamming74_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_HAMMING74, 64,  NULL)
+void benchmark_fec_dec_hamming84_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_HAMMING84, 64,  NULL)
+void benchmark_fec_dec_hamming128_n64   FEC_DECODE_BENCH_API(LIQUID_FEC_HAMMING128,64,  NULL)
+
+// SEC-DED block codes
+void benchmark_fec_dec_secded2216_n64   FEC_DECODE_BENCH_API(LIQUID_FEC_SECDED2216,64,  NULL)
+void benchmark_fec_dec_secded3932_n64   FEC_DECODE_BENCH_API(LIQUID_FEC_SECDED3932,64,  NULL)
+void benchmark_fec_dec_secded7264_n64   FEC_DECODE_BENCH_API(LIQUID_FEC_SECDED7264,64,  NULL)
+
+void benchmark_fec_dec_golay2412_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_GOLAY2412, 64,  NULL)
+
+void benchmark_fec_dec_conv27_n64       FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27,  64,  NULL)
+void benchmark_fec_dec_conv29_n64       FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29,  64,  NULL)
+void benchmark_fec_dec_conv39_n64       FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V39,  64,  NULL)
+void benchmark_fec_dec_conv615_n64      FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V615, 64,  NULL)
+
+void benchmark_fec_dec_conv27p23_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P23,64, NULL)
+void benchmark_fec_dec_conv27p34_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P34,64, NULL)
+void benchmark_fec_dec_conv27p45_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P45,64, NULL)
+void benchmark_fec_dec_conv27p56_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P56,64, NULL)
+void benchmark_fec_dec_conv27p67_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P67,64, NULL)
+void benchmark_fec_dec_conv27p78_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P78,64, NULL)
+
+void benchmark_fec_dec_conv29p23_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P23,64, NULL)
+void benchmark_fec_dec_conv29p34_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P34,64, NULL)
+void benchmark_fec_dec_conv29p45_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P45,64, NULL)
+void benchmark_fec_dec_conv29p56_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P56,64, NULL)
+void benchmark_fec_dec_conv29p67_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P67,64, NULL)
+void benchmark_fec_dec_conv29p78_n64    FEC_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P78,64, NULL)
+
+void benchmark_fec_dec_rs8_n64          FEC_DECODE_BENCH_API(LIQUID_FEC_RS_M8,      64,  NULL)
+
diff --git a/src/fec/bench/fec_encode_benchmark.c b/src/fec/bench/fec_encode_benchmark.c
new file mode 100644
index 0000000..9e863cb
--- /dev/null
+++ b/src/fec/bench/fec_encode_benchmark.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define FEC_ENCODE_BENCH_API(FS,N, OPT) \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ fec_encode_bench(_start, _finish, _num_iterations, FS, N, OPT); }
+
+// Helper function to keep code base small
+void fec_encode_bench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations,
+    fec_scheme _fs,
+    unsigned int _n,
+    void * _opts)
+{
+#if !LIBFEC_ENABLED
+    if ( _fs == LIQUID_FEC_CONV_V27    ||
+         _fs == LIQUID_FEC_CONV_V29    ||
+         _fs == LIQUID_FEC_CONV_V39    ||
+         _fs == LIQUID_FEC_CONV_V615   ||
+         _fs == LIQUID_FEC_CONV_V27P23 ||
+         _fs == LIQUID_FEC_CONV_V27P34 ||
+         _fs == LIQUID_FEC_CONV_V27P45 ||
+         _fs == LIQUID_FEC_CONV_V27P56 ||
+         _fs == LIQUID_FEC_CONV_V27P67 ||
+         _fs == LIQUID_FEC_CONV_V27P78 ||
+         _fs == LIQUID_FEC_CONV_V29P23 ||
+         _fs == LIQUID_FEC_CONV_V29P34 ||
+         _fs == LIQUID_FEC_CONV_V29P45 ||
+         _fs == LIQUID_FEC_CONV_V29P56 ||
+         _fs == LIQUID_FEC_CONV_V29P67 ||
+         _fs == LIQUID_FEC_CONV_V29P78 ||
+         _fs == LIQUID_FEC_RS_M8)
+    {
+        fprintf(stderr,"warning: convolutional, Reed-Solomon codes unavailable (install libfec)\n");
+        getrusage(RUSAGE_SELF, _start);
+        memmove((void*)_finish,(void*)_start,sizeof(struct rusage));
+        return;
+    }
+#endif
+
+    // normalize number of iterations
+    *_num_iterations /= _n;
+
+    switch (_fs) {
+    case LIQUID_FEC_NONE:          *_num_iterations *= 500;    break;
+    case LIQUID_FEC_REP3:          *_num_iterations *= 200;    break;
+    case LIQUID_FEC_REP5:          *_num_iterations *= 100;    break;
+    case LIQUID_FEC_HAMMING74:     *_num_iterations *=  30;    break;
+    case LIQUID_FEC_HAMMING84:     *_num_iterations *= 100;    break;
+    case LIQUID_FEC_HAMMING128:    *_num_iterations *= 100;    break;
+    case LIQUID_FEC_SECDED2216:    *_num_iterations *=  10;    break;
+    case LIQUID_FEC_SECDED3932:    *_num_iterations *=  10;    break;
+    case LIQUID_FEC_SECDED7264:    *_num_iterations *=  10;    break;
+    case LIQUID_FEC_GOLAY2412:     *_num_iterations *= 2;      break;
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+    case LIQUID_FEC_RS_M8:
+        *_num_iterations *= 1;
+        break;
+    default:;
+    }
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+
+    // generate fec object
+    fec q = fec_create(_fs,_opts);
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(_fs,_n);
+    unsigned char msg[_n];          // original message
+    unsigned char msg_enc[n_enc];   // encoded message
+
+    // initialze message
+    unsigned long int i;
+    for (i=0; i<_n; i++) {
+        msg[i] = rand() & 0xff;
+    }
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fec_encode(q,_n,msg,msg_enc);
+        fec_encode(q,_n,msg,msg_enc);
+        fec_encode(q,_n,msg,msg_enc);
+        fec_encode(q,_n,msg,msg_enc);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+//
+// BENCHMARKS
+//
+void benchmark_fec_enc_none_n64         FEC_ENCODE_BENCH_API(LIQUID_FEC_NONE,      64,  NULL)
+
+void benchmark_fec_enc_rep3_n64         FEC_ENCODE_BENCH_API(LIQUID_FEC_REP3,      64,  NULL)
+void benchmark_fec_enc_rep5_n64         FEC_ENCODE_BENCH_API(LIQUID_FEC_REP5,      64,  NULL)
+void benchmark_fec_enc_hamming74_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_HAMMING74, 64,  NULL)
+void benchmark_fec_enc_hamming84_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_HAMMING84, 64,  NULL)
+void benchmark_fec_enc_hamming128_n64   FEC_ENCODE_BENCH_API(LIQUID_FEC_HAMMING128,64,  NULL)
+
+// SEC-DED block codes
+void benchmark_fec_enc_secded2216_n64   FEC_ENCODE_BENCH_API(LIQUID_FEC_SECDED2216,64,  NULL)
+void benchmark_fec_enc_secded3932_n64   FEC_ENCODE_BENCH_API(LIQUID_FEC_SECDED3932,64,  NULL)
+void benchmark_fec_enc_secded7264_n64   FEC_ENCODE_BENCH_API(LIQUID_FEC_SECDED7264,64,  NULL)
+
+void benchmark_fec_enc_golay2412_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_GOLAY2412, 64,  NULL)
+
+void benchmark_fec_enc_conv27_n64       FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V27,  64,  NULL)
+void benchmark_fec_enc_conv29_n64       FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V29,  64,  NULL)
+void benchmark_fec_enc_conv39_n64       FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V39,  64,  NULL)
+void benchmark_fec_enc_conv615_n64      FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V615, 64,  NULL)
+
+void benchmark_fec_enc_conv27p23_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V27P23,64, NULL)
+void benchmark_fec_enc_conv27p34_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V27P34,64, NULL)
+void benchmark_fec_enc_conv27p45_n64    FEC_ENCODE_BENCH_API(LIQUID_FEC_CONV_V27P45,64, NULL)
+
+void benchmark_fec_enc_rs8_n64          FEC_ENCODE_BENCH_API(LIQUID_FEC_RS_M8,     64,  NULL)
+
diff --git a/src/fec/bench/fecsoft_decode_benchmark.c b/src/fec/bench/fecsoft_decode_benchmark.c
new file mode 100644
index 0000000..08436ab
--- /dev/null
+++ b/src/fec/bench/fecsoft_decode_benchmark.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fecsoft_decode_benchmark.h
+//
+// Test soft decoding speeds of certain FEC codecs
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define FECSOFT_DECODE_BENCH_API(FS,N, OPT) \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ fecsoft_decode_bench(_start, _finish, _num_iterations, FS, N, OPT); }
+
+// Helper function to keep code base small
+void fecsoft_decode_bench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations,
+    fec_scheme _fs,
+    unsigned int _n,
+    void * _opts)
+{
+#if !LIBFEC_ENABLED
+    if ( _fs == LIQUID_FEC_CONV_V27    ||
+         _fs == LIQUID_FEC_CONV_V29    ||
+         _fs == LIQUID_FEC_CONV_V39    ||
+         _fs == LIQUID_FEC_CONV_V615   ||
+         _fs == LIQUID_FEC_CONV_V27P23 ||
+         _fs == LIQUID_FEC_CONV_V27P34 ||
+         _fs == LIQUID_FEC_CONV_V27P45 ||
+         _fs == LIQUID_FEC_CONV_V27P56 ||
+         _fs == LIQUID_FEC_CONV_V27P67 ||
+         _fs == LIQUID_FEC_CONV_V27P78 ||
+         _fs == LIQUID_FEC_CONV_V29P23 ||
+         _fs == LIQUID_FEC_CONV_V29P34 ||
+         _fs == LIQUID_FEC_CONV_V29P45 ||
+         _fs == LIQUID_FEC_CONV_V29P56 ||
+         _fs == LIQUID_FEC_CONV_V29P67 ||
+         _fs == LIQUID_FEC_CONV_V29P78 ||
+         _fs == LIQUID_FEC_RS_M8)
+    {
+        fprintf(stderr,"warning: convolutional, Reed-Solomon codes unavailable (install libfec)\n");
+        getrusage(RUSAGE_SELF, _start);
+        memmove((void*)_finish,(void*)_start,sizeof(struct rusage));
+        return;
+    }
+#endif
+
+    // normalize number of iterations
+    *_num_iterations /= _n;
+
+    switch (_fs) {
+    case LIQUID_FEC_NONE:          *_num_iterations *= 50;  break;
+    case LIQUID_FEC_REP3:          *_num_iterations *= 20;  break;
+    case LIQUID_FEC_REP5:          *_num_iterations *= 10;  break;
+    case LIQUID_FEC_HAMMING74:     *_num_iterations *= 1;   break;
+    case LIQUID_FEC_HAMMING84:     *_num_iterations *= 1;   break;
+    case LIQUID_FEC_HAMMING128:    *_num_iterations *= 1;   break;
+    case LIQUID_FEC_CONV_V27:      *_num_iterations /= 5;   break;
+    case LIQUID_FEC_CONV_V29:      *_num_iterations /= 50;  break;
+    case LIQUID_FEC_CONV_V39:      *_num_iterations /= 50;  break;
+    case LIQUID_FEC_CONV_V615:     *_num_iterations /= 800; break;
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+        *_num_iterations /= 5;
+        break;
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        *_num_iterations /= 50;
+        break;
+    case LIQUID_FEC_RS_M8:
+        *_num_iterations *= 1;
+        break;
+    default:;
+    }
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // generate fec object
+    fec q = fec_create(_fs,_opts);
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(_fs,_n);
+    unsigned char msg[_n];          // original message
+    unsigned char msg_enc[n_enc];   // encoded message
+    unsigned char msg_soft[8*n_enc];// encoded message (soft bits)
+    unsigned char msg_dec[_n];      // decoded message
+
+    // initialze message
+    unsigned long int i;
+    for (i=0; i<_n; i++)
+        msg[i] = rand() & 0xff;
+
+    // encode message
+    fec_encode(q,_n,msg,msg_enc);
+
+    // expand to soft bits
+    for (i=0; i<n_enc; i++) {
+        msg_soft[8*i+0] = (msg_enc[i] & 0x80) ? 255 : 0;
+        msg_soft[8*i+1] = (msg_enc[i] & 0x40) ? 255 : 0;
+        msg_soft[8*i+2] = (msg_enc[i] & 0x20) ? 255 : 0;
+        msg_soft[8*i+3] = (msg_enc[i] & 0x10) ? 255 : 0;
+        msg_soft[8*i+4] = (msg_enc[i] & 0x08) ? 255 : 0;
+        msg_soft[8*i+5] = (msg_enc[i] & 0x04) ? 255 : 0;
+        msg_soft[8*i+6] = (msg_enc[i] & 0x02) ? 255 : 0;
+        msg_soft[8*i+7] = (msg_enc[i] & 0x01) ? 255 : 0;
+    }
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fec_decode_soft(q, _n, msg_soft, msg_dec);
+        fec_decode_soft(q, _n, msg_soft, msg_dec);
+        fec_decode_soft(q, _n, msg_soft, msg_dec);
+        fec_decode_soft(q, _n, msg_soft, msg_dec);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+//
+// BENCHMARKS
+//
+void benchmark_fecsoft_dec_none_n64       FECSOFT_DECODE_BENCH_API(LIQUID_FEC_NONE,      64,  NULL)
+
+void benchmark_fecsoft_dec_rep3_n64       FECSOFT_DECODE_BENCH_API(LIQUID_FEC_REP3,      64,  NULL)
+void benchmark_fecsoft_dec_rep5_n64       FECSOFT_DECODE_BENCH_API(LIQUID_FEC_REP5,      64,  NULL)
+void benchmark_fecsoft_dec_hamming74_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_HAMMING74, 64,  NULL)
+void benchmark_fecsoft_dec_hamming84_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_HAMMING84, 64,  NULL)
+void benchmark_fecsoft_dec_hamming128_n64 FECSOFT_DECODE_BENCH_API(LIQUID_FEC_HAMMING128,64,  NULL)
+
+void benchmark_fecsoft_dec_conv27_n64     FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27,  64,  NULL)
+void benchmark_fecsoft_dec_conv29_n64     FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29,  64,  NULL)
+void benchmark_fecsoft_dec_conv39_n64     FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V39,  64,  NULL)
+void benchmark_fecsoft_dec_conv615_n64    FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V615, 64,  NULL)
+
+void benchmark_fecsoft_dec_conv27p23_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P23,64, NULL)
+void benchmark_fecsoft_dec_conv27p34_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P34,64, NULL)
+void benchmark_fecsoft_dec_conv27p45_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P45,64, NULL)
+void benchmark_fecsoft_dec_conv27p56_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P56,64, NULL)
+void benchmark_fecsoft_dec_conv27p67_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P67,64, NULL)
+void benchmark_fecsoft_dec_conv27p78_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V27P78,64, NULL)
+
+void benchmark_fecsoft_dec_conv29p23_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P23,64, NULL)
+void benchmark_fecsoft_dec_conv29p34_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P34,64, NULL)
+void benchmark_fecsoft_dec_conv29p45_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P45,64, NULL)
+void benchmark_fecsoft_dec_conv29p56_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P56,64, NULL)
+void benchmark_fecsoft_dec_conv29p67_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P67,64, NULL)
+void benchmark_fecsoft_dec_conv29p78_n64  FECSOFT_DECODE_BENCH_API(LIQUID_FEC_CONV_V29P78,64, NULL)
+
+void benchmark_fecsoft_dec_rs8_n64        FECSOFT_DECODE_BENCH_API(LIQUID_FEC_RS_M8,      64, NULL)
+
diff --git a/src/fec/bench/interleaver_benchmark.c b/src/fec/bench/interleaver_benchmark.c
new file mode 100644
index 0000000..35f84a8
--- /dev/null
+++ b/src/fec/bench/interleaver_benchmark.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define INTERLEAVER_BENCH_API(N)        \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ interleaver_bench(_start, _finish, _num_iterations, N); }
+
+// Helper function to keep code base small
+void interleaver_bench(struct rusage *_start,
+                       struct rusage *_finish,
+                       unsigned long int *_num_iterations,
+                       unsigned int _n)
+{
+    // scale number of iterations by block size
+    // iterations = 4: cycles/trial ~ exp( -0.883 + 0.708*log(_n) )
+    *_num_iterations /= 0.7f*expf( -0.883 + 0.708*logf(_n) );
+
+    // initialize interleaver
+    interleaver q = interleaver_create(_n);
+    interleaver_set_depth(q, 4);
+
+    unsigned char x[_n];
+    unsigned char y[_n];
+    
+    unsigned long int i;
+    for (i=0; i<_n; i++)
+        x[i] = rand() & 0xff;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        interleaver_encode(q, x, y);
+        interleaver_encode(q, x, y);
+        interleaver_encode(q, x, y);
+        interleaver_encode(q, x, y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy interleaver object
+    interleaver_destroy(q);
+}
+
+void benchmark_interleaver_8    INTERLEAVER_BENCH_API(8     )
+void benchmark_interleaver_16   INTERLEAVER_BENCH_API(16    )
+void benchmark_interleaver_32   INTERLEAVER_BENCH_API(32    )
+void benchmark_interleaver_64   INTERLEAVER_BENCH_API(64    )
+void benchmark_interleaver_128  INTERLEAVER_BENCH_API(128   )
+void benchmark_interleaver_256  INTERLEAVER_BENCH_API(256   )
+void benchmark_interleaver_512  INTERLEAVER_BENCH_API(512   )
+void benchmark_interleaver_1024 INTERLEAVER_BENCH_API(1024  )
+
diff --git a/src/fec/bench/packetizer_decode_benchmark.c b/src/fec/bench/packetizer_decode_benchmark.c
new file mode 100644
index 0000000..83e70fb
--- /dev/null
+++ b/src/fec/bench/packetizer_decode_benchmark.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define PACKETIZER_DECODE_BENCH_API(N, CRC, FEC0, FEC1) \
+(   struct rusage *_start,                              \
+    struct rusage *_finish,                             \
+    unsigned long int *_num_iterations)                 \
+{ packetizer_decode_bench(_start, _finish, _num_iterations, N, CRC, FEC0, FEC1); }
+
+// Helper function to keep code base small
+void packetizer_decode_bench(struct rusage *     _start,
+                             struct rusage *     _finish,
+                             unsigned long int * _num_iterations,
+                             unsigned int        _n,
+                             crc_scheme          _crc,
+                             fec_scheme          _fec0,
+                             fec_scheme          _fec1)
+{
+    //
+    unsigned int msg_dec_len = _n;
+    unsigned int msg_enc_len = packetizer_compute_enc_msg_len(_n,_crc,_fec0,_fec1);
+
+    // adjust number of iterations
+    //  k-cycles/trial ~ 221 + 1.6125*msg_dec_len;
+    // TODO: adjust iterations based on encoder types
+    *_num_iterations *= 1000;
+    *_num_iterations /= 221 + 1.6125*msg_dec_len;
+
+    unsigned char msg_rec[msg_enc_len];
+    unsigned char msg_dec[msg_dec_len];
+
+    // initialize data
+    unsigned long int i;
+    for (i=0; i<msg_enc_len; i++) msg_rec[i] = rand() & 0xff;
+    for (i=0; i<msg_dec_len; i++) msg_dec[i] = 0x00;
+
+    // create packet generator
+    packetizer q = packetizer_create(msg_dec_len, _crc, _fec0, _fec1);
+    int crc_pass = 0;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // decode packet
+        crc_pass |= packetizer_decode(q, msg_rec, msg_dec);
+        crc_pass |= packetizer_decode(q, msg_rec, msg_dec);
+        crc_pass |= packetizer_decode(q, msg_rec, msg_dec);
+        crc_pass |= packetizer_decode(q, msg_rec, msg_dec);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up allocated objects
+    packetizer_destroy(q);
+}
+
+
+//
+// BENCHMARKS
+//
+void benchmark_packetizer_n16   PACKETIZER_DECODE_BENCH_API(16,   LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n32   PACKETIZER_DECODE_BENCH_API(32,   LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n64   PACKETIZER_DECODE_BENCH_API(64,   LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n128  PACKETIZER_DECODE_BENCH_API(128,  LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n256  PACKETIZER_DECODE_BENCH_API(256,  LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n512  PACKETIZER_DECODE_BENCH_API(512,  LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+void benchmark_packetizer_n1024 PACKETIZER_DECODE_BENCH_API(1024, LIQUID_CRC_NONE, LIQUID_FEC_NONE, LIQUID_FEC_NONE)
+
diff --git a/src/fec/bench/sumproduct_benchmark.c b/src/fec/bench/sumproduct_benchmark.c
new file mode 100644
index 0000000..1a79253
--- /dev/null
+++ b/src/fec/bench/sumproduct_benchmark.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// benchmark sum-product algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+#define SUMPRODUCT_BENCH_API(M)         \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ sumproduct_bench(_start, _finish, _num_iterations, M); }
+
+// generate half-rate LDPC generator and parity-check matrices
+void sumproduct_generate(unsigned int    _m,
+                         unsigned char * _G,
+                         unsigned char * _H);
+
+// Helper function to keep code base small
+void sumproduct_bench(struct rusage *     _start,
+                      struct rusage *     _finish,
+                      unsigned long int * _num_iterations,
+                      unsigned int        _m)
+{
+    // normalize number of iterations
+    // M cycles/trial ~ 2^{ -11.5920 + 2.503*log2(_m) }
+    *_num_iterations /= 4*_m*_m;
+    if (*_num_iterations < 1)
+        *_num_iterations = 1;
+
+    unsigned long int i;
+
+    // derived values
+    unsigned int _n = 2*_m;
+
+    // create arrays
+    unsigned char Gs[_m*_n]; // generator matrix [m x n]
+    unsigned char Hs[_m*_n]; // parity check matrix [m x n]
+    sumproduct_generate(_m, Gs, Hs);
+
+    // generate sparse binary matrices
+    smatrixb G = smatrixb_create_array(Gs, _n, _m);
+    smatrixb H = smatrixb_create_array(Hs, _m, _n);
+
+    // print matrices
+    //printf("G:\n"); smatrixb_print_expanded(G);
+    //printf("H:\n"); smatrixb_print_expanded(H);
+
+    unsigned char x[_m];     // original message signal
+    unsigned char c[_n];     // transmitted codeword
+    float LLR[_n];           // log-likelihood ratio
+    unsigned char c_hat[_n]; // estimated codeword
+
+    // initialize message array
+    for (i=0; i<_m; i++)
+        x[i] = rand() % 2;
+
+    // compute encoded message
+    smatrixb_vmul(G, x, c);
+
+    // print status
+    //printf("x = ["); for (i=0; i<_m; i++) printf("%2u", x[i]); printf(" ];\n");
+    //printf("c = ["); for (i=0; i<_n; i++) printf("%2u", c[i]); printf(" ];\n");
+
+    // compute log-likelihood ratio (LLR)
+    for (i=0; i<_n; i++)
+        LLR[i] = (c[i] == 0 ? 1.0f : -1.0f) + 0.5*randnf();
+    
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    int parity_pass;
+    for (i=0; i<(*_num_iterations); i++) {
+        parity_pass = fec_sumproduct(_m, _n, H, LLR, c_hat, 1); LLR[i%_m] += parity_pass ? 1 : -1;
+        parity_pass = fec_sumproduct(_m, _n, H, LLR, c_hat, 1); LLR[i%_m] += parity_pass ? 1 : -1;
+        parity_pass = fec_sumproduct(_m, _n, H, LLR, c_hat, 1); LLR[i%_m] += parity_pass ? 1 : -1;
+        parity_pass = fec_sumproduct(_m, _n, H, LLR, c_hat, 1); LLR[i%_m] += parity_pass ? 1 : -1;
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+//
+// BENCHMARKS
+//
+void benchmark_sumproduct_m16   SUMPRODUCT_BENCH_API(16)
+void benchmark_sumproduct_m32   SUMPRODUCT_BENCH_API(32)
+void benchmark_sumproduct_m64   SUMPRODUCT_BENCH_API(64)
+void benchmark_sumproduct_m128  SUMPRODUCT_BENCH_API(128)
+
+// generate half-rate LDPC generator and parity-check matrices
+void sumproduct_generate(unsigned int    _m,
+                         unsigned char * _G,
+                         unsigned char * _H)
+{
+    unsigned int i;
+    unsigned int j;
+
+    // derived values
+    unsigned int _n = 2*_m;
+
+    // initial generator polynomial [1 x m]
+    unsigned char p[_m];
+
+    // initialize generator polynomial (systematic)
+    for (i=0; i<_m; i++)
+        p[i] = 0;
+    unsigned int t = 0;
+    unsigned int k = 2;
+    for (i=0; i<_m; i++) {
+        t++;
+        if (t == k) {
+            t = 0;
+            k *= 2;
+            p[i] = 1;
+        }
+    }
+
+    // initialze matrices
+    for (i=0; i<_m; i++) {
+        for (j=0; j<_m; j++) {
+            // G = [I(m) P]^T
+            _G[j*_m + i]         = (i==j) ? 1 : 0;
+            _G[j*_m + i + _m*_m] = p[(i+j)%_m];
+
+            // H = [P^T I(m)]
+            _H[i*_n + j + _m] = (i==j) ? 1 : 0;
+            _H[i*_n + j]      = p[(i+j)%_m];
+        }
+    }
+}
+
diff --git a/src/fec/readme.rs.txt b/src/fec/readme.rs.txt
new file mode 100644
index 0000000..2c74ead
--- /dev/null
+++ b/src/fec/readme.rs.txt
@@ -0,0 +1,52 @@
+===================================
+ Reed-Solomon codes
+===================================
+
+This document gives several notes on the usage of Reed-Solomon (RS) codes in
+liquid.  The fec object for RS codes allows for any length input sequence to
+be encoded.  This is accomplished by splitting the uncoded input sequence into
+a discrete number of blocks of nearly equal length.  Each block is then padded
+with zeros (internally) before the encoding process.
+
+For example, the 8-bit (255,223) code adds 32 parity symbols to an uncoded
+input message of length 223 symbols. To encode messages of lengths less than
+223, the input is padded with zeros until its length is exactly 223.  The 32
+parity symbols are then computed and appended to the end. Internally, libfec
+does this efficiently and seamlessly.  However, there is no simply way to
+encode messages of lengths larger than 223 (for this 8-bit example).
+
+Let us assume that we want to encode a message of 1024 8-bit symbols.
+Unfortuantely 223 does not evenly divide 1024, so we cannot simply break the
+original message into blocks that neatly fit our codec.  As an alternative, we
+could split the message into four blocks of 223 and one block of 132, viz.
+    1024 = (4)*223 + 132
+At first glance this is a tractable solution, however it has several inherent
+disadvantages:
+    1.  libfec uses an internal object to handle padding for blocks less than
+        223, so if we want to take advantage of its efficient implementation
+        we will need to either create two RS libfec objects (one for blocks of
+        lenght 223 and one for blocks of length 132) or re-create the object
+        every time.  As an alternative we could just use one RS object which
+        operates on blocks of length 223 and just manually pad the data for
+        the block of length 132, however this can be cumbersome and
+        inefficient.
+    2.  Because the same number of parity symbols (in this case, 32) are
+        applied to each block during the encoding process, blocks of shorter
+        lengths will have more error-correction capabilities.  In this case,
+        the blocks of length 223 will have weaker error protection than those
+        of length 132 and the resulting total error protection will be
+        degraded.
+For these reasons, liquid splits the input message into (nearly) equally-sized
+blocks.  For our example, we need a minimum of five blocks because
+    ceil(1024 / 223) = ceil(4.5919) = 5
+We would like to split the message into five blocks whose lenghts are of
+perfect equality, but unfortunately five does not evenly divide 1024.  The
+closest we can get is four blocks of 205 and one block of 204, viz.
+    1024 = (4)*205 + 204
+As a result, each block has (nearly) the same error protection, and just one
+RS libfec object can be used.  It is important to remember that the last block
+needs to be padded by just one zero before encoding.
+
+References:
+    [Karn:2002] Karn, P. "libfec," http://www.ka9q.net/code/fec/
+
diff --git a/src/fec/src/c_ones_mod2.c b/src/fec/src/c_ones_mod2.c
new file mode 100644
index 0000000..46866e0
--- /dev/null
+++ b/src/fec/src/c_ones_mod2.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//
+//
+
+unsigned char c_ones_mod2[] = {
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0
+};
+
diff --git a/src/fec/src/c_ones_mod2_gentab.c b/src/fec/src/c_ones_mod2_gentab.c
new file mode 100644
index 0000000..11ff0b3
--- /dev/null
+++ b/src/fec/src/c_ones_mod2_gentab.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// script to generate c_ones_mod2 table
+//
+
+#include <stdio.h>
+
+// count the number of ones in a byte
+unsigned int count_ones(unsigned char);
+
+int main() {
+
+    unsigned char c_ones_mod2[256];
+
+    unsigned int b;
+    for (b=0; b<256; b++)
+        c_ones_mod2[b] = count_ones((unsigned char)b) % 2;
+    
+    // print results
+    printf("unsigned char c_ones_mod2[] = {\n    ");
+    for (b=0; b<256; b++) {
+        printf("%1u", c_ones_mod2[b]);
+        if (b==255)
+            printf("\n");
+        else if (((b+1)%16)==0)
+            printf(",\n    ");
+        else
+            printf(", ");
+    }
+    printf("};\n");
+    return 0;
+}
+
+// count the number of ones in a byte
+unsigned int count_ones(unsigned char _b)
+{
+    unsigned int i, n=0;
+    for (i=0; i<8; i++) {
+        n += _b & 0x01;
+        _b >>= 1;
+    }
+    return n;
+}
diff --git a/src/fec/src/crc.c b/src/fec/src/crc.c
new file mode 100644
index 0000000..3f2f640
--- /dev/null
+++ b/src/fec/src/crc.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// cyclic redundancy check (and family)
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+
+// object-independent methods
+
+const char * crc_scheme_str[LIQUID_CRC_NUM_SCHEMES][2] = {
+    // short name,  long name
+    {"unknown",     "unknown"},
+    {"none",        "none"},
+    {"checksum",    "checksum (8-bit)"},
+    {"crc8",        "CRC (8-bit)"},
+    {"crc16",       "CRC (16-bit)"},
+    {"crc24",       "CRC (24-bit)"},
+    {"crc32",       "CRC (32-bit)"}
+};
+
+
+// Print compact list of existing and available crc schemes
+void liquid_print_crc_schemes()
+{
+    unsigned int i;
+    unsigned int len = 10;
+
+    // print all available MOD schemes
+    printf("          ");
+    for (i=0; i<LIQUID_CRC_NUM_SCHEMES; i++) {
+        printf("%s", crc_scheme_str[i][0]);
+
+        if (i != LIQUID_CRC_NUM_SCHEMES-1)
+            printf(", ");
+
+        len += strlen(crc_scheme_str[i][0]);
+        if (len > 48 && i != LIQUID_CRC_NUM_SCHEMES-1) {
+            len = 10;
+            printf("\n          ");
+        }
+    }
+    printf("\n");
+}
+
+crc_scheme liquid_getopt_str2crc(const char * _str)
+{
+    // compare each string to short name
+    unsigned int i;
+    for (i=0; i<LIQUID_CRC_NUM_SCHEMES; i++) {
+        if (strcmp(_str,crc_scheme_str[i][0])==0) {
+            return i;
+        }
+    }
+
+    fprintf(stderr,"warning: liquid_getopt_str2crc(), unknown/unsupported crc scheme : %s\n", _str);
+    return LIQUID_CRC_UNKNOWN;
+}
+
+// get length of CRC (bytes)
+unsigned int crc_get_length(crc_scheme _scheme)
+{
+    switch (_scheme) {
+    case LIQUID_CRC_UNKNOWN:   return 0;
+    case LIQUID_CRC_NONE:      return 0;
+    case LIQUID_CRC_CHECKSUM:  return 1;
+    case LIQUID_CRC_8:         return 1;
+    case LIQUID_CRC_16:        return 2;
+    case LIQUID_CRC_24:        return 3;
+    case LIQUID_CRC_32:        return 4;
+    default:
+        fprintf(stderr,"error: crc_get_length(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(1);
+    }
+
+    return 0;
+}
+
+// generate error-detection key
+//  _scheme     :   error-detection scheme
+//  _msg        :   input data message, [size: _n x 1]
+//  _n          :   input data message size
+unsigned int crc_generate_key(crc_scheme      _scheme,
+                              unsigned char * _msg,
+                              unsigned int    _n)
+{
+    switch (_scheme) {
+    case LIQUID_CRC_UNKNOWN:
+        fprintf(stderr,"error: crc_generate_key(), cannot generate key with CRC type \"UNKNOWN\"\n");
+        exit(-1);
+    case LIQUID_CRC_NONE:      return 0;
+    case LIQUID_CRC_CHECKSUM:  return checksum_generate_key(_msg, _n);
+    case LIQUID_CRC_8:         return crc8_generate_key(_msg, _n);
+    case LIQUID_CRC_16:        return crc16_generate_key(_msg, _n);
+    case LIQUID_CRC_24:        return crc24_generate_key(_msg, _n);
+    case LIQUID_CRC_32:        return crc32_generate_key(_msg, _n);
+    default:
+        fprintf(stderr,"error: crc_generate_key(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(1);
+    }
+
+    return 0;
+}
+
+// generate error-detection key and append to end of message
+//  _scheme     :   error-detection scheme (resulting in 'p' bytes)
+//  _msg        :   input data message, [size: _n+p x 1]
+//  _n          :   input data message size (excluding key at end)
+void crc_append_key(crc_scheme      _scheme,
+                    unsigned char * _msg,
+                    unsigned int    _n)
+{
+    // get key size
+    unsigned int len = crc_sizeof_key(_scheme);
+
+    // generate key
+    unsigned int key = crc_generate_key(_scheme, _msg, _n);
+
+    // append key to end of message
+    unsigned int i;
+    for (i=0; i<len; i++)
+        _msg[_n+i] = (key >> (len - i - 1)*8) & 0xff;
+}
+
+// validate message using error-detection key
+//  _scheme     :   error-detection scheme
+//  _msg        :   input data message, [size: _n x 1]
+//  _n          :   input data message size
+//  _key        :   error-detection key
+int crc_validate_message(crc_scheme      _scheme,
+                         unsigned char * _msg,
+                         unsigned int    _n,
+                         unsigned int    _key)
+{
+    if (_scheme == LIQUID_CRC_UNKNOWN) {
+        fprintf(stderr,"error: crc_validate_message(), cannot validate with CRC type \"UNKNOWN\"\n");
+        exit(-1);
+    } else if (_scheme == LIQUID_CRC_NONE) {
+        return 1;
+    }
+
+    return crc_generate_key(_scheme, _msg, _n) == _key;
+}
+
+// check message with key appended to end of array
+//  _scheme     :   error-detection scheme (resulting in 'p' bytes)
+//  _msg        :   input data message, [size: _n+p x 1]
+//  _n          :   input data message size (excluding key at end)
+int crc_check_key(crc_scheme      _scheme,
+                  unsigned char * _msg,
+                  unsigned int    _n)
+{
+    // get key size
+    unsigned int len = crc_sizeof_key(_scheme);
+
+    // extract key from end of message
+    unsigned int key = 0;
+    unsigned int i;
+    for (i=0; i<len; i++) {
+        key <<= 8;
+        key |= _msg[_n+i];
+    }
+
+    // validate message against key
+    return crc_validate_message(_scheme, _msg, _n, key);
+}
+
+// get size of key (bytes)
+unsigned int crc_sizeof_key(crc_scheme _scheme)
+{
+    switch (_scheme) {
+    case LIQUID_CRC_UNKNOWN:
+        fprintf(stderr,"error: crc_sizeof_key(), cannot get size of type 'LIQUID_CRC_UNKNOWN'\n");
+        exit(-1);
+    case LIQUID_CRC_NONE:      return 0;
+    case LIQUID_CRC_CHECKSUM:  return 1;
+    case LIQUID_CRC_8:         return 1;
+    case LIQUID_CRC_16:        return 2;
+    case LIQUID_CRC_24:        return 3;
+    case LIQUID_CRC_32:        return 4;
+    default:
+        fprintf(stderr,"error: crc_sizeof_key(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(1);
+    }
+
+    return 0;
+}
+
+
+
+//
+// Checksum
+//
+
+// generate 8-bit checksum key
+//
+//  _scheme     :   error-detection scheme
+//  _msg        :   input data message, [size: _n x 1]
+//  _n          :   input data message size
+unsigned int checksum_generate_key(unsigned char *_data,
+                                   unsigned int _n)
+{
+    unsigned int i, sum=0;
+    for (i=0; i<_n; i++)
+        sum += (unsigned int) (_data[i]);
+    //sum &= 0x00ff;
+
+    // mask and convert to 2's complement
+    unsigned char key = ~(sum&0x00ff) + 1;
+
+    return key;
+}
+
+
+// 
+// CRC-8
+//
+
+// generate 8-bit cyclic redundancy check key.
+//
+// slow method, operates one bit at a time
+// algorithm from: http://www.hackersdelight.org/crc.pdf
+//
+//  _msg    :   input data message [size: _n x 1]
+//  _n      :   input data message size
+unsigned int crc8_generate_key(unsigned char *_msg,
+                               unsigned int _n)
+{
+    unsigned int i, j, b, mask, key8=~0;
+    unsigned int poly = liquid_reverse_byte_gentab[CRC8_POLY];
+    for (i=0; i<_n; i++) {
+        b = _msg[i];
+        key8 ^= b;
+        for (j=0; j<8; j++) {
+            mask = -(key8 & 1);
+            key8 = (key8>>1) ^ (poly & mask);
+        }
+    }
+    return (~key8) & 0xff;
+}
+
+
+// 
+// CRC-16
+//
+
+// generate 16-bit cyclic redundancy check key.
+//
+// slow method, operates one bit at a time
+// algorithm from: http://www.hackersdelight.org/crc.pdf
+//
+//  _msg    :   input data message [size: _n x 1]
+//  _n      :   input data message size
+unsigned int crc16_generate_key(unsigned char *_msg,
+                                unsigned int _n)
+{
+    unsigned int i, j, b, mask, key16=~0;
+    unsigned int poly = liquid_reverse_uint16(CRC16_POLY);
+    for (i=0; i<_n; i++) {
+        b = _msg[i];
+        key16 ^= b;
+        for (j=0; j<8; j++) {
+            mask = -(key16 & 1);
+            key16 = (key16>>1) ^ (poly & mask);
+        }
+    }
+    return (~key16) & 0xffff;
+}
+
+
+// 
+// CRC-24
+//
+
+// generate 24-bit cyclic redundancy check key.
+//
+// slow method, operates one bit at a time
+// algorithm from: http://www.hackersdelight.org/crc.pdf
+//
+//  _msg    :   input data message [size: _n x 1]
+//  _n      :   input data message size
+unsigned int crc24_generate_key(unsigned char *_msg,
+                                unsigned int _n)
+{
+    unsigned int i, j, b, mask, key24=~0;
+    unsigned int poly = liquid_reverse_uint24(CRC24_POLY);
+    for (i=0; i<_n; i++) {
+        b = _msg[i];
+        key24 ^= b;
+        for (j=0; j<8; j++) {
+            mask = -(key24 & 1);
+            key24 = (key24>>1) ^ (poly & mask);
+        }
+    }
+    return (~key24) & 0xffffff;
+}
+
+
+// 
+// CRC-32
+//
+
+// generate 32-bit cyclic redundancy check key.
+//
+// slow method, operates one bit at a time
+// algorithm from: http://www.hackersdelight.org/crc.pdf
+//
+//  _msg    :   input data message [size: _n x 1]
+//  _n      :   input data message size
+unsigned int crc32_generate_key(unsigned char *_msg,
+                                unsigned int _n)
+{
+    unsigned int i, j, b, mask, key32=~0;
+    unsigned int poly = liquid_reverse_uint32(CRC32_POLY);
+    for (i=0; i<_n; i++) {
+        b = _msg[i];
+        key32 ^= b;
+        for (j=0; j<8; j++) {
+            mask = -(key32 & 1);
+            key32 = (key32>>1) ^ (poly & mask);
+        }
+    }
+    return (~key32) & 0xffffffff;
+}
+
+#if 0
+void crc32_generate_key(unsigned char *_msg,
+                        unsigned int _n,
+                        unsigned char *_key)
+{
+    unsigned int key32 = crc32_generate_key32(_msg,_n);
+    _key[0] = (key32 & 0xFF000000) >> 24;
+    _key[1] = (key32 & 0x00FF0000) >> 16;
+    _key[2] = (key32 & 0x0000FF00) >> 8;
+    _key[3] = (key32 & 0x000000FF);
+}
+#endif
+
diff --git a/src/fec/src/fec.c b/src/fec/src/fec.c
new file mode 100644
index 0000000..2b92975
--- /dev/null
+++ b/src/fec/src/fec.c
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// FEC (generic functions)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// object-independent methods
+
+const char * fec_scheme_str[LIQUID_FEC_NUM_SCHEMES][2] = {
+    // short name,  long name
+    {"unknown",     "unknown"},
+    {"none",        "none"},
+    {"rep3",        "repeat(3)"},
+    {"rep5",        "repeat(5)"},
+    {"h74",         "Hamming(7,4)"},
+    {"h84",         "Hamming(8,4)"},
+    {"h128",        "Hamming(12,8)"},
+    {"g2412",       "Golay(24,12)"},
+    {"secded2216",  "SEC-DEC(22,16)"},
+    {"secded3932",  "SEC-DEC(39,32)"},
+    {"secded7264",  "SEC-DEC(72,64)"},
+    {"v27",         "convolutional r1/2 K=7"},
+    {"v29",         "convolutional r1/2 K=9"},
+    {"v39",         "convolutional r1/3 K=9"},
+    {"v615",        "convolutional r1/6 K=15"},
+    {"v27p23",      "convolutional r2/3 K=7 (punctured)"},
+    {"v27p34",      "convolutional r3/4 K=7 (punctured)"},
+    {"v27p45",      "convolutional r4/5 K=7 (punctured)"},
+    {"v27p56",      "convolutional r5/6 K=7 (punctured)"},
+    {"v27p67",      "convolutional r6/7 K=7 (punctured)"},
+    {"v27p78",      "convolutional r7/8 K=7 (punctured)"},
+    {"v29p23",      "convolutional r2/3 K=9 (punctured)"},
+    {"v29p34",      "convolutional r3/4 K=9 (punctured)"},
+    {"v29p45",      "convolutional r4/5 K=9 (punctured)"},
+    {"v29p56",      "convolutional r5/6 K=9 (punctured)"},
+    {"v29p67",      "convolutional r6/7 K=9 (punctured)"},
+    {"v29p78",      "convolutional r7/8 K=9 (punctured)"},
+    {"rs8",         "Reed-Solomon, 223/255"}
+};
+
+// Print compact list of existing and available fec schemes
+void liquid_print_fec_schemes()
+{
+    unsigned int i;
+    unsigned int len = 10;
+
+    // print all available MOD schemes
+    printf("          ");
+    for (i=0; i<LIQUID_FEC_NUM_SCHEMES; i++) {
+#if !LIBFEC_ENABLED
+        if ( fec_scheme_is_convolutional(i) || fec_scheme_is_reedsolomon(i) )
+            continue;
+#endif
+        printf("%s", fec_scheme_str[i][0]);
+
+        if (i != LIQUID_FEC_NUM_SCHEMES-1)
+            printf(", ");
+
+        len += strlen(fec_scheme_str[i][0]);
+        if (len > 48 && i != LIQUID_FEC_NUM_SCHEMES-1) {
+            len = 10;
+            printf("\n          ");
+        }
+    }
+    printf("\n");
+}
+
+
+fec_scheme liquid_getopt_str2fec(const char * _str)
+{
+    // compare each string to short name
+    unsigned int i;
+    for (i=0; i<LIQUID_FEC_NUM_SCHEMES; i++) {
+        if (strcmp(_str,fec_scheme_str[i][0])==0) {
+            return i;
+        }
+    }
+
+    fprintf(stderr,"warning: liquid_getopt_str2fec(), unknown/unsupported fec scheme : %s\n", _str);
+    return LIQUID_FEC_UNKNOWN;
+}
+
+// is scheme convolutional?
+int fec_scheme_is_convolutional(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    // convolutional codes (punctured or otherwise)
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        return 1;
+
+    default:;
+    }
+
+    return 0;
+}
+
+// is scheme punctured?
+int fec_scheme_is_punctured(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    // convolutional codes (punctured)
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        return 1;
+
+    default:;
+    }
+
+    return 0;
+}
+
+// is scheme Reed-Solomon?
+int fec_scheme_is_reedsolomon(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    // Reed-Solomon codes
+    case LIQUID_FEC_RS_M8:
+        return 1;
+    default:;
+    }
+    return 0;
+}
+
+// is scheme Hamming?
+int fec_scheme_is_hamming(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    case LIQUID_FEC_HAMMING74:
+    case LIQUID_FEC_HAMMING84:
+    case LIQUID_FEC_HAMMING128:
+        return 1;
+    default:;
+    }
+    return 0;
+}
+
+// is scheme repeat?
+int fec_scheme_is_repeat(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    case LIQUID_FEC_REP3:
+    case LIQUID_FEC_REP5:
+        return 1;
+    default:;
+    }
+    return 0;
+}
+
+
+// return the encoded message length using a particular error-
+// correction scheme (object-independent method)
+//  _scheme     :   forward error-correction scheme
+//  _msg_len    :   raw, uncoded message length
+unsigned int fec_get_enc_msg_length(fec_scheme _scheme,
+                                    unsigned int _msg_len)
+{
+    switch (_scheme) {
+    case LIQUID_FEC_UNKNOWN:        return 0;
+    case LIQUID_FEC_NONE:           return _msg_len;
+    case LIQUID_FEC_REP3:           return 3*_msg_len;
+    case LIQUID_FEC_REP5:           return 5*_msg_len;
+    case LIQUID_FEC_HAMMING74:      return fec_block_get_enc_msg_len(_msg_len,4,7);
+    case LIQUID_FEC_HAMMING84:      return fec_block_get_enc_msg_len(_msg_len,4,8);
+    case LIQUID_FEC_HAMMING128:     return fec_block_get_enc_msg_len(_msg_len,8,12);
+    case LIQUID_FEC_GOLAY2412:      return fec_block_get_enc_msg_len(_msg_len,12,24);
+    case LIQUID_FEC_SECDED2216:     return _msg_len + _msg_len/2 + ((_msg_len%2) ? 1 : 0);
+    case LIQUID_FEC_SECDED3932:     return _msg_len + _msg_len/4 + ((_msg_len%4) ? 1 : 0);
+    case LIQUID_FEC_SECDED7264:     return _msg_len + _msg_len/8 + ((_msg_len%8) ? 1 : 0);
+
+#if LIBFEC_ENABLED
+    // convolutional codes
+    case LIQUID_FEC_CONV_V27:       return 2*_msg_len + 2;  // (K-1)/r=12, round up to 2 bytes
+    case LIQUID_FEC_CONV_V29:       return 2*_msg_len + 2;  // (K-1)/r=16, 2 bytes
+    case LIQUID_FEC_CONV_V39:       return 3*_msg_len + 3;  // (K-1)/r=24, 3 bytes
+    case LIQUID_FEC_CONV_V615:      return 6*_msg_len + 11; // (K-1)/r=84, round up to 11 bytes
+    case LIQUID_FEC_CONV_V27P23:    return fec_conv_get_enc_msg_len(_msg_len,7,2);
+    case LIQUID_FEC_CONV_V27P34:    return fec_conv_get_enc_msg_len(_msg_len,7,3);
+    case LIQUID_FEC_CONV_V27P45:    return fec_conv_get_enc_msg_len(_msg_len,7,4);
+    case LIQUID_FEC_CONV_V27P56:    return fec_conv_get_enc_msg_len(_msg_len,7,5);
+    case LIQUID_FEC_CONV_V27P67:    return fec_conv_get_enc_msg_len(_msg_len,7,6);
+    case LIQUID_FEC_CONV_V27P78:    return fec_conv_get_enc_msg_len(_msg_len,7,7);
+
+    case LIQUID_FEC_CONV_V29P23:    return fec_conv_get_enc_msg_len(_msg_len,9,2);
+    case LIQUID_FEC_CONV_V29P34:    return fec_conv_get_enc_msg_len(_msg_len,9,3);
+    case LIQUID_FEC_CONV_V29P45:    return fec_conv_get_enc_msg_len(_msg_len,9,4);
+    case LIQUID_FEC_CONV_V29P56:    return fec_conv_get_enc_msg_len(_msg_len,9,5);
+    case LIQUID_FEC_CONV_V29P67:    return fec_conv_get_enc_msg_len(_msg_len,9,6);
+    case LIQUID_FEC_CONV_V29P78:    return fec_conv_get_enc_msg_len(_msg_len,9,7);
+
+    // Reed-Solomon codes
+    case LIQUID_FEC_RS_M8:          return fec_rs_get_enc_msg_len(_msg_len,32,255,223);
+#else
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        fprintf(stderr, "error: fec_get_enc_msg_length(), convolutional codes unavailable (install libfec)\n");
+        exit(-1);
+
+    case LIQUID_FEC_RS_M8:
+        fprintf(stderr, "error: fec_get_enc_msg_length(), Reed-Solomon codes unavailable (install libfec)\n");
+        exit(-1);
+#endif
+    default:
+        printf("error: fec_get_enc_msg_length(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(-1);
+    }
+
+    return 0;
+}
+
+// compute encoded message length for block codes
+//  _dec_msg_len    :   decoded message length (bytes)
+//  _m              :   input block size (bits)
+//  _k              :   output block size (bits)
+unsigned int fec_block_get_enc_msg_len(unsigned int _dec_msg_len,
+                                       unsigned int _m,
+                                       unsigned int _k)
+{
+    // validate input
+    if (_m == 0) {
+        fprintf(stderr,"fec_block_get_enc_msg_len(), input block size cannot be zero\n");
+        exit(1);
+    } else if (_k < _m) {
+        fprintf(stderr,"fec_block_get_enc_msg_len(), output block size cannot be smaller than input\n");
+        exit(1);
+    }
+
+    // compute total number of bits in decoded message
+    unsigned int num_bits_in = _dec_msg_len*8;
+
+    // compute total number of blocks: ceil(num_bits_in/_m)
+    unsigned int num_blocks = num_bits_in / _m + (num_bits_in%_m ? 1 : 0);
+
+    // compute total number of bits out
+    unsigned int num_bits_out = num_blocks * _k;
+
+    // compute total number of bytes out: ceil(num_bits_out/8)
+    unsigned int num_bytes_out = num_bits_out/8 + (num_bits_out%8 ? 1 : 0);
+#if 0
+    printf("fec_block_get_enc_msg_len(%u,%u,%u)\n", _dec_msg_len, _m, _k);
+    printf("    dec msg len :   %u bytes\n", _dec_msg_len);
+    printf("    m           :   %u bits\n", _m);
+    printf("    k           :   %u bits\n", _k);
+    printf("    num bits in :   %u bits\n", num_bits_in);
+    printf("    num blocks  :   %u\n", num_blocks);
+    printf("    num bits out:   %u bits\n", num_bits_out);
+    printf("    enc msg len :   %u bytes\n", num_bytes_out);
+#endif
+    return num_bytes_out;
+}
+
+// compute encoded message length for convolutional codes
+//  _dec_msg_len    :   decoded message length
+//  _K              :   constraint length
+//  _p              :   puncturing rate, r = _p / (_p+1)
+unsigned int fec_conv_get_enc_msg_len(unsigned int _dec_msg_len,
+                                      unsigned int _K,
+                                      unsigned int _p)
+{
+    unsigned int num_bits_in = _dec_msg_len*8;
+    unsigned int n = num_bits_in + _K - 1;
+    unsigned int num_bits_out = n + (n+_p-1)/_p;
+    unsigned int num_bytes_out = num_bits_out/8 + (num_bits_out%8 ? 1 : 0);
+#if 0
+    printf("msg len :       %3u\n", _dec_msg_len);
+    printf("num bits in :   %3u\n", num_bits_in);
+    printf("n (constraint): %3u\n", n);
+    printf("num bits out:   %3u", num_bits_out);
+    printf(" = n+(n+p-1)/p = %u+(%u+%u-1)/%u\n", n,n,_p,_p);
+    printf("num bytes out:  %3u\n", num_bytes_out);
+#endif
+    return num_bytes_out;
+}
+
+// compute encoded message length for Reed-Solomon codes
+//  _dec_msg_len    :   decoded message length
+//  _nroots         :   number of roots in polynomial
+//  _nn             :   
+//  _kk             :   
+// Example : if we are using the 8-bit code,
+//      _nroots  = 32
+//      _nn      = 255
+//      _kk      = 223
+// Let _dec_msg_len = 1024, then
+//      num_blocks = ceil(1024/223)
+//                 = ceil(4.5919)
+//                 = 5
+//      dec_block_len = ceil(1024/num_blocks)
+//                    = ceil(204.8)
+//                    = 205
+//      enc_block_len = dec_block_len + nroots
+//                    = 237
+//      enc_msg_len = num_blocks * enc_block_len
+//                  = 1185
+unsigned int fec_rs_get_enc_msg_len(unsigned int _dec_msg_len,
+                                    unsigned int _nroots,
+                                    unsigned int _nn,
+                                    unsigned int _kk)
+{
+    // validate input
+    if (_dec_msg_len == 0) {
+        fprintf(stderr,"error: fec_rs_get_enc_msg_len(), _dec_msg_len must be greater than 0\n");
+        exit(1);
+    }
+
+    div_t d;
+
+    // compute the number of blocks in the full message sequence
+    d = div(_dec_msg_len, _kk);
+    unsigned int num_blocks = d.quot + (d.rem==0 ? 0 : 1);
+
+    // compute the length of each decoded block
+    d = div(_dec_msg_len, num_blocks);
+    unsigned int dec_block_len = d.quot + (d.rem == 0 ? 0 : 1);
+
+    // compute the encoded block length
+    unsigned int enc_block_len = dec_block_len + _nroots;
+
+    // compute the number of bytes in the full encoded message
+    unsigned int enc_msg_len = enc_block_len * num_blocks;
+#if 0
+    printf("dec_msg_len     :   %u\n", _dec_msg_len);
+    printf("num_blocks      :   %u\n",  num_blocks);
+    printf("dec_block_len   :   %u\n",  dec_block_len);
+    printf("enc_block_len   :   %u\n",  enc_block_len);
+    printf("enc_msg_len     :   %u\n",  enc_msg_len);
+#endif
+    return enc_msg_len;
+}
+
+
+// get the theoretical rate of a particular forward error-
+// correction scheme (object-independent method)
+float fec_get_rate(fec_scheme _scheme)
+{
+    switch (_scheme) {
+    case LIQUID_FEC_UNKNOWN:        return 0;
+    case LIQUID_FEC_NONE:           return 1.;
+    case LIQUID_FEC_REP3:           return 1./3.;
+    case LIQUID_FEC_REP5:           return 1./5.;
+    case LIQUID_FEC_HAMMING74:      return 4./7.;
+    case LIQUID_FEC_HAMMING84:      return 4./8.;
+    case LIQUID_FEC_HAMMING128:     return 8./12.;
+    case LIQUID_FEC_GOLAY2412:      return 1./2.;
+    case LIQUID_FEC_SECDED2216:     return 2./3.;   // ultimately 16/22 ~ 0.72727
+    case LIQUID_FEC_SECDED3932:     return 4./5.;   // ultimately 32/39 ~ 0.82051
+    case LIQUID_FEC_SECDED7264:     return 8./9.;
+
+    // convolutional codes
+#if LIBFEC_ENABLED
+    case LIQUID_FEC_CONV_V27:       return 1./2.;
+    case LIQUID_FEC_CONV_V29:       return 1./2.;
+    case LIQUID_FEC_CONV_V39:       return 1./3.;
+    case LIQUID_FEC_CONV_V615:      return 1./6.;
+    case LIQUID_FEC_CONV_V27P23:    return 2./3.;
+    case LIQUID_FEC_CONV_V27P34:    return 3./4.;
+    case LIQUID_FEC_CONV_V27P45:    return 4./5.;
+    case LIQUID_FEC_CONV_V27P56:    return 5./6.;
+    case LIQUID_FEC_CONV_V27P67:    return 6./7.;
+    case LIQUID_FEC_CONV_V27P78:    return 7./8.;
+    case LIQUID_FEC_CONV_V29P23:    return 2./3.;
+    case LIQUID_FEC_CONV_V29P34:    return 3./4.;
+    case LIQUID_FEC_CONV_V29P45:    return 4./5.;
+    case LIQUID_FEC_CONV_V29P56:    return 5./6.;
+    case LIQUID_FEC_CONV_V29P67:    return 6./7.;
+    case LIQUID_FEC_CONV_V29P78:    return 7./8.;
+
+    // Reed-Solomon codes
+    case LIQUID_FEC_RS_M8:          return 223./255.;
+#else
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        fprintf(stderr,"error: fec_get_rate(), convolutional codes unavailable (install libfec)\n");
+        exit(-1);
+
+    case LIQUID_FEC_RS_M8:
+        fprintf(stderr,"error: fec_get_rate(), Reed-Solomon codes unavailable (install libfec)\n");
+        exit(-1);
+#endif
+
+    default:
+        printf("error: fec_get_rate(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(-1);
+    }
+    return 0;
+}
+
+// create a fec object of a particular scheme
+//  _scheme     :   error-correction scheme
+//  _opts       :   (ignored)
+fec fec_create(fec_scheme _scheme, void *_opts)
+{
+    switch (_scheme) {
+    case LIQUID_FEC_UNKNOWN:
+        printf("error: fec_create(), cannot create fec object of type \"UNKNOWN\"\n");
+        exit(-1);
+    case LIQUID_FEC_NONE:
+        return fec_pass_create(NULL);
+    case LIQUID_FEC_REP3:
+        return fec_rep3_create(_opts);
+    case LIQUID_FEC_REP5:
+        return fec_rep5_create(_opts);
+    case LIQUID_FEC_HAMMING74:
+        return fec_hamming74_create(_opts);
+    case LIQUID_FEC_HAMMING84:
+        return fec_hamming84_create(_opts);
+    case LIQUID_FEC_HAMMING128:
+        return fec_hamming128_create(_opts);
+
+    case LIQUID_FEC_GOLAY2412:
+        return fec_golay2412_create(_opts);
+
+    // SEC-DED codecs (single error correction, double error detection)
+    case LIQUID_FEC_SECDED2216:
+        return fec_secded2216_create(_opts);
+    case LIQUID_FEC_SECDED3932:
+        return fec_secded3932_create(_opts);
+    case LIQUID_FEC_SECDED7264:
+        return fec_secded7264_create(_opts);
+
+    // convolutional codes
+#if LIBFEC_ENABLED
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+        return fec_conv_create(_scheme);
+
+    // punctured
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        return fec_conv_punctured_create(_scheme);
+
+    // Reed-Solomon codes
+    case LIQUID_FEC_RS_M8:
+        return fec_rs_create(_scheme);
+#else
+    case LIQUID_FEC_CONV_V27:
+    case LIQUID_FEC_CONV_V29:
+    case LIQUID_FEC_CONV_V39:
+    case LIQUID_FEC_CONV_V615:
+
+    case LIQUID_FEC_CONV_V27P23:
+    case LIQUID_FEC_CONV_V27P34:
+    case LIQUID_FEC_CONV_V27P45:
+    case LIQUID_FEC_CONV_V27P56:
+    case LIQUID_FEC_CONV_V27P67:
+    case LIQUID_FEC_CONV_V27P78:
+
+    case LIQUID_FEC_CONV_V29P23:
+    case LIQUID_FEC_CONV_V29P34:
+    case LIQUID_FEC_CONV_V29P45:
+    case LIQUID_FEC_CONV_V29P56:
+    case LIQUID_FEC_CONV_V29P67:
+    case LIQUID_FEC_CONV_V29P78:
+        fprintf(stderr,"error: fec_create(), convolutional codes unavailable (install libfec)\n");
+        exit(-1);
+
+    case LIQUID_FEC_RS_M8:
+        fprintf(stderr,"error: fec_create(), Reed-Solomon codes unavailable (install libfec)\n");
+        exit(-1);
+#endif
+
+    default:
+        printf("error: fec_create(), unknown/unsupported scheme: %d\n", _scheme);
+        exit(-1);
+    }
+
+    // should never get to this point, but return NULL to keep
+    // compiler happy
+    return NULL;
+}
+
+// recreate a fec object
+//  _q      :   initial fec object
+//  _scheme :   new scheme
+//  _opts   :   options (ignored)
+fec fec_recreate(fec _q,
+                 fec_scheme _scheme,
+                 void *_opts)
+{
+    if (_q->scheme != _scheme) {
+        // destroy old object and create new one
+        fec_destroy(_q);
+        _q = fec_create(_scheme,_opts);
+    }
+
+    // scheme hasn't changed; just return original object
+    return _q;
+}
+
+// destroy fec object
+void fec_destroy(fec _q)
+{
+    free(_q);
+}
+
+// print basic fec object internals
+void fec_print(fec _q)
+{
+    printf("fec: %s [rate: %4.3f]\n",
+        fec_scheme_str[_q->scheme][1],
+        _q->rate);
+}
+
+// encode a block of data using a fec scheme
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_dec        :   decoded message
+//  _msg_enc        :   encoded message
+void fec_encode(fec _q,
+                unsigned int _dec_msg_len,
+                unsigned char * _msg_dec,
+                unsigned char * _msg_enc)
+{
+    // call internal encoding method
+    _q->encode_func(_q, _dec_msg_len, _msg_dec, _msg_enc);
+}
+
+// decode a block of data using a fec scheme
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_enc        :   encoded message
+//  _msg_dec        :   decoded message
+void fec_decode(fec _q,
+                unsigned int _dec_msg_len,
+                unsigned char * _msg_enc,
+                unsigned char * _msg_dec)
+{
+    // call internal decoding method
+    _q->decode_func(_q, _dec_msg_len, _msg_enc, _msg_dec);
+}
+
+// decode a block of data using a fec scheme
+//  _q              :   fec object
+//  _dec_msg_len    :   decoded message length
+//  _msg_enc        :   encoded message
+//  _msg_dec        :   decoded message
+void fec_decode_soft(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char * _msg_enc,
+                     unsigned char * _msg_dec)
+{
+    if (_q->decode_soft_func != NULL) {
+        // call internal decoding method
+        _q->decode_soft_func(_q, _dec_msg_len, _msg_enc, _msg_dec);
+    } else {
+        // pack bytes and use hard-decision decoding
+        unsigned enc_msg_len = fec_get_enc_msg_length(_q->scheme, _dec_msg_len);
+        unsigned char msg_enc_hard[enc_msg_len];
+        unsigned int i;
+        for (i=0; i<enc_msg_len; i++) {
+            // TODO : use pack bytes
+            msg_enc_hard[i] = 0;
+            msg_enc_hard[i] |= (_msg_enc[8*i+0] >> 0) & 0x80;
+            msg_enc_hard[i] |= (_msg_enc[8*i+1] >> 1) & 0x40;
+            msg_enc_hard[i] |= (_msg_enc[8*i+2] >> 2) & 0x20;
+            msg_enc_hard[i] |= (_msg_enc[8*i+3] >> 3) & 0x10;
+            msg_enc_hard[i] |= (_msg_enc[8*i+4] >> 4) & 0x08;
+            msg_enc_hard[i] |= (_msg_enc[8*i+5] >> 5) & 0x04;
+            msg_enc_hard[i] |= (_msg_enc[8*i+6] >> 6) & 0x02;
+            msg_enc_hard[i] |= (_msg_enc[8*i+7] >> 7) & 0x01;
+        }
+
+        // use hard-decoding method
+        fec_decode(_q, _dec_msg_len, msg_enc_hard, _msg_dec);
+    }
+}
+
+
diff --git a/src/fec/src/fec_conv.c b/src/fec/src/fec_conv.c
new file mode 100644
index 0000000..573d817
--- /dev/null
+++ b/src/fec/src/fec_conv.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// convolutional code (macros)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define VERBOSE_FEC_CONV    0
+
+#if LIBFEC_ENABLED
+#include "fec.h"
+
+fec fec_conv_create(fec_scheme _fs)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = _fs;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_conv_encode;
+    q->decode_func      = &fec_conv_decode_hard; // default to hard-decision decoding
+    q->decode_soft_func = &fec_conv_decode_soft;
+
+    switch (q->scheme) {
+    case LIQUID_FEC_CONV_V27:  fec_conv_init_v27(q);   break;
+    case LIQUID_FEC_CONV_V29:  fec_conv_init_v29(q);   break;
+    case LIQUID_FEC_CONV_V39:  fec_conv_init_v39(q);   break;
+    case LIQUID_FEC_CONV_V615: fec_conv_init_v615(q);  break;
+    default:
+        fprintf(stderr,"error: fec_conv_create(), invalid type\n");
+        exit(1);
+    }
+
+    // convolutional-specific decoding
+    q->num_dec_bytes = 0;
+    q->enc_bits = NULL;
+    q->vp = NULL;
+
+    return q;
+}
+
+void fec_conv_destroy(fec _q)
+{
+    // delete viterbi decoder
+    if (_q->vp != NULL)
+        _q->delete_viterbi(_q->vp);
+
+    free(_q);
+}
+
+void fec_conv_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char *_msg_dec,
+                     unsigned char *_msg_enc)
+{
+    unsigned int i,j,r; // bookkeeping
+    unsigned int sr=0;  // convolutional shift register
+    unsigned int n=0;   // output bit counter
+
+    unsigned char bit;
+    unsigned char byte_in;
+    unsigned char byte_out=0;
+
+    for (i=0; i<_dec_msg_len; i++) {
+        byte_in = _msg_dec[i];
+
+        // break byte into individual bits
+        for (j=0; j<8; j++) {
+            // shift bit starting with most significant
+            bit = (byte_in >> (7-j)) & 0x01;
+            sr = (sr << 1) | bit;
+
+            // compute parity bits for each polynomial
+            for (r=0; r<_q->R; r++) {
+                byte_out = (byte_out<<1) | parity(sr & _q->poly[r]);
+                _msg_enc[n/8] = byte_out;
+                n++;
+            }
+        }
+    }
+
+    // tail bits
+    for (i=0; i<(_q->K)-1; i++) {
+        // shift register: push zeros
+        sr = (sr << 1);
+
+        // compute parity bits for each polynomial
+        for (r=0; r<_q->R; r++) {
+            byte_out = (byte_out<<1) | parity(sr & _q->poly[r]);
+            _msg_enc[n/8] = byte_out;
+            n++;
+        }
+    }
+
+    // ensure even number of bytes
+    while (n%8) {
+        // shift zeros
+        byte_out <<= 1;
+        _msg_enc[n/8] = byte_out;
+        n++;
+    }
+
+    assert(n == 8*fec_get_enc_msg_length(_q->scheme,_dec_msg_len));
+}
+
+//unsigned int
+void fec_conv_decode_hard(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    // re-allocate resources if necessary
+    fec_conv_setlength(_q, _dec_msg_len);
+
+    // unpack bytes
+    unsigned int num_written;
+    liquid_unpack_bytes(_msg_enc,               // encoded message (bytes)
+                        _q->num_enc_bytes,      // encoded message length (#bytes)
+                        _q->enc_bits,           // encoded messsage (bits)
+                        _q->num_enc_bytes*8,    // encoded message length (#bits)
+                        &num_written);
+
+#if VERBOSE_FEC_CONV
+    unsigned int i;
+    printf("msg encoded (bits):\n");
+    for (i=0; i<8*_q->num_enc_bytes; i++) {
+        printf("%1u", _q->enc_bits[i]);
+        if (((i+1)%8)==0)
+            printf(" ");
+    }
+    printf("\n");
+#endif
+
+    // invoke hard-decision scaling
+    unsigned int k;
+    for (k=0; k<8*_q->num_enc_bytes; k++)
+        _q->enc_bits[k] = _q->enc_bits[k] ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+
+    // run internal decoder
+    fec_conv_decode(_q, _msg_dec);
+}
+
+//unsigned int
+void fec_conv_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    // re-allocate resources if necessary
+    fec_conv_setlength(_q, _dec_msg_len);
+
+    // copy soft input bits
+    unsigned int k;
+    for (k=0; k<8*_q->num_enc_bytes; k++)
+        _q->enc_bits[k] = _msg_enc[k];
+
+    // run internal decoder
+    fec_conv_decode(_q, _msg_dec);
+}
+
+//unsigned int
+void fec_conv_decode(fec _q,
+                     unsigned char *_msg_dec)
+{
+    // run decoder
+    _q->init_viterbi(_q->vp,0);
+    _q->update_viterbi_blk(_q->vp, _q->enc_bits, 8*_q->num_dec_bytes+_q->K-1);
+    _q->chainback_viterbi(_q->vp, _msg_dec, 8*_q->num_dec_bytes, 0);
+
+#if VERBOSE_FEC_CONV
+    for (i=0; i<_dec_msg_len; i++)
+        printf("%.2x ", _msg_dec[i]);
+    printf("\n");
+#endif
+}
+
+void fec_conv_setlength(fec _q, unsigned int _dec_msg_len)
+{
+    // re-allocate resources as necessary
+    unsigned int num_dec_bytes = _dec_msg_len;
+
+    // return if length has not changed
+    if (num_dec_bytes == _q->num_dec_bytes)
+        return;
+
+#if VERBOSE_FEC_CONV
+    printf("(re)creating viterbi decoder, %u frame bytes\n", num_dec_bytes);
+#endif
+
+    // reset number of framebits
+    _q->num_dec_bytes = num_dec_bytes;
+    _q->num_enc_bytes = fec_get_enc_msg_length(_q->scheme,
+                                               _dec_msg_len);
+
+    // delete old decoder if necessary
+    if (_q->vp != NULL)
+        _q->delete_viterbi(_q->vp);
+
+    // re-create / re-allocate memory buffers
+    _q->vp = _q->create_viterbi(8*_q->num_dec_bytes);
+    _q->enc_bits = (unsigned char*) realloc(_q->enc_bits,
+                                            _q->num_enc_bytes*8*sizeof(unsigned char));
+}
+
+// 
+// internal
+//
+
+void fec_conv_init_v27(fec _q)
+{
+    _q->R=2;
+    _q->K=7;
+    _q->poly = fec_conv27_poly;
+    _q->create_viterbi = create_viterbi27;
+    _q->init_viterbi = init_viterbi27;
+    _q->update_viterbi_blk = update_viterbi27_blk;
+    _q->chainback_viterbi = chainback_viterbi27;
+    _q->delete_viterbi = delete_viterbi27;
+}
+
+void fec_conv_init_v29(fec _q)
+{
+    _q->R=2;
+    _q->K=9;
+    _q->poly = fec_conv29_poly;
+    _q->create_viterbi = create_viterbi29;
+    _q->init_viterbi = init_viterbi29;
+    _q->update_viterbi_blk = update_viterbi29_blk;
+    _q->chainback_viterbi = chainback_viterbi29;
+    _q->delete_viterbi = delete_viterbi29;
+}
+
+void fec_conv_init_v39(fec _q)
+{
+    _q->R=3;
+    _q->K=9;
+    _q->poly = fec_conv39_poly;
+    _q->create_viterbi = create_viterbi39;
+    _q->init_viterbi = init_viterbi39;
+    _q->update_viterbi_blk = update_viterbi39_blk;
+    _q->chainback_viterbi = chainback_viterbi39;
+    _q->delete_viterbi = delete_viterbi39;
+}
+
+void fec_conv_init_v615(fec _q)
+{
+    _q->R=6;
+    _q->K=15;
+    _q->poly = fec_conv615_poly;
+    _q->create_viterbi = create_viterbi615;
+    _q->init_viterbi = init_viterbi615;
+    _q->update_viterbi_blk = update_viterbi615_blk;
+    _q->chainback_viterbi = chainback_viterbi615;
+    _q->delete_viterbi = delete_viterbi615;
+}
+
+
+
+#else   // LIBFEC_ENABLED
+
+fec fec_conv_create(fec_scheme _fs)
+{
+    return NULL;
+}
+
+void fec_conv_destroy(fec _q)
+{
+}
+
+#endif  // LIBFEC_ENABLED
+
diff --git a/src/fec/src/fec_conv_pmatrix.c b/src/fec/src/fec_conv_pmatrix.c
new file mode 100644
index 0000000..fa23e49
--- /dev/null
+++ b/src/fec/src/fec_conv_pmatrix.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "liquid.internal.h"
+
+// 2/3-rate K=7 punctured convolutional code
+int fec_conv27p23_matrix[4] = {
+    1, 1,
+    1, 0
+};
+
+// 3/4-rate K=7 punctured convolutional code
+int fec_conv27p34_matrix[6] = {
+    1, 1, 0,
+    1, 0, 1
+};
+
+// 4/5-rate K=7 punctured convolutional code
+int fec_conv27p45_matrix[8] = {
+    1, 1, 1, 1,
+    1, 0, 0, 0
+};
+
+// 5/6-rate K=7 punctured convolutional code
+int fec_conv27p56_matrix[10] = {
+    1, 1, 0, 1, 0,
+    1, 0, 1, 0, 1
+};
+
+// 6/7-rate K=7 punctured convolutional code
+int fec_conv27p67_matrix[12] = {
+    1, 1, 1, 0, 1, 0,
+    1, 0, 0, 1, 0, 1
+};
+
+// 7/8-rate K=7 punctured convolutional code
+int fec_conv27p78_matrix[14] = {
+    1, 1, 1, 1, 0, 1, 0,
+    1, 0, 0, 0, 1, 0, 1
+};
+
+
+
+
+// 2/3-rate K=9 punctured convolutional code
+int fec_conv29p23_matrix[4] = {
+    1, 1,
+    1, 0
+};
+
+// 3/4-rate K=9 punctured convolutional code
+int fec_conv29p34_matrix[6] = {
+    1, 1, 1,
+    1, 0, 0
+};
+
+// 4/5-rate K=9 punctured convolutional code
+int fec_conv29p45_matrix[8] = {
+    1, 1, 0, 1,
+    1, 0, 1, 0
+};
+
+// 5/6-rate K=9 punctured convolutional code
+int fec_conv29p56_matrix[10] = {
+    1, 0, 1, 1, 0,
+    1, 1, 0, 0, 1
+};
+
+// 6/7-rate K=9 punctured convolutional code
+int fec_conv29p67_matrix[12] = {
+    1, 1, 0, 1, 1, 0,
+    1, 0, 1, 0, 0, 1
+};
+
+// 7/8-rate K=9 punctured convolutional code
+int fec_conv29p78_matrix[14] = {
+    1, 1, 0, 1, 0, 1, 1,
+    1, 0, 1, 0, 1, 0, 0
+};
+
diff --git a/src/fec/src/fec_conv_poly.c b/src/fec/src/fec_conv_poly.c
new file mode 100644
index 0000000..a2a920a
--- /dev/null
+++ b/src/fec/src/fec_conv_poly.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// convolutional code polynomials
+//
+
+#include "liquid.internal.h"
+
+#if LIBFEC_ENABLED
+#include <fec.h>
+
+int fec_conv27_poly[2]  = {V27POLYA,
+                           V27POLYB};
+
+int fec_conv29_poly[2]  = {V29POLYA,
+                           V29POLYB};
+
+int fec_conv39_poly[3]  = {V39POLYA,
+                           V39POLYB,
+                           V39POLYC};
+
+int fec_conv615_poly[6] = {V615POLYA,
+                           V615POLYB,
+                           V615POLYC,
+                           V615POLYD,
+                           V615POLYE,
+                           V615POLYF};
+
+#else
+
+int fec_conv27_poly[2]  = {0,0};
+int fec_conv29_poly[2]  = {0,0};
+int fec_conv39_poly[3]  = {0,0,0};
+int fec_conv615_poly[6] = {0,0,0,0,0,0};
+
+#endif
+
diff --git a/src/fec/src/fec_conv_punctured.c b/src/fec/src/fec_conv_punctured.c
new file mode 100644
index 0000000..f3f25a5
--- /dev/null
+++ b/src/fec/src/fec_conv_punctured.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// convolutional code (macros)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define VERBOSE_FEC_CONV_PUNCTURED    0
+
+#if LIBFEC_ENABLED
+#include "fec.h"
+
+fec fec_conv_punctured_create(fec_scheme _fs)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = _fs;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_conv_punctured_encode;
+    q->decode_func      = &fec_conv_punctured_decode_hard;
+    q->decode_soft_func = &fec_conv_punctured_decode_soft;
+
+    switch (q->scheme) {
+    case LIQUID_FEC_CONV_V27P23:   fec_conv_init_v27p23(q);    break;
+    case LIQUID_FEC_CONV_V27P34:   fec_conv_init_v27p34(q);    break;
+    case LIQUID_FEC_CONV_V27P45:   fec_conv_init_v27p45(q);    break;
+    case LIQUID_FEC_CONV_V27P56:   fec_conv_init_v27p56(q);    break;
+    case LIQUID_FEC_CONV_V27P67:   fec_conv_init_v27p67(q);    break;
+    case LIQUID_FEC_CONV_V27P78:   fec_conv_init_v27p78(q);    break;
+
+    case LIQUID_FEC_CONV_V29P23:   fec_conv_init_v29p23(q);    break;
+    case LIQUID_FEC_CONV_V29P34:   fec_conv_init_v29p34(q);    break;
+    case LIQUID_FEC_CONV_V29P45:   fec_conv_init_v29p45(q);    break;
+    case LIQUID_FEC_CONV_V29P56:   fec_conv_init_v29p56(q);    break;
+    case LIQUID_FEC_CONV_V29P67:   fec_conv_init_v29p67(q);    break;
+    case LIQUID_FEC_CONV_V29P78:   fec_conv_init_v29p78(q);    break;
+    default:
+        fprintf(stderr,"error: fec_conv_punctured_create(), invalid type\n");
+        exit(1);
+    }
+
+    // convolutional-specific decoding
+    q->num_dec_bytes = 0;
+    q->enc_bits = NULL;
+    q->vp = NULL;
+
+    return q;
+}
+
+void fec_conv_punctured_destroy(fec _q)
+{
+    // delete viterbi decoder
+    if (_q->vp != NULL)
+        _q->delete_viterbi(_q->vp);
+
+    free(_q);
+}
+
+void fec_conv_punctured_encode(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char *_msg_dec,
+                               unsigned char *_msg_enc)
+{
+    unsigned int i,j,r; // bookkeeping
+    unsigned int sr=0;  // convolutional shift register
+    unsigned int n=0;   // output bit counter
+    unsigned int p=0;   // puncturing matrix column index
+
+    unsigned char bit;
+    unsigned char byte_in;
+    unsigned char byte_out=0;
+
+    for (i=0; i<_dec_msg_len; i++) {
+        byte_in = _msg_dec[i];
+
+        // break byte into individual bits
+        for (j=0; j<8; j++) {
+            // shift bit starting with most significant
+            bit = (byte_in >> (7-j)) & 0x01;
+            sr = (sr << 1) | bit;
+
+            // compute parity bits for each polynomial
+            for (r=0; r<_q->R; r++) {
+                // enable output determined by puncturing matrix
+                if (_q->puncturing_matrix[r*(_q->P)+p]) {
+                    byte_out = (byte_out<<1) | parity(sr & _q->poly[r]);
+                    _msg_enc[n/8] = byte_out;
+                    n++;
+                } else {
+                }
+            }
+
+            // update puncturing matrix column index
+            p = (p+1) % _q->P;
+        }
+    }
+    //printf("\n");
+    //printf("*** n = %u\n", n);
+
+    // tail bits
+    for (i=0; i<_q->K-1; i++) {
+        // shift register: push zeros
+        sr = (sr << 1);
+
+        // compute parity bits for each polynomial
+        for (r=0; r<_q->R; r++) {
+            if (_q->puncturing_matrix[r*(_q->P)+p]) {
+                byte_out = (byte_out<<1) | parity(sr & _q->poly[r]);
+                _msg_enc[n/8] = byte_out;
+                n++;
+            }
+        }
+
+        // update puncturing matrix column index
+        p = (p+1) % _q->P;
+    }
+    //printf("+++ n = %u\n", n);
+
+    // ensure even number of bytes
+    while (n%8) {
+        // shift zeros
+        byte_out <<= 1;
+        _msg_enc[n/8] = byte_out;
+        n++;
+    }
+
+    //printf("n = %u (expected %u)\n", n, 8*fec_get_enc_msg_length(LIQUID_FEC_CONV(_mode),_dec_msg_len));
+    assert(n == 8*fec_get_enc_msg_length(_q->scheme,_dec_msg_len));
+}
+
+//unsigned int
+void fec_conv_punctured_decode_hard(fec _q,
+                                    unsigned int _dec_msg_len,
+                                    unsigned char *_msg_enc,
+                                    unsigned char *_msg_dec)
+{
+    // re-allocate resources if necessary
+    fec_conv_punctured_setlength(_q, _dec_msg_len);
+
+    // unpack bytes, adding erasures at punctured indices
+    unsigned int num_dec_bits = _q->num_dec_bytes * 8 + _q->K - 1;
+    unsigned int num_enc_bits = num_dec_bits * _q->R;
+    unsigned int i,r;
+    unsigned int n=0;   // input byte index
+    unsigned int k=0;   // intput bit index (0<=k<8)
+    unsigned int p=0;   // puncturing matrix column index
+    unsigned char bit;
+    unsigned char byte_in = _msg_enc[n];
+    for (i=0; i<num_enc_bits; i+=_q->R) {
+        //
+        for (r=0; r<_q->R; r++) {
+            if (_q->puncturing_matrix[r*(_q->P)+p]) {
+                // push bit from input
+                bit = (byte_in >> (7-k)) & 0x01;
+                _q->enc_bits[i+r] = bit ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+                k++;
+                if (k==8) {
+                    k = 0;
+                    n++;
+                    byte_in = _msg_enc[n];
+                }
+            } else {
+                // push erasure
+                _q->enc_bits[i+r] = LIQUID_SOFTBIT_ERASURE;
+            }
+        }
+        p = (p+1) % _q->P;
+    }
+
+#if VERBOSE_FEC_CONV_PUNCTURED
+    unsigned int ii;
+    printf("msg encoded (bits):\n");
+    for (ii=0; ii<num_enc_bits; ii++) {
+        printf("%3u ", _q->enc_bits[ii]);
+        if (((ii+1)%8)==0)
+            printf("\n");
+    }
+    printf("\n");
+#endif
+
+    // run decoder
+    _q->init_viterbi(_q->vp,0);
+    // TODO : check to see if this shouldn't be num_enc_bits (punctured)
+    _q->update_viterbi_blk(_q->vp, _q->enc_bits, 8*_q->num_dec_bytes+_q->K-1);
+    _q->chainback_viterbi(_q->vp, _msg_dec, 8*_q->num_dec_bytes, 0);
+
+#if VERBOSE_FEC_CONV_PUNCTURED
+    for (ii=0; ii<_dec_msg_len; ii++)
+        printf("%.2x ", _msg_dec[ii]);
+    printf("\n");
+#endif
+}
+
+//unsigned int
+void fec_conv_punctured_decode_soft(fec _q,
+                                    unsigned int _dec_msg_len,
+                                    unsigned char *_msg_enc,
+                                    unsigned char *_msg_dec)
+{
+    // re-allocate resources if necessary
+    fec_conv_punctured_setlength(_q, _dec_msg_len);
+
+    // unpack bytes, adding erasures at punctured indices
+    unsigned int num_dec_bits = _q->num_dec_bytes * 8 + _q->K - 1;
+    unsigned int num_enc_bits = num_dec_bits * _q->R;
+    unsigned int i,r;
+    unsigned int n=0;   // input soft bit index
+    unsigned int p=0;   // puncturing matrix column index
+    for (i=0; i<num_enc_bits; i+=_q->R) {
+        //
+        for (r=0; r<_q->R; r++) {
+            if (_q->puncturing_matrix[r*(_q->P)+p]) {
+                // push bit from input
+                _q->enc_bits[i+r] = _msg_enc[n++];
+            } else {
+                // push erasure
+                _q->enc_bits[i+r] = LIQUID_SOFTBIT_ERASURE;
+            }
+        }
+        p = (p+1) % _q->P;
+    }
+
+#if VERBOSE_FEC_CONV_PUNCTURED
+    unsigned int ii;
+    printf("msg encoded (bits):\n");
+    for (ii=0; ii<num_enc_bits; ii++) {
+        printf("%3u ", _q->enc_bits[ii]);
+        if (((ii+1)%8)==0)
+            printf("\n");
+    }
+    printf("\n");
+#endif
+
+    // run decoder
+    _q->init_viterbi(_q->vp,0);
+    // TODO : check to see if this shouldn't be num_enc_bits (punctured)
+    _q->update_viterbi_blk(_q->vp, _q->enc_bits, 8*_q->num_dec_bytes+_q->K-1);
+    _q->chainback_viterbi(_q->vp, _msg_dec, 8*_q->num_dec_bytes, 0);
+
+#if VERBOSE_FEC_CONV_PUNCTURED
+    for (ii=0; ii<_dec_msg_len; ii++)
+        printf("%.2x ", _msg_dec[ii]);
+    printf("\n");
+#endif
+}
+
+void fec_conv_punctured_setlength(fec _q, unsigned int _dec_msg_len)
+{
+    // re-allocate resources as necessary
+    unsigned int num_dec_bytes = _dec_msg_len;
+
+    // return if length has not changed
+    if (num_dec_bytes == _q->num_dec_bytes)
+        return;
+
+    // reset number of framebits
+    _q->num_dec_bytes = num_dec_bytes;
+    _q->num_enc_bytes = fec_get_enc_msg_length(_q->scheme,
+                                               _dec_msg_len);
+
+    // puncturing: need to expand to full length (decoder
+    //             injects erasures at punctured values)
+    unsigned int num_dec_bits = 8*_q->num_dec_bytes;
+    unsigned int n = num_dec_bits + _q->K - 1;
+    unsigned int num_enc_bits = n*(_q->R);
+#if VERBOSE_FEC_CONV_PUNCTURED
+    printf("(re)creating viterbi decoder, %u frame bytes\n", num_dec_bytes);
+    printf("  num decoded bytes         :   %u\n", _q->num_dec_bytes);
+    printf("  num encoded bytes         :   %u\n", _q->num_enc_bytes);
+    printf("  num decoded bits          :   %u\n", num_dec_bits);
+    printf("  num decoded bits (padded) :   %u\n", n);
+    printf("  num encoded bits (full)   :   %u\n", num_enc_bits);
+#endif
+
+    // delete old decoder if necessary
+    if (_q->vp != NULL)
+        _q->delete_viterbi(_q->vp);
+
+    // re-create / re-allocate memory buffers
+    _q->vp = _q->create_viterbi(8*_q->num_dec_bytes);
+    _q->enc_bits = (unsigned char*) realloc(_q->enc_bits,
+                                            num_enc_bits*sizeof(unsigned char));
+
+}
+
+// 
+// internal
+//
+
+void fec_conv_init_v27p23(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 2;
+    _q->puncturing_matrix = fec_conv27p23_matrix;
+}
+
+void fec_conv_init_v27p34(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 3;
+    _q->puncturing_matrix = fec_conv27p34_matrix;
+}
+
+void fec_conv_init_v27p45(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 4;
+    _q->puncturing_matrix = fec_conv27p45_matrix;
+}
+
+void fec_conv_init_v27p56(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 5;
+    _q->puncturing_matrix = fec_conv27p56_matrix;
+}
+
+
+void fec_conv_init_v27p67(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 6;
+    _q->puncturing_matrix = fec_conv27p67_matrix;
+}
+
+void fec_conv_init_v27p78(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v27(_q);
+
+    _q->P = 7;
+    _q->puncturing_matrix = fec_conv27p78_matrix;
+}
+
+
+void fec_conv_init_v29p23(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 2;
+    _q->puncturing_matrix = fec_conv29p23_matrix;
+}
+
+void fec_conv_init_v29p34(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 3;
+    _q->puncturing_matrix = fec_conv29p34_matrix;
+}
+
+void fec_conv_init_v29p45(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 4;
+    _q->puncturing_matrix = fec_conv29p45_matrix;
+}
+
+void fec_conv_init_v29p56(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 5;
+    _q->puncturing_matrix = fec_conv29p56_matrix;
+}
+
+
+void fec_conv_init_v29p67(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 6;
+    _q->puncturing_matrix = fec_conv29p67_matrix;
+}
+
+void fec_conv_init_v29p78(fec _q)
+{
+    // initialize R, K, polynomial, and viterbi methods
+    fec_conv_init_v29(_q);
+
+    _q->P = 7;
+    _q->puncturing_matrix = fec_conv29p78_matrix;
+}
+
+
+#else   // LIBFEC_ENABLED
+
+fec fec_conv_punctured_create(fec_scheme _fs)
+{
+    return NULL;
+}
+
+void fec_conv_punctured_destroy(fec _q)
+{
+}
+
+void fec_conv_punctured_encode(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char *_msg_dec,
+                               unsigned char *_msg_enc)
+{
+}
+
+//unsigned int
+void fec_conv_punctured_decode(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char *_msg_enc,
+                               unsigned char *_msg_dec)
+{
+}
+
+#endif  // LIBFEC_ENABLED
+
diff --git a/src/fec/src/fec_golay2412.c b/src/fec/src/fec_golay2412.c
new file mode 100644
index 0000000..fe80f3d
--- /dev/null
+++ b/src/fec/src/fec_golay2412.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Golay(24,12) half-rate forward error-correction code
+//
+// References:
+//  [Lin:2004] Lin, Shu and Costello, Daniel L. Jr., "Error Control
+//      Coding," Prentice Hall, New Jersey, 2nd edition, 2004.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_GOLAY2412 0
+
+// P matrix [12 x 12]
+unsigned int golay2412_P[12] = {
+    0x08ed, 0x01db, 0x03b5, 0x0769,
+    0x0ed1, 0x0da3, 0x0b47, 0x068f,
+    0x0d1d, 0x0a3b, 0x0477, 0x0ffe};
+
+#if 0
+// generator matrix [12 x 24]
+unsigned int golay2412_G[12] = {
+    0x008ed800, 0x001db400, 0x003b5200, 0x00769100,
+    0x00ed1080, 0x00da3040, 0x00b47020, 0x0068f010,
+    0x00d1d008, 0x00a3b004, 0x00477002, 0x00ffe001};
+#endif
+
+// generator matrix transposed [24 x 12]
+unsigned int golay2412_Gt[24] = {
+    0x08ed, 0x01db, 0x03b5, 0x0769, 0x0ed1, 0x0da3, 0x0b47, 0x068f, 
+    0x0d1d, 0x0a3b, 0x0477, 0x0ffe, 0x0800, 0x0400, 0x0200, 0x0100, 
+    0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001};
+
+// parity check matrix [12 x 24]
+unsigned int golay2412_H[12] = {
+    0x008008ed, 0x004001db, 0x002003b5, 0x00100769,
+    0x00080ed1, 0x00040da3, 0x00020b47, 0x0001068f,
+    0x00008d1d, 0x00004a3b, 0x00002477, 0x00001ffe};
+
+// multiply input vector with parity check matrix, H
+unsigned int golay2412_matrix_mul(unsigned int   _v,
+                                  unsigned int * _A,
+                                  unsigned int   _n)
+{
+    unsigned int x = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        x <<= 1;
+#if 0
+        // compute dot product mod 2
+        x |= liquid_count_ones_mod2( _A[i] & _v );
+#else
+        // same as above, but exploit the fact that vectors are at
+        // most 24 bits long; liquid_count_ones_mod2() assumes full
+        // 32- or 64-bit integer
+        unsigned int c = 0;
+        unsigned int p = _A[i] & _v;
+        c += liquid_c_ones[ p & 0xff ]; p >>= 8;
+        c += liquid_c_ones[ p & 0xff ]; p >>= 8;
+        c += liquid_c_ones[ p & 0xff ];
+
+        x |= c & 0x0001;    // mod 2
+#endif
+    }
+    return x;
+}
+
+unsigned int fec_golay2412_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<12)) {
+        fprintf(stderr,"error, fec_golay2412_encode_symbol(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute encoded/transmitted message: v = m*G
+    return golay2412_matrix_mul(_sym_dec, golay2412_Gt, 24);
+}
+
+// search for p[i] such that w(v+p[i]) <= 2, return -1 on fail
+int golay2412_parity_search(unsigned int _v)
+{
+    //assert( _v < (1<<12) );
+
+    unsigned int i;
+    for (i=0; i<12; i++) {
+#if 0
+        unsigned int wj = liquid_count_ones(_v ^ golay2412_P[i]);
+#else
+        // same as above but faster, exploiting fact that P has
+        // only 12 bits of resolution
+        unsigned int wj = 0;
+        unsigned int p  = _v ^ golay2412_P[i];
+        wj += liquid_c_ones[ (p     ) & 0xff ];
+        wj += liquid_c_ones[ (p >> 8) & 0xff ];
+#endif
+        if (wj <= 2)
+            return i;
+    }
+
+    // could not find p[i] to satisfy criteria
+    return -1;
+}
+
+unsigned int fec_golay2412_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<24)) {
+        fprintf(stderr,"error, fec_golay2412_decode_symbol(), input symbol too large\n");
+        exit(1);
+    }
+
+    // state variables
+    unsigned int s=0;       // syndrome vector
+    unsigned int e_hat=0;   // estimated error vector
+    unsigned int v_hat=0;   // estimated transmitted message
+    unsigned int m_hat=0;   // estimated original message
+    
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    s = golay2412_matrix_mul(_sym_enc, golay2412_H, 12);
+#if DEBUG_FEC_GOLAY2412
+    printf("s (syndrome vector): "); liquid_print_bitstring(s,12); printf("\n");
+#endif
+
+    // compute weight of s (12 bits)
+    unsigned int ws = liquid_count_ones_uint16(s);
+#if DEBUG_FEC_GOLAY2412
+    printf("w(s) = %u\n", ws);
+#endif
+
+    // step 2:
+    e_hat = 0;
+    if (ws <= 3) {
+#if DEBUG_FEC_GOLAY2412
+        printf("    w(s) <= 3: estimating error vector as [s, 0(12)]\n");
+#endif
+        // set e_hat = [s 0(12)]
+        e_hat = (s << 12) & 0xfff000;
+    } else {
+        // step 3: search for p[i] s.t. w(s+p[i]) <= 2
+#if DEBUG_FEC_GOLAY2412
+        printf("    searching for w(s + p_i) <= 2...\n");
+#endif
+        int s_index = golay2412_parity_search(s);
+
+        if (s_index >= 0) {
+            // vector found!
+#if DEBUG_FEC_GOLAY2412
+            printf("    w(s + p[%2u]) <= 2: estimating error vector as [s+p[%2u],u[%2u]]\n", s_index, s_index, s_index);
+#endif
+            // NOTE : uj = 1 << (12-j-1)
+            e_hat = ((s ^ golay2412_P[s_index]) << 12) | (1 << (11-s_index));
+        } else {
+            // step 4: compute s*P
+            unsigned int sP = golay2412_matrix_mul(s, golay2412_P, 12);
+#if DEBUG_FEC_GOLAY2412
+            printf("s*P: "); liquid_print_bitstring(sP,12); printf("\n");
+#endif
+
+            // compute weight of sP (12 bits)
+            unsigned int wsP = liquid_count_ones_uint16(sP);
+#if DEBUG_FEC_GOLAY2412
+            printf("w(s*P) = %u\n", wsP);
+#endif
+
+            if (wsP == 2 || wsP == 3) {
+                // step 5: set e = [0, s*P]
+#if DEBUG_FEC_GOLAY2412
+                printf("    w(s*P) in [2,3]: estimating error vector as [0(12), s*P]\n");
+#endif
+                e_hat = sP;
+            } else {
+                // step 6: search for p[i] s.t. w(s*P + p[i]) == 2...
+
+#if DEBUG_FEC_GOLAY2412
+                printf("    searching for w(s*P + p_i) == 2...\n");
+#endif
+                int sP_index = golay2412_parity_search(sP);
+
+                if (sP_index >= 0) {
+                    // vector found!
+#if DEBUG_FEC_GOLAY2412
+                    printf("    w(s*P + p[%2u]) == 2: estimating error vector as [u[%2u],s*P+p[%2u]]\n", sP_index, sP_index, sP_index);
+#endif
+                    // NOTE : uj = 1 << (12-j-1)
+                    //      [      uj << 1 2    ] [    sP + p[j]    ]
+                    e_hat = (1 << (23-sP_index)) | (sP ^ golay2412_P[sP_index]);
+                } else {
+                    // step 7: decoding error
+#if DEBUG_FEC_GOLAY2412
+                    printf("  **** decoding error\n");
+#endif
+                }
+            }
+        }
+    }
+
+    // step 8: compute estimated transmitted message: v_hat = r + e_hat
+    v_hat = _sym_enc ^ e_hat;
+#if DEBUG_FEC_GOLAY2412
+    printf("r (recevied vector):            "); liquid_print_bitstring(_sym_enc,24); printf("\n");
+    printf("e-hat (estimated error vector): "); liquid_print_bitstring(e_hat,24);    printf("\n");
+    printf("v-hat (estimated tx vector):    "); liquid_print_bitstring(v_hat,24);    printf("\n");
+#endif
+    
+    // compute estimated original message: (last 12 bits of encoded message)
+    m_hat = v_hat & 0x0fff;
+
+    return m_hat;
+}
+
+// create Golay(24,12) codec object
+fec fec_golay2412_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_GOLAY2412;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_golay2412_encode;
+    q->decode_func      = &fec_golay2412_decode;
+    q->decode_soft_func = NULL;
+
+    return q;
+}
+
+// destroy Golay(24,12) object
+void fec_golay2412_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using Golay(24,12) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_golay2412_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_dec,
+                          unsigned char *_msg_enc)
+{
+    unsigned int i=0;           // decoded byte counter
+    unsigned int j=0;           // encoded byte counter
+    unsigned int s0, s1, s2;    // three 8-bit symbols
+    unsigned int m0, m1;        // two 12-bit symbols (uncoded)
+    unsigned int v0, v1;        // two 24-bit symbols (encoded)
+
+    // determine remainder of input length / 3
+    unsigned int r = _dec_msg_len % 3;
+
+    for (i=0; i<_dec_msg_len-r; i+=3) {
+        // strip three input bytes (two uncoded symbols)
+        s0 = _msg_dec[i+0];
+        s1 = _msg_dec[i+1];
+        s2 = _msg_dec[i+2];
+
+        // pack into two 12-bit symbols
+        m0 = ((s0 << 4) & 0x0ff0) | ((s1 >> 4) & 0x000f);
+        m1 = ((s1 << 8) & 0x0f00) | ((s2     ) & 0x00ff);
+
+        // encode each 12-bit symbol into a 24-bit symbol
+        v0 = fec_golay2412_encode_symbol(m0);
+        v1 = fec_golay2412_encode_symbol(m1);
+
+        // unpack two 24-bit symbols into six 8-bit bytes
+        // retaining order of bits in output
+        _msg_enc[j+0] = (v0 >> 16) & 0xff;
+        _msg_enc[j+1] = (v0 >>  8) & 0xff;
+        _msg_enc[j+2] = (v0      ) & 0xff;
+        _msg_enc[j+3] = (v1 >> 16) & 0xff;
+        _msg_enc[j+4] = (v1 >>  8) & 0xff;
+        _msg_enc[j+5] = (v1      ) & 0xff;
+
+        j += 6;
+    }
+
+    // if input length isn't divisible by 3, encode last 1 or two bytes
+    for (i=_dec_msg_len-r; i<_dec_msg_len; i++) {
+        // strip last input symbol
+        s0 = _msg_dec[i];
+
+        // extend as 12-bit symbol
+        m0 = s0;
+
+        // encode into 24-bit symbol
+        v0 = fec_golay2412_encode_symbol(m0);
+
+        // unpack one 24-bit symbol into three 8-bit bytes, and
+        // append to output array
+        _msg_enc[j+0] = ( v0 >> 16 ) & 0xff;
+        _msg_enc[j+1] = ( v0 >>  8 ) & 0xff;
+        _msg_enc[j+2] = ( v0       ) & 0xff;
+
+        j += 3;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_GOLAY2412,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+}
+
+// decode block of data using Golay(24,12) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_golay2412_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    unsigned int i=0;                       // decoded byte counter
+    unsigned int j=0;                       // encoded byte counter
+    unsigned int r0, r1, r2, r3, r4, r5;    // six 8-bit bytes
+    unsigned int v0, v1;                    // two 24-bit encoded symbols
+    unsigned int m0_hat, m1_hat;            // two 12-bit decoded symbols
+    
+    // determine remainder of input length / 3
+    unsigned int r = _dec_msg_len % 3;
+
+    for (i=0; i<_dec_msg_len-r; i+=3) {
+        // strip six input bytes (two encoded symbols)
+        r0 = _msg_enc[j+0];
+        r1 = _msg_enc[j+1];
+        r2 = _msg_enc[j+2];
+        r3 = _msg_enc[j+3];
+        r4 = _msg_enc[j+4];
+        r5 = _msg_enc[j+5];
+
+        // pack six 8-bit symbols into two 24-bit symbols
+        v0 = ((r0 << 16) & 0xff0000) | ((r1 <<  8) & 0x00ff00) | ((r2     ) & 0x0000ff);
+        v1 = ((r3 << 16) & 0xff0000) | ((r4 <<  8) & 0x00ff00) | ((r5 << 0) & 0x0000ff);
+
+        // decode each symbol into a 12-bit symbol
+        m0_hat = fec_golay2412_decode_symbol(v0);
+        m1_hat = fec_golay2412_decode_symbol(v1);
+
+        // unpack two 12-bit symbols into three 8-bit bytes
+        _msg_dec[i+0] = ((m0_hat >> 4) & 0xff);
+        _msg_dec[i+1] = ((m0_hat << 4) & 0xf0) | ((m1_hat >> 8) & 0x0f);
+        _msg_dec[i+2] = ((m1_hat     ) & 0xff);
+
+        j += 6;
+    }
+
+    // if input length isn't divisible by 3, decode last 1 or two bytes
+    for (i=_dec_msg_len-r; i<_dec_msg_len; i++) {
+        // strip last input symbol (three bytes)
+        r0 = _msg_enc[j+0];
+        r1 = _msg_enc[j+1];
+        r2 = _msg_enc[j+2];
+
+        // pack three 8-bit symbols into one 24-bit symbol
+        v0 = ((r0 << 16) & 0xff0000) | ((r1 <<  8) & 0x00ff00) | ((r2     ) & 0x0000ff);
+
+        // decode into a 12-bit symbol
+        m0_hat = fec_golay2412_decode_symbol(v0);
+
+        // retain last 8 bits of 12-bit symbol
+        _msg_dec[i] = m0_hat & 0xff;
+
+        j += 3;
+    }
+
+    assert( j== fec_get_enc_msg_length(LIQUID_FEC_GOLAY2412,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+
+    //return num_errors;
+}
+
diff --git a/src/fec/src/fec_hamming128.c b/src/fec/src/fec_hamming128.c
new file mode 100644
index 0000000..2fadc29
--- /dev/null
+++ b/src/fec/src/fec_hamming128.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 2/3-rate (12,8) Hamming code
+//
+//  bit position    1   2   3   4   5   6   7   8   9   10  11  12
+//  encoded bits    P1  P2  1   P4  2   3   4   P8  5   6   7   8
+//
+//  parity bit  P1  x   .   x   .   x   .   x   .   x   .   x   .
+//  coveratge   P2  .   x   x   .   .   x   x   .   .   x   x   .
+//              P4  .   .   .   x   x   x   x   .   .   .   .   x
+//              P8  .   .   .   .   .   .   .   x   x   x   x   x
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_HAMMING128        0   // debugging flag
+#define FEC_HAMMING128_ENC_GENTAB   1   // use look-up table for encoding?
+
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:8)
+//
+// bit position     3   5   6   7   9   10  11  12
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   =   1101 1010
+//  coverage    P2  x   .   x   x   .   x   x   .   =   1011 0110
+//              P4  .   x   x   x   .   .   .   x   =   0111 0001
+//              P8  .   .   .   .   x   x   x   x   =   0000 1111
+#define HAMMING128_M1   0xda    // 1101 1010
+#define HAMMING128_M2   0xb6    // 1011 0110
+#define HAMMING128_M4   0x71    // 0111 0001
+#define HAMMING128_M8   0x0f    // 0000 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING128_S1   0x0aaa  // .... 1010 1010 1010
+#define HAMMING128_S2   0x0666  // .... 0110 0110 0110
+#define HAMMING128_S4   0x01e1  // .... 0001 1110 0001
+#define HAMMING128_S8   0x001f  // .... 0000 0001 1111
+
+unsigned int fec_hamming128_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<8)) {
+        fprintf(stderr,"error, fec_hamming128_encode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute parity bits
+    unsigned int p1 = liquid_bdotprod_uint16(_sym_dec, HAMMING128_M1);
+    unsigned int p2 = liquid_bdotprod_uint16(_sym_dec, HAMMING128_M2);
+    unsigned int p4 = liquid_bdotprod_uint16(_sym_dec, HAMMING128_M4);
+    unsigned int p8 = liquid_bdotprod_uint16(_sym_dec, HAMMING128_M8);
+
+#if DEBUG_FEC_HAMMING128
+    printf("parity bits (p1,p2,p4,p8) : (%1u,%1u,%1u,%1u)\n", p1, p2, p4, p8);
+#endif
+
+    // encode symbol by inserting parity bits with data bits to
+    // make a 12-bit symbol
+    unsigned int sym_enc = ((_sym_dec & 0x000f) << 0) |
+                           ((_sym_dec & 0x0070) << 1) |
+                           ((_sym_dec & 0x0080) << 2) |
+                           ( p1 << 11 ) |
+                           ( p2 << 10 ) |
+                           ( p4 << 8  ) |
+                           ( p8 << 4  );
+
+    return sym_enc;
+}
+
+unsigned int fec_hamming128_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<12)) {
+        fprintf(stderr,"error, fec_hamming128_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute syndrome bits
+    unsigned int s1 = liquid_bdotprod_uint16(_sym_enc, HAMMING128_S1);
+    unsigned int s2 = liquid_bdotprod_uint16(_sym_enc, HAMMING128_S2);
+    unsigned int s4 = liquid_bdotprod_uint16(_sym_enc, HAMMING128_S4);
+    unsigned int s8 = liquid_bdotprod_uint16(_sym_enc, HAMMING128_S8);
+
+    // index
+    unsigned int z = (s8<<3) | (s4<<2) | (s2<<1) | s1;
+
+#if DEBUG_FEC_HAMMING128
+    printf("syndrome bits (s1,s2,s4,s8) : (%1u,%1u,%1u,%1u)\n", s1, s2, s4, s8);
+    printf("syndrome z : %u\n", z);
+#endif
+
+    // flip bit at this position
+    if (z) {
+        if (z > 12) {
+            // if this happens there are likely too many errors
+            // to correct; just pass without trying to do anything
+            //fprintf(stderr,"warning, fec_hamming128_decode_symbol(), syndrome index exceeds 12\n");
+        } else {
+            //printf("error detected!\n");
+            _sym_enc ^= 1 << (12-z);
+        }
+    }
+
+    // strip data bits (x) from encoded symbol with parity bits (.)
+    //      symbol:  [..x. xxx. xxxx]
+    //                0000 0000 1111     >  0x000f
+    //                0000 1110 0000     >  0x00e0
+    //                0010 0000 0000     >  0x0200
+    unsigned int sym_dec = ((_sym_enc & 0x000f)     )   |
+                           ((_sym_enc & 0x00e0) >> 1)   |
+                           ((_sym_enc & 0x0200) >> 2);
+
+
+    return sym_dec;
+}
+
+// create Hamming(12,8) codec object
+fec fec_hamming128_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_HAMMING128;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_hamming128_encode;
+    q->decode_func      = &fec_hamming128_decode;
+    q->decode_soft_func = &fec_hamming128_decode_soft;
+
+    return q;
+}
+
+// destroy Hamming(12,8) object
+void fec_hamming128_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using Hamming(12,8) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_hamming128_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_dec,
+                          unsigned char *_msg_enc)
+{
+    unsigned int i, j=0;    // input/output symbol counters
+    unsigned char s0, s1;   // input 8-bit symbols
+    unsigned int m0, m1;    // output 12-bit symbols
+
+    // determine if input length is odd
+    unsigned int r = _dec_msg_len % 2;
+
+    for (i=0; i<_dec_msg_len-r; i+=2) {
+        // strip two input bytes
+        s0 = _msg_dec[i+0];
+        s1 = _msg_dec[i+1];
+
+        // encode each byte into 12-bit symbols
+#if FEC_HAMMING128_ENC_GENTAB
+        m0 = hamming128_enc_gentab[s0];
+        m1 = hamming128_enc_gentab[s1];
+#else
+        m0 = fec_hamming128_encode_symbol(s0);
+        m1 = fec_hamming128_encode_symbol(s1);
+#endif
+
+        // append both 12-bit symbols to output (three 8-bit bytes),
+        // retaining order of bits in output
+        _msg_enc[j+0] =  (m0 >> 4) & 0xff;
+        _msg_enc[j+1] = ((m0 << 4) & 0xf0) | ((m1 >> 8) & 0x0f);
+        _msg_enc[j+2] =  (m1     ) & 0xff;
+
+        j += 3;
+    }
+
+    // if input length is even, encode last symbol by itself
+    if (r) {
+        // strip last input symbol
+        s0 = _msg_dec[_dec_msg_len-1];
+
+        // encode into 12-bit symbol
+#if FEC_HAMMING128_ENC_GENTAB
+        m0 = hamming128_enc_gentab[s0];
+#else
+        m0 = fec_hamming128_encode_symbol(s0);
+#endif
+
+        // append to output
+        _msg_enc[j+0] = ( m0 & 0x0ff0 ) >> 4;
+        _msg_enc[j+1] = ( m0 & 0x000f ) << 4;
+
+        j += 2;
+    }
+
+    assert(j== fec_get_enc_msg_length(LIQUID_FEC_HAMMING128,_dec_msg_len));
+}
+
+// decode block of data using Hamming(12,8) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_hamming128_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    unsigned int i=0,j=0;
+    unsigned int r = _dec_msg_len % 2;
+    unsigned char r0, r1, r2;
+    unsigned int m0, m1;
+
+    for (i=0; i<_dec_msg_len-r; i+=2) {
+        // strip three input symbols
+        r0 = _msg_enc[j+0];
+        r1 = _msg_enc[j+1];
+        r2 = _msg_enc[j+2];
+
+        // combine three 8-bit symbols into two 12-bit symbols
+        m0 = ((r0 << 4) & 0x0ff0) | ((r1 >> 4) & 0x000f);
+        m1 = ((r1 << 8) & 0x0f00) | ((r2     ) & 0x00ff);
+
+        // decode each symbol into an 8-bit byte
+        _msg_dec[i+0] = fec_hamming128_decode_symbol(m0);
+        _msg_dec[i+1] = fec_hamming128_decode_symbol(m1);
+
+        j += 3;
+    }
+
+    // if input length is even, decode last symbol by itself
+    if (r) {
+        // strip last two input bytes (last byte should only contain
+        // for bits)
+        r0 = _msg_enc[j+0];
+        r1 = _msg_enc[j+1];
+
+        // pack into 12-bit symbol
+        m0 = ((r0 << 4) & 0x0ff0) | ((r1 >> 4) & 0x000f);
+
+        // decode symbol into an 8-bit byte
+        _msg_dec[i++] = fec_hamming128_decode_symbol(m0);
+
+        j += 2;
+    }
+
+    assert(j== fec_get_enc_msg_length(LIQUID_FEC_HAMMING128,_dec_msg_len));
+
+    //return num_errors;
+}
+
+
+// decode block of data using Hamming(12,8) soft decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 8*_enc_msg_len x 1]
+//  _msg_dec        :   decoded message [size: _dec_msg_len x 1]
+//
+//unsigned int
+void fec_hamming128_decode_soft(fec _q,
+                                unsigned int _dec_msg_len,
+                                unsigned char *_msg_enc,
+                                unsigned char *_msg_dec)
+{
+    unsigned int i;
+    unsigned int k=0;       // array bit index
+    unsigned int r = _dec_msg_len % 2;
+
+    // compute encoded message length
+    unsigned int enc_msg_len = (3*_dec_msg_len)/2 + r;
+
+    unsigned char s;    // decoded 8-bit symbol
+
+    //unsigned char num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+#if 0
+        // use true ML soft decoding: about 1.45 dB improvement in Eb/N_0 for a BER of 10^-5
+        // with a decoding complexity of 1.43M cycles/trial (64-byte block)
+        s = fecsoft_hamming128_decode(&_msg_enc[k]);
+#else
+        // use n-3 nearest neighbors: about 0.54 dB improvement in Eb/N_0 for a BER of 10^-5
+        // with a decoding complexity of 124k cycles/trial (64-byte block)
+        s = fecsoft_hamming128_decode_n3(&_msg_enc[k]);
+#endif
+        k += 12;
+
+        _msg_dec[i] = (unsigned char)(s & 0xff);
+
+        //printf("  %3u : 0x%.2x > 0x%.2x,  0x%.2x > 0x%.2x (k=%u)\n", i, r0, s0, r1, s1, k);
+    }
+    k += r*4;   // for assert method
+    assert(k == 8*enc_msg_len);
+    //return num_errors;
+}
+
+// 
+// internal methods
+//
+
+// soft decoding of one symbol
+// NOTE : because this method compares the received symbol to every
+//        possible (256) encoded symbols, it is painfully slow to
+//        run.
+unsigned int fecsoft_hamming128_decode(unsigned char * _soft_bits)
+{
+    // find symbol with minimum distance from all 2^8 possible
+    unsigned int d;             // distance metric
+    unsigned int dmin = 0;      // minimum distance
+    unsigned int s_hat = 0;     // estimated transmitted symbol
+    unsigned int c;             // encoded symbol
+
+    unsigned int s;
+    for (s=0; s<256; s++) {
+        // encode symbol
+#if FEC_HAMMING128_ENC_GENTAB
+        c = hamming128_enc_gentab[s];
+#else
+        c = fec_hamming128_encode_symbol(s);
+#endif
+
+        // compute distance metric
+        d = 0;
+        d += (c & 0x0800) ? 255 - _soft_bits[ 0] : _soft_bits[ 0];
+        d += (c & 0x0400) ? 255 - _soft_bits[ 1] : _soft_bits[ 1];
+        d += (c & 0x0200) ? 255 - _soft_bits[ 2] : _soft_bits[ 2];
+        d += (c & 0x0100) ? 255 - _soft_bits[ 3] : _soft_bits[ 3];
+        d += (c & 0x0080) ? 255 - _soft_bits[ 4] : _soft_bits[ 4];
+        d += (c & 0x0040) ? 255 - _soft_bits[ 5] : _soft_bits[ 5];
+        d += (c & 0x0020) ? 255 - _soft_bits[ 6] : _soft_bits[ 6];
+        d += (c & 0x0010) ? 255 - _soft_bits[ 7] : _soft_bits[ 7];
+        d += (c & 0x0008) ? 255 - _soft_bits[ 8] : _soft_bits[ 8];
+        d += (c & 0x0004) ? 255 - _soft_bits[ 9] : _soft_bits[ 9];
+        d += (c & 0x0002) ? 255 - _soft_bits[10] : _soft_bits[10];
+        d += (c & 0x0001) ? 255 - _soft_bits[11] : _soft_bits[11];
+
+        if (d < dmin || s==0) {
+            s_hat = s;
+            dmin = d;
+        }
+    }
+    return s_hat;
+}
+
+// soft decoding of one symbol using nearest neighbors
+unsigned int fecsoft_hamming128_decode_n3(unsigned char * _soft_bits)
+{
+    // find symbol with minimum distance from 17 nearest neighbors
+    unsigned int d;             // distance metric
+    unsigned int dmin = 0;      // minimum distance
+    unsigned int s_hat = 0;     // estimated transmitted symbol
+    unsigned int c;             // encoded symbol
+
+    // compute hard-decoded symbol
+    c = 0x0000;
+    c |= (_soft_bits[ 0] > 127) ? 0x0800 : 0;
+    c |= (_soft_bits[ 1] > 127) ? 0x0400 : 0;
+    c |= (_soft_bits[ 2] > 127) ? 0x0200 : 0;
+    c |= (_soft_bits[ 3] > 127) ? 0x0100 : 0;
+    c |= (_soft_bits[ 4] > 127) ? 0x0080 : 0;
+    c |= (_soft_bits[ 5] > 127) ? 0x0040 : 0;
+    c |= (_soft_bits[ 6] > 127) ? 0x0020 : 0;
+    c |= (_soft_bits[ 7] > 127) ? 0x0010 : 0;
+    c |= (_soft_bits[ 8] > 127) ? 0x0008 : 0;
+    c |= (_soft_bits[ 9] > 127) ? 0x0004 : 0;
+    c |= (_soft_bits[10] > 127) ? 0x0002 : 0;
+    c |= (_soft_bits[11] > 127) ? 0x0001 : 0;
+
+    // decode symbol
+    s_hat = fec_hamming128_decode_symbol(c);
+
+    // re-encode and compute distance
+#if FEC_HAMMING128_ENC_GENTAB
+    c = hamming128_enc_gentab[s_hat];
+#else
+    c = fec_hamming128_encode_symbol(s_hat);
+#endif
+
+    // compute distance metric
+    d = 0;
+    d += (c & 0x0800) ? 255 - _soft_bits[ 0] : _soft_bits[ 0];
+    d += (c & 0x0400) ? 255 - _soft_bits[ 1] : _soft_bits[ 1];
+    d += (c & 0x0200) ? 255 - _soft_bits[ 2] : _soft_bits[ 2];
+    d += (c & 0x0100) ? 255 - _soft_bits[ 3] : _soft_bits[ 3];
+    d += (c & 0x0080) ? 255 - _soft_bits[ 4] : _soft_bits[ 4];
+    d += (c & 0x0040) ? 255 - _soft_bits[ 5] : _soft_bits[ 5];
+    d += (c & 0x0020) ? 255 - _soft_bits[ 6] : _soft_bits[ 6];
+    d += (c & 0x0010) ? 255 - _soft_bits[ 7] : _soft_bits[ 7];
+    d += (c & 0x0008) ? 255 - _soft_bits[ 8] : _soft_bits[ 8];
+    d += (c & 0x0004) ? 255 - _soft_bits[ 9] : _soft_bits[ 9];
+    d += (c & 0x0002) ? 255 - _soft_bits[10] : _soft_bits[10];
+    d += (c & 0x0001) ? 255 - _soft_bits[11] : _soft_bits[11];
+
+    dmin = d;
+
+    // search over 17 nearest neighbors
+    unsigned int s;
+    unsigned int i;
+    for (i=0; i<17; i++) {
+        // use look-up table for nearest neighbors
+        s = fecsoft_hamming128_n3[s_hat][i];
+
+        // encode symbol
+#if FEC_HAMMING128_ENC_GENTAB
+        c = hamming128_enc_gentab[s];
+#else
+        c = fec_hamming128_encode_symbol(s);
+#endif
+
+        // compute distance metric
+        d = 0;
+        d += (c & 0x0800) ? 255 - _soft_bits[ 0] : _soft_bits[ 0];
+        d += (c & 0x0400) ? 255 - _soft_bits[ 1] : _soft_bits[ 1];
+        d += (c & 0x0200) ? 255 - _soft_bits[ 2] : _soft_bits[ 2];
+        d += (c & 0x0100) ? 255 - _soft_bits[ 3] : _soft_bits[ 3];
+        d += (c & 0x0080) ? 255 - _soft_bits[ 4] : _soft_bits[ 4];
+        d += (c & 0x0040) ? 255 - _soft_bits[ 5] : _soft_bits[ 5];
+        d += (c & 0x0020) ? 255 - _soft_bits[ 6] : _soft_bits[ 6];
+        d += (c & 0x0010) ? 255 - _soft_bits[ 7] : _soft_bits[ 7];
+        d += (c & 0x0008) ? 255 - _soft_bits[ 8] : _soft_bits[ 8];
+        d += (c & 0x0004) ? 255 - _soft_bits[ 9] : _soft_bits[ 9];
+        d += (c & 0x0002) ? 255 - _soft_bits[10] : _soft_bits[10];
+        d += (c & 0x0001) ? 255 - _soft_bits[11] : _soft_bits[11];
+
+        if (d < dmin) {
+            s_hat = s;
+            dmin = d;
+        }
+    }
+
+    return s_hat;
+}
+
diff --git a/src/fec/src/fec_hamming128_gentab.c b/src/fec/src/fec_hamming128_gentab.c
new file mode 100644
index 0000000..d23baf0
--- /dev/null
+++ b/src/fec/src/fec_hamming128_gentab.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 2/3-rate (12,8) Hamming code generated tables
+//
+
+// encoding table
+unsigned short int hamming128_enc_gentab[256] = {
+    0x0000, 0x0111, 0x0c12, 0x0d03, 0x0414, 0x0505, 0x0806, 0x0917, 
+    0x0818, 0x0909, 0x040a, 0x051b, 0x0c0c, 0x0d1d, 0x001e, 0x010f, 
+    0x0d20, 0x0c31, 0x0132, 0x0023, 0x0934, 0x0825, 0x0526, 0x0437, 
+    0x0538, 0x0429, 0x092a, 0x083b, 0x012c, 0x003d, 0x0d3e, 0x0c2f, 
+    0x0540, 0x0451, 0x0952, 0x0843, 0x0154, 0x0045, 0x0d46, 0x0c57, 
+    0x0d58, 0x0c49, 0x014a, 0x005b, 0x094c, 0x085d, 0x055e, 0x044f, 
+    0x0860, 0x0971, 0x0472, 0x0563, 0x0c74, 0x0d65, 0x0066, 0x0177, 
+    0x0078, 0x0169, 0x0c6a, 0x0d7b, 0x046c, 0x057d, 0x087e, 0x096f, 
+    0x0980, 0x0891, 0x0592, 0x0483, 0x0d94, 0x0c85, 0x0186, 0x0097, 
+    0x0198, 0x0089, 0x0d8a, 0x0c9b, 0x058c, 0x049d, 0x099e, 0x088f, 
+    0x04a0, 0x05b1, 0x08b2, 0x09a3, 0x00b4, 0x01a5, 0x0ca6, 0x0db7, 
+    0x0cb8, 0x0da9, 0x00aa, 0x01bb, 0x08ac, 0x09bd, 0x04be, 0x05af, 
+    0x0cc0, 0x0dd1, 0x00d2, 0x01c3, 0x08d4, 0x09c5, 0x04c6, 0x05d7, 
+    0x04d8, 0x05c9, 0x08ca, 0x09db, 0x00cc, 0x01dd, 0x0cde, 0x0dcf, 
+    0x01e0, 0x00f1, 0x0df2, 0x0ce3, 0x05f4, 0x04e5, 0x09e6, 0x08f7, 
+    0x09f8, 0x08e9, 0x05ea, 0x04fb, 0x0dec, 0x0cfd, 0x01fe, 0x00ef, 
+    0x0e00, 0x0f11, 0x0212, 0x0303, 0x0a14, 0x0b05, 0x0606, 0x0717, 
+    0x0618, 0x0709, 0x0a0a, 0x0b1b, 0x020c, 0x031d, 0x0e1e, 0x0f0f, 
+    0x0320, 0x0231, 0x0f32, 0x0e23, 0x0734, 0x0625, 0x0b26, 0x0a37, 
+    0x0b38, 0x0a29, 0x072a, 0x063b, 0x0f2c, 0x0e3d, 0x033e, 0x022f, 
+    0x0b40, 0x0a51, 0x0752, 0x0643, 0x0f54, 0x0e45, 0x0346, 0x0257, 
+    0x0358, 0x0249, 0x0f4a, 0x0e5b, 0x074c, 0x065d, 0x0b5e, 0x0a4f, 
+    0x0660, 0x0771, 0x0a72, 0x0b63, 0x0274, 0x0365, 0x0e66, 0x0f77, 
+    0x0e78, 0x0f69, 0x026a, 0x037b, 0x0a6c, 0x0b7d, 0x067e, 0x076f, 
+    0x0780, 0x0691, 0x0b92, 0x0a83, 0x0394, 0x0285, 0x0f86, 0x0e97, 
+    0x0f98, 0x0e89, 0x038a, 0x029b, 0x0b8c, 0x0a9d, 0x079e, 0x068f, 
+    0x0aa0, 0x0bb1, 0x06b2, 0x07a3, 0x0eb4, 0x0fa5, 0x02a6, 0x03b7, 
+    0x02b8, 0x03a9, 0x0eaa, 0x0fbb, 0x06ac, 0x07bd, 0x0abe, 0x0baf, 
+    0x02c0, 0x03d1, 0x0ed2, 0x0fc3, 0x06d4, 0x07c5, 0x0ac6, 0x0bd7, 
+    0x0ad8, 0x0bc9, 0x06ca, 0x07db, 0x0ecc, 0x0fdd, 0x02de, 0x03cf, 
+    0x0fe0, 0x0ef1, 0x03f2, 0x02e3, 0x0bf4, 0x0ae5, 0x07e6, 0x06f7, 
+    0x07f8, 0x06e9, 0x0bea, 0x0afb, 0x03ec, 0x02fd, 0x0ffe, 0x0eef};
+
+// nearest neighbors with Hamming distance 3 (for soft decoding)
+unsigned char fecsoft_hamming128_n3[256][17] = {
+    {0x01, 0x04, 0x06, 0x08, 0x0a, 0x13, 0x20, 0x25, 0x30, 0x40, 0x49, 0x50, 0x80, 0x82, 0x8c, 0x90, 0xe0},
+    {0x00, 0x05, 0x07, 0x09, 0x0b, 0x12, 0x21, 0x24, 0x31, 0x41, 0x48, 0x51, 0x81, 0x83, 0x8d, 0x91, 0xe1},
+    {0x03, 0x04, 0x06, 0x08, 0x0a, 0x11, 0x22, 0x27, 0x32, 0x42, 0x4b, 0x52, 0x80, 0x82, 0x8e, 0x92, 0xe2},
+    {0x02, 0x05, 0x07, 0x09, 0x0b, 0x10, 0x23, 0x26, 0x33, 0x43, 0x4a, 0x53, 0x81, 0x83, 0x8f, 0x93, 0xe3},
+    {0x00, 0x02, 0x05, 0x0c, 0x0e, 0x17, 0x21, 0x24, 0x34, 0x44, 0x4d, 0x54, 0x84, 0x86, 0x88, 0x94, 0xe4},
+    {0x01, 0x03, 0x04, 0x0d, 0x0f, 0x16, 0x20, 0x25, 0x35, 0x45, 0x4c, 0x55, 0x85, 0x87, 0x89, 0x95, 0xe5},
+    {0x00, 0x02, 0x07, 0x0c, 0x0e, 0x15, 0x23, 0x26, 0x36, 0x46, 0x4f, 0x56, 0x84, 0x86, 0x8a, 0x96, 0xe6},
+    {0x01, 0x03, 0x06, 0x0d, 0x0f, 0x14, 0x22, 0x27, 0x37, 0x47, 0x4e, 0x57, 0x85, 0x87, 0x8b, 0x97, 0xe7},
+    {0x00, 0x02, 0x09, 0x0c, 0x0e, 0x1b, 0x28, 0x2d, 0x38, 0x41, 0x48, 0x58, 0x84, 0x88, 0x8a, 0x98, 0xe8},
+    {0x01, 0x03, 0x08, 0x0d, 0x0f, 0x1a, 0x29, 0x2c, 0x39, 0x40, 0x49, 0x59, 0x85, 0x89, 0x8b, 0x99, 0xe9},
+    {0x00, 0x02, 0x0b, 0x0c, 0x0e, 0x19, 0x2a, 0x2f, 0x3a, 0x43, 0x4a, 0x5a, 0x86, 0x88, 0x8a, 0x9a, 0xea},
+    {0x01, 0x03, 0x0a, 0x0d, 0x0f, 0x18, 0x2b, 0x2e, 0x3b, 0x42, 0x4b, 0x5b, 0x87, 0x89, 0x8b, 0x9b, 0xeb},
+    {0x04, 0x06, 0x08, 0x0a, 0x0d, 0x1f, 0x29, 0x2c, 0x3c, 0x45, 0x4c, 0x5c, 0x80, 0x8c, 0x8e, 0x9c, 0xec},
+    {0x05, 0x07, 0x09, 0x0b, 0x0c, 0x1e, 0x28, 0x2d, 0x3d, 0x44, 0x4d, 0x5d, 0x81, 0x8d, 0x8f, 0x9d, 0xed},
+    {0x04, 0x06, 0x08, 0x0a, 0x0f, 0x1d, 0x2b, 0x2e, 0x3e, 0x47, 0x4e, 0x5e, 0x82, 0x8c, 0x8e, 0x9e, 0xee},
+    {0x05, 0x07, 0x09, 0x0b, 0x0e, 0x1c, 0x2a, 0x2f, 0x3f, 0x46, 0x4f, 0x5f, 0x83, 0x8d, 0x8f, 0x9f, 0xef},
+    {0x03, 0x11, 0x14, 0x16, 0x18, 0x1a, 0x20, 0x30, 0x35, 0x40, 0x50, 0x59, 0x80, 0x90, 0x92, 0x9c, 0xf0},
+    {0x02, 0x10, 0x15, 0x17, 0x19, 0x1b, 0x21, 0x31, 0x34, 0x41, 0x51, 0x58, 0x81, 0x91, 0x93, 0x9d, 0xf1},
+    {0x01, 0x13, 0x14, 0x16, 0x18, 0x1a, 0x22, 0x32, 0x37, 0x42, 0x52, 0x5b, 0x82, 0x90, 0x92, 0x9e, 0xf2},
+    {0x00, 0x12, 0x15, 0x17, 0x19, 0x1b, 0x23, 0x33, 0x36, 0x43, 0x53, 0x5a, 0x83, 0x91, 0x93, 0x9f, 0xf3},
+    {0x07, 0x10, 0x12, 0x15, 0x1c, 0x1e, 0x24, 0x31, 0x34, 0x44, 0x54, 0x5d, 0x84, 0x94, 0x96, 0x98, 0xf4},
+    {0x06, 0x11, 0x13, 0x14, 0x1d, 0x1f, 0x25, 0x30, 0x35, 0x45, 0x55, 0x5c, 0x85, 0x95, 0x97, 0x99, 0xf5},
+    {0x05, 0x10, 0x12, 0x17, 0x1c, 0x1e, 0x26, 0x33, 0x36, 0x46, 0x56, 0x5f, 0x86, 0x94, 0x96, 0x9a, 0xf6},
+    {0x04, 0x11, 0x13, 0x16, 0x1d, 0x1f, 0x27, 0x32, 0x37, 0x47, 0x57, 0x5e, 0x87, 0x95, 0x97, 0x9b, 0xf7},
+    {0x0b, 0x10, 0x12, 0x19, 0x1c, 0x1e, 0x28, 0x38, 0x3d, 0x48, 0x51, 0x58, 0x88, 0x94, 0x98, 0x9a, 0xf8},
+    {0x0a, 0x11, 0x13, 0x18, 0x1d, 0x1f, 0x29, 0x39, 0x3c, 0x49, 0x50, 0x59, 0x89, 0x95, 0x99, 0x9b, 0xf9},
+    {0x09, 0x10, 0x12, 0x1b, 0x1c, 0x1e, 0x2a, 0x3a, 0x3f, 0x4a, 0x53, 0x5a, 0x8a, 0x96, 0x98, 0x9a, 0xfa},
+    {0x08, 0x11, 0x13, 0x1a, 0x1d, 0x1f, 0x2b, 0x3b, 0x3e, 0x4b, 0x52, 0x5b, 0x8b, 0x97, 0x99, 0x9b, 0xfb},
+    {0x0f, 0x14, 0x16, 0x18, 0x1a, 0x1d, 0x2c, 0x39, 0x3c, 0x4c, 0x55, 0x5c, 0x8c, 0x90, 0x9c, 0x9e, 0xfc},
+    {0x0e, 0x15, 0x17, 0x19, 0x1b, 0x1c, 0x2d, 0x38, 0x3d, 0x4d, 0x54, 0x5d, 0x8d, 0x91, 0x9d, 0x9f, 0xfd},
+    {0x0d, 0x14, 0x16, 0x18, 0x1a, 0x1f, 0x2e, 0x3b, 0x3e, 0x4e, 0x57, 0x5e, 0x8e, 0x92, 0x9c, 0x9e, 0xfe},
+    {0x0c, 0x15, 0x17, 0x19, 0x1b, 0x1e, 0x2f, 0x3a, 0x3f, 0x4f, 0x56, 0x5f, 0x8f, 0x93, 0x9d, 0x9f, 0xff},
+    {0x00, 0x05, 0x10, 0x21, 0x24, 0x26, 0x28, 0x2a, 0x33, 0x60, 0x69, 0x70, 0xa0, 0xa2, 0xac, 0xb0, 0xc0},
+    {0x01, 0x04, 0x11, 0x20, 0x25, 0x27, 0x29, 0x2b, 0x32, 0x61, 0x68, 0x71, 0xa1, 0xa3, 0xad, 0xb1, 0xc1},
+    {0x02, 0x07, 0x12, 0x23, 0x24, 0x26, 0x28, 0x2a, 0x31, 0x62, 0x6b, 0x72, 0xa0, 0xa2, 0xae, 0xb2, 0xc2},
+    {0x03, 0x06, 0x13, 0x22, 0x25, 0x27, 0x29, 0x2b, 0x30, 0x63, 0x6a, 0x73, 0xa1, 0xa3, 0xaf, 0xb3, 0xc3},
+    {0x01, 0x04, 0x14, 0x20, 0x22, 0x25, 0x2c, 0x2e, 0x37, 0x64, 0x6d, 0x74, 0xa4, 0xa6, 0xa8, 0xb4, 0xc4},
+    {0x00, 0x05, 0x15, 0x21, 0x23, 0x24, 0x2d, 0x2f, 0x36, 0x65, 0x6c, 0x75, 0xa5, 0xa7, 0xa9, 0xb5, 0xc5},
+    {0x03, 0x06, 0x16, 0x20, 0x22, 0x27, 0x2c, 0x2e, 0x35, 0x66, 0x6f, 0x76, 0xa4, 0xa6, 0xaa, 0xb6, 0xc6},
+    {0x02, 0x07, 0x17, 0x21, 0x23, 0x26, 0x2d, 0x2f, 0x34, 0x67, 0x6e, 0x77, 0xa5, 0xa7, 0xab, 0xb7, 0xc7},
+    {0x08, 0x0d, 0x18, 0x20, 0x22, 0x29, 0x2c, 0x2e, 0x3b, 0x61, 0x68, 0x78, 0xa4, 0xa8, 0xaa, 0xb8, 0xc8},
+    {0x09, 0x0c, 0x19, 0x21, 0x23, 0x28, 0x2d, 0x2f, 0x3a, 0x60, 0x69, 0x79, 0xa5, 0xa9, 0xab, 0xb9, 0xc9},
+    {0x0a, 0x0f, 0x1a, 0x20, 0x22, 0x2b, 0x2c, 0x2e, 0x39, 0x63, 0x6a, 0x7a, 0xa6, 0xa8, 0xaa, 0xba, 0xca},
+    {0x0b, 0x0e, 0x1b, 0x21, 0x23, 0x2a, 0x2d, 0x2f, 0x38, 0x62, 0x6b, 0x7b, 0xa7, 0xa9, 0xab, 0xbb, 0xcb},
+    {0x09, 0x0c, 0x1c, 0x24, 0x26, 0x28, 0x2a, 0x2d, 0x3f, 0x65, 0x6c, 0x7c, 0xa0, 0xac, 0xae, 0xbc, 0xcc},
+    {0x08, 0x0d, 0x1d, 0x25, 0x27, 0x29, 0x2b, 0x2c, 0x3e, 0x64, 0x6d, 0x7d, 0xa1, 0xad, 0xaf, 0xbd, 0xcd},
+    {0x0b, 0x0e, 0x1e, 0x24, 0x26, 0x28, 0x2a, 0x2f, 0x3d, 0x67, 0x6e, 0x7e, 0xa2, 0xac, 0xae, 0xbe, 0xce},
+    {0x0a, 0x0f, 0x1f, 0x25, 0x27, 0x29, 0x2b, 0x2e, 0x3c, 0x66, 0x6f, 0x7f, 0xa3, 0xad, 0xaf, 0xbf, 0xcf},
+    {0x00, 0x10, 0x15, 0x23, 0x31, 0x34, 0x36, 0x38, 0x3a, 0x60, 0x70, 0x79, 0xa0, 0xb0, 0xb2, 0xbc, 0xd0},
+    {0x01, 0x11, 0x14, 0x22, 0x30, 0x35, 0x37, 0x39, 0x3b, 0x61, 0x71, 0x78, 0xa1, 0xb1, 0xb3, 0xbd, 0xd1},
+    {0x02, 0x12, 0x17, 0x21, 0x33, 0x34, 0x36, 0x38, 0x3a, 0x62, 0x72, 0x7b, 0xa2, 0xb0, 0xb2, 0xbe, 0xd2},
+    {0x03, 0x13, 0x16, 0x20, 0x32, 0x35, 0x37, 0x39, 0x3b, 0x63, 0x73, 0x7a, 0xa3, 0xb1, 0xb3, 0xbf, 0xd3},
+    {0x04, 0x11, 0x14, 0x27, 0x30, 0x32, 0x35, 0x3c, 0x3e, 0x64, 0x74, 0x7d, 0xa4, 0xb4, 0xb6, 0xb8, 0xd4},
+    {0x05, 0x10, 0x15, 0x26, 0x31, 0x33, 0x34, 0x3d, 0x3f, 0x65, 0x75, 0x7c, 0xa5, 0xb5, 0xb7, 0xb9, 0xd5},
+    {0x06, 0x13, 0x16, 0x25, 0x30, 0x32, 0x37, 0x3c, 0x3e, 0x66, 0x76, 0x7f, 0xa6, 0xb4, 0xb6, 0xba, 0xd6},
+    {0x07, 0x12, 0x17, 0x24, 0x31, 0x33, 0x36, 0x3d, 0x3f, 0x67, 0x77, 0x7e, 0xa7, 0xb5, 0xb7, 0xbb, 0xd7},
+    {0x08, 0x18, 0x1d, 0x2b, 0x30, 0x32, 0x39, 0x3c, 0x3e, 0x68, 0x71, 0x78, 0xa8, 0xb4, 0xb8, 0xba, 0xd8},
+    {0x09, 0x19, 0x1c, 0x2a, 0x31, 0x33, 0x38, 0x3d, 0x3f, 0x69, 0x70, 0x79, 0xa9, 0xb5, 0xb9, 0xbb, 0xd9},
+    {0x0a, 0x1a, 0x1f, 0x29, 0x30, 0x32, 0x3b, 0x3c, 0x3e, 0x6a, 0x73, 0x7a, 0xaa, 0xb6, 0xb8, 0xba, 0xda},
+    {0x0b, 0x1b, 0x1e, 0x28, 0x31, 0x33, 0x3a, 0x3d, 0x3f, 0x6b, 0x72, 0x7b, 0xab, 0xb7, 0xb9, 0xbb, 0xdb},
+    {0x0c, 0x19, 0x1c, 0x2f, 0x34, 0x36, 0x38, 0x3a, 0x3d, 0x6c, 0x75, 0x7c, 0xac, 0xb0, 0xbc, 0xbe, 0xdc},
+    {0x0d, 0x18, 0x1d, 0x2e, 0x35, 0x37, 0x39, 0x3b, 0x3c, 0x6d, 0x74, 0x7d, 0xad, 0xb1, 0xbd, 0xbf, 0xdd},
+    {0x0e, 0x1b, 0x1e, 0x2d, 0x34, 0x36, 0x38, 0x3a, 0x3f, 0x6e, 0x77, 0x7e, 0xae, 0xb2, 0xbc, 0xbe, 0xde},
+    {0x0f, 0x1a, 0x1f, 0x2c, 0x35, 0x37, 0x39, 0x3b, 0x3e, 0x6f, 0x76, 0x7f, 0xaf, 0xb3, 0xbd, 0xbf, 0xdf},
+    {0x00, 0x09, 0x10, 0x41, 0x44, 0x46, 0x48, 0x4a, 0x53, 0x60, 0x65, 0x70, 0xa0, 0xc0, 0xc2, 0xcc, 0xd0},
+    {0x01, 0x08, 0x11, 0x40, 0x45, 0x47, 0x49, 0x4b, 0x52, 0x61, 0x64, 0x71, 0xa1, 0xc1, 0xc3, 0xcd, 0xd1},
+    {0x02, 0x0b, 0x12, 0x43, 0x44, 0x46, 0x48, 0x4a, 0x51, 0x62, 0x67, 0x72, 0xa2, 0xc0, 0xc2, 0xce, 0xd2},
+    {0x03, 0x0a, 0x13, 0x42, 0x45, 0x47, 0x49, 0x4b, 0x50, 0x63, 0x66, 0x73, 0xa3, 0xc1, 0xc3, 0xcf, 0xd3},
+    {0x04, 0x0d, 0x14, 0x40, 0x42, 0x45, 0x4c, 0x4e, 0x57, 0x61, 0x64, 0x74, 0xa4, 0xc4, 0xc6, 0xc8, 0xd4},
+    {0x05, 0x0c, 0x15, 0x41, 0x43, 0x44, 0x4d, 0x4f, 0x56, 0x60, 0x65, 0x75, 0xa5, 0xc5, 0xc7, 0xc9, 0xd5},
+    {0x06, 0x0f, 0x16, 0x40, 0x42, 0x47, 0x4c, 0x4e, 0x55, 0x63, 0x66, 0x76, 0xa6, 0xc4, 0xc6, 0xca, 0xd6},
+    {0x07, 0x0e, 0x17, 0x41, 0x43, 0x46, 0x4d, 0x4f, 0x54, 0x62, 0x67, 0x77, 0xa7, 0xc5, 0xc7, 0xcb, 0xd7},
+    {0x01, 0x08, 0x18, 0x40, 0x42, 0x49, 0x4c, 0x4e, 0x5b, 0x68, 0x6d, 0x78, 0xa8, 0xc4, 0xc8, 0xca, 0xd8},
+    {0x00, 0x09, 0x19, 0x41, 0x43, 0x48, 0x4d, 0x4f, 0x5a, 0x69, 0x6c, 0x79, 0xa9, 0xc5, 0xc9, 0xcb, 0xd9},
+    {0x03, 0x0a, 0x1a, 0x40, 0x42, 0x4b, 0x4c, 0x4e, 0x59, 0x6a, 0x6f, 0x7a, 0xaa, 0xc6, 0xc8, 0xca, 0xda},
+    {0x02, 0x0b, 0x1b, 0x41, 0x43, 0x4a, 0x4d, 0x4f, 0x58, 0x6b, 0x6e, 0x7b, 0xab, 0xc7, 0xc9, 0xcb, 0xdb},
+    {0x05, 0x0c, 0x1c, 0x44, 0x46, 0x48, 0x4a, 0x4d, 0x5f, 0x69, 0x6c, 0x7c, 0xac, 0xc0, 0xcc, 0xce, 0xdc},
+    {0x04, 0x0d, 0x1d, 0x45, 0x47, 0x49, 0x4b, 0x4c, 0x5e, 0x68, 0x6d, 0x7d, 0xad, 0xc1, 0xcd, 0xcf, 0xdd},
+    {0x07, 0x0e, 0x1e, 0x44, 0x46, 0x48, 0x4a, 0x4f, 0x5d, 0x6b, 0x6e, 0x7e, 0xae, 0xc2, 0xcc, 0xce, 0xde},
+    {0x06, 0x0f, 0x1f, 0x45, 0x47, 0x49, 0x4b, 0x4e, 0x5c, 0x6a, 0x6f, 0x7f, 0xaf, 0xc3, 0xcd, 0xcf, 0xdf},
+    {0x00, 0x10, 0x19, 0x43, 0x51, 0x54, 0x56, 0x58, 0x5a, 0x60, 0x70, 0x75, 0xb0, 0xc0, 0xd0, 0xd2, 0xdc},
+    {0x01, 0x11, 0x18, 0x42, 0x50, 0x55, 0x57, 0x59, 0x5b, 0x61, 0x71, 0x74, 0xb1, 0xc1, 0xd1, 0xd3, 0xdd},
+    {0x02, 0x12, 0x1b, 0x41, 0x53, 0x54, 0x56, 0x58, 0x5a, 0x62, 0x72, 0x77, 0xb2, 0xc2, 0xd0, 0xd2, 0xde},
+    {0x03, 0x13, 0x1a, 0x40, 0x52, 0x55, 0x57, 0x59, 0x5b, 0x63, 0x73, 0x76, 0xb3, 0xc3, 0xd1, 0xd3, 0xdf},
+    {0x04, 0x14, 0x1d, 0x47, 0x50, 0x52, 0x55, 0x5c, 0x5e, 0x64, 0x71, 0x74, 0xb4, 0xc4, 0xd4, 0xd6, 0xd8},
+    {0x05, 0x15, 0x1c, 0x46, 0x51, 0x53, 0x54, 0x5d, 0x5f, 0x65, 0x70, 0x75, 0xb5, 0xc5, 0xd5, 0xd7, 0xd9},
+    {0x06, 0x16, 0x1f, 0x45, 0x50, 0x52, 0x57, 0x5c, 0x5e, 0x66, 0x73, 0x76, 0xb6, 0xc6, 0xd4, 0xd6, 0xda},
+    {0x07, 0x17, 0x1e, 0x44, 0x51, 0x53, 0x56, 0x5d, 0x5f, 0x67, 0x72, 0x77, 0xb7, 0xc7, 0xd5, 0xd7, 0xdb},
+    {0x08, 0x11, 0x18, 0x4b, 0x50, 0x52, 0x59, 0x5c, 0x5e, 0x68, 0x78, 0x7d, 0xb8, 0xc8, 0xd4, 0xd8, 0xda},
+    {0x09, 0x10, 0x19, 0x4a, 0x51, 0x53, 0x58, 0x5d, 0x5f, 0x69, 0x79, 0x7c, 0xb9, 0xc9, 0xd5, 0xd9, 0xdb},
+    {0x0a, 0x13, 0x1a, 0x49, 0x50, 0x52, 0x5b, 0x5c, 0x5e, 0x6a, 0x7a, 0x7f, 0xba, 0xca, 0xd6, 0xd8, 0xda},
+    {0x0b, 0x12, 0x1b, 0x48, 0x51, 0x53, 0x5a, 0x5d, 0x5f, 0x6b, 0x7b, 0x7e, 0xbb, 0xcb, 0xd7, 0xd9, 0xdb},
+    {0x0c, 0x15, 0x1c, 0x4f, 0x54, 0x56, 0x58, 0x5a, 0x5d, 0x6c, 0x79, 0x7c, 0xbc, 0xcc, 0xd0, 0xdc, 0xde},
+    {0x0d, 0x14, 0x1d, 0x4e, 0x55, 0x57, 0x59, 0x5b, 0x5c, 0x6d, 0x78, 0x7d, 0xbd, 0xcd, 0xd1, 0xdd, 0xdf},
+    {0x0e, 0x17, 0x1e, 0x4d, 0x54, 0x56, 0x58, 0x5a, 0x5f, 0x6e, 0x7b, 0x7e, 0xbe, 0xce, 0xd2, 0xdc, 0xde},
+    {0x0f, 0x16, 0x1f, 0x4c, 0x55, 0x57, 0x59, 0x5b, 0x5e, 0x6f, 0x7a, 0x7f, 0xbf, 0xcf, 0xd3, 0xdd, 0xdf},
+    {0x20, 0x29, 0x30, 0x40, 0x45, 0x50, 0x61, 0x64, 0x66, 0x68, 0x6a, 0x73, 0x80, 0xe0, 0xe2, 0xec, 0xf0},
+    {0x21, 0x28, 0x31, 0x41, 0x44, 0x51, 0x60, 0x65, 0x67, 0x69, 0x6b, 0x72, 0x81, 0xe1, 0xe3, 0xed, 0xf1},
+    {0x22, 0x2b, 0x32, 0x42, 0x47, 0x52, 0x63, 0x64, 0x66, 0x68, 0x6a, 0x71, 0x82, 0xe0, 0xe2, 0xee, 0xf2},
+    {0x23, 0x2a, 0x33, 0x43, 0x46, 0x53, 0x62, 0x65, 0x67, 0x69, 0x6b, 0x70, 0x83, 0xe1, 0xe3, 0xef, 0xf3},
+    {0x24, 0x2d, 0x34, 0x41, 0x44, 0x54, 0x60, 0x62, 0x65, 0x6c, 0x6e, 0x77, 0x84, 0xe4, 0xe6, 0xe8, 0xf4},
+    {0x25, 0x2c, 0x35, 0x40, 0x45, 0x55, 0x61, 0x63, 0x64, 0x6d, 0x6f, 0x76, 0x85, 0xe5, 0xe7, 0xe9, 0xf5},
+    {0x26, 0x2f, 0x36, 0x43, 0x46, 0x56, 0x60, 0x62, 0x67, 0x6c, 0x6e, 0x75, 0x86, 0xe4, 0xe6, 0xea, 0xf6},
+    {0x27, 0x2e, 0x37, 0x42, 0x47, 0x57, 0x61, 0x63, 0x66, 0x6d, 0x6f, 0x74, 0x87, 0xe5, 0xe7, 0xeb, 0xf7},
+    {0x21, 0x28, 0x38, 0x48, 0x4d, 0x58, 0x60, 0x62, 0x69, 0x6c, 0x6e, 0x7b, 0x88, 0xe4, 0xe8, 0xea, 0xf8},
+    {0x20, 0x29, 0x39, 0x49, 0x4c, 0x59, 0x61, 0x63, 0x68, 0x6d, 0x6f, 0x7a, 0x89, 0xe5, 0xe9, 0xeb, 0xf9},
+    {0x23, 0x2a, 0x3a, 0x4a, 0x4f, 0x5a, 0x60, 0x62, 0x6b, 0x6c, 0x6e, 0x79, 0x8a, 0xe6, 0xe8, 0xea, 0xfa},
+    {0x22, 0x2b, 0x3b, 0x4b, 0x4e, 0x5b, 0x61, 0x63, 0x6a, 0x6d, 0x6f, 0x78, 0x8b, 0xe7, 0xe9, 0xeb, 0xfb},
+    {0x25, 0x2c, 0x3c, 0x49, 0x4c, 0x5c, 0x64, 0x66, 0x68, 0x6a, 0x6d, 0x7f, 0x8c, 0xe0, 0xec, 0xee, 0xfc},
+    {0x24, 0x2d, 0x3d, 0x48, 0x4d, 0x5d, 0x65, 0x67, 0x69, 0x6b, 0x6c, 0x7e, 0x8d, 0xe1, 0xed, 0xef, 0xfd},
+    {0x27, 0x2e, 0x3e, 0x4b, 0x4e, 0x5e, 0x64, 0x66, 0x68, 0x6a, 0x6f, 0x7d, 0x8e, 0xe2, 0xec, 0xee, 0xfe},
+    {0x26, 0x2f, 0x3f, 0x4a, 0x4f, 0x5f, 0x65, 0x67, 0x69, 0x6b, 0x6e, 0x7c, 0x8f, 0xe3, 0xed, 0xef, 0xff},
+    {0x20, 0x30, 0x39, 0x40, 0x50, 0x55, 0x63, 0x71, 0x74, 0x76, 0x78, 0x7a, 0x90, 0xe0, 0xf0, 0xf2, 0xfc},
+    {0x21, 0x31, 0x38, 0x41, 0x51, 0x54, 0x62, 0x70, 0x75, 0x77, 0x79, 0x7b, 0x91, 0xe1, 0xf1, 0xf3, 0xfd},
+    {0x22, 0x32, 0x3b, 0x42, 0x52, 0x57, 0x61, 0x73, 0x74, 0x76, 0x78, 0x7a, 0x92, 0xe2, 0xf0, 0xf2, 0xfe},
+    {0x23, 0x33, 0x3a, 0x43, 0x53, 0x56, 0x60, 0x72, 0x75, 0x77, 0x79, 0x7b, 0x93, 0xe3, 0xf1, 0xf3, 0xff},
+    {0x24, 0x34, 0x3d, 0x44, 0x51, 0x54, 0x67, 0x70, 0x72, 0x75, 0x7c, 0x7e, 0x94, 0xe4, 0xf4, 0xf6, 0xf8},
+    {0x25, 0x35, 0x3c, 0x45, 0x50, 0x55, 0x66, 0x71, 0x73, 0x74, 0x7d, 0x7f, 0x95, 0xe5, 0xf5, 0xf7, 0xf9},
+    {0x26, 0x36, 0x3f, 0x46, 0x53, 0x56, 0x65, 0x70, 0x72, 0x77, 0x7c, 0x7e, 0x96, 0xe6, 0xf4, 0xf6, 0xfa},
+    {0x27, 0x37, 0x3e, 0x47, 0x52, 0x57, 0x64, 0x71, 0x73, 0x76, 0x7d, 0x7f, 0x97, 0xe7, 0xf5, 0xf7, 0xfb},
+    {0x28, 0x31, 0x38, 0x48, 0x58, 0x5d, 0x6b, 0x70, 0x72, 0x79, 0x7c, 0x7e, 0x98, 0xe8, 0xf4, 0xf8, 0xfa},
+    {0x29, 0x30, 0x39, 0x49, 0x59, 0x5c, 0x6a, 0x71, 0x73, 0x78, 0x7d, 0x7f, 0x99, 0xe9, 0xf5, 0xf9, 0xfb},
+    {0x2a, 0x33, 0x3a, 0x4a, 0x5a, 0x5f, 0x69, 0x70, 0x72, 0x7b, 0x7c, 0x7e, 0x9a, 0xea, 0xf6, 0xf8, 0xfa},
+    {0x2b, 0x32, 0x3b, 0x4b, 0x5b, 0x5e, 0x68, 0x71, 0x73, 0x7a, 0x7d, 0x7f, 0x9b, 0xeb, 0xf7, 0xf9, 0xfb},
+    {0x2c, 0x35, 0x3c, 0x4c, 0x59, 0x5c, 0x6f, 0x74, 0x76, 0x78, 0x7a, 0x7d, 0x9c, 0xec, 0xf0, 0xfc, 0xfe},
+    {0x2d, 0x34, 0x3d, 0x4d, 0x58, 0x5d, 0x6e, 0x75, 0x77, 0x79, 0x7b, 0x7c, 0x9d, 0xed, 0xf1, 0xfd, 0xff},
+    {0x2e, 0x37, 0x3e, 0x4e, 0x5b, 0x5e, 0x6d, 0x74, 0x76, 0x78, 0x7a, 0x7f, 0x9e, 0xee, 0xf2, 0xfc, 0xfe},
+    {0x2f, 0x36, 0x3f, 0x4f, 0x5a, 0x5f, 0x6c, 0x75, 0x77, 0x79, 0x7b, 0x7e, 0x9f, 0xef, 0xf3, 0xfd, 0xff},
+    {0x00, 0x02, 0x0c, 0x10, 0x60, 0x81, 0x84, 0x86, 0x88, 0x8a, 0x93, 0xa0, 0xa5, 0xb0, 0xc0, 0xc9, 0xd0},
+    {0x01, 0x03, 0x0d, 0x11, 0x61, 0x80, 0x85, 0x87, 0x89, 0x8b, 0x92, 0xa1, 0xa4, 0xb1, 0xc1, 0xc8, 0xd1},
+    {0x00, 0x02, 0x0e, 0x12, 0x62, 0x83, 0x84, 0x86, 0x88, 0x8a, 0x91, 0xa2, 0xa7, 0xb2, 0xc2, 0xcb, 0xd2},
+    {0x01, 0x03, 0x0f, 0x13, 0x63, 0x82, 0x85, 0x87, 0x89, 0x8b, 0x90, 0xa3, 0xa6, 0xb3, 0xc3, 0xca, 0xd3},
+    {0x04, 0x06, 0x08, 0x14, 0x64, 0x80, 0x82, 0x85, 0x8c, 0x8e, 0x97, 0xa1, 0xa4, 0xb4, 0xc4, 0xcd, 0xd4},
+    {0x05, 0x07, 0x09, 0x15, 0x65, 0x81, 0x83, 0x84, 0x8d, 0x8f, 0x96, 0xa0, 0xa5, 0xb5, 0xc5, 0xcc, 0xd5},
+    {0x04, 0x06, 0x0a, 0x16, 0x66, 0x80, 0x82, 0x87, 0x8c, 0x8e, 0x95, 0xa3, 0xa6, 0xb6, 0xc6, 0xcf, 0xd6},
+    {0x05, 0x07, 0x0b, 0x17, 0x67, 0x81, 0x83, 0x86, 0x8d, 0x8f, 0x94, 0xa2, 0xa7, 0xb7, 0xc7, 0xce, 0xd7},
+    {0x04, 0x08, 0x0a, 0x18, 0x68, 0x80, 0x82, 0x89, 0x8c, 0x8e, 0x9b, 0xa8, 0xad, 0xb8, 0xc1, 0xc8, 0xd8},
+    {0x05, 0x09, 0x0b, 0x19, 0x69, 0x81, 0x83, 0x88, 0x8d, 0x8f, 0x9a, 0xa9, 0xac, 0xb9, 0xc0, 0xc9, 0xd9},
+    {0x06, 0x08, 0x0a, 0x1a, 0x6a, 0x80, 0x82, 0x8b, 0x8c, 0x8e, 0x99, 0xaa, 0xaf, 0xba, 0xc3, 0xca, 0xda},
+    {0x07, 0x09, 0x0b, 0x1b, 0x6b, 0x81, 0x83, 0x8a, 0x8d, 0x8f, 0x98, 0xab, 0xae, 0xbb, 0xc2, 0xcb, 0xdb},
+    {0x00, 0x0c, 0x0e, 0x1c, 0x6c, 0x84, 0x86, 0x88, 0x8a, 0x8d, 0x9f, 0xa9, 0xac, 0xbc, 0xc5, 0xcc, 0xdc},
+    {0x01, 0x0d, 0x0f, 0x1d, 0x6d, 0x85, 0x87, 0x89, 0x8b, 0x8c, 0x9e, 0xa8, 0xad, 0xbd, 0xc4, 0xcd, 0xdd},
+    {0x02, 0x0c, 0x0e, 0x1e, 0x6e, 0x84, 0x86, 0x88, 0x8a, 0x8f, 0x9d, 0xab, 0xae, 0xbe, 0xc7, 0xce, 0xde},
+    {0x03, 0x0d, 0x0f, 0x1f, 0x6f, 0x85, 0x87, 0x89, 0x8b, 0x8e, 0x9c, 0xaa, 0xaf, 0xbf, 0xc6, 0xcf, 0xdf},
+    {0x00, 0x10, 0x12, 0x1c, 0x70, 0x83, 0x91, 0x94, 0x96, 0x98, 0x9a, 0xa0, 0xb0, 0xb5, 0xc0, 0xd0, 0xd9},
+    {0x01, 0x11, 0x13, 0x1d, 0x71, 0x82, 0x90, 0x95, 0x97, 0x99, 0x9b, 0xa1, 0xb1, 0xb4, 0xc1, 0xd1, 0xd8},
+    {0x02, 0x10, 0x12, 0x1e, 0x72, 0x81, 0x93, 0x94, 0x96, 0x98, 0x9a, 0xa2, 0xb2, 0xb7, 0xc2, 0xd2, 0xdb},
+    {0x03, 0x11, 0x13, 0x1f, 0x73, 0x80, 0x92, 0x95, 0x97, 0x99, 0x9b, 0xa3, 0xb3, 0xb6, 0xc3, 0xd3, 0xda},
+    {0x04, 0x14, 0x16, 0x18, 0x74, 0x87, 0x90, 0x92, 0x95, 0x9c, 0x9e, 0xa4, 0xb1, 0xb4, 0xc4, 0xd4, 0xdd},
+    {0x05, 0x15, 0x17, 0x19, 0x75, 0x86, 0x91, 0x93, 0x94, 0x9d, 0x9f, 0xa5, 0xb0, 0xb5, 0xc5, 0xd5, 0xdc},
+    {0x06, 0x14, 0x16, 0x1a, 0x76, 0x85, 0x90, 0x92, 0x97, 0x9c, 0x9e, 0xa6, 0xb3, 0xb6, 0xc6, 0xd6, 0xdf},
+    {0x07, 0x15, 0x17, 0x1b, 0x77, 0x84, 0x91, 0x93, 0x96, 0x9d, 0x9f, 0xa7, 0xb2, 0xb7, 0xc7, 0xd7, 0xde},
+    {0x08, 0x14, 0x18, 0x1a, 0x78, 0x8b, 0x90, 0x92, 0x99, 0x9c, 0x9e, 0xa8, 0xb8, 0xbd, 0xc8, 0xd1, 0xd8},
+    {0x09, 0x15, 0x19, 0x1b, 0x79, 0x8a, 0x91, 0x93, 0x98, 0x9d, 0x9f, 0xa9, 0xb9, 0xbc, 0xc9, 0xd0, 0xd9},
+    {0x0a, 0x16, 0x18, 0x1a, 0x7a, 0x89, 0x90, 0x92, 0x9b, 0x9c, 0x9e, 0xaa, 0xba, 0xbf, 0xca, 0xd3, 0xda},
+    {0x0b, 0x17, 0x19, 0x1b, 0x7b, 0x88, 0x91, 0x93, 0x9a, 0x9d, 0x9f, 0xab, 0xbb, 0xbe, 0xcb, 0xd2, 0xdb},
+    {0x0c, 0x10, 0x1c, 0x1e, 0x7c, 0x8f, 0x94, 0x96, 0x98, 0x9a, 0x9d, 0xac, 0xb9, 0xbc, 0xcc, 0xd5, 0xdc},
+    {0x0d, 0x11, 0x1d, 0x1f, 0x7d, 0x8e, 0x95, 0x97, 0x99, 0x9b, 0x9c, 0xad, 0xb8, 0xbd, 0xcd, 0xd4, 0xdd},
+    {0x0e, 0x12, 0x1c, 0x1e, 0x7e, 0x8d, 0x94, 0x96, 0x98, 0x9a, 0x9f, 0xae, 0xbb, 0xbe, 0xce, 0xd7, 0xde},
+    {0x0f, 0x13, 0x1d, 0x1f, 0x7f, 0x8c, 0x95, 0x97, 0x99, 0x9b, 0x9e, 0xaf, 0xba, 0xbf, 0xcf, 0xd6, 0xdf},
+    {0x20, 0x22, 0x2c, 0x30, 0x40, 0x80, 0x85, 0x90, 0xa1, 0xa4, 0xa6, 0xa8, 0xaa, 0xb3, 0xe0, 0xe9, 0xf0},
+    {0x21, 0x23, 0x2d, 0x31, 0x41, 0x81, 0x84, 0x91, 0xa0, 0xa5, 0xa7, 0xa9, 0xab, 0xb2, 0xe1, 0xe8, 0xf1},
+    {0x20, 0x22, 0x2e, 0x32, 0x42, 0x82, 0x87, 0x92, 0xa3, 0xa4, 0xa6, 0xa8, 0xaa, 0xb1, 0xe2, 0xeb, 0xf2},
+    {0x21, 0x23, 0x2f, 0x33, 0x43, 0x83, 0x86, 0x93, 0xa2, 0xa5, 0xa7, 0xa9, 0xab, 0xb0, 0xe3, 0xea, 0xf3},
+    {0x24, 0x26, 0x28, 0x34, 0x44, 0x81, 0x84, 0x94, 0xa0, 0xa2, 0xa5, 0xac, 0xae, 0xb7, 0xe4, 0xed, 0xf4},
+    {0x25, 0x27, 0x29, 0x35, 0x45, 0x80, 0x85, 0x95, 0xa1, 0xa3, 0xa4, 0xad, 0xaf, 0xb6, 0xe5, 0xec, 0xf5},
+    {0x24, 0x26, 0x2a, 0x36, 0x46, 0x83, 0x86, 0x96, 0xa0, 0xa2, 0xa7, 0xac, 0xae, 0xb5, 0xe6, 0xef, 0xf6},
+    {0x25, 0x27, 0x2b, 0x37, 0x47, 0x82, 0x87, 0x97, 0xa1, 0xa3, 0xa6, 0xad, 0xaf, 0xb4, 0xe7, 0xee, 0xf7},
+    {0x24, 0x28, 0x2a, 0x38, 0x48, 0x88, 0x8d, 0x98, 0xa0, 0xa2, 0xa9, 0xac, 0xae, 0xbb, 0xe1, 0xe8, 0xf8},
+    {0x25, 0x29, 0x2b, 0x39, 0x49, 0x89, 0x8c, 0x99, 0xa1, 0xa3, 0xa8, 0xad, 0xaf, 0xba, 0xe0, 0xe9, 0xf9},
+    {0x26, 0x28, 0x2a, 0x3a, 0x4a, 0x8a, 0x8f, 0x9a, 0xa0, 0xa2, 0xab, 0xac, 0xae, 0xb9, 0xe3, 0xea, 0xfa},
+    {0x27, 0x29, 0x2b, 0x3b, 0x4b, 0x8b, 0x8e, 0x9b, 0xa1, 0xa3, 0xaa, 0xad, 0xaf, 0xb8, 0xe2, 0xeb, 0xfb},
+    {0x20, 0x2c, 0x2e, 0x3c, 0x4c, 0x89, 0x8c, 0x9c, 0xa4, 0xa6, 0xa8, 0xaa, 0xad, 0xbf, 0xe5, 0xec, 0xfc},
+    {0x21, 0x2d, 0x2f, 0x3d, 0x4d, 0x88, 0x8d, 0x9d, 0xa5, 0xa7, 0xa9, 0xab, 0xac, 0xbe, 0xe4, 0xed, 0xfd},
+    {0x22, 0x2c, 0x2e, 0x3e, 0x4e, 0x8b, 0x8e, 0x9e, 0xa4, 0xa6, 0xa8, 0xaa, 0xaf, 0xbd, 0xe7, 0xee, 0xfe},
+    {0x23, 0x2d, 0x2f, 0x3f, 0x4f, 0x8a, 0x8f, 0x9f, 0xa5, 0xa7, 0xa9, 0xab, 0xae, 0xbc, 0xe6, 0xef, 0xff},
+    {0x20, 0x30, 0x32, 0x3c, 0x50, 0x80, 0x90, 0x95, 0xa3, 0xb1, 0xb4, 0xb6, 0xb8, 0xba, 0xe0, 0xf0, 0xf9},
+    {0x21, 0x31, 0x33, 0x3d, 0x51, 0x81, 0x91, 0x94, 0xa2, 0xb0, 0xb5, 0xb7, 0xb9, 0xbb, 0xe1, 0xf1, 0xf8},
+    {0x22, 0x30, 0x32, 0x3e, 0x52, 0x82, 0x92, 0x97, 0xa1, 0xb3, 0xb4, 0xb6, 0xb8, 0xba, 0xe2, 0xf2, 0xfb},
+    {0x23, 0x31, 0x33, 0x3f, 0x53, 0x83, 0x93, 0x96, 0xa0, 0xb2, 0xb5, 0xb7, 0xb9, 0xbb, 0xe3, 0xf3, 0xfa},
+    {0x24, 0x34, 0x36, 0x38, 0x54, 0x84, 0x91, 0x94, 0xa7, 0xb0, 0xb2, 0xb5, 0xbc, 0xbe, 0xe4, 0xf4, 0xfd},
+    {0x25, 0x35, 0x37, 0x39, 0x55, 0x85, 0x90, 0x95, 0xa6, 0xb1, 0xb3, 0xb4, 0xbd, 0xbf, 0xe5, 0xf5, 0xfc},
+    {0x26, 0x34, 0x36, 0x3a, 0x56, 0x86, 0x93, 0x96, 0xa5, 0xb0, 0xb2, 0xb7, 0xbc, 0xbe, 0xe6, 0xf6, 0xff},
+    {0x27, 0x35, 0x37, 0x3b, 0x57, 0x87, 0x92, 0x97, 0xa4, 0xb1, 0xb3, 0xb6, 0xbd, 0xbf, 0xe7, 0xf7, 0xfe},
+    {0x28, 0x34, 0x38, 0x3a, 0x58, 0x88, 0x98, 0x9d, 0xab, 0xb0, 0xb2, 0xb9, 0xbc, 0xbe, 0xe8, 0xf1, 0xf8},
+    {0x29, 0x35, 0x39, 0x3b, 0x59, 0x89, 0x99, 0x9c, 0xaa, 0xb1, 0xb3, 0xb8, 0xbd, 0xbf, 0xe9, 0xf0, 0xf9},
+    {0x2a, 0x36, 0x38, 0x3a, 0x5a, 0x8a, 0x9a, 0x9f, 0xa9, 0xb0, 0xb2, 0xbb, 0xbc, 0xbe, 0xea, 0xf3, 0xfa},
+    {0x2b, 0x37, 0x39, 0x3b, 0x5b, 0x8b, 0x9b, 0x9e, 0xa8, 0xb1, 0xb3, 0xba, 0xbd, 0xbf, 0xeb, 0xf2, 0xfb},
+    {0x2c, 0x30, 0x3c, 0x3e, 0x5c, 0x8c, 0x99, 0x9c, 0xaf, 0xb4, 0xb6, 0xb8, 0xba, 0xbd, 0xec, 0xf5, 0xfc},
+    {0x2d, 0x31, 0x3d, 0x3f, 0x5d, 0x8d, 0x98, 0x9d, 0xae, 0xb5, 0xb7, 0xb9, 0xbb, 0xbc, 0xed, 0xf4, 0xfd},
+    {0x2e, 0x32, 0x3c, 0x3e, 0x5e, 0x8e, 0x9b, 0x9e, 0xad, 0xb4, 0xb6, 0xb8, 0xba, 0xbf, 0xee, 0xf7, 0xfe},
+    {0x2f, 0x33, 0x3d, 0x3f, 0x5f, 0x8f, 0x9a, 0x9f, 0xac, 0xb5, 0xb7, 0xb9, 0xbb, 0xbe, 0xef, 0xf6, 0xff},
+    {0x20, 0x40, 0x42, 0x4c, 0x50, 0x80, 0x89, 0x90, 0xc1, 0xc4, 0xc6, 0xc8, 0xca, 0xd3, 0xe0, 0xe5, 0xf0},
+    {0x21, 0x41, 0x43, 0x4d, 0x51, 0x81, 0x88, 0x91, 0xc0, 0xc5, 0xc7, 0xc9, 0xcb, 0xd2, 0xe1, 0xe4, 0xf1},
+    {0x22, 0x40, 0x42, 0x4e, 0x52, 0x82, 0x8b, 0x92, 0xc3, 0xc4, 0xc6, 0xc8, 0xca, 0xd1, 0xe2, 0xe7, 0xf2},
+    {0x23, 0x41, 0x43, 0x4f, 0x53, 0x83, 0x8a, 0x93, 0xc2, 0xc5, 0xc7, 0xc9, 0xcb, 0xd0, 0xe3, 0xe6, 0xf3},
+    {0x24, 0x44, 0x46, 0x48, 0x54, 0x84, 0x8d, 0x94, 0xc0, 0xc2, 0xc5, 0xcc, 0xce, 0xd7, 0xe1, 0xe4, 0xf4},
+    {0x25, 0x45, 0x47, 0x49, 0x55, 0x85, 0x8c, 0x95, 0xc1, 0xc3, 0xc4, 0xcd, 0xcf, 0xd6, 0xe0, 0xe5, 0xf5},
+    {0x26, 0x44, 0x46, 0x4a, 0x56, 0x86, 0x8f, 0x96, 0xc0, 0xc2, 0xc7, 0xcc, 0xce, 0xd5, 0xe3, 0xe6, 0xf6},
+    {0x27, 0x45, 0x47, 0x4b, 0x57, 0x87, 0x8e, 0x97, 0xc1, 0xc3, 0xc6, 0xcd, 0xcf, 0xd4, 0xe2, 0xe7, 0xf7},
+    {0x28, 0x44, 0x48, 0x4a, 0x58, 0x81, 0x88, 0x98, 0xc0, 0xc2, 0xc9, 0xcc, 0xce, 0xdb, 0xe8, 0xed, 0xf8},
+    {0x29, 0x45, 0x49, 0x4b, 0x59, 0x80, 0x89, 0x99, 0xc1, 0xc3, 0xc8, 0xcd, 0xcf, 0xda, 0xe9, 0xec, 0xf9},
+    {0x2a, 0x46, 0x48, 0x4a, 0x5a, 0x83, 0x8a, 0x9a, 0xc0, 0xc2, 0xcb, 0xcc, 0xce, 0xd9, 0xea, 0xef, 0xfa},
+    {0x2b, 0x47, 0x49, 0x4b, 0x5b, 0x82, 0x8b, 0x9b, 0xc1, 0xc3, 0xca, 0xcd, 0xcf, 0xd8, 0xeb, 0xee, 0xfb},
+    {0x2c, 0x40, 0x4c, 0x4e, 0x5c, 0x85, 0x8c, 0x9c, 0xc4, 0xc6, 0xc8, 0xca, 0xcd, 0xdf, 0xe9, 0xec, 0xfc},
+    {0x2d, 0x41, 0x4d, 0x4f, 0x5d, 0x84, 0x8d, 0x9d, 0xc5, 0xc7, 0xc9, 0xcb, 0xcc, 0xde, 0xe8, 0xed, 0xfd},
+    {0x2e, 0x42, 0x4c, 0x4e, 0x5e, 0x87, 0x8e, 0x9e, 0xc4, 0xc6, 0xc8, 0xca, 0xcf, 0xdd, 0xeb, 0xee, 0xfe},
+    {0x2f, 0x43, 0x4d, 0x4f, 0x5f, 0x86, 0x8f, 0x9f, 0xc5, 0xc7, 0xc9, 0xcb, 0xce, 0xdc, 0xea, 0xef, 0xff},
+    {0x30, 0x40, 0x50, 0x52, 0x5c, 0x80, 0x90, 0x99, 0xc3, 0xd1, 0xd4, 0xd6, 0xd8, 0xda, 0xe0, 0xf0, 0xf5},
+    {0x31, 0x41, 0x51, 0x53, 0x5d, 0x81, 0x91, 0x98, 0xc2, 0xd0, 0xd5, 0xd7, 0xd9, 0xdb, 0xe1, 0xf1, 0xf4},
+    {0x32, 0x42, 0x50, 0x52, 0x5e, 0x82, 0x92, 0x9b, 0xc1, 0xd3, 0xd4, 0xd6, 0xd8, 0xda, 0xe2, 0xf2, 0xf7},
+    {0x33, 0x43, 0x51, 0x53, 0x5f, 0x83, 0x93, 0x9a, 0xc0, 0xd2, 0xd5, 0xd7, 0xd9, 0xdb, 0xe3, 0xf3, 0xf6},
+    {0x34, 0x44, 0x54, 0x56, 0x58, 0x84, 0x94, 0x9d, 0xc7, 0xd0, 0xd2, 0xd5, 0xdc, 0xde, 0xe4, 0xf1, 0xf4},
+    {0x35, 0x45, 0x55, 0x57, 0x59, 0x85, 0x95, 0x9c, 0xc6, 0xd1, 0xd3, 0xd4, 0xdd, 0xdf, 0xe5, 0xf0, 0xf5},
+    {0x36, 0x46, 0x54, 0x56, 0x5a, 0x86, 0x96, 0x9f, 0xc5, 0xd0, 0xd2, 0xd7, 0xdc, 0xde, 0xe6, 0xf3, 0xf6},
+    {0x37, 0x47, 0x55, 0x57, 0x5b, 0x87, 0x97, 0x9e, 0xc4, 0xd1, 0xd3, 0xd6, 0xdd, 0xdf, 0xe7, 0xf2, 0xf7},
+    {0x38, 0x48, 0x54, 0x58, 0x5a, 0x88, 0x91, 0x98, 0xcb, 0xd0, 0xd2, 0xd9, 0xdc, 0xde, 0xe8, 0xf8, 0xfd},
+    {0x39, 0x49, 0x55, 0x59, 0x5b, 0x89, 0x90, 0x99, 0xca, 0xd1, 0xd3, 0xd8, 0xdd, 0xdf, 0xe9, 0xf9, 0xfc},
+    {0x3a, 0x4a, 0x56, 0x58, 0x5a, 0x8a, 0x93, 0x9a, 0xc9, 0xd0, 0xd2, 0xdb, 0xdc, 0xde, 0xea, 0xfa, 0xff},
+    {0x3b, 0x4b, 0x57, 0x59, 0x5b, 0x8b, 0x92, 0x9b, 0xc8, 0xd1, 0xd3, 0xda, 0xdd, 0xdf, 0xeb, 0xfb, 0xfe},
+    {0x3c, 0x4c, 0x50, 0x5c, 0x5e, 0x8c, 0x95, 0x9c, 0xcf, 0xd4, 0xd6, 0xd8, 0xda, 0xdd, 0xec, 0xf9, 0xfc},
+    {0x3d, 0x4d, 0x51, 0x5d, 0x5f, 0x8d, 0x94, 0x9d, 0xce, 0xd5, 0xd7, 0xd9, 0xdb, 0xdc, 0xed, 0xf8, 0xfd},
+    {0x3e, 0x4e, 0x52, 0x5c, 0x5e, 0x8e, 0x97, 0x9e, 0xcd, 0xd4, 0xd6, 0xd8, 0xda, 0xdf, 0xee, 0xfb, 0xfe},
+    {0x3f, 0x4f, 0x53, 0x5d, 0x5f, 0x8f, 0x96, 0x9f, 0xcc, 0xd5, 0xd7, 0xd9, 0xdb, 0xde, 0xef, 0xfa, 0xff},
+    {0x00, 0x60, 0x62, 0x6c, 0x70, 0xa0, 0xa9, 0xb0, 0xc0, 0xc5, 0xd0, 0xe1, 0xe4, 0xe6, 0xe8, 0xea, 0xf3},
+    {0x01, 0x61, 0x63, 0x6d, 0x71, 0xa1, 0xa8, 0xb1, 0xc1, 0xc4, 0xd1, 0xe0, 0xe5, 0xe7, 0xe9, 0xeb, 0xf2},
+    {0x02, 0x60, 0x62, 0x6e, 0x72, 0xa2, 0xab, 0xb2, 0xc2, 0xc7, 0xd2, 0xe3, 0xe4, 0xe6, 0xe8, 0xea, 0xf1},
+    {0x03, 0x61, 0x63, 0x6f, 0x73, 0xa3, 0xaa, 0xb3, 0xc3, 0xc6, 0xd3, 0xe2, 0xe5, 0xe7, 0xe9, 0xeb, 0xf0},
+    {0x04, 0x64, 0x66, 0x68, 0x74, 0xa4, 0xad, 0xb4, 0xc1, 0xc4, 0xd4, 0xe0, 0xe2, 0xe5, 0xec, 0xee, 0xf7},
+    {0x05, 0x65, 0x67, 0x69, 0x75, 0xa5, 0xac, 0xb5, 0xc0, 0xc5, 0xd5, 0xe1, 0xe3, 0xe4, 0xed, 0xef, 0xf6},
+    {0x06, 0x64, 0x66, 0x6a, 0x76, 0xa6, 0xaf, 0xb6, 0xc3, 0xc6, 0xd6, 0xe0, 0xe2, 0xe7, 0xec, 0xee, 0xf5},
+    {0x07, 0x65, 0x67, 0x6b, 0x77, 0xa7, 0xae, 0xb7, 0xc2, 0xc7, 0xd7, 0xe1, 0xe3, 0xe6, 0xed, 0xef, 0xf4},
+    {0x08, 0x64, 0x68, 0x6a, 0x78, 0xa1, 0xa8, 0xb8, 0xc8, 0xcd, 0xd8, 0xe0, 0xe2, 0xe9, 0xec, 0xee, 0xfb},
+    {0x09, 0x65, 0x69, 0x6b, 0x79, 0xa0, 0xa9, 0xb9, 0xc9, 0xcc, 0xd9, 0xe1, 0xe3, 0xe8, 0xed, 0xef, 0xfa},
+    {0x0a, 0x66, 0x68, 0x6a, 0x7a, 0xa3, 0xaa, 0xba, 0xca, 0xcf, 0xda, 0xe0, 0xe2, 0xeb, 0xec, 0xee, 0xf9},
+    {0x0b, 0x67, 0x69, 0x6b, 0x7b, 0xa2, 0xab, 0xbb, 0xcb, 0xce, 0xdb, 0xe1, 0xe3, 0xea, 0xed, 0xef, 0xf8},
+    {0x0c, 0x60, 0x6c, 0x6e, 0x7c, 0xa5, 0xac, 0xbc, 0xc9, 0xcc, 0xdc, 0xe4, 0xe6, 0xe8, 0xea, 0xed, 0xff},
+    {0x0d, 0x61, 0x6d, 0x6f, 0x7d, 0xa4, 0xad, 0xbd, 0xc8, 0xcd, 0xdd, 0xe5, 0xe7, 0xe9, 0xeb, 0xec, 0xfe},
+    {0x0e, 0x62, 0x6c, 0x6e, 0x7e, 0xa7, 0xae, 0xbe, 0xcb, 0xce, 0xde, 0xe4, 0xe6, 0xe8, 0xea, 0xef, 0xfd},
+    {0x0f, 0x63, 0x6d, 0x6f, 0x7f, 0xa6, 0xaf, 0xbf, 0xca, 0xcf, 0xdf, 0xe5, 0xe7, 0xe9, 0xeb, 0xee, 0xfc},
+    {0x10, 0x60, 0x70, 0x72, 0x7c, 0xa0, 0xb0, 0xb9, 0xc0, 0xd0, 0xd5, 0xe3, 0xf1, 0xf4, 0xf6, 0xf8, 0xfa},
+    {0x11, 0x61, 0x71, 0x73, 0x7d, 0xa1, 0xb1, 0xb8, 0xc1, 0xd1, 0xd4, 0xe2, 0xf0, 0xf5, 0xf7, 0xf9, 0xfb},
+    {0x12, 0x62, 0x70, 0x72, 0x7e, 0xa2, 0xb2, 0xbb, 0xc2, 0xd2, 0xd7, 0xe1, 0xf3, 0xf4, 0xf6, 0xf8, 0xfa},
+    {0x13, 0x63, 0x71, 0x73, 0x7f, 0xa3, 0xb3, 0xba, 0xc3, 0xd3, 0xd6, 0xe0, 0xf2, 0xf5, 0xf7, 0xf9, 0xfb},
+    {0x14, 0x64, 0x74, 0x76, 0x78, 0xa4, 0xb4, 0xbd, 0xc4, 0xd1, 0xd4, 0xe7, 0xf0, 0xf2, 0xf5, 0xfc, 0xfe},
+    {0x15, 0x65, 0x75, 0x77, 0x79, 0xa5, 0xb5, 0xbc, 0xc5, 0xd0, 0xd5, 0xe6, 0xf1, 0xf3, 0xf4, 0xfd, 0xff},
+    {0x16, 0x66, 0x74, 0x76, 0x7a, 0xa6, 0xb6, 0xbf, 0xc6, 0xd3, 0xd6, 0xe5, 0xf0, 0xf2, 0xf7, 0xfc, 0xfe},
+    {0x17, 0x67, 0x75, 0x77, 0x7b, 0xa7, 0xb7, 0xbe, 0xc7, 0xd2, 0xd7, 0xe4, 0xf1, 0xf3, 0xf6, 0xfd, 0xff},
+    {0x18, 0x68, 0x74, 0x78, 0x7a, 0xa8, 0xb1, 0xb8, 0xc8, 0xd8, 0xdd, 0xeb, 0xf0, 0xf2, 0xf9, 0xfc, 0xfe},
+    {0x19, 0x69, 0x75, 0x79, 0x7b, 0xa9, 0xb0, 0xb9, 0xc9, 0xd9, 0xdc, 0xea, 0xf1, 0xf3, 0xf8, 0xfd, 0xff},
+    {0x1a, 0x6a, 0x76, 0x78, 0x7a, 0xaa, 0xb3, 0xba, 0xca, 0xda, 0xdf, 0xe9, 0xf0, 0xf2, 0xfb, 0xfc, 0xfe},
+    {0x1b, 0x6b, 0x77, 0x79, 0x7b, 0xab, 0xb2, 0xbb, 0xcb, 0xdb, 0xde, 0xe8, 0xf1, 0xf3, 0xfa, 0xfd, 0xff},
+    {0x1c, 0x6c, 0x70, 0x7c, 0x7e, 0xac, 0xb5, 0xbc, 0xcc, 0xd9, 0xdc, 0xef, 0xf4, 0xf6, 0xf8, 0xfa, 0xfd},
+    {0x1d, 0x6d, 0x71, 0x7d, 0x7f, 0xad, 0xb4, 0xbd, 0xcd, 0xd8, 0xdd, 0xee, 0xf5, 0xf7, 0xf9, 0xfb, 0xfc},
+    {0x1e, 0x6e, 0x72, 0x7c, 0x7e, 0xae, 0xb7, 0xbe, 0xce, 0xdb, 0xde, 0xed, 0xf4, 0xf6, 0xf8, 0xfa, 0xff},
+    {0x1f, 0x6f, 0x73, 0x7d, 0x7f, 0xaf, 0xb6, 0xbf, 0xcf, 0xda, 0xdf, 0xec, 0xf5, 0xf7, 0xf9, 0xfb, 0xfe}};
+
diff --git a/src/fec/src/fec_hamming1511.c b/src/fec/src/fec_hamming1511.c
new file mode 100644
index 0000000..5c41072
--- /dev/null
+++ b/src/fec/src/fec_hamming1511.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_HAMMING1511 0
+
+//
+// Hamming(15,11) code
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8 and only including
+// data bits 1:11)
+//
+//  parity bit  P1  x   x   .   x   x   .   x   .   x   .   x   = .110 1101 0101
+//  coverage    P2  x   .   x   x   .   x   x   .   .   x   x   = .101 1011 0011 
+//              P4  .   x   x   x   .   .   .   x   x   x   x   = .011 1000 1111
+//              P8  .   .   .   .   x   x   x   x   x   x   x   = .000 0111 1111
+
+#define HAMMING_M1   0x06d5     // .110 1101 0101
+#define HAMMING_M2   0x05b3     // .101 1011 0011
+#define HAMMING_M4   0x038f     // .011 1000 1111
+#define HAMMING_M8   0x007f     // .000 0111 1111
+
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see first figure, above).
+#define HAMMING_S1   0x5555  // .101 0101 0101 0101
+#define HAMMING_S2   0x3333  // .011 0011 0011 0011
+#define HAMMING_S4   0x0f0f  // .000 1111 0000 1111
+#define HAMMING_S8   0x00ff  // .000 0000 1111 1111
+
+unsigned int fec_hamming1511_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<11)) {
+        fprintf(stderr,"error, fec_hamming_encode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute parity bits
+    unsigned int p1 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M1);
+    unsigned int p2 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M2);
+    unsigned int p4 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M4);
+    unsigned int p8 = liquid_bdotprod_uint16(_sym_dec, HAMMING_M8);
+
+#if DEBUG_FEC_HAMMING1511
+    printf("parity bits (p1,p2,p4,p8) : (%1u,%1u,%1u,%1u)\n", p1, p2, p4, p8);
+#endif
+
+    // encode symbol by inserting parity bits with data bits to
+    // make a 15-bit symbol
+    unsigned int sym_enc = ((_sym_dec & 0x007f) << 0) |
+                           ((_sym_dec & 0x0380) << 1) |
+                           ((_sym_dec & 0x0400) << 2) |
+                           ( p1 << 14 ) |
+                           ( p2 << 13 ) |
+                           ( p4 << 11 ) |
+                           ( p8 << 7  );
+
+    return sym_enc;
+}
+
+unsigned int fec_hamming1511_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<15)) {
+        fprintf(stderr,"error, fec_hamming_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute syndrome bits
+    unsigned int s1 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S1);
+    unsigned int s2 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S2);
+    unsigned int s4 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S4);
+    unsigned int s8 = liquid_bdotprod_uint16(_sym_enc, HAMMING_S8);
+
+    // index
+    unsigned int z = (s8<<3) | (s4<<2) | (s2<<1) | s1;
+
+#if DEBUG_FEC_HAMMING1511
+    printf("syndrome bits (s1,s2,s4,s8) : (%1u,%1u,%1u,%1u)\n", s1, s2, s4, s8);
+    printf("syndrome z : %u\n", z);
+#endif
+
+    // flip bit at this position
+    if (z) {
+        if (z > 15) {
+            // if this happens there are likely too many errors
+            // to correct; just pass without trying to do anything
+            fprintf(stderr,"warning, fec_hamming1511_decode_symbol(), syndrome index exceeds maximum\n");
+        } else {
+            //printf("error detected!\n");
+            _sym_enc ^= 1 << (15-z);
+        }
+    }
+
+    // strip data bits (x) from encoded symbol with parity bits (.)
+    //      symbol:  [..x. xxx. xxxx]
+    //                0000 0000 1111     >  0x000f
+    //                0000 1110 0000     >  0x00e0
+    //                0010 0000 0000     >  0x0200
+    //
+    //      symbol: [ -..x .xxx .xxx xxxx]
+    //                -000 0000 0xxx xxxx   > 0x007f
+    //                -000 0111 0000 0000   > 0x0700
+    //                -001 0000 0000 0000   > 0x1000
+    unsigned int sym_dec = ((_sym_enc & 0x007f)     )   |
+                           ((_sym_enc & 0x0700) >> 1)   |
+                           ((_sym_enc & 0x1000) >> 2);
+
+
+    return sym_dec;
+}
+
diff --git a/src/fec/src/fec_hamming3126.c b/src/fec/src/fec_hamming3126.c
new file mode 100644
index 0000000..b61cd60
--- /dev/null
+++ b/src/fec/src/fec_hamming3126.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_HAMMING3126 0
+
+//
+// (31,26) Hamming code
+//
+// parity bit coverage mask for encoder (collapsed version of figure
+// above, stripping out parity bits P1, P2, P4, P8, P16 and only including
+// data bits 1:26)
+//
+//  bit position    3   5   6   7   8   9   10  11  12  13  14  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30
+//                          *               *               *               *               *               *
+//  parity bit  P1  x   x   .   x   x   .   x   .   x   .   x   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   = ..11 0110 1010 1101 0101 0101 0101
+//  coverage    P2  x   .   x   x   .   x   x   .   .   x   x   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   = ..10 1101 1001 1011 0011 0011 0011
+//              P4  .   x   x   x   .   .   .   x   x   x   x   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   = ..01 1100 0111 1000 1111 0000 1111
+//              P8  .   .   .   .   x   x   x   x   x   x   x   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   = ..00 0011 1111 1000 0000 1111 1111
+//              P16 .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   = ..00 0000 0000 0111 1111 1111 1111
+
+#define HAMMING_M1  0x036AD555  //  ..11 0110 1010 1101 0101 0101 0101
+#define HAMMING_M2  0x02D9B333  //  ..10 1101 1001 1011 0011 0011 0011
+#define HAMMING_M4  0x01C78F0F  //  ..01 1100 0111 1000 1111 0000 1111
+#define HAMMING_M8  0x003F80FF  //  ..00 0011 1111 1000 0000 1111 1111
+#define HAMMING_M16 0x00007FFF  //  ..00 0000 0000 0111 1111 1111 1111
+
+//  bit position    1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31
+//                              *               *               *               *               *               *               *
+//  parity bit  P1  x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   .   x   = .101 0101 0101 0101 0101 0101 0101 0101
+//  coverage    P2  .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   .   .   x   x   = .011 0011 0011 0011 0011 0011 0011 0011
+//              P4  .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   .   .   .   .   x   x   x   x   = .000 1111 0000 1111 0000 1111 0000 1111
+//              P8  .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   = .000 0000 1111 1111 0000 0000 1111 1111
+//              P16 .   .   .   .   .   .   .   .   .   .   .   .   .   .   .   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   x   = .000 0000 0000 0000 1111 1111 1111 1111
+//
+// parity bit coverage mask for decoder; used to compute syndromes
+// for decoding a received message (see figure, above).
+#define HAMMING_S1  0x55555555  //  .101 0101 0101 0101 0101 0101 0101 0101
+#define HAMMING_S2  0x33333333  //  .011 0011 0011 0011 0011 0011 0011 0011
+#define HAMMING_S4  0x0f0f0f0f  //  .000 1111 0000 1111 0000 1111 0000 1111
+#define HAMMING_S8  0x00ff00ff  //  .000 0000 1111 1111 0000 0000 1111 1111
+#define HAMMING_S16 0x0000ffff  //  .000 0000 0000 0000 1111 1111 1111 1111
+
+
+
+unsigned int fec_hamming3126_encode_symbol(unsigned int _sym_dec)
+{
+    // validate input
+    if (_sym_dec >= (1<<26)) {
+        fprintf(stderr,"error, fec_hamming_encode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute parity bits
+    unsigned int p1  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M1);
+    unsigned int p2  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M2);
+    unsigned int p4  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M4);
+    unsigned int p8  = liquid_bdotprod_uint32(_sym_dec, HAMMING_M8);
+    unsigned int p16 = liquid_bdotprod_uint32(_sym_dec, HAMMING_M16);
+
+#if DEBUG_FEC_HAMMING
+    printf("parity bits (p1,p2,p4,p8,p16) : (%1u,%1u,%1u,%1u,%1u)\n", p1, p2, p4, p8, p16);
+#endif
+
+    // encode symbol by inserting parity bits with data bits to
+    // make a 31-bit symbol
+    unsigned int sym_enc = ((_sym_dec & 0x00007fff) << 0) | //  ..00 0000 0000 0111 1111 1111 1111
+                           ((_sym_dec & 0x003F8000) << 1) | //  ..00 0011 1111 1000 0000 0000 0000
+                           ((_sym_dec & 0x01C00000) << 2) | //  ..01 1100 0000 0000 0000 0000 0000
+                           ((_sym_dec & 0x02000000) << 3) | //  ..10 0000 0000 0000 0000 0000 0000
+                           ( p1  << 30 ) |  // 30 = 31 - 1  (position of P1)
+                           ( p2  << 29 ) |  // 29 = 31 - 2  (position of P2)
+                           ( p4  << 27 ) |  // 27 = 31 - 4  (position of P4)
+                           ( p8  << 23 ) |  // 23 = 31 - 8  (position of P8)
+                           ( p16 << 15 );   // 15 = 31 - 16 (position of P16)
+
+    return sym_enc;
+}
+
+unsigned int fec_hamming3126_decode_symbol(unsigned int _sym_enc)
+{
+    // validate input
+    if (_sym_enc >= (1<<31)) {
+        fprintf(stderr,"error, fec_hamming_decode(), input symbol too large\n");
+        exit(1);
+    }
+
+    // compute syndrome bits
+    unsigned int s1  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S1);
+    unsigned int s2  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S2);
+    unsigned int s4  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S4);
+    unsigned int s8  = liquid_bdotprod_uint32(_sym_enc, HAMMING_S8);
+    unsigned int s16 = liquid_bdotprod_uint32(_sym_enc, HAMMING_S16);
+
+    // index
+    unsigned int z = (s16<<4) | (s8<<3) | (s4<<2) | (s2<<1) | s1;
+
+#if DEBUG_FEC_HAMMING
+    printf("syndrome bits (s1,s2,s4,s8,16) : (%1u,%1u,%1u,%1u,%1u)\n", s1, s2, s4, s8, s16);
+    printf("syndrome z : %u\n", z);
+#endif
+
+    // flip bit at this position
+    if (z) {
+        if (z > 31) {
+            // if this happens there are likely too many errors
+            // to correct; just pass without trying to do anything
+            fprintf(stderr,"warning, fec_hamming3126_decode_symbol(), syndrome index exceeds maximum\n");
+        } else {
+            //printf("error detected!\n");
+            _sym_enc ^= 1 << (31-z);
+        }
+    }
+
+    // strip data bits from encoded symbol with parity bits
+    unsigned int sym_dec = ((_sym_enc & 0x00007fff)     )   |   //  .000 0000 0000 0000 0111 1111 1111 1111
+                           ((_sym_enc & 0x007f0000) >> 1)   |   //  .000 0000 0111 1111 0000 0000 0000 0000
+                           ((_sym_enc & 0x07000000) >> 2)   |   //  .000 0111 0000 0000 0000 0000 0000 0000
+                           ((_sym_enc & 0x10000000) >> 3);      //  .001 0000 0000 0000 0000 0000 0000 0000
+    return sym_dec;
+}
+
diff --git a/src/fec/src/fec_hamming74.c b/src/fec/src/fec_hamming74.c
new file mode 100644
index 0000000..72085fc
--- /dev/null
+++ b/src/fec/src/fec_hamming74.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 1/2-rate (7,4) Hamming code
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// encoder look-up table
+unsigned char hamming74_enc_gentab[16] = {
+    0x00, 0x69, 0x2a, 0x43, 0x4c, 0x25, 0x66, 0x0f,
+    0x70, 0x19, 0x5a, 0x33, 0x3c, 0x55, 0x16, 0x7f};
+
+// decoder look-up table
+unsigned char hamming74_dec_gentab[128] = {
+    0x00, 0x00, 0x00, 0x03, 0x00, 0x05, 0x0e, 0x07,
+    0x00, 0x09, 0x02, 0x07, 0x04, 0x07, 0x07, 0x07,
+    0x00, 0x09, 0x0e, 0x0b, 0x0e, 0x0d, 0x0e, 0x0e,
+    0x09, 0x09, 0x0a, 0x09, 0x0c, 0x09, 0x0e, 0x07,
+    0x00, 0x05, 0x02, 0x0b, 0x05, 0x05, 0x06, 0x05,
+    0x02, 0x01, 0x02, 0x02, 0x0c, 0x05, 0x02, 0x07,
+    0x08, 0x0b, 0x0b, 0x0b, 0x0c, 0x05, 0x0e, 0x0b,
+    0x0c, 0x09, 0x02, 0x0b, 0x0c, 0x0c, 0x0c, 0x0f,
+    0x00, 0x03, 0x03, 0x03, 0x04, 0x0d, 0x06, 0x03,
+    0x04, 0x01, 0x0a, 0x03, 0x04, 0x04, 0x04, 0x07,
+    0x08, 0x0d, 0x0a, 0x03, 0x0d, 0x0d, 0x0e, 0x0d,
+    0x0a, 0x09, 0x0a, 0x0a, 0x04, 0x0d, 0x0a, 0x0f,
+    0x08, 0x01, 0x06, 0x03, 0x06, 0x05, 0x06, 0x06,
+    0x01, 0x01, 0x02, 0x01, 0x04, 0x01, 0x06, 0x0f,
+    0x08, 0x08, 0x08, 0x0b, 0x08, 0x0d, 0x06, 0x0f,
+    0x08, 0x01, 0x0a, 0x0f, 0x0c, 0x0f, 0x0f, 0x0f};
+
+// create Hamming(7,4) codec object
+fec fec_hamming74_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_HAMMING74;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_hamming74_encode;
+    q->decode_func      = &fec_hamming74_decode;
+    q->decode_soft_func = &fec_hamming74_decode_soft;
+
+    return q;
+}
+
+// destroy Hamming(7,4) object
+void fec_hamming74_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using Hamming(7,4) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: _dec_msg_len x 1]
+//  _msg_enc        :   encoded message [size: ...]
+void fec_hamming74_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_dec,
+                          unsigned char *_msg_enc)
+{
+    unsigned int i;         // input byte counter
+    unsigned int k=0;       // array bit index
+
+    // compute encoded message length
+    unsigned int enc_msg_len = fec_block_get_enc_msg_len(_dec_msg_len,4,7);
+
+    unsigned char s0, s1;   // decoded symbols
+    unsigned char m0, m1;   // encoded symbols
+
+    for (i=0; i<_dec_msg_len; i++) {
+        // strip two 4-bit symbols from input byte
+        s0 = (_msg_dec[i] >> 4) & 0x0f;
+        s1 = (_msg_dec[i] >> 0) & 0x0f;
+
+        // encode two 7-bit symbols
+        m0 = hamming74_enc_gentab[s0];
+        m1 = hamming74_enc_gentab[s1];
+
+        // pack encoded symbols into output array
+        liquid_pack_array(_msg_enc, enc_msg_len, k, 7, m0);
+        k += 7;
+        liquid_pack_array(_msg_enc, enc_msg_len, k, 7, m1);
+        k += 7;
+
+        //printf("  %3u : 0x%.2x > 0x%.2x,  0x%.2x > 0x%.2x (k=%u)\n", i, s0, m0, s1, m1, k);
+    }
+}
+
+// decode block of data using Hamming(7,4) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: ...]
+//  _msg_dec        :   decoded message [size: _dec_msg_len x 1]
+//
+//unsigned int
+void fec_hamming74_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    unsigned int i;
+    unsigned int k=0;       // array bit index
+
+    // compute encoded message length
+    unsigned int enc_msg_len = fec_block_get_enc_msg_len(_dec_msg_len,4,7);
+
+    unsigned char r0, r1;   // received 7-bit symbols
+    unsigned char s0, s1;   // decoded 4-bit symbols
+
+    //unsigned char num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        // strip two 7-bit symbols from 
+        liquid_unpack_array(_msg_enc, enc_msg_len, k, 7, &r0);
+        k += 7;
+        liquid_unpack_array(_msg_enc, enc_msg_len, k, 7, &r1);
+        k += 7;
+
+        s0 = hamming74_dec_gentab[r0];
+        s1 = hamming74_dec_gentab[r1];
+
+        _msg_dec[i] = (s0 << 4) | s1;
+
+        //printf("  %3u : 0x%.2x > 0x%.2x,  0x%.2x > 0x%.2x (k=%u)\n", i, r0, s0, r1, s1, k);
+    }
+    //return num_errors;
+}
+
+// decode block of data using Hamming(7,4) soft decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 8*_enc_msg_len x 1]
+//  _msg_dec        :   decoded message [size: _dec_msg_len x 1]
+//
+//unsigned int
+void fec_hamming74_decode_soft(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char *_msg_enc,
+                               unsigned char *_msg_dec)
+{
+    unsigned int i;
+    unsigned int k=0;       // array bit index
+
+    // compute encoded message length
+    unsigned int enc_msg_len = fec_block_get_enc_msg_len(_dec_msg_len,4,7);
+
+    // decoded 4-bit symbols
+    unsigned char s0;
+    unsigned char s1;
+
+    //unsigned char num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        s0 = fecsoft_hamming74_decode(&_msg_enc[k  ]);
+        s1 = fecsoft_hamming74_decode(&_msg_enc[k+7]);
+        k += 14;
+
+        // pack two 4-bit symbols into one 8-bit byte
+        _msg_dec[i] = (s0 << 4) | s1;
+
+        //printf("  %3u : 0x%.2x > 0x%.2x,  0x%.2x > 0x%.2x (k=%u)\n", i, r0, s0, r1, s1, k);
+    }
+    assert(k == 8*enc_msg_len);
+    //return num_errors;
+}
+
+// 
+// internal methods
+//
+
+// soft decoding of one symbol
+unsigned char fecsoft_hamming74_decode(unsigned char * _soft_bits)
+{
+    // find symbol with minimum distance from all 2^4 possible
+    unsigned int d;             // distance metric
+    unsigned int dmin = 0;      // minimum distance
+    unsigned char s_hat = 0;    // estimated transmitted symbol
+    unsigned char c;            // encoded symbol
+
+    unsigned char s;
+    for (s=0; s<16; s++) {
+        // encode symbol
+        c = hamming74_enc_gentab[s];
+
+        // compute distance metric
+        d = 0;
+        d += (c & 0x40) ? 255 - _soft_bits[0] : _soft_bits[0];
+        d += (c & 0x20) ? 255 - _soft_bits[1] : _soft_bits[1];
+        d += (c & 0x10) ? 255 - _soft_bits[2] : _soft_bits[2];
+        d += (c & 0x08) ? 255 - _soft_bits[3] : _soft_bits[3];
+        d += (c & 0x04) ? 255 - _soft_bits[4] : _soft_bits[4];
+        d += (c & 0x02) ? 255 - _soft_bits[5] : _soft_bits[5];
+        d += (c & 0x01) ? 255 - _soft_bits[6] : _soft_bits[6];
+
+        if (d < dmin || s==0) {
+            s_hat = s;
+            dmin = d;
+        }
+    }
+    return s_hat;
+}
diff --git a/src/fec/src/fec_hamming84.c b/src/fec/src/fec_hamming84.c
new file mode 100644
index 0000000..4e707e9
--- /dev/null
+++ b/src/fec/src/fec_hamming84.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 1/2-rate (8,4) Hamming code
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// encoder look-up table
+unsigned char hamming84_enc_gentab[16] = {
+    0x00, 0xd2, 0x55, 0x87, 0x99, 0x4b, 0xcc, 0x1e,
+    0xe1, 0x33, 0xb4, 0x66, 0x78, 0xaa, 0x2d, 0xff};
+
+// decoder look-up table
+unsigned char hamming84_dec_gentab[256] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03,
+    0x00, 0x00, 0x05, 0x05, 0x0e, 0x0e, 0x07, 0x07,
+    0x00, 0x00, 0x09, 0x09, 0x02, 0x02, 0x07, 0x07,
+    0x04, 0x04, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+    0x00, 0x00, 0x09, 0x09, 0x0e, 0x0e, 0x0b, 0x0b,
+    0x0e, 0x0e, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e,
+    0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x09, 0x09,
+    0x0c, 0x0c, 0x09, 0x09, 0x0e, 0x0e, 0x07, 0x07,
+    0x00, 0x00, 0x05, 0x05, 0x02, 0x02, 0x0b, 0x0b,
+    0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x05, 0x05,
+    0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02,
+    0x0c, 0x0c, 0x05, 0x05, 0x02, 0x02, 0x07, 0x07,
+    0x08, 0x08, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+    0x0c, 0x0c, 0x05, 0x05, 0x0e, 0x0e, 0x0b, 0x0b,
+    0x0c, 0x0c, 0x09, 0x09, 0x02, 0x02, 0x0b, 0x0b,
+    0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0f, 0x0f,
+    0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+    0x04, 0x04, 0x0d, 0x0d, 0x06, 0x06, 0x03, 0x03,
+    0x04, 0x04, 0x01, 0x01, 0x0a, 0x0a, 0x03, 0x03,
+    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x07, 0x07,
+    0x08, 0x08, 0x0d, 0x0d, 0x0a, 0x0a, 0x03, 0x03,
+    0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0d, 0x0d,
+    0x0a, 0x0a, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a,
+    0x04, 0x04, 0x0d, 0x0d, 0x0a, 0x0a, 0x0f, 0x0f,
+    0x08, 0x08, 0x01, 0x01, 0x06, 0x06, 0x03, 0x03,
+    0x06, 0x06, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06,
+    0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01,
+    0x04, 0x04, 0x01, 0x01, 0x06, 0x06, 0x0f, 0x0f,
+    0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0b, 0x0b,
+    0x08, 0x08, 0x0d, 0x0d, 0x06, 0x06, 0x0f, 0x0f,
+    0x08, 0x08, 0x01, 0x01, 0x0a, 0x0a, 0x0f, 0x0f,
+    0x0c, 0x0c, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f};
+
+// create Hamming(8,4) codec object
+fec fec_hamming84_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_HAMMING84;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_hamming84_encode;
+    q->decode_func      = &fec_hamming84_decode;
+    q->decode_soft_func = &fec_hamming84_decode_soft;
+
+    return q;
+}
+
+// destroy Hamming(8,4) object
+void fec_hamming84_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using Hamming(8,4) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_hamming84_encode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_dec,
+                          unsigned char *_msg_enc)
+{
+    unsigned int i, j=0;
+    unsigned char s0, s1;
+    for (i=0; i<_dec_msg_len; i++) {
+        s0 = (_msg_dec[i] >> 4) & 0x0f;
+        s1 = (_msg_dec[i] >> 0) & 0x0f;
+        _msg_enc[j+0] = hamming84_enc_gentab[s0];
+        _msg_enc[j+1] = hamming84_enc_gentab[s1];
+        j+=2;
+    }
+}
+
+// decode block of data using Hamming(8,4) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_hamming84_decode(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char *_msg_enc,
+                          unsigned char *_msg_dec)
+{
+    unsigned int i;
+    unsigned char r0, r1;   // received 8-bit symbols
+    unsigned char s0, s1;   // decoded 4-bit symbols
+    //unsigned char num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        r0 = _msg_enc[2*i+0] & 0xff;
+        r1 = _msg_enc[2*i+1] & 0xff;
+
+        s0 = hamming84_dec_gentab[r0];
+        s1 = hamming84_dec_gentab[r1];
+
+        _msg_dec[i] = (s0 << 4) | s1;
+    }
+    //return num_errors;
+}
+
+// decode block of data using Hamming(8,4) soft decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 8*_enc_msg_len x 1]
+//  _msg_dec        :   decoded message [size: _dec_msg_len x 1]
+//
+//unsigned int
+void fec_hamming84_decode_soft(fec _q,
+                               unsigned int _dec_msg_len,
+                               unsigned char *_msg_enc,
+                               unsigned char *_msg_dec)
+{
+    unsigned int i;
+    unsigned int k=0;       // array bit index
+
+    // compute encoded message length
+    unsigned int enc_msg_len = fec_block_get_enc_msg_len(_dec_msg_len,4,8);
+
+    // decoded 4-bit symbols
+    unsigned char s0;
+    unsigned char s1;
+
+    //unsigned char num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        s0 = fecsoft_hamming84_decode(&_msg_enc[k  ]);
+        s1 = fecsoft_hamming84_decode(&_msg_enc[k+8]);
+        k += 16;
+
+        // pack two 4-bit symbols into one 8-bit byte
+        _msg_dec[i] = (s0 << 4) | s1;
+
+        //printf("  %3u : 0x%.2x > 0x%.2x,  0x%.2x > 0x%.2x (k=%u)\n", i, r0, s0, r1, s1, k);
+    }
+    assert(k == 8*enc_msg_len);
+    //return num_errors;
+}
+
+// 
+// internal methods
+//
+
+// soft decoding of one symbol
+unsigned char fecsoft_hamming84_decode(unsigned char * _soft_bits)
+{
+    // find symbol with minimum distance from all 2^4 possible
+    unsigned int d;             // distance metric
+    unsigned int dmin = 0;      // minimum distance
+    unsigned char s_hat = 0;    // estimated transmitted symbol
+    unsigned char c;            // encoded symbol
+
+    unsigned char s;
+    for (s=0; s<16; s++) {
+        // encode symbol
+        c = hamming84_enc_gentab[s];
+
+        // compute distance metric
+        d = 0;
+        d += (c & 0x80) ? 255 - _soft_bits[0] : _soft_bits[0];
+        d += (c & 0x40) ? 255 - _soft_bits[1] : _soft_bits[1];
+        d += (c & 0x20) ? 255 - _soft_bits[2] : _soft_bits[2];
+        d += (c & 0x10) ? 255 - _soft_bits[3] : _soft_bits[3];
+        d += (c & 0x08) ? 255 - _soft_bits[4] : _soft_bits[4];
+        d += (c & 0x04) ? 255 - _soft_bits[5] : _soft_bits[5];
+        d += (c & 0x02) ? 255 - _soft_bits[6] : _soft_bits[6];
+        d += (c & 0x01) ? 255 - _soft_bits[7] : _soft_bits[7];
+
+        if (d < dmin || s==0) {
+            s_hat = s;
+            dmin = d;
+        }
+    }
+    return s_hat;
+}
diff --git a/src/fec/src/fec_pass.c b/src/fec/src/fec_pass.c
new file mode 100644
index 0000000..a261fa5
--- /dev/null
+++ b/src/fec/src/fec_pass.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// FEC, none/pass
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+fec fec_pass_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = LIQUID_FEC_NONE;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_pass_encode;
+    q->decode_func      = &fec_pass_decode;
+    q->decode_soft_func = NULL;
+
+    return q;
+}
+
+void fec_pass_destroy(fec _q)
+{
+    free(_q);
+}
+
+void fec_pass_print(fec _q)
+{
+    printf("fec_pass [r: %3.2f]\n", _q->rate);
+}
+
+void fec_pass_encode(fec _q, unsigned int _dec_msg_len, unsigned char *_msg_dec, unsigned char *_msg_enc)
+{
+    memmove(_msg_enc, _msg_dec, _dec_msg_len);
+}
+
+void fec_pass_decode(fec _q, unsigned int _dec_msg_len, unsigned char *_msg_enc, unsigned char *_msg_dec)
+{
+    memmove(_msg_dec, _msg_enc, _dec_msg_len);
+}
+
diff --git a/src/fec/src/fec_rep3.c b/src/fec/src/fec_rep3.c
new file mode 100644
index 0000000..3e37466
--- /dev/null
+++ b/src/fec/src/fec_rep3.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// FEC, repeat code
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+// create rep3 codec object
+fec fec_rep3_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = LIQUID_FEC_REP3;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_rep3_encode;
+    q->decode_func      = &fec_rep3_decode;
+    q->decode_soft_func = &fec_rep3_decode_soft;
+
+    return q;
+}
+
+// destroy rep3 object
+void fec_rep3_destroy(fec _q)
+{
+    free(_q);
+}
+
+// print rep3 object
+void fec_rep3_print(fec _q)
+{
+    printf("fec_rep3 [r: %3.2f]\n", _q->rate);
+}
+
+// encode block of data using rep3 encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 3*_dec_msg_len]
+void fec_rep3_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char *_msg_dec,
+                     unsigned char *_msg_enc)
+{
+    unsigned int i;
+    for (i=0; i<3; i++) {
+        memcpy(&_msg_enc[i*_dec_msg_len], _msg_dec, _dec_msg_len);
+    }
+}
+
+// decode block of data using rep3 decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 3*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+void fec_rep3_decode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char *_msg_enc,
+                     unsigned char *_msg_dec)
+{
+    unsigned char s0, s1, s2;
+    unsigned int i, num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        s0 = _msg_enc[i];
+        s1 = _msg_enc[i +   _dec_msg_len];
+        s2 = _msg_enc[i + 2*_dec_msg_len];
+
+        //  s0  s1  s2  |   y   e
+        //  ------------+---------
+        //  0   0   0   |   0   0
+        //  0   0   1   |   0   1
+        //  0   1   0   |   0   1
+        //  0   1   1   |   1   1
+        //  1   0   0   |   0   1
+        //  1   0   1   |   1   1
+        //  1   1   0   |   1   1
+        //  1   1   1   |   1   0
+
+        _msg_dec[i] = (s0 & s1) | (s0 & s2) | (s1 & s2);
+    
+        //num_errors += (s0 ^ s1) | (s0 ^ s2) | (s1 ^ s2) ? 1 : 0;
+        num_errors += 0;
+    }
+    //return num_errors;
+}
+
+// decode block of data using rep3 decoder (soft metrics)
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 3*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+void fec_rep3_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec)
+{
+    unsigned char s0, s1, s2;
+    unsigned int i;
+    unsigned int j;
+    unsigned int s_hat;
+    //unsigned int num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+
+        // clear decoded message
+        _msg_dec[i] = 0x00;
+
+        for (j=0; j<8; j++) {
+            s0 = _msg_enc[8*i                    + j];
+            s1 = _msg_enc[8*(i +   _dec_msg_len) + j];
+            s2 = _msg_enc[8*(i + 2*_dec_msg_len) + j];
+
+            // average three symbols and make decision
+            s_hat = (s0 + s1 + s2)/3;
+
+            _msg_dec[i] |= (s_hat > LIQUID_SOFTBIT_ERASURE) ? (1 << (8-j-1)) : 0x00;
+            
+        }
+
+    }
+}
diff --git a/src/fec/src/fec_rep5.c b/src/fec/src/fec_rep5.c
new file mode 100644
index 0000000..9ac0ac8
--- /dev/null
+++ b/src/fec/src/fec_rep5.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// FEC, repeat code
+// 
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+// create rep5 codec object
+fec fec_rep5_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = LIQUID_FEC_REP5;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_rep5_encode;
+    q->decode_func      = &fec_rep5_decode;
+    q->decode_soft_func = &fec_rep5_decode_soft;
+
+    return q;
+}
+
+// destroy rep5 object
+void fec_rep5_destroy(fec _q)
+{
+    free(_q);
+}
+
+// print rep5 object
+void fec_rep5_print(fec _q)
+{
+    printf("fec_rep5 [r: %3.2f]\n", _q->rate);
+}
+
+// encode block of data using rep5 encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 5*_dec_msg_len]
+void fec_rep5_encode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char *_msg_dec,
+                     unsigned char *_msg_enc)
+{
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        memcpy(&_msg_enc[i*_dec_msg_len], _msg_dec, _dec_msg_len);
+    }
+}
+
+// decode block of data using rep5 decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 5*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+void fec_rep5_decode(fec _q,
+                     unsigned int _dec_msg_len,
+                     unsigned char *_msg_enc,
+                     unsigned char *_msg_dec)
+{
+    unsigned char s0, s1, s2, s3, s4;
+    unsigned int i, num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+        s0 = _msg_enc[i                 ];
+        s1 = _msg_enc[i +   _dec_msg_len];
+        s2 = _msg_enc[i + 2*_dec_msg_len];
+        s3 = _msg_enc[i + 3*_dec_msg_len];
+        s4 = _msg_enc[i + 4*_dec_msg_len];
+
+        // compute all triplet combinations
+        _msg_dec[i] =   (s0 & s1 & s2) |
+                        (s0 & s1 & s3) |
+                        (s0 & s1 & s4) |
+                        (s0 & s2 & s3) |
+                        (s0 & s2 & s4) |
+                        (s0 & s3 & s4) |
+                        (s1 & s2 & s3) |
+                        (s1 & s2 & s4) |
+                        (s1 & s3 & s4) |
+                        (s2 & s3 & s4);
+    
+        //num_errors += (s0 ^ s1) | (s0 ^ s2) | (s1 ^ s2) ? 1 : 0;
+        num_errors += 0;
+    }
+    //return num_errors;
+}
+
+// decode block of data using rep5 decoder (soft metrics)
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 5*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+void fec_rep5_decode_soft(fec _q,
+                          unsigned int _dec_msg_len,
+                          unsigned char * _msg_enc,
+                          unsigned char * _msg_dec)
+{
+    unsigned char s0, s1, s2, s3, s4;
+    unsigned int i;
+    unsigned int j;
+    unsigned int s_hat;
+    //unsigned int num_errors=0;
+    for (i=0; i<_dec_msg_len; i++) {
+
+        // clear decoded message
+        _msg_dec[i] = 0x00;
+
+        for (j=0; j<8; j++) {
+            s0 = _msg_enc[8*i                    + j];
+            s1 = _msg_enc[8*(i +   _dec_msg_len) + j];
+            s2 = _msg_enc[8*(i + 2*_dec_msg_len) + j];
+            s3 = _msg_enc[8*(i + 3*_dec_msg_len) + j];
+            s4 = _msg_enc[8*(i + 4*_dec_msg_len) + j];
+
+            // average three symbols and make decision
+            s_hat = (s0 + s1 + s2 + s3 + s4)/5;
+
+            _msg_dec[i] |= (s_hat > LIQUID_SOFTBIT_ERASURE) ? (1 << (8-j-1)) : 0x00;
+            
+        }
+
+    }
+}
diff --git a/src/fec/src/fec_rs.c b/src/fec/src/fec_rs.c
new file mode 100644
index 0000000..32a0fbd
--- /dev/null
+++ b/src/fec/src/fec_rs.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Reed-Solomon (macros)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define VERBOSE_FEC_RS    0
+
+#if LIBFEC_ENABLED
+#include "fec.h"
+
+fec fec_rs_create(fec_scheme _fs)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    q->scheme = _fs;
+    q->rate = fec_get_rate(q->scheme);
+
+    q->encode_func      = &fec_rs_encode;
+    q->decode_func      = &fec_rs_decode;
+    q->decode_soft_func = NULL;
+
+    switch (q->scheme) {
+    case LIQUID_FEC_RS_M8: fec_rs_init_p8(q);   break;
+    default:
+        fprintf(stderr,"error: fec_rs_create(), invalid type\n");
+        exit(1);
+    }
+
+    // initialize basic parameters
+    q->nn = (1 << q->symsize) - 1;
+    q->kk = q->nn - q->nroots;
+
+    // lengths
+    q->num_dec_bytes = 0;
+    q->rs = NULL;
+
+    // allocate memory for arrays
+    q->tblock   = (unsigned char*) malloc(q->nn*sizeof(unsigned char));
+    q->errlocs  = (int *) malloc(q->nn*sizeof(int));
+    q->derrlocs = (int *) malloc(q->nn*sizeof(int));
+
+    return q;
+}
+
+void fec_rs_destroy(fec _q)
+{
+    // delete internal Reed-Solomon decoder object
+    free_rs_char(_q->rs);
+
+    // delete internal memory arrays
+    free(_q->tblock);
+    free(_q->errlocs);
+    free(_q->derrlocs);
+
+    // delete fec object
+    free(_q);
+}
+
+void fec_rs_encode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char *_msg_dec,
+                   unsigned char *_msg_enc)
+{
+    // validate input
+    if (_dec_msg_len == 0) {
+        fprintf(stderr,"error: fec_rs_encode(), input lenght must be > 0\n");
+        exit(1);
+    }
+
+    // re-allocate resources if necessary
+    fec_rs_setlength(_q, _dec_msg_len);
+
+    unsigned int i;
+    unsigned int n0=0;  // input index
+    unsigned int n1=0;  // output index
+    unsigned int block_size = _q->dec_block_len;
+    for (i=0; i<_q->num_blocks; i++) {
+
+        // the last block is smaller by the residual block length
+        if (i == _q->num_blocks-1)
+            block_size -= _q->res_block_len;
+
+        // copy sequence
+        memmove(_q->tblock, &_msg_dec[n0], block_size*sizeof(unsigned char));
+
+        // last block: we could pad end with zeros, but it's not really
+        // necessary as these bits are going to be thrown away anyway
+
+        // encode data, appending parity bits to end of sequence
+        encode_rs_char(_q->rs, _q->tblock, &_q->tblock[_q->dec_block_len]);
+
+        // copy result to output
+        memmove(&_msg_enc[n1], _q->tblock, _q->enc_block_len*sizeof(unsigned char));
+
+        // increment counters
+        n0 += block_size;
+        n1 += _q->enc_block_len;
+    }
+
+    // sanity check
+    assert( n0 == _q->num_dec_bytes );
+    assert( n1 == _q->num_enc_bytes );
+}
+
+//unsigned int
+void fec_rs_decode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char *_msg_enc,
+                   unsigned char *_msg_dec)
+{
+    // validate input
+    if (_dec_msg_len == 0) {
+        fprintf(stderr,"error: fec_rs_encode(), input lenght must be > 0\n");
+        exit(1);
+    }
+
+    // re-allocate resources if necessary
+    fec_rs_setlength(_q, _dec_msg_len);
+
+    // set erasures, error locations to zero
+    memset(_q->errlocs,  0x00, _q->nn*sizeof(unsigned char));
+    memset(_q->derrlocs, 0x00, _q->nn*sizeof(unsigned char));
+    _q->erasures = 0;
+
+    unsigned int i;
+    unsigned int n0=0;
+    unsigned int n1=0;
+    unsigned int block_size = _q->dec_block_len;
+    //int derrors; // number of decoder errors
+    for (i=0; i<_q->num_blocks; i++) {
+
+        // the last block is smaller by the residual block length
+        if (i == _q->num_blocks-1)
+            block_size -= _q->res_block_len;
+
+        // copy sequence
+        memmove(_q->tblock, &_msg_enc[n0], _q->enc_block_len*sizeof(unsigned char));
+
+        // decode block
+        //derrors = 
+        decode_rs_char(_q->rs,
+                       _q->tblock,
+                       _q->derrlocs,
+                       _q->erasures);
+
+        // copy result
+        memmove(&_msg_dec[n1], _q->tblock, block_size*sizeof(unsigned char));
+
+        // increment counters
+        n0 += _q->enc_block_len;
+        n1 += block_size;
+    }
+
+    // sanity check
+    assert( n0 == _q->num_enc_bytes );
+    assert( n1 == _q->num_dec_bytes );
+}
+
+// Set dec_msg_len, re-allocating resources as necessary.  Effectively, it
+// divides the input message into several blocks and allows the decoder to
+// pad each block appropraitely.
+//
+// For example : if we are using the 8-bit code,
+//      nroots  = 32
+//      nn      = 255
+//      kk      = 223
+// Let _dec_msg_len = 1024, then
+//      num_blocks = ceil(1024/223)
+//                 = ceil(4.5919)
+//                 = 5
+//      dec_block_len = ceil(1024/num_blocks)
+//                    = ceil(204.8)
+//                    = 205
+//      enc_block_len = dec_block_len + nroots
+//                    = 237
+//      res_block_len = mod(num_blocks*dec_block_len,_dec_msg_len)
+//                    = mod(5*205,1024)
+//                    = mod(1025,1024)
+//                    = 1 (cannot evenly divide input sequence)
+//      pad = kk - dec_block_len
+//          = 223 - 205
+//          = 18
+//
+// Thus, the 1024-byte input message is broken into 5 blocks, the first
+// four have a length 205, and the last block has a length 204 (which is
+// externally padded to 205, e.g. res_block_len = 1). This code adds 32
+// parity symbols, so each block is extended to 237 bytes. libfec auto-
+// matically extends the internal data to 255 bytes by padding with 18
+// symbols.  Therefore, the final output length is 237 * 5 = 1185 symbols.
+void fec_rs_setlength(fec _q,
+                      unsigned int _dec_msg_len)
+{
+    // return if length has not changed
+    if (_dec_msg_len == _q->num_dec_bytes)
+        return;
+
+    // reset lengths
+    _q->num_dec_bytes = _dec_msg_len;
+
+    div_t d;
+
+    // compute the total number of blocks necessary: ceil(num_dec_bytes / kk)
+    d = div(_q->num_dec_bytes, _q->kk);
+    _q->num_blocks = d.quot + (d.rem==0 ? 0 : 1);
+
+    // compute the decoded block length: ceil(num_dec_bytes / num_blocks)
+    d = div(_dec_msg_len, _q->num_blocks);
+    _q->dec_block_len = d.quot + (d.rem == 0 ? 0 : 1);
+
+    // compute the encoded block length: dec_block_len + nroots
+    _q->enc_block_len = _q->dec_block_len + _q->nroots;
+
+    // compute the residual padding symbols in the last block:
+    // mod(num_blocks*dec_block_len, num_dec_bytes)
+    _q->res_block_len = (_q->num_blocks*_q->dec_block_len) % _q->num_dec_bytes;
+
+    // compute the internal libfec padding factor: kk - dec_block_len
+    _q->pad = _q->kk - _q->dec_block_len;
+
+    // compute the final encoded block length: enc_block_len * num_blocks
+    _q->num_enc_bytes = _q->enc_block_len * _q->num_blocks;
+    
+#if VERBOSE_FEC_RS
+    printf("dec_msg_len     :   %u\n", _q->num_dec_bytes);
+    printf("num_blocks      :   %u\n", _q->num_blocks);
+    printf("dec_block_len   :   %u\n", _q->dec_block_len);
+    printf("enc_block_len   :   %u\n", _q->enc_block_len);
+    printf("res_block_len   :   %u\n", _q->res_block_len);
+    printf("pad             :   %u\n", _q->pad);
+    printf("enc_msg_len     :   %u\n", _q->num_enc_bytes);
+#endif
+
+    // delete old decoder if necessary
+    if (_q->rs != NULL)
+        free_rs_char(_q->rs);
+
+    // Reed-Solomon specific decoding
+    _q->rs = init_rs_char(_q->symsize,
+                          _q->genpoly,
+                          _q->fcs,
+                          _q->prim,
+                          _q->nroots,
+                          _q->pad);
+}
+
+// 
+// internal
+//
+
+void fec_rs_init_p8(fec _q)
+{
+    _q->symsize = 8;
+    _q->genpoly = 0x11d;
+    _q->fcs = 1;
+    _q->prim = 1;
+    _q->nroots = 32;
+}
+
+#else   // LIBFEC_ENABLED
+
+fec fec_rs_create(fec_scheme _fs)
+{
+    return NULL;
+}
+
+void fec_rs_destroy(fec _q)
+{
+}
+
+void fec_rs_encode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char *_msg_dec,
+                   unsigned char *_msg_enc)
+{
+}
+
+//unsigned int
+void fec_rs_decode(fec _q,
+                   unsigned int _dec_msg_len,
+                   unsigned char *_msg_enc,
+                   unsigned char *_msg_dec)
+{
+}
+
+#endif  // LIBFEC_ENABLED
+
diff --git a/src/fec/src/fec_secded2216.c b/src/fec/src/fec_secded2216.c
new file mode 100644
index 0000000..fb60c80
--- /dev/null
+++ b/src/fec/src/fec_secded2216.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// SEC-DED (22,16) 8/11-rate forward error-correction block code
+//
+// References:
+//  [Lin:2004] Lin, Shu and Costello, Daniel L. Jr., "Error Control
+//      Coding," Prentice Hall, New Jersey, 2nd edition, 2004.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED2216 0
+
+// P matrix [6 x 16 bits], [6 x 2 bytes]
+//  1001 1001 0011 1100 :
+//  0011 1110 1000 1010 :
+//  1110 1110 0110 0000 :
+//  1110 0001 1101 0001 :
+//  0001 0011 1100 0111 :
+//  0100 0100 0011 1111 :
+unsigned char secded2216_P[12] = {
+    0x99, 0x3c,
+    0x3e, 0x8a,
+    0xee, 0x60,
+    0xe1, 0xd1,
+    0x13, 0xc7,
+    0x44, 0x3f};
+
+// syndrome vectors for errors of weight 1
+unsigned char secded2216_syndrome_w1[22] = {
+    0x07, 0x13, 0x23, 0x31, 
+    0x25, 0x29, 0x0e, 0x16, 
+    0x26, 0x1a, 0x19, 0x38, 
+    0x32, 0x1c, 0x0d, 0x2c, 
+    0x01, 0x02, 0x04, 0x08, 
+    0x10, 0x20};
+
+// compute parity on 16-bit input
+unsigned char fec_secded2216_compute_parity(unsigned char * _m)
+{
+    // compute encoded/transmitted message: v = m*G
+    unsigned char parity = 0x00;
+
+    // TODO : unwrap this loop
+    unsigned int i;
+    for (i=0; i<6; i++) {
+        parity <<= 1;
+
+        unsigned int p = liquid_c_ones[ secded2216_P[2*i+0] & _m[0] ] +
+                         liquid_c_ones[ secded2216_P[2*i+1] & _m[1] ];
+
+        parity |= p & 0x01;
+    }
+
+    return parity;
+}
+
+// compute syndrome on 22-bit input
+unsigned char fec_secded2216_compute_syndrome(unsigned char * _v)
+{
+    // TODO : unwrap this loop
+    unsigned int i;
+    unsigned char syndrome = 0x00;
+    for (i=0; i<6; i++) {
+        syndrome <<= 1;
+
+        unsigned int p =
+            ( (_v[0] & (1<<(6-i-1))) ? 1 : 0 )+
+            liquid_c_ones[ secded2216_P[2*i+0] & _v[1] ] +
+            liquid_c_ones[ secded2216_P[2*i+1] & _v[2] ];
+
+        syndrome |= p & 0x01;
+    }
+
+    return syndrome;
+}
+
+// encode symbol
+//  _sym_dec    :   decoded symbol [size: 2 x 1]
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+void fec_secded2216_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc)
+{
+    // first six bits is parity block
+    _sym_enc[0] = fec_secded2216_compute_parity(_sym_dec);
+
+    // copy last two values
+    _sym_enc[1] = _sym_dec[0];
+    _sym_enc[2] = _sym_dec[1];
+}
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors
+// detected, respectively
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+//  _sym_dec    :   decoded symbol [size: 2 x 1]
+int fec_secded2216_decode_symbol(unsigned char * _sym_enc,
+                                 unsigned char * _sym_dec)
+{
+#if 0
+    // validate input
+    if (_sym_enc[0] >= (1<<6)) {
+        fprintf(stderr,"warning, fec_secded2216_decode_symbol(), input symbol too large\n");
+    }
+#endif
+
+    // estimate error vector
+    unsigned char e_hat[3] = {0,0,0};
+    int syndrome_flag = fec_secded2216_estimate_ehat(_sym_enc, e_hat);
+
+    // compute estimated transmit vector (last 64 bits of encoded message)
+    // NOTE: indices take into account first element in _sym_enc and e_hat
+    //       arrays holds the parity bits
+    _sym_dec[0] = _sym_enc[1] ^ e_hat[1];
+    _sym_dec[1] = _sym_enc[2] ^ e_hat[2];
+
+#if DEBUG_FEC_SECDED2216
+    if (syndrome_flag == 1) {
+        printf("secded2216_decode_symbol(): single error detected!\n");
+    } else if (syndrome_flag == 2) {
+        printf("secded2216_decode_symbol(): no match found (multiple errors detected)\n");
+    }
+#endif
+
+    // return syndrome flag
+    return syndrome_flag;
+}
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors
+// detected, respectively
+//  _sym_enc    :   encoded symbol [size: 3 x 1], _sym_enc[0] has only 6 bits
+//  _e_hat      :   estimated error vector [size: 3 x 1]
+int fec_secded2216_estimate_ehat(unsigned char * _sym_enc,
+                                 unsigned char * _e_hat)
+{
+    // clear output array
+    _e_hat[0] = 0x00;
+    _e_hat[1] = 0x00;
+    _e_hat[2] = 0x00;
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    unsigned char s = fec_secded2216_compute_syndrome(_sym_enc);
+
+    // compute weight of s
+    unsigned int ws = liquid_c_ones[s];
+    
+    if (ws == 0) {
+        // no errors detected
+        return 0;
+    } else {
+        // estimate error location; search for syndrome with error
+        // vector of weight one
+
+        unsigned int n;
+        // estimate error location
+        for (n=0; n<22; n++) {
+            if (s == secded2216_syndrome_w1[n]) {
+                // single error detected at location 'n'
+                div_t d = div(n,8);
+                _e_hat[3-d.quot-1] = 1 << d.rem;
+
+                return 1;
+            }
+        }
+
+    }
+
+    // no syndrome match; multiple errors detected
+    return 2;
+}
+
+// create SEC-DED (22,16) codec object
+fec fec_secded2216_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_SECDED2216;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_secded2216_encode;
+    q->decode_func      = &fec_secded2216_decode;
+    q->decode_soft_func = NULL;
+
+    return q;
+}
+
+// destroy SEC-DEC (22,16) object
+void fec_secded2216_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using SEC-DEC (22,16) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_secded2216_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_dec,
+                           unsigned char *_msg_enc)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+
+    // determine remainder of input length / 8
+    unsigned int r = _dec_msg_len % 2;
+
+    // for now simply encode as 2/3-rate codec (eat
+    // 2 bits of parity)
+    // TODO : make more efficient
+
+    for (i=0; i<_dec_msg_len-r; i+=2) {
+        // compute parity (6 bits) on two input bytes (16 bits)
+        _msg_enc[j+0] = fec_secded2216_compute_parity(&_msg_dec[i]);
+
+        // copy remaining two input bytes (16 bits)
+        _msg_enc[j+1] = _msg_dec[i+0];
+        _msg_enc[j+2] = _msg_dec[i+1];
+
+        // increment output counter
+        j += 3;
+    }
+
+    // if input length isn't divisible by 2, encode last few bytes
+    if (r) {
+        // one 16-bit symbol (decoded)
+        unsigned char m[2] = {_msg_dec[i], 0x00};
+
+        // one 22-bit symbol (encoded)
+        unsigned char v[3];
+
+        // encode
+        fec_secded2216_encode_symbol(m, v);
+
+        // there is no need to actually send all three bytes;
+        // the last byte is zero and can be artificially
+        // inserted at the decoder
+        _msg_enc[j+0] = v[0];
+        _msg_enc[j+1] = v[1];
+
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED2216,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+}
+
+// decode block of data using SEC-DEC (22,16) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_secded2216_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_enc,
+                           unsigned char *_msg_dec)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+    
+    // determine remainder of input length / 8
+    unsigned int r = _dec_msg_len % 2;
+
+    for (i=0; i<_dec_msg_len-r; i+=2) {
+        // decode straight to output
+        fec_secded2216_decode_symbol(&_msg_enc[j], &_msg_dec[i]);
+
+        j += 3;
+    }
+
+    // if input length isn't divisible by 2, decode last several bytes
+    if (r) {
+        // one 22-bit symbol (encoded), with last byte artifically
+        // set to '00000000'
+        unsigned char v[3] = {_msg_enc[j+0], _msg_enc[j+1], 0x00};
+
+        // one 16-bit symbol (decoded)
+        unsigned char m_hat[2];
+
+        // decode symbol
+        fec_secded2216_decode_symbol(v, m_hat);
+
+        // copy just first byte to output
+        _msg_dec[i] = m_hat[0];
+
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED2216,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+
+    //return num_errors;
+}
diff --git a/src/fec/src/fec_secded3932.c b/src/fec/src/fec_secded3932.c
new file mode 100644
index 0000000..58c1363
--- /dev/null
+++ b/src/fec/src/fec_secded3932.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// SEC-DED (39,32) forward error-correction block code
+//
+// References:
+//  [Lin:2004] Lin, Shu and Costello, Daniel L. Jr., "Error Control
+//      Coding," Prentice Hall, New Jersey, 2nd edition, 2004.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED3932 0
+
+// P matrix [7 x 32 bits], [7 x 4 bytes]
+//  1000 1010 1000 0010 0000 1111 0001 1011
+//  0001 0000 0001 1111 0111 0001 0110 0001
+//  0001 0110 1111 0000 1001 0010 1010 0110
+//  1111 1111 0000 0001 1010 0100 0100 0100
+//  0110 1100 1111 1111 0000 1000 0000 1000
+//  0010 0001 0010 0100 1111 1111 1001 0000
+//  1100 0001 0100 1000 0100 0000 1111 1111
+unsigned char secded3932_P[28] = {
+    0x8a, 0x82, 0x0f, 0x1b,
+    0x10, 0x1f, 0x71, 0x61,
+    0x16, 0xf0, 0x92, 0xa6,
+    0xff, 0x01, 0xa4, 0x44,
+    0x6c, 0xff, 0x08, 0x08,
+    0x21, 0x24, 0xff, 0x90,
+    0xc1, 0x48, 0x40, 0xff};
+
+
+// syndrome vectors for errors of weight 1
+unsigned char secded3932_syndrome_w1[39] = {
+    0x61, 0x51, 0x19, 0x45, 
+    0x43, 0x31, 0x29, 0x13, 
+    0x62, 0x52, 0x4a, 0x46, 
+    0x32, 0x2a, 0x23, 0x1a, 
+    0x2c, 0x64, 0x26, 0x25, 
+    0x34, 0x16, 0x15, 0x54, 
+    0x0b, 0x58, 0x1c, 0x4c, 
+    0x38, 0x0e, 0x0d, 0x49, 
+    0x01, 0x02, 0x04, 0x08, 
+    0x10, 0x20, 0x40};
+
+// compute parity on 32-bit input
+unsigned char fec_secded3932_compute_parity(unsigned char * _m)
+{
+    // compute encoded/transmitted message: v = m*G
+    unsigned char parity = 0x00;
+
+    // TODO : unwrap this loop
+    unsigned int i;
+    for (i=0; i<7; i++) {
+        parity <<= 1;
+
+        unsigned int p = liquid_c_ones[ secded3932_P[4*i+0] & _m[0] ] +
+                         liquid_c_ones[ secded3932_P[4*i+1] & _m[1] ] +
+                         liquid_c_ones[ secded3932_P[4*i+2] & _m[2] ] +
+                         liquid_c_ones[ secded3932_P[4*i+3] & _m[3] ];
+
+        parity |= p & 0x01;
+    }
+
+    return parity;
+}
+
+// compute syndrome on 39-bit input
+unsigned char fec_secded3932_compute_syndrome(unsigned char * _v)
+{
+    // TODO : unwrap this loop
+    unsigned int i;
+    unsigned char syndrome = 0x00;
+    for (i=0; i<7; i++) {
+        syndrome <<= 1;
+
+        unsigned int p =
+            ( (_v[0] & (1<<(7-i-1))) ? 1 : 0 )+
+            liquid_c_ones[ secded3932_P[4*i+0] & _v[1] ] +
+            liquid_c_ones[ secded3932_P[4*i+1] & _v[2] ] +
+            liquid_c_ones[ secded3932_P[4*i+2] & _v[3] ] +
+            liquid_c_ones[ secded3932_P[4*i+3] & _v[4] ];
+
+        syndrome |= p & 0x01;
+    }
+
+    return syndrome;
+}
+
+// encode symbol
+//  _sym_dec    :   decoded symbol [size: 4 x 1]
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 7 bits
+void fec_secded3932_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc)
+{
+    // first six bits is parity block
+    _sym_enc[0] = fec_secded3932_compute_parity(_sym_dec);
+
+    // concatenate original message
+    _sym_enc[1] = _sym_dec[0];
+    _sym_enc[2] = _sym_dec[1];
+    _sym_enc[3] = _sym_dec[2];
+    _sym_enc[4] = _sym_dec[3];
+}
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 7 bits
+//  _sym_dec    :   decoded symbol [size: 4 x 1]
+int fec_secded3932_decode_symbol(unsigned char * _sym_enc,
+                                 unsigned char * _sym_dec)
+{
+#if 0
+    // validate input
+    if (_sym_enc[0] >= (1<<7)) {
+        fprintf(stderr,"warning, fec_secded3932_decode_symbol(), input symbol too large\n");
+    }
+#endif
+
+    // estimate error vector
+    unsigned char e_hat[5] = {0,0,0,0,0};    // estimated error vector
+    int syndrome_flag = fec_secded3932_estimate_ehat(_sym_enc, e_hat);
+
+    // compute estimated transmit vector (last 64 bits of encoded message)
+    // NOTE: indices take into account first element in _sym_enc and e_hat
+    //       arrays holds the parity bits
+    _sym_dec[0] = _sym_enc[1] ^ e_hat[1];
+    _sym_dec[1] = _sym_enc[2] ^ e_hat[2];
+    _sym_dec[2] = _sym_enc[3] ^ e_hat[3];
+    _sym_dec[3] = _sym_enc[4] ^ e_hat[4];
+
+#if DEBUG_FEC_SECDED3932
+    if (syndrome_flag == 1) {
+        printf("secded3932_decode_symbol(): single error detected!\n");
+    } else if (syndrome_flag == 2) {
+        printf("secded3932_decode_symbol(): no match found (multiple errors detected)\n");
+    }
+#endif
+
+    // return syndrome flag
+    return syndrome_flag;
+}
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors
+// detected, respectively
+//  _sym_enc    :   encoded symbol [size: 5 x 1], _sym_enc[0] has only 6 bits
+//  _e_hat      :   estimated error vector [size: 5 x 1]
+int fec_secded3932_estimate_ehat(unsigned char * _sym_enc,
+                                 unsigned char * _e_hat)
+{
+    // clear output array
+    _e_hat[0] = 0x00;
+    _e_hat[1] = 0x00;
+    _e_hat[2] = 0x00;
+    _e_hat[3] = 0x00;
+    _e_hat[4] = 0x00;
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    unsigned char s = fec_secded3932_compute_syndrome(_sym_enc);
+
+    // compute weight of s
+    unsigned int ws = liquid_c_ones[s];
+    
+    if (ws == 0) {
+        // no errors detected
+        return 0;
+    } else {
+        // estimate error location; search for syndrome with error
+        // vector of weight one
+
+        unsigned int n;
+        // estimate error location
+        for (n=0; n<39; n++) {
+            if (s == secded3932_syndrome_w1[n]) {
+                // single error detected at location 'n'
+                div_t d = div(n,8);
+                _e_hat[5-d.quot-1] = 1 << d.rem;
+
+                return 1;
+            }
+        }
+
+    }
+
+    // no syndrome match; multiple errors detected
+    return 2;
+}
+
+// create SEC-DED (39,32) codec object
+fec fec_secded3932_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_SECDED3932;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_secded3932_encode;
+    q->decode_func      = &fec_secded3932_decode;
+    q->decode_soft_func = NULL;
+
+    return q;
+}
+
+// destroy SEC-DEC (39,32) object
+void fec_secded3932_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using SEC-DEC (39,32) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_secded3932_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_dec,
+                           unsigned char *_msg_enc)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+
+    // determine remainder of input length / 4
+    unsigned int r = _dec_msg_len % 4;
+
+    // for now simply encode as 4/5-rate codec (eat
+    // last parity bit)
+    // TODO : make more efficient
+
+    for (i=0; i<_dec_msg_len-r; i+=4) {
+        // compute parity (7 bits) on two input bytes (32 bits)
+        _msg_enc[j+0] = fec_secded3932_compute_parity(&_msg_dec[i]);
+
+        // copy remaining two input bytes (32 bits)
+        _msg_enc[j+1] = _msg_dec[i+0];
+        _msg_enc[j+2] = _msg_dec[i+1];
+        _msg_enc[j+3] = _msg_dec[i+2];
+        _msg_enc[j+4] = _msg_dec[i+3];
+
+        // increment output counter
+        j += 5;
+    }
+
+    // if input length isn't divisible by 4, encode last few bytes
+    if (r) {
+        // one 32-bit symbol (decoded)
+        unsigned char m[4] = {0,0,0,0};
+        unsigned int n;
+        for (n=0; n<r; n++)
+            m[n] = _msg_dec[i+n];
+
+        // one 39-bit symbol (encoded)
+        unsigned char v[5];
+
+        // encode
+        fec_secded3932_encode_symbol(m, v);
+
+        // there is no need to actually send all five bytes;
+        // the last few bytes are zero and can be artificially
+        // inserted at the decoder
+        _msg_enc[j+0] = v[0];
+        for (n=0; n<r; n++)
+            _msg_enc[j+n+1] = v[n+1];
+
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED3932,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+}
+
+// decode block of data using SEC-DEC (39,32) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_secded3932_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_enc,
+                           unsigned char *_msg_dec)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+    
+    // determine remainder of input length / 4
+    unsigned int r = _dec_msg_len % 4;
+
+    for (i=0; i<_dec_msg_len-r; i+=4) {
+        // decode straight to output
+        fec_secded3932_decode_symbol(&_msg_enc[j], &_msg_dec[i]);
+
+        j += 5;
+    }
+
+    // if input length isn't divisible by 4, decode last several bytes
+    if (r) {
+        // one 39-bit symbol
+        unsigned char v[5] = {_msg_enc[j+0], 0, 0, 0, 0};
+        unsigned int n;
+        for (n=0; n<r; n++)
+            v[n+1] = _msg_enc[j+n+1];
+
+        // one 32-bit symbol (decoded)
+        unsigned char m_hat[4];
+
+        // decode symbol
+        fec_secded3932_decode_symbol(v, m_hat);
+
+        // copy non-zero bytes to output (ignore zeros artifically
+        // inserted at receiver)
+        for (n=0; n<r; n++)
+            _msg_dec[i+n] = m_hat[n];
+
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED3932,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+
+    //return num_errors;
+}
diff --git a/src/fec/src/fec_secded7264.c b/src/fec/src/fec_secded7264.c
new file mode 100644
index 0000000..8aa3e3b
--- /dev/null
+++ b/src/fec/src/fec_secded7264.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// SEC-DED (72,64) 8/9-rate forward error-correction block code
+//
+// References:
+//  [Lin:2004] Lin, Shu and Costello, Daniel L. Jr., "Error Control
+//      Coding," Prentice Hall, New Jersey, 2nd edition, 2004.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FEC_SECDED7264 0
+
+// P matrix [8 x 64]
+//  11111111 00001111 00001111 00001100 01101000 10001000 10001000 10000000 : 
+//  11110000 11111111 00000000 11110011 01100100 01000100 01000100 01000000 : 
+//  00110000 11110000 11111111 00001111 00000010 00100010 00100010 00100110 : 
+//  11001111 00000000 11110000 11111111 00000001 00010001 00010001 00010110 : 
+//  01101000 10001000 10001000 10000000 11111111 00001111 00000000 11110011 : 
+//  01100100 01000100 01000100 01000000 11110000 11111111 00001111 00001100 : 
+//  00000010 00100010 00100010 00100110 11001111 00000000 11111111 00001111 : 
+//  00000001 00010001 00010001 00010110 00110000 11110000 11110000 11111111 : 
+unsigned char secded7264_P[64] = {
+    0xFF, 0x0F, 0x0F, 0x0C, 0x68, 0x88, 0x88, 0x80,
+    0xF0, 0xFF, 0x00, 0xF3, 0x64, 0x44, 0x44, 0x40,
+    0x30, 0xF0, 0xFF, 0x0F, 0x02, 0x22, 0x22, 0x26,
+    0xCF, 0x00, 0xF0, 0xFF, 0x01, 0x11, 0x11, 0x16,
+    0x68, 0x88, 0x88, 0x80, 0xFF, 0x0F, 0x00, 0xF3,
+    0x64, 0x44, 0x44, 0x40, 0xF0, 0xFF, 0x0F, 0x0C,
+    0x02, 0x22, 0x22, 0x26, 0xCF, 0x00, 0xFF, 0x0F,
+    0x01, 0x11, 0x11, 0x16, 0x30, 0xF0, 0xF0, 0xFF};
+
+// syndrome vectors for errors of weight 1
+unsigned char secded7264_syndrome_w1[72] = {
+    0x0b, 0x3b, 0x37, 0x07, 0x19, 0x29, 0x49, 0x89,
+    0x16, 0x26, 0x46, 0x86, 0x13, 0x23, 0x43, 0x83,
+    0x1c, 0x2c, 0x4c, 0x8c, 0x15, 0x25, 0x45, 0x85,
+    0x1a, 0x2a, 0x4a, 0x8a, 0x0d, 0xcd, 0xce, 0x0e,
+    0x70, 0x73, 0xb3, 0xb0, 0x51, 0x52, 0x54, 0x58,
+    0xa1, 0xa2, 0xa4, 0xa8, 0x31, 0x32, 0x34, 0x38,
+    0xc1, 0xc2, 0xc4, 0xc8, 0x61, 0x62, 0x64, 0x68,
+    0x91, 0x92, 0x94, 0x98, 0xe0, 0xec, 0xdc, 0xd0,
+    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
+
+
+// compute parity byte on 64-byte input
+unsigned char fec_secded7264_compute_parity(unsigned char * _v)
+{
+    // compute parity byte on message
+    unsigned int i;
+    unsigned char parity = 0x00;
+    for (i=0; i<8; i++) {
+        parity <<= 1;
+
+        unsigned int p = liquid_c_ones[ secded7264_P[8*i+0] & _v[0] ] +
+                         liquid_c_ones[ secded7264_P[8*i+1] & _v[1] ] +
+                         liquid_c_ones[ secded7264_P[8*i+2] & _v[2] ] +
+                         liquid_c_ones[ secded7264_P[8*i+3] & _v[3] ] +
+                         liquid_c_ones[ secded7264_P[8*i+4] & _v[4] ] +
+                         liquid_c_ones[ secded7264_P[8*i+5] & _v[5] ] +
+                         liquid_c_ones[ secded7264_P[8*i+6] & _v[6] ] +
+                         liquid_c_ones[ secded7264_P[8*i+7] & _v[7] ];
+
+        parity |= p & 0x01;
+    }
+
+    // return parity byte
+    return parity;
+}
+
+// compute syndrome on 72-bit input
+unsigned char fec_secded7264_compute_syndrome(unsigned char * _v)
+{
+    // TODO : unwrap this loop
+    unsigned int i;
+    unsigned char syndrome = 0x00;
+    for (i=0; i<8; i++) {
+        syndrome <<= 1;
+
+        // compute parity bit
+        unsigned int p =
+            ( (_v[0] & (1<<(8-i-1))) ? 1 : 0 ) +
+            liquid_c_ones[ secded7264_P[8*i+0] & _v[1] ] +
+            liquid_c_ones[ secded7264_P[8*i+1] & _v[2] ] +
+            liquid_c_ones[ secded7264_P[8*i+2] & _v[3] ] +
+            liquid_c_ones[ secded7264_P[8*i+3] & _v[4] ] +
+            liquid_c_ones[ secded7264_P[8*i+4] & _v[5] ] +
+            liquid_c_ones[ secded7264_P[8*i+5] & _v[6] ] +
+            liquid_c_ones[ secded7264_P[8*i+6] & _v[7] ] +
+            liquid_c_ones[ secded7264_P[8*i+7] & _v[8] ];
+
+        syndrome |= p & 0x01;
+    }
+
+    return syndrome;
+}
+
+void fec_secded7264_encode_symbol(unsigned char * _sym_dec,
+                                  unsigned char * _sym_enc)
+{
+    // compute parity on input
+    _sym_enc[0] = fec_secded7264_compute_parity(_sym_dec);
+
+    // copy input to output
+    _sym_enc[1] = _sym_dec[0];
+    _sym_enc[2] = _sym_dec[1];
+    _sym_enc[3] = _sym_dec[2];
+    _sym_enc[4] = _sym_dec[3];
+    _sym_enc[5] = _sym_dec[4];
+    _sym_enc[6] = _sym_dec[5];
+    _sym_enc[7] = _sym_dec[6];
+    _sym_enc[8] = _sym_dec[7];
+}
+
+// decode symbol, returning 0/1/2 for zero/one/multiple errors detected
+//  _sym_enc    :   encoded symbol [size: 9 x 1]
+//  _sym_dec    :   decoded symbol [size: 8 x 1]
+int fec_secded7264_decode_symbol(unsigned char * _sym_enc,
+                                 unsigned char * _sym_dec)
+{
+    // estimate error vector
+    unsigned char e_hat[9] = {0,0,0,0,0,0,0,0,0};
+    int syndrome_flag = fec_secded7264_estimate_ehat(_sym_enc, e_hat);
+
+    // compute estimated transmit vector (last 64 bits of encoded message)
+    // NOTE: indices take into account first element in _sym_enc and e_hat
+    //       arrays holds the parity bits
+    _sym_dec[0] = _sym_enc[1] ^ e_hat[1];
+    _sym_dec[1] = _sym_enc[2] ^ e_hat[2];
+    _sym_dec[2] = _sym_enc[3] ^ e_hat[3];
+    _sym_dec[3] = _sym_enc[4] ^ e_hat[4];
+    _sym_dec[4] = _sym_enc[5] ^ e_hat[5];
+    _sym_dec[5] = _sym_enc[6] ^ e_hat[6];
+    _sym_dec[6] = _sym_enc[7] ^ e_hat[7];
+    _sym_dec[7] = _sym_enc[8] ^ e_hat[8];
+
+#if DEBUG_FEC_SECDED7264
+    if (syndrome_flag == 1) {
+        printf("secded7264_decode_symbol(): single error detected!\n");
+    } else if (syndrome_flag == 2) {
+        printf("secded7264_decode_symbol(): no match found (multiple errors detected)\n");
+    }
+#endif
+
+    // return syndrome flag
+    return syndrome_flag;
+}
+
+// estimate error vector, returning 0/1/2 for zero/one/multiple errors
+// detected, respectively
+//  _sym_enc    :   encoded symbol [size: 9 x 1],
+//  _e_hat      :   estimated error vector [size: 9 x 1]
+int fec_secded7264_estimate_ehat(unsigned char * _sym_enc,
+                                 unsigned char * _e_hat)
+{
+    // clear output array
+    memset(_e_hat, 0x00, 9*sizeof(unsigned char));
+
+    // compute syndrome vector, s = r*H^T = ( H*r^T )^T
+    unsigned char s = fec_secded7264_compute_syndrome(_sym_enc);
+
+    // compute weight of s
+    unsigned int ws = liquid_c_ones[s];
+    
+    if (ws == 0) {
+        // no errors detected
+        return 0;
+    } else {
+        // estimate error location; search for syndrome with error
+        // vector of weight one
+
+        unsigned int n;
+        // estimate error location
+        for (n=0; n<72; n++) {
+            if (s == secded7264_syndrome_w1[n]) {
+                // single error detected at location 'n'
+                div_t d = div(n,8);
+                _e_hat[9-d.quot-1] = 1 << d.rem;
+
+                return 1;
+            }
+        }
+
+    }
+
+    // no syndrome match; multiple errors detected
+    return 2;
+}
+
+// create SEC-DED (72,64) codec object
+fec fec_secded7264_create(void * _opts)
+{
+    fec q = (fec) malloc(sizeof(struct fec_s));
+
+    // set scheme
+    q->scheme = LIQUID_FEC_SECDED7264;
+    q->rate = fec_get_rate(q->scheme);
+
+    // set internal function pointers
+    q->encode_func      = &fec_secded7264_encode;
+    q->decode_func      = &fec_secded7264_decode;
+    q->decode_soft_func = NULL;
+
+    return q;
+}
+
+// destroy SEC-DEC (72,64) object
+void fec_secded7264_destroy(fec _q)
+{
+    free(_q);
+}
+
+// encode block of data using SEC-DEC (72,64) encoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+void fec_secded7264_encode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_dec,
+                           unsigned char *_msg_enc)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+    unsigned char parity;   // parity byte
+
+    // determine remainder of input length / 8
+    unsigned int r = _dec_msg_len % 8;
+
+    // TODO : devise more efficient way of doing this
+    for (i=0; i<_dec_msg_len-r; i+=8) {
+        // encode directly to output
+        fec_secded7264_encode_symbol(&_msg_dec[i], &_msg_enc[j]);
+
+        j += 9;
+    }
+
+    // if input length isn't divisible by 8, encode last few bytes
+    if (r) {
+        unsigned char v[8] = {0,0,0,0,0,0,0,0};
+        unsigned int n;
+        for (n=0; n<r; n++)
+            v[n] = _msg_dec[i+n];
+
+        // compute parity
+        parity = fec_secded7264_compute_parity(v);
+        
+        // there is no need to actually send all the bytes; the
+        // last 8-r bytes are zeros and can be added at the
+        // decoder
+        _msg_enc[j+0] = parity;
+        for (n=0; n<r; n++)
+            _msg_enc[j+n+1] = _msg_dec[i+n];
+
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED7264,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+}
+
+// decode block of data using SEC-DEC (72,64) decoder
+//
+//  _q              :   encoder/decoder object
+//  _dec_msg_len    :   decoded message length (number of bytes)
+//  _msg_enc        :   encoded message [size: 1 x 2*_dec_msg_len]
+//  _msg_dec        :   decoded message [size: 1 x _dec_msg_len]
+//
+//unsigned int
+void fec_secded7264_decode(fec _q,
+                           unsigned int _dec_msg_len,
+                           unsigned char *_msg_enc,
+                           unsigned char *_msg_dec)
+{
+    unsigned int i=0;       // decoded byte counter
+    unsigned int j=0;       // encoded byte counter
+    
+    // determine remainder of input length / 8
+    unsigned int r = _dec_msg_len % 8;
+
+    for (i=0; i<_dec_msg_len-r; i+=8) {
+        // decode nine input bytes
+        fec_secded7264_decode_symbol(&_msg_enc[j], &_msg_dec[i]);
+
+        j += 9;
+    }
+
+
+    // if input length isn't divisible by 8, decode last several bytes
+    if (r) {
+        unsigned char v[9] = {0,0,0,0,0,0,0,0,0};   // received message
+        unsigned char c[8] = {0,0,0,0,0,0,0,0};     // decoded message
+
+        unsigned int n;
+        // output length is input + 1 (parity byte)
+        for (n=0; n<r+1; n++)
+            v[n] = _msg_enc[j+n];
+
+        // decode symbol
+        fec_secded7264_decode_symbol(v,c);
+
+        // store only relevant bytes
+        for (n=0; n<r; n++)
+            _msg_dec[i+n] = c[n];
+        
+        i += r;
+        j += r+1;
+    }
+
+    assert( j == fec_get_enc_msg_length(LIQUID_FEC_SECDED7264,_dec_msg_len) );
+    assert( i == _dec_msg_len);
+
+    //return num_errors;
+}
diff --git a/src/fec/src/interleaver.c b/src/fec/src/interleaver.c
new file mode 100644
index 0000000..06e4091
--- /dev/null
+++ b/src/fec/src/interleaver.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// interleaver_create.c
+//
+// Create and initialize interleaver objects
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// internal methods
+//
+
+// permute one iteration
+void interleaver_permute(unsigned char * _x,
+                         unsigned int    _n,
+                         unsigned int    _M,
+                         unsigned int    _N);
+
+// permute one iteration (soft bit input)
+void interleaver_permute_soft(unsigned char * _x,
+                              unsigned int    _n,
+                              unsigned int    _M,
+                              unsigned int    _N);
+
+// permute one iteration with mask
+void interleaver_permute_mask(unsigned char * _x,
+                              unsigned int    _n,
+                              unsigned int    _M,
+                              unsigned int    _N,
+                              unsigned char   _mask);
+
+// permute one iteration (soft bit input) with mask
+void interleaver_permute_mask_soft(unsigned char * _x,
+                                   unsigned int    _n,
+                                   unsigned int    _M,
+                                   unsigned int    _N,
+                                   unsigned char   _mask);
+
+
+// structured interleaver object
+struct interleaver_s {
+    unsigned int n;     // number of bytes
+
+    unsigned int M;     // row dimension
+    unsigned int N;     // col dimension
+
+    // interleaving depth (number of permutations)
+    unsigned int depth;
+};
+
+// create interleaver of length _n input/output bytes
+interleaver interleaver_create(unsigned int _n)
+{
+    interleaver q = (interleaver) malloc(sizeof(struct interleaver_s));
+    q->n = _n;
+
+    // set internal properties
+    q->depth = 4;   // default depth to maximum 
+
+    // compute block dimensions
+    q->M = 1 + (unsigned int) floorf(sqrtf(q->n));
+
+    q->N = q->n / q->M;
+    while (q->n >= (q->M*q->N)) q->N++;  // ensures M*N >= n
+
+    return q;
+}
+
+// destroy interleaver object
+void interleaver_destroy(interleaver _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+// print interleaver internals
+void interleaver_print(interleaver _q)
+{
+    printf("interleaver [block, %u bytes] :\n", _q->n);
+    printf("    M       :   %u\n", _q->M);
+    printf("    N       :   %u\n", _q->N);
+    printf("    depth   :   %u\n", _q->depth);
+}
+
+// set depth (number of internal iterations)
+void interleaver_set_depth(interleaver  _q,
+                           unsigned int _depth)
+{
+    _q->depth = _depth;
+}
+
+// execute forward interleaver (encoder)
+//  _q          :   interleaver object
+//  _msg_dec    :   decoded (un-interleaved) message
+//  _msg_enc    :   encoded (interleaved) message
+void interleaver_encode(interleaver _q,
+                        unsigned char * _msg_dec,
+                        unsigned char * _msg_enc)
+{
+    // copy data to output
+    memmove(_msg_enc, _msg_dec, _q->n);
+
+    if (_q->depth > 0) interleaver_permute(_msg_enc, _q->n, _q->M, _q->N);
+    if (_q->depth > 1) interleaver_permute_mask(_msg_enc, _q->n, _q->M, _q->N+2, 0x0f);
+    if (_q->depth > 2) interleaver_permute_mask(_msg_enc, _q->n, _q->M, _q->N+4, 0x55);
+    if (_q->depth > 3) interleaver_permute_mask(_msg_enc, _q->n, _q->M, _q->N+8, 0x33);
+}
+
+// execute forward interleaver (encoder) on soft bits
+//  _q          :   interleaver object
+//  _msg_dec    :   decoded (un-interleaved) message
+//  _msg_enc    :   encoded (interleaved) message
+void interleaver_encode_soft(interleaver _q,
+                             unsigned char * _msg_dec,
+                             unsigned char * _msg_enc)
+{
+    // copy data to output
+    memmove(_msg_enc, _msg_dec, 8*_q->n);
+
+    if (_q->depth > 0) interleaver_permute_soft(_msg_enc, _q->n, _q->M, _q->N);
+    if (_q->depth > 1) interleaver_permute_mask_soft(_msg_enc, _q->n, _q->M, _q->N+2, 0x0f);
+    if (_q->depth > 2) interleaver_permute_mask_soft(_msg_enc, _q->n, _q->M, _q->N+4, 0x55);
+    if (_q->depth > 3) interleaver_permute_mask_soft(_msg_enc, _q->n, _q->M, _q->N+8, 0x33);
+}
+
+// execute reverse interleaver (decoder)
+//  _q          :   interleaver object
+//  _msg_enc    :   encoded (interleaved) message
+//  _msg_dec    :   decoded (un-interleaved) message
+void interleaver_decode(interleaver _q,
+                        unsigned char * _msg_enc,
+                        unsigned char * _msg_dec)
+{
+    // copy data to output
+    memmove(_msg_dec, _msg_enc, _q->n);
+
+    if (_q->depth > 3) interleaver_permute_mask(_msg_dec, _q->n, _q->M, _q->N+8, 0x33);
+    if (_q->depth > 2) interleaver_permute_mask(_msg_dec, _q->n, _q->M, _q->N+4, 0x55);
+    if (_q->depth > 1) interleaver_permute_mask(_msg_dec, _q->n, _q->M, _q->N+2, 0x0f);
+    if (_q->depth > 0) interleaver_permute(_msg_dec, _q->n, _q->M, _q->N);
+}
+
+// execute reverse interleaver (decoder) on soft bits
+//  _q          :   interleaver object
+//  _msg_enc    :   encoded (interleaved) message
+//  _msg_dec    :   decoded (un-interleaved) message
+void interleaver_decode_soft(interleaver _q,
+                             unsigned char * _msg_enc,
+                             unsigned char * _msg_dec)
+{
+    // copy data to output
+    memmove(_msg_dec, _msg_enc, 8*_q->n);
+
+    if (_q->depth > 3) interleaver_permute_mask_soft(_msg_dec, _q->n, _q->M, _q->N+8, 0x33);
+    if (_q->depth > 2) interleaver_permute_mask_soft(_msg_dec, _q->n, _q->M, _q->N+4, 0x55);
+    if (_q->depth > 1) interleaver_permute_mask_soft(_msg_dec, _q->n, _q->M, _q->N+2, 0x0f);
+    if (_q->depth > 0) interleaver_permute_soft(_msg_dec, _q->n, _q->M, _q->N);
+}
+
+// 
+// internal permutation methods
+//
+
+// permute one iteration
+void interleaver_permute(unsigned char * _x,
+                         unsigned int    _n,
+                         unsigned int    _M,
+                         unsigned int    _N)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int m=0;
+    unsigned int n=_n/3;
+    unsigned int n2=_n/2;
+    unsigned char tmp;
+    for (i=0; i<n2; i++) {
+        //j = m*N + n; // input
+        do {
+            j = m*_N + n; // output
+            m++;
+            if (m == _M) {
+                n = (n+1) % (_N);
+                m=0;
+            }
+        } while (j>=n2);
+
+        // swap indices
+        tmp = _x[2*j+1];
+        _x[2*j+1] = _x[2*i+0];
+        _x[2*i+0] = tmp;
+    }
+}
+
+// permute one iteration (soft bit input)
+void interleaver_permute_soft(unsigned char * _x,
+                              unsigned int    _n,
+                              unsigned int    _M,
+                              unsigned int    _N)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int m=0;
+    unsigned int n=_n/3;
+    unsigned int n2=_n/2;
+    unsigned char tmp[8];
+    for (i=0; i<n2; i++) {
+        //j = m*N + n; // input
+        do {
+            j = m*_N + n; // output
+            m++;
+            if (m == _M) {
+                n = (n+1) % (_N);
+                m=0;
+            }
+        } while (j>=n2);
+    
+        // swap soft bits at indices
+        memmove( tmp,            &_x[8*(2*j+1)], 8);
+        memmove( &_x[8*(2*j+1)], &_x[8*(2*i+0)], 8);
+        memmove( &_x[8*(2*i+0)], tmp,            8);
+    }
+    //printf("\n");
+}
+
+
+// permute one iteration with mask
+void interleaver_permute_mask(unsigned char * _x,
+                              unsigned int    _n,
+                              unsigned int    _M,
+                              unsigned int    _N,
+                              unsigned char   _mask)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int m=0;
+    unsigned int n=_n/3;
+    unsigned int n2=_n/2;
+    unsigned char tmp0;
+    unsigned char tmp1;
+    for (i=0; i<n2; i++) {
+        //j = m*N + n; // input
+        do {
+            j = m*_N + n; // output
+            m++;
+            if (m == _M) {
+                n = (n+1) % (_N);
+                m=0;
+            }
+        } while (j>=n2);
+
+        // swap indices, applying mask
+        tmp0 = (_x[2*i+0] & (~_mask)) | (_x[2*j+1] & ( _mask));
+        tmp1 = (_x[2*i+0] & ( _mask)) | (_x[2*j+1] & (~_mask));
+        _x[2*i+0] = tmp0;
+        _x[2*j+1] = tmp1;
+    }
+}
+
+// permute one iteration (soft bit input)
+void interleaver_permute_mask_soft(unsigned char * _x,
+                                   unsigned int    _n,
+                                   unsigned int    _M,
+                                   unsigned int    _N,
+                                   unsigned char   _mask)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+    unsigned int m=0;
+    unsigned int n=_n/3;
+    unsigned int n2=_n/2;
+    unsigned char tmp;
+    for (i=0; i<n2; i++) {
+        //j = m*N + n; // input
+        do {
+            j = m*_N + n; // output
+            m++;
+            if (m == _M) {
+                n = (n+1) % (_N);
+                m=0;
+            }
+        } while (j>=n2);
+
+        // swap bits matching the mask
+        for (k=0; k<8; k++) {
+            if ( (_mask >> (8-k-1)) & 0x01 ) {
+                tmp = _x[8*(2*j+1)+k];
+                _x[8*(2*j+1)+k] = _x[8*(2*i+0)+k];
+                _x[8*(2*i+0)+k] = tmp;
+            }
+        }
+    }
+    //printf("\n");
+}
+
diff --git a/src/fec/src/packetizer.c b/src/fec/src/packetizer.c
new file mode 100644
index 0000000..efd05dc
--- /dev/null
+++ b/src/fec/src/packetizer.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Packetizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// reallocate memory for buffers
+void packetizer_realloc_buffers(packetizer _p, unsigned int _len);
+
+// computes the number of encoded bytes after packetizing
+//
+//  _n      :   number of uncoded input bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+unsigned int packetizer_compute_enc_msg_len(unsigned int _n,
+                                            int _crc,
+                                            int _fec0,
+                                            int _fec1)
+{
+    unsigned int k = _n + crc_get_length(_crc);
+    unsigned int n0 = fec_get_enc_msg_length(_fec0, k);
+    unsigned int n1 = fec_get_enc_msg_length(_fec1, n0);
+
+    return n1;
+}
+
+// computes the number of decoded bytes before packetizing
+//
+//  _k      :   number of encoded bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+unsigned int packetizer_compute_dec_msg_len(unsigned int _k,
+                                            int _crc,
+                                            int _fec0,
+                                            int _fec1)
+{
+    unsigned int n_hat = 0;
+    unsigned int k_hat = 0;
+
+    // check for zero-length packet
+    // TODO : implement faster method
+    while (k_hat < _k) {
+        // compute encoded packet length
+        k_hat = packetizer_compute_enc_msg_len(n_hat, _crc, _fec0, _fec1);
+
+        //
+        if (k_hat == _k)
+            return n_hat;
+        else if (k_hat > _k)
+            return n_hat;   // TODO : check this special condition
+        else
+            n_hat++;
+    }
+
+    return 0;
+}
+
+
+// create packetizer object
+//
+//  _n      :   number of uncoded intput bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+packetizer packetizer_create(unsigned int _n,
+                             int _crc,
+                             int _fec0,
+                             int _fec1)
+{
+    packetizer p = (packetizer) malloc(sizeof(struct packetizer_s));
+
+    p->msg_len      = _n;
+    p->packet_len   = packetizer_compute_enc_msg_len(_n, _crc, _fec0, _fec1);
+    p->check        = _crc;
+    p->crc_length   = crc_get_length(p->check);
+
+    // allocate memory for buffers (scale by 8 for soft decoding)
+    p->buffer_len = p->packet_len;
+    p->buffer_0 = (unsigned char*) malloc(8*p->buffer_len);
+    p->buffer_1 = (unsigned char*) malloc(8*p->buffer_len);
+
+    // create plan
+    p->plan_len = 2;
+    p->plan = (struct fecintlv_plan*) malloc((p->plan_len)*sizeof(struct fecintlv_plan));
+
+    // set schemes
+    unsigned int i;
+    unsigned int n0 = _n + p->crc_length;
+    for (i=0; i<p->plan_len; i++) {
+        // set schemes
+        p->plan[i].fs = (i==0) ? _fec0 : _fec1;
+
+        // compute lengths
+        p->plan[i].dec_msg_len = n0;
+        p->plan[i].enc_msg_len = fec_get_enc_msg_length(p->plan[i].fs,
+                                                        p->plan[i].dec_msg_len);
+
+        // create objects
+        p->plan[i].f = fec_create(p->plan[i].fs, NULL);
+        p->plan[i].q = interleaver_create(p->plan[i].enc_msg_len);
+
+        // set interleaver depth to zero if no error correction scheme
+        // is applied to this plan
+        if (p->plan[i].fs == LIQUID_FEC_NONE)
+            interleaver_set_depth(p->plan[i].q, 0);
+
+        // update length
+        n0 = p->plan[i].enc_msg_len;
+    }
+
+    return p;
+}
+
+// re-create packetizer object
+//
+//  _p      :   initialz packetizer object
+//  _n      :   number of uncoded intput bytes
+//  _crc    :   error-detecting scheme
+//  _fec0   :   inner forward error-correction code
+//  _fec1   :   outer forward error-correction code
+packetizer packetizer_recreate(packetizer _p,
+                               unsigned int _n,
+                               int _crc,
+                               int _fec0,
+                               int _fec1)
+{
+    if (_p == NULL) {
+        // packetizer was never created
+        return packetizer_create(_n, _crc, _fec0, _fec1);
+    }
+
+    // check values
+    if (_p->msg_len     ==  _n      &&
+        _p->check       ==  _crc    &&
+        _p->plan[0].fs  ==  _fec0   &&
+        _p->plan[1].fs  ==  _fec1 )
+    {
+        // no change; return input pointer
+        return _p;
+    } else {
+        // something has changed; destroy old object and create new one
+        // TODO : rather than completely destroying object, only change values that are necessary
+        packetizer_destroy(_p);
+        return packetizer_create(_n,_crc,_fec0,_fec1);
+    }
+}
+
+// destroy packetizer object
+void packetizer_destroy(packetizer _p)
+{
+    // free fec, interleaver objects
+    unsigned int i;
+    for (i=0; i<_p->plan_len; i++) {
+        fec_destroy(_p->plan[i].f);
+        interleaver_destroy(_p->plan[i].q);
+    };
+
+    // free plan
+    free(_p->plan);
+
+    // free buffers
+    free(_p->buffer_0);
+    free(_p->buffer_1);
+
+    // free packetizer object
+    free(_p);
+}
+
+// print packetizer object internals
+void packetizer_print(packetizer _p)
+{
+    printf("packetizer [dec: %u, enc: %u]\n", _p->msg_len, _p->packet_len);
+    printf("     : crc      %-10u %-10u %-16s\n",
+            _p->msg_len,
+            _p->msg_len + _p->crc_length,
+            crc_scheme_str[_p->check][0]);
+    unsigned int i;
+    for (i=0; i<_p->plan_len; i++) {
+        printf("%4u : fec      %-10u %-10u %-16s\n",
+            i,
+            _p->plan[i].dec_msg_len,
+            _p->plan[i].enc_msg_len,
+            fec_scheme_str[_p->plan[i].fs][1]);
+    }
+}
+
+// get decoded message length
+unsigned int packetizer_get_dec_msg_len(packetizer _p)
+{
+    return _p->msg_len;
+}
+
+// get encoded message length
+unsigned int packetizer_get_enc_msg_len(packetizer _p)
+{
+    return _p->packet_len;
+}
+
+crc_scheme packetizer_get_crc(packetizer _p)
+{
+    return _p->check;
+}
+
+fec_scheme packetizer_get_fec0(packetizer _p)
+{
+    return _p->plan[0].fs;
+}
+
+fec_scheme packetizer_get_fec1(packetizer _p)
+{
+    return _p->plan[1].fs;
+}
+
+// Execute the packetizer on an input message
+//
+//  _p      :   packetizer object
+//  _msg    :   input message (uncoded bytes)
+//  _pkt    :   encoded output message
+void packetizer_encode(packetizer            _p,
+                       const unsigned char * _msg,
+                       unsigned char *       _pkt)
+{
+    unsigned int i;
+
+    // copy input message to internal buffer[0]
+    memmove(_p->buffer_0, _msg, _p->msg_len);
+
+    // compute crc, append to buffer
+    unsigned int key = crc_generate_key(_p->check, _p->buffer_0, _p->msg_len);
+    for (i=0; i<_p->crc_length; i++) {
+        // append byte to buffer
+        _p->buffer_0[_p->msg_len+_p->crc_length-i-1] = key & 0xff;
+
+        // shift key by 8 bits
+        key >>= 8;
+    }
+
+    // execute fec/interleaver plans
+    for (i=0; i<_p->plan_len; i++) {
+        // run the encoder: buffer[0] > buffer[1]
+        fec_encode(_p->plan[i].f,
+                   _p->plan[i].dec_msg_len,
+                   _p->buffer_0,
+                   _p->buffer_1);
+
+        // run the interleaver: buffer[1] > buffer[0]
+        interleaver_encode(_p->plan[i].q,
+                           _p->buffer_1,
+                           _p->buffer_0);
+    }
+
+    // copy result to output
+    memmove(_pkt, _p->buffer_0, _p->packet_len);
+}
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded bytes)
+//  _msg    :   decoded output message
+int packetizer_decode(packetizer            _p,
+                      const unsigned char * _pkt,
+                      unsigned char *       _msg)
+{
+    // copy coded message to internal buffer[0]
+    memmove(_p->buffer_0, _pkt, _p->packet_len);
+
+    // execute fec/interleaver plans
+    unsigned int i;
+    for (i=_p->plan_len; i>0; i--) {
+        // run the de-interleaver: buffer[0] > buffer[1]
+        interleaver_decode(_p->plan[i-1].q,
+                           _p->buffer_0,
+                           _p->buffer_1);
+
+        // run the decoder: buffer[1] > buffer[0]
+        fec_decode(_p->plan[i-1].f,
+                   _p->plan[i-1].dec_msg_len,
+                   _p->buffer_1,
+                   _p->buffer_0);
+    }
+
+    // strip crc, validate message
+    unsigned int key = 0;
+    for (i=0; i<_p->crc_length; i++) {
+        key <<= 8;
+
+        key |= _p->buffer_0[_p->msg_len+i];
+    }
+
+    // copy result to output
+    memmove(_msg, _p->buffer_0, _p->msg_len);
+
+    // return crc validity
+    return crc_validate_message(_p->check,
+                                _p->buffer_0,
+                                _p->msg_len,
+                                key);
+}
+
+// Execute the packetizer to decode an input message, return validity
+// check of resulting data
+//
+//  _p      :   packetizer object
+//  _pkt    :   input message (coded soft bits)
+//  _msg    :   decoded output message
+int packetizer_decode_soft(packetizer            _p,
+                           const unsigned char * _pkt,
+                           unsigned char *       _msg)
+{
+    // copy coded message to internal buffer[0]
+    memmove(_p->buffer_0, _pkt, 8*_p->packet_len);
+
+    // 
+    // decode outer level using soft decoding
+    //
+
+    // run the de-interleaver: buffer[0] > buffer[1]
+    interleaver_decode_soft(_p->plan[1].q,
+                            _p->buffer_0,
+                            _p->buffer_1);
+
+    // run the decoder: buffer[1] > buffer[0]
+    fec_decode_soft(_p->plan[1].f,
+                    _p->plan[1].dec_msg_len,
+                    _p->buffer_1,
+                    _p->buffer_0);
+
+    // 
+    // decode inner level using hard decoding
+    //
+
+    // run the de-interleaver: buffer[0] > buffer[1]
+    interleaver_decode(_p->plan[0].q,
+                       _p->buffer_0,
+                       _p->buffer_1);
+
+    // run the decoder: buffer[1] > buffer[0]
+    fec_decode(_p->plan[0].f,
+               _p->plan[0].dec_msg_len,
+               _p->buffer_1,
+               _p->buffer_0);
+
+    // strip crc, validate message
+    unsigned int key = 0;
+    unsigned int i;
+    for (i=0; i<_p->crc_length; i++) {
+        key <<= 8;
+
+        key |= _p->buffer_0[_p->msg_len+i];
+    }
+
+    // copy result to output
+    memmove(_msg, _p->buffer_0, _p->msg_len);
+
+    // return crc validity
+    return crc_validate_message(_p->check,
+                                _p->buffer_0,
+                                _p->msg_len,
+                                key);
+}
+
+void packetizer_set_scheme(packetizer _p, int _fec0, int _fec1)
+{
+    //
+}
+
+// 
+// internal methods
+//
+
+void packetizer_realloc_buffers(packetizer _p, unsigned int _len)
+{
+    _p->buffer_len = _len;
+    _p->buffer_0 = (unsigned char*) realloc(_p->buffer_0, _p->buffer_len);
+    _p->buffer_1 = (unsigned char*) realloc(_p->buffer_1, _p->buffer_len);
+}
+
diff --git a/src/fec/src/sumproduct.c b/src/fec/src/sumproduct.c
new file mode 100644
index 0000000..2a28e62
--- /dev/null
+++ b/src/fec/src/sumproduct.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sumproduct.c
+//
+// floating-point implementation of the sum-product algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_SUMPRODUCT 0
+
+// phi(x) = -logf( tanhf( x/2 ) )
+float sumproduct_phi(float _x) {
+    return -logf(tanhf(_x/2.0f + 1e-12));
+}
+
+// iterate over the sum-product algorithm:
+// returns 1 if parity checks, 0 otherwise
+//  _m          :   rows
+//  _n          :   cols
+//  _H          :   sparse binary parity check matrix [size: _m x _n]
+//  _LLR        :   received signal (soft bits, LLR) [size: _n x 1]
+//  _c_hat      :   estimated transmitted signal [size: _n x 1]
+//  _max_steps  :   maximum number of steps before bailing
+int fec_sumproduct(unsigned int    _m,
+                   unsigned int    _n,
+                   smatrixb        _H,
+                   float *         _LLR,
+                   unsigned char * _c_hat,
+                   unsigned int    _max_steps)
+{
+    // TODO : validate input
+    if (_n == 0 || _m == 0) {
+        fprintf(stderr,"error: fec_sumproduct(), matrix dimensions cannot be zero\n");
+        exit(1);
+    }
+
+    // internal variables
+    unsigned int num_iterations = 0;
+    float Lq[_m*_n];
+    float Lr[_m*_n];
+    float Lc[_n];
+    float LQ[_n];
+    unsigned char parity[_m];
+    unsigned int i;
+    unsigned int j;
+    int parity_pass;
+    int continue_running = 1;
+
+    // initialize Lq with log-likelihood values
+    for (i=0; i<_n; i++)
+        Lc[i] = _LLR[i];
+        //Lc[i] = 2.0f * _y[i] / (sigma*sigma);
+
+    for (j=0; j<_m; j++) {
+        for (i=0; i<_n; i++) {
+            Lq[j*_n+i] = smatrixb_get(_H,j,i) ? Lc[i] : 0.0f;
+        }
+    }
+
+#if DEBUG_SUMPRODUCT
+    // print Lc
+    matrixf_print(Lc,1,_n);
+#endif
+
+    // TODO : run multiple iterations
+    while (continue_running) {
+#if DEBUG_SUMPRODUCT
+        //
+        printf("\n");
+        printf("************* iteration %u ****************\n", num_iterations);
+#endif
+
+        // step sum-product algorithm
+        //parity_pass = fec_sumproduct_step(_H,_m,_n,_c_hat,Lq,Lr,Lc,LQ,parity);
+        parity_pass = fec_sumproduct_step(_m,_n,_H,_c_hat,Lq,Lr,Lc,LQ,parity);
+
+        // update...
+        num_iterations++;
+        if (parity_pass || num_iterations == _max_steps)
+            continue_running = 0;
+    }
+
+    return parity_pass;
+}
+
+// sum-product algorithm, returns 1 if parity checks, 0 otherwise
+//  _m      :   rows
+//  _n      :   cols
+//  _H      :   sparse binary parity check matrix [size: _m x _n]
+//  _c_hat  :   estimated transmitted signal [size: _n x 1]
+//
+// internal state arrays
+//  _Lq     :   [size: _m x _n]
+//  _Lr     :   [size: _m x _n]
+//  _Lc     :   [size: _n x 1]
+//  _LQ     :   [size: _n x 1]
+//  _parity :   _H * _c_hat [size: _m x 1]
+int fec_sumproduct_step(unsigned int    _m,
+                        unsigned int    _n,
+                        smatrixb        _H,
+                        unsigned char * _c_hat,
+                        float *         _Lq,
+                        float *         _Lr,
+                        float *         _Lc,
+                        float *         _LQ,
+                        unsigned char * _parity)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int ip;
+    unsigned int jp;
+    float alpha_prod;
+    float phi_sum;
+    int parity_pass;
+
+    // compute Lr
+    for (i=0; i<_n; i++) {
+        for (j=0; j<_m; j++) {
+            alpha_prod = 1.0f;
+            phi_sum    = 0.0f;
+            for (ip=0; ip<_n; ip++) {
+                //if (_H[j*_n+ip]==1 && i != ip) {
+                if (smatrixb_get(_H,j,ip)==1 && i != ip) {
+                    float alpha = _Lq[j*_n+ip] > 0.0f ? 1.0f : -1.0f;
+                    float beta  = fabsf(_Lq[j*_n+ip]);
+                    phi_sum += sumproduct_phi(beta);
+                    alpha_prod *= alpha;
+                }
+            }
+            _Lr[j*_n+i] = alpha_prod * sumproduct_phi(phi_sum);
+        }
+    }
+
+#if DEBUG_SUMPRODUCT
+    // print Lq
+    matrixf_print(_Lq,_m,_n);
+
+    // print Lr
+    matrixf_print(_Lr,_m,_n);
+#endif
+
+    // compute next iteration of Lq
+    for (i=0; i<_n; i++) {
+        for (j=0; j<_m; j++) {
+            // initialize with LLR
+            _Lq[j*_n+i] = _Lc[i];
+
+            for (jp=0; jp<_m; jp++) {
+                //if (_H[jp*_n+i]==1 && j != jp)
+                if (smatrixb_get(_H,jp,i)==1 && j != jp)
+                    _Lq[j*_n+i] += _Lr[jp*_n+i];
+            }
+        }
+    }
+
+#if DEBUG_SUMPRODUCT
+    // print Lq
+    matrixf_print(_Lq,_m,_n);
+#endif
+
+    // compute LQ
+    for (i=0; i<_n; i++) {
+        _LQ[i] = _Lc[i];  // initialize with LLR value
+
+        for (j=0; j<_m; j++) {
+            //if (_H[j*_n+i]==1)
+            if (smatrixb_get(_H,j,i)==1)
+                _LQ[i] += _Lr[j*_n+i];
+        }
+    }
+
+#if DEBUG_SUMPRODUCT
+    // print LQ
+    matrixf_print(_LQ,1,_n);
+#endif
+
+    // compute hard-decoded value
+    for (i=0; i<_n; i++)
+        _c_hat[i] = _LQ[i] < 0.0f ? 1 : 0;
+
+    // compute parity check: p = H*c_hat
+    smatrixb_vmul(_H, _c_hat, _parity);
+
+    // check parity
+    parity_pass = 1;
+    for (j=0; j<_m; j++) {
+        if (_parity[j]) parity_pass = 0;
+    }
+
+#if DEBUG_SUMPRODUCT
+    // print hard-decision output
+    printf("    : c hat = [");
+    for (i=0; i<_n; i++)
+        printf(" %1u", _c_hat[i]);
+    printf(" ],  ");
+
+    // print parity
+    printf("parity = [");
+    for (j=0; j<_m; j++)
+        printf(" %1u", _parity[j]);
+    printf(" ],  ");
+
+    printf(" (%s)\n", parity_pass ? "pass" : "FAIL");
+#endif
+
+    return parity_pass;
+}
+
diff --git a/src/fec/tests/crc_autotest.c b/src/fec/tests/crc_autotest.c
new file mode 100644
index 0000000..eb4c436
--- /dev/null
+++ b/src/fec/tests/crc_autotest.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: reverse byte
+//
+void autotest_reverse_byte()
+{
+    // 0110 0010
+    unsigned char b = 0x62;
+
+    // 0100 0110
+    unsigned char r = 0x46;
+
+    // 
+    CONTEND_EQUALITY(liquid_reverse_byte(b),r);
+}
+
+//
+// AUTOTEST: reverse uint16_t
+//
+void autotest_reverse_uint16()
+{
+    // 1111 0111 0101 1001
+    unsigned int b = 0xF759;
+
+    // 1001 1010 1110 1111
+    unsigned int r = 0x9AEF;
+
+    // 
+    CONTEND_EQUALITY(liquid_reverse_uint16(b),r);
+}
+
+
+
+//
+// AUTOTEST: reverse uint32_t
+//
+void autotest_reverse_uint32()
+{
+    // 0110 0010 1101 1001 0011 1011 1111 0000
+    unsigned int b = 0x62D93BF0;
+
+    // 0000 1111 1101 1100 1001 1011 0100 0110
+    unsigned int r = 0x0FDC9B46;
+
+    // 
+    CONTEND_EQUALITY(liquid_reverse_uint32(b),r);
+}
+
+// 
+// autotest helper function
+//
+void validate_crc(crc_scheme _check,
+                  unsigned int _n)
+{
+    unsigned int i;
+
+    // generate pseudo-random data
+    unsigned char data[_n];
+    msequence ms = msequence_create_default(9);
+    for (i=0; i<_n; i++)
+        data[i] = msequence_generate_symbol(ms,8);
+    msequence_destroy(ms);
+
+    // generate key
+    unsigned int key = crc_generate_key(_check, data, _n);
+
+    // contend data/key are valid
+    CONTEND_EXPRESSION(crc_validate_message(_check, data, _n, key));
+
+    //
+    unsigned char data_corrupt[_n];
+    unsigned int j;
+    for (i=0; i<_n; i++) {
+        for (j=0; j<8; j++) {
+            // copy original data sequence
+            memmove(data_corrupt, data, _n*sizeof(unsigned char));
+
+            // flip bit j at byte i
+            data[i] ^= (1 << j);
+
+            // contend data/key are invalid
+            CONTEND_EXPRESSION(crc_validate_message(_check, data, _n, key)==0);
+        }
+    }
+}
+
+// 
+// AUTOTESTS : validate error-detection tests
+//
+void autotest_checksum() { validate_crc(LIQUID_CRC_CHECKSUM,    16); }
+void autotest_crc8()     { validate_crc(LIQUID_CRC_8,           16); }
+void autotest_crc16()    { validate_crc(LIQUID_CRC_16,          64); }
+void autotest_crc24()    { validate_crc(LIQUID_CRC_24,          64); }
+void autotest_crc32()    { validate_crc(LIQUID_CRC_32,          64); }
+
+
diff --git a/src/fec/tests/fec_autotest.c b/src/fec/tests/fec_autotest.c
new file mode 100644
index 0000000..49beb8d
--- /dev/null
+++ b/src/fec/tests/fec_autotest.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// Helper function to keep code base small
+void fec_test_codec(fec_scheme _fs, unsigned int _n, void * _opts)
+{
+#if !LIBFEC_ENABLED
+    if ( _fs == LIQUID_FEC_CONV_V27    ||
+         _fs == LIQUID_FEC_CONV_V29    ||
+         _fs == LIQUID_FEC_CONV_V39    ||
+         _fs == LIQUID_FEC_CONV_V615   ||
+         _fs == LIQUID_FEC_CONV_V27P23 ||
+         _fs == LIQUID_FEC_CONV_V27P34 ||
+         _fs == LIQUID_FEC_CONV_V27P45 ||
+         _fs == LIQUID_FEC_CONV_V27P56 ||
+         _fs == LIQUID_FEC_CONV_V27P67 ||
+         _fs == LIQUID_FEC_CONV_V27P78 ||
+         _fs == LIQUID_FEC_CONV_V29P23 ||
+         _fs == LIQUID_FEC_CONV_V29P34 ||
+         _fs == LIQUID_FEC_CONV_V29P45 ||
+         _fs == LIQUID_FEC_CONV_V29P56 ||
+         _fs == LIQUID_FEC_CONV_V29P67 ||
+         _fs == LIQUID_FEC_CONV_V29P78 ||
+         _fs == LIQUID_FEC_RS_M8)
+    {
+        AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)\n");
+        return;
+    }
+#endif
+
+    // generate fec object
+    fec q = fec_create(_fs,_opts);
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(_fs,_n);
+    unsigned char msg[_n];          // original message
+    unsigned char msg_enc[n_enc];   // encoded message
+    unsigned char msg_dec[_n];      // decoded message
+
+    // initialze message
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        msg[i] = rand() & 0xff;
+        msg_dec[i] = 0;
+    }
+
+    // encode message
+    fec_encode(q,_n,msg,msg_enc);
+
+    // channel: add single error
+    msg_enc[0] ^= 0x01;
+
+    // decode message
+    fec_decode(q,_n,msg_enc,msg_dec);
+
+    // validate output
+    CONTEND_SAME_DATA(msg,msg_dec,_n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+// 
+// AUTOTESTS: basic encode/decode functionality
+//
+
+// repeat codes
+void autotest_fec_r3()      { fec_test_codec(LIQUID_FEC_REP3,          64, NULL); }
+void autotest_fec_r5()      { fec_test_codec(LIQUID_FEC_REP5,          64, NULL); }
+
+// Hamming block codes
+void autotest_fec_h74()     { fec_test_codec(LIQUID_FEC_HAMMING74,     64, NULL); }
+void autotest_fec_h84()     { fec_test_codec(LIQUID_FEC_HAMMING84,     64, NULL); }
+void autotest_fec_h128()    { fec_test_codec(LIQUID_FEC_HAMMING128,    64, NULL); }
+
+// Golay block codes
+void autotest_fec_g2412()   { fec_test_codec(LIQUID_FEC_GOLAY2412,     64, NULL); }
+
+// SEC-DED block codecs
+void autotest_fec_secded2216() { fec_test_codec(LIQUID_FEC_SECDED2216, 64, NULL); }
+void autotest_fec_secded3932() { fec_test_codec(LIQUID_FEC_SECDED3932, 64, NULL); }
+void autotest_fec_secded7264() { fec_test_codec(LIQUID_FEC_SECDED7264, 64, NULL); }
+
+// convolutional codes
+void autotest_fec_v27()     { fec_test_codec(LIQUID_FEC_CONV_V27,      64, NULL); }
+void autotest_fec_v29()     { fec_test_codec(LIQUID_FEC_CONV_V29,      64, NULL); }
+void autotest_fec_v39()     { fec_test_codec(LIQUID_FEC_CONV_V39,      64, NULL); }
+void autotest_fec_v615()    { fec_test_codec(LIQUID_FEC_CONV_V615,     64, NULL); }
+
+// convolutional codes (punctured)
+void autotest_fec_v27p23()  { fec_test_codec(LIQUID_FEC_CONV_V27P23,   64, NULL); }
+void autotest_fec_v27p34()  { fec_test_codec(LIQUID_FEC_CONV_V27P34,   64, NULL); }
+void autotest_fec_v27p45()  { fec_test_codec(LIQUID_FEC_CONV_V27P45,   64, NULL); }
+void autotest_fec_v27p56()  { fec_test_codec(LIQUID_FEC_CONV_V27P56,   64, NULL); }
+void autotest_fec_v27p67()  { fec_test_codec(LIQUID_FEC_CONV_V27P67,   64, NULL); }
+void autotest_fec_v27p78()  { fec_test_codec(LIQUID_FEC_CONV_V27P78,   64, NULL); }
+
+void autotest_fec_v29p23()  { fec_test_codec(LIQUID_FEC_CONV_V29P23,   64, NULL); }
+void autotest_fec_v29p34()  { fec_test_codec(LIQUID_FEC_CONV_V29P34,   64, NULL); }
+void autotest_fec_v29p45()  { fec_test_codec(LIQUID_FEC_CONV_V29P45,   64, NULL); }
+void autotest_fec_v29p56()  { fec_test_codec(LIQUID_FEC_CONV_V29P56,   64, NULL); }
+void autotest_fec_v29p67()  { fec_test_codec(LIQUID_FEC_CONV_V29P67,   64, NULL); }
+void autotest_fec_v29p78()  { fec_test_codec(LIQUID_FEC_CONV_V29P78,   64, NULL); }
+
+// Reed-Solomon block codes
+void autotest_fec_rs8()     { fec_test_codec(LIQUID_FEC_RS_M8,         64, NULL); }
+
+
diff --git a/src/fec/tests/fec_golay2412_autotest.c b/src/fec/tests/fec_golay2412_autotest.c
new file mode 100644
index 0000000..ff8f88f
--- /dev/null
+++ b/src/fec/tests/fec_golay2412_autotest.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// generate random error vector with 'n' ones;
+// maybe not efficient but effective
+unsigned int golay2412_generate_error_vector(unsigned int _n)
+{
+    if (_n > 24) {
+        fprintf(stderr,"error: golay2412_generate_error_vector(), cannot generate more than 24 errors\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    unsigned int error_locations[24];
+    for (i=0; i<24; i++)
+        error_locations[i] = 0;
+
+    unsigned int t=0;
+    for (i=0; i<_n; i++) {
+        do {
+            // generate random error location
+            t = rand() % 24;
+
+            // check error location
+        } while (error_locations[t]);
+
+        error_locations[t] = 1;
+    }
+
+    // generate error vector
+    unsigned int e = 0;
+    for (i=0; i<24; i++)
+        e |= error_locations[i] ? (1 << i) : 0;
+
+    return e;
+}
+
+//
+// AUTOTEST: Golay(24,12) codec
+//
+void autotest_golay2412_codec()
+{
+    unsigned int num_trials=50; // number of symbol trials
+    unsigned int num_errors;    // number of errors
+    unsigned int i;
+
+    for (num_errors=0; num_errors<=3; num_errors++) {
+        for (i=0; i<num_trials; i++) {
+            // generate symbol
+            unsigned int sym_org = rand() % (1<<12);
+
+            // encoded symbol
+            unsigned int sym_enc = fec_golay2412_encode_symbol(sym_org);
+
+            // generate error vector
+            unsigned int e = golay2412_generate_error_vector(num_errors);
+
+            // received symbol
+            unsigned int sym_rec = sym_enc ^ e;
+
+            // decoded symbol
+            unsigned int sym_dec = fec_golay2412_decode_symbol(sym_rec);
+
+#if 0
+            printf("error index : %u\n", i);
+            // print results
+            printf("    sym org     :   "); liquid_print_bitstring(sym_org, n); printf("\n");
+            printf("    sym enc     :   "); liquid_print_bitstring(sym_enc, k); printf("\n");
+            printf("    sym rec     :   "); liquid_print_bitstring(sym_rec, k); printf("\n");
+            printf("    sym dec     :   "); liquid_print_bitstring(sym_dec, n); printf("\n");
+
+            // print number of bit errors
+            printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+#endif
+
+            // validate data are the same
+            CONTEND_EQUALITY(sym_org, sym_dec);
+        }
+    }
+}
+
diff --git a/src/fec/tests/fec_hamming128_autotest.c b/src/fec/tests/fec_hamming128_autotest.c
new file mode 100644
index 0000000..185f4c0
--- /dev/null
+++ b/src/fec/tests/fec_hamming128_autotest.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Hamming (12,8) codec
+//
+void autotest_hamming128_codec()
+{
+    unsigned int n=8;   //
+    unsigned int k=12;  //
+    unsigned int i;     // index of bit to corrupt
+
+    for (i=0; i<k; i++) {
+        // generate symbol
+        unsigned int sym_org = rand() % (1<<n);
+
+        // encoded symbol
+        unsigned int sym_enc = fec_hamming128_encode_symbol(sym_org);
+
+        // received symbol
+        unsigned int sym_rec = sym_enc ^ (1<<(k-i-1));
+
+        // decoded symbol
+        unsigned int sym_dec = fec_hamming128_decode_symbol(sym_rec);
+
+        if (liquid_autotest_verbose) {
+            printf("error index : %u\n", i);
+            // print results
+            printf("    sym org     :   "); liquid_print_bitstring(sym_org, n); printf("\n");
+            printf("    sym enc     :   "); liquid_print_bitstring(sym_enc, k); printf("\n");
+            printf("    sym rec     :   "); liquid_print_bitstring(sym_rec, k); printf("\n");
+            printf("    sym dec     :   "); liquid_print_bitstring(sym_dec, n); printf("\n");
+
+            // print number of bit errors
+            printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+        }
+
+        // validate data are the same
+        CONTEND_EQUALITY(sym_org, sym_dec);
+    }
+}
+
+//
+// AUTOTEST: Hamming (12,8) codec (soft decoding)
+//
+void autotest_hamming128_codec_soft()
+{
+    // generate each of the 2^8=256 symbols, encode, and decode
+    // using soft decoding algorithm
+    unsigned int s;             // original 8-bit symbol
+    unsigned int c;             // encoded 8-bit symbol
+    unsigned char c_soft[12];   // soft bits
+    unsigned int s_hat;         // decoded symbol
+
+    for (s=0; s<256; s++) {
+        // encode using internal method
+        c = fec_hamming128_encode_symbol(s);
+
+        // expand soft bits
+        c_soft[ 0] = (c & 0x0800) ? 255 : 0;
+        c_soft[ 1] = (c & 0x0400) ? 255 : 0;
+        c_soft[ 2] = (c & 0x0200) ? 255 : 0;
+        c_soft[ 3] = (c & 0x0100) ? 255 : 0;
+        c_soft[ 4] = (c & 0x0080) ? 255 : 0;
+        c_soft[ 5] = (c & 0x0040) ? 255 : 0;
+        c_soft[ 6] = (c & 0x0020) ? 255 : 0;
+        c_soft[ 7] = (c & 0x0010) ? 255 : 0;
+        c_soft[ 8] = (c & 0x0008) ? 255 : 0;
+        c_soft[ 9] = (c & 0x0004) ? 255 : 0;
+        c_soft[10] = (c & 0x0002) ? 255 : 0;
+        c_soft[11] = (c & 0x0001) ? 255 : 0;
+
+        // decode using internal soft decoding method
+        s_hat = fecsoft_hamming128_decode(c_soft);
+
+        // contend that data are the same
+        CONTEND_EQUALITY(s, s_hat);
+    }
+}
+
diff --git a/src/fec/tests/fec_hamming1511_autotest.c b/src/fec/tests/fec_hamming1511_autotest.c
new file mode 100644
index 0000000..85c3c48
--- /dev/null
+++ b/src/fec/tests/fec_hamming1511_autotest.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Hamming (15,11) codec
+//
+void autotest_hamming1511_codec()
+{
+    unsigned int n=11;  //
+    unsigned int k=15;  //
+    unsigned int i;     // index of bit to corrupt
+
+    for (i=0; i<k; i++) {
+        // generate symbol
+        unsigned int sym_org = rand() % (1<<n);
+
+        // encoded symbol
+        unsigned int sym_enc = fec_hamming1511_encode_symbol(sym_org);
+
+        // received symbol
+        unsigned int sym_rec = sym_enc ^ (1<<(k-i-1));
+
+        // decoded symbol
+        unsigned int sym_dec = fec_hamming1511_decode_symbol(sym_rec);
+
+        if (liquid_autotest_verbose) {
+            printf("error index : %u\n", i);
+            // print results
+            printf("    sym org     :   "); liquid_print_bitstring(sym_org, n); printf("\n");
+            printf("    sym enc     :   "); liquid_print_bitstring(sym_enc, k); printf("\n");
+            printf("    sym rec     :   "); liquid_print_bitstring(sym_rec, k); printf("\n");
+            printf("    sym dec     :   "); liquid_print_bitstring(sym_dec, n); printf("\n");
+
+            // print number of bit errors
+            printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+        }
+
+        // validate data are the same
+        CONTEND_EQUALITY(sym_org, sym_dec);
+    }
+}
+
diff --git a/src/fec/tests/fec_hamming3126_autotest.c b/src/fec/tests/fec_hamming3126_autotest.c
new file mode 100644
index 0000000..a3731c0
--- /dev/null
+++ b/src/fec/tests/fec_hamming3126_autotest.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Hamming (31,26) codec
+//
+void autotest_hamming3126_codec()
+{
+    unsigned int n=26;  //
+    unsigned int k=31;  //
+    unsigned int i;     // index of bit to corrupt
+
+    for (i=0; i<k; i++) {
+        // generate symbol
+        unsigned int sym_org = rand() % (1<<n);
+
+        // encoded symbol
+        unsigned int sym_enc = fec_hamming3126_encode_symbol(sym_org);
+
+        // received symbol
+        unsigned int sym_rec = sym_enc ^ (1<<(k-i-1));
+
+        // decoded symbol
+        unsigned int sym_dec = fec_hamming3126_decode_symbol(sym_rec);
+
+        if (liquid_autotest_verbose) {
+            printf("error index : %u\n", i);
+            // print results
+            printf("    sym org     :   "); liquid_print_bitstring(sym_org, n); printf("\n");
+            printf("    sym enc     :   "); liquid_print_bitstring(sym_enc, k); printf("\n");
+            printf("    sym rec     :   "); liquid_print_bitstring(sym_rec, k); printf("\n");
+            printf("    sym dec     :   "); liquid_print_bitstring(sym_dec, n); printf("\n");
+
+            // print number of bit errors
+            printf("    bit errors  :   %u\n", count_bit_errors(sym_org, sym_dec));
+        }
+
+        // validate data are the same
+        CONTEND_EQUALITY(sym_org, sym_dec);
+    }
+}
+
diff --git a/src/fec/tests/fec_hamming74_autotest.c b/src/fec/tests/fec_hamming74_autotest.c
new file mode 100644
index 0000000..b30f110
--- /dev/null
+++ b/src/fec/tests/fec_hamming74_autotest.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Hamming (7,4) codec
+//
+void autotest_hamming74_codec()
+{
+    unsigned int n=4;
+    unsigned char msg[] = {0x25, 0x62, 0x3F, 0x52};
+    fec_scheme fs = LIQUID_FEC_HAMMING74;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    unsigned char msg_dec[n];
+    unsigned char msg_enc[n_enc];
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    if (liquid_autotest_verbose)
+        fec_print(q);
+
+    // encode message
+    fec_encode(q, n, msg, msg_enc);
+    
+    // corrupt encoded message
+    msg_enc[0] ^= 0x04; // position 5
+#if 0
+    msg_enc[1] ^= 0x04; //
+    msg_enc[2] ^= 0x02; //
+    msg_enc[3] ^= 0x01; //
+    msg_enc[4] ^= 0x80; //
+    msg_enc[5] ^= 0x40; //
+    msg_enc[6] ^= 0x20; //
+#endif
+
+    // decode message
+    fec_decode(q, n, msg_enc, msg_dec);
+
+    // validate data are the same
+    CONTEND_SAME_DATA(msg, msg_dec, n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+//
+// AUTOTEST: Hamming (7,4) codec (soft decoding)
+//
+void autotest_hamming74_codec_soft()
+{
+    // generate each of the 2^4=16 symbols, encode, and decode
+    // using soft decoding algorithm
+    unsigned char s;            // original 4-bit symbol
+    unsigned char c;            // encoded 7-bit symbol
+    unsigned char c_soft[7];    // soft bits
+    unsigned char s_hat;        // decoded symbol
+
+    for (s=0; s<16; s++) {
+        // encode using look-up table
+        c = hamming74_enc_gentab[s];
+
+        // expand soft bits
+        c_soft[0] = (c & 0x40) ? 255 : 0;
+        c_soft[1] = (c & 0x20) ? 255 : 0;
+        c_soft[2] = (c & 0x10) ? 255 : 0;
+        c_soft[3] = (c & 0x08) ? 255 : 0;
+        c_soft[4] = (c & 0x04) ? 255 : 0;
+        c_soft[5] = (c & 0x02) ? 255 : 0;
+        c_soft[6] = (c & 0x01) ? 255 : 0;
+
+        // decode using internal soft decoding method
+        s_hat = fecsoft_hamming74_decode(c_soft);
+
+        // contend that data are the same
+        CONTEND_EQUALITY(s, s_hat);
+    }
+}
+
diff --git a/src/fec/tests/fec_hamming84_autotest.c b/src/fec/tests/fec_hamming84_autotest.c
new file mode 100644
index 0000000..0dff0c0
--- /dev/null
+++ b/src/fec/tests/fec_hamming84_autotest.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Hamming (8,4) codec
+//
+void autotest_hamming84_codec()
+{
+    unsigned int n=4;
+    unsigned char msg[] = {0x25, 0x62, 0x3F, 0x52};
+    fec_scheme fs = LIQUID_FEC_HAMMING84;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    unsigned char msg_dec[n];
+    unsigned char msg_enc[n_enc];
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    if (liquid_autotest_verbose)
+        fec_print(q);
+
+    // encode message
+    fec_encode(q, n, msg, msg_enc);
+    
+    // corrupt encoded message
+    msg_enc[0] ^= 0x04; // position 5
+    msg_enc[1] ^= 0x04; //
+    msg_enc[2] ^= 0x02; //
+    msg_enc[3] ^= 0x01; //
+    msg_enc[4] ^= 0x80; //
+    msg_enc[5] ^= 0x40; //
+    msg_enc[6] ^= 0x20; //
+    msg_enc[7] ^= 0x10; //
+
+    // decode message
+    fec_decode(q, n, msg_enc, msg_dec);
+
+    // validate data are the same
+    CONTEND_SAME_DATA(msg, msg_dec, n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+//
+// AUTOTEST: Hamming (8,4) codec (soft decoding)
+//
+void autotest_hamming84_codec_soft()
+{
+    // generate each of the 2^4=16 symbols, encode, and decode
+    // using soft decoding algorithm
+    unsigned char s;            // original 4-bit symbol
+    unsigned char c;            // encoded 8-bit symbol
+    unsigned char c_soft[8];    // soft bits
+    unsigned char s_hat;        // decoded symbol
+
+    for (s=0; s<16; s++) {
+        // encode using look-up table
+        c = hamming84_enc_gentab[s];
+
+        // expand soft bits
+        c_soft[0] = (c & 0x80) ? 255 : 0;
+        c_soft[1] = (c & 0x40) ? 255 : 0;
+        c_soft[2] = (c & 0x20) ? 255 : 0;
+        c_soft[3] = (c & 0x10) ? 255 : 0;
+        c_soft[4] = (c & 0x08) ? 255 : 0;
+        c_soft[5] = (c & 0x04) ? 255 : 0;
+        c_soft[6] = (c & 0x02) ? 255 : 0;
+        c_soft[7] = (c & 0x01) ? 255 : 0;
+
+        // decode using internal soft decoding method
+        s_hat = fecsoft_hamming84_decode(c_soft);
+
+        // contend that data are the same
+        CONTEND_EQUALITY(s, s_hat);
+    }
+}
+
diff --git a/src/fec/tests/fec_reedsolomon_autotest.c b/src/fec/tests/fec_reedsolomon_autotest.c
new file mode 100644
index 0000000..7f15ab9
--- /dev/null
+++ b/src/fec/tests/fec_reedsolomon_autotest.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: Reed-Solomon codecs
+//
+void autotest_reedsolomon_223_255()
+{
+#if !LIBFEC_ENABLED
+    printf("warning: Reed-Solomon codes unavailable\n");
+    return;
+#endif
+
+    unsigned int dec_msg_len = 223;
+
+    // compute and test encoded message length
+    unsigned int enc_msg_len = fec_get_enc_msg_length(LIQUID_FEC_RS_M8,dec_msg_len);
+    CONTEND_EQUALITY( enc_msg_len, 255 );
+
+    // create arrays
+    unsigned char msg_org[dec_msg_len]; // original message
+    unsigned char msg_enc[enc_msg_len]; // encoded message
+    unsigned char msg_rec[enc_msg_len]; // received message
+    unsigned char msg_dec[dec_msg_len]; // decoded message
+
+    // initialize original message
+    unsigned int i;
+    for (i=0; i<dec_msg_len; i++)
+        msg_org[i] = i & 0xff;
+
+    // create object
+    fec q = fec_create(LIQUID_FEC_RS_M8,NULL);
+    if (liquid_autotest_verbose)
+        fec_print(q);
+
+    // encode message
+    fec_encode(q, dec_msg_len, msg_org, msg_enc);
+    
+    // corrupt encoded message; can withstand up to 16 symbol errors
+    memmove(msg_rec, msg_enc, enc_msg_len*sizeof(unsigned char));
+    for (i=0; i<16; i++)
+        msg_rec[i] ^= 0x75;
+
+    // decode message
+    fec_decode(q, dec_msg_len, msg_rec, msg_dec);
+
+    // validate data are the same
+    CONTEND_SAME_DATA(msg_org, msg_dec, dec_msg_len);
+
+    if (liquid_autotest_verbose) {
+        printf("enc   dec\n");
+        printf("---   ---\n");
+        for (i=0; i<dec_msg_len; i++)
+            printf("%3u   %3u\n", msg_org[i], msg_dec[i]);
+    }
+
+    // clean up objects
+    fec_destroy(q);
+}
+
diff --git a/src/fec/tests/fec_rep3_autotest.c b/src/fec/tests/fec_rep3_autotest.c
new file mode 100644
index 0000000..2e425d9
--- /dev/null
+++ b/src/fec/tests/fec_rep3_autotest.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: repeat/3 codec
+//
+void autotest_rep3_codec()
+{
+    unsigned int n=4;
+    unsigned char msg[] = {0x25, 0x62, 0x3F, 0x52};
+    fec_scheme fs = LIQUID_FEC_REP3;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    unsigned char msg_dec[n];
+    unsigned char msg_enc[n_enc];
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    if (liquid_autotest_verbose)
+        fec_print(q);
+
+    // encode message
+    fec_encode(q, n, msg, msg_enc);
+    
+    // corrupt encoded message
+    msg_enc[0] = ~msg_enc[0];
+    msg_enc[1] = ~msg_enc[1];
+    msg_enc[2] = ~msg_enc[2];
+    msg_enc[3] = ~msg_enc[3];
+
+    // decode message
+    fec_decode(q, n, msg_enc, msg_dec);
+
+    // validate data are the same
+    CONTEND_SAME_DATA(msg, msg_dec, n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
diff --git a/src/fec/tests/fec_rep5_autotest.c b/src/fec/tests/fec_rep5_autotest.c
new file mode 100644
index 0000000..d27e09b
--- /dev/null
+++ b/src/fec/tests/fec_rep5_autotest.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: repeat/3 codec
+//
+void autotest_rep5_codec()
+{
+    unsigned int n=4;
+    unsigned char msg[] = {0x25, 0x62, 0x3F, 0x52};
+    fec_scheme fs = LIQUID_FEC_REP5;
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(fs,n);
+    unsigned char msg_dec[n];
+    unsigned char msg_enc[n_enc];
+
+    // create object
+    fec q = fec_create(fs,NULL);
+    if (liquid_autotest_verbose)
+        fec_print(q);
+
+    // encode message
+    fec_encode(q, n, msg, msg_enc);
+    
+    // corrupt encoded message, but no so much that it
+    // can't be decoded
+    msg_enc[ 0] = ~msg_enc[ 0];
+    msg_enc[ 4] = ~msg_enc[ 4];
+//  msg_enc[ 8] = ~msg_enc[ 8];
+//  msg_enc[12] = ~msg_enc[12];
+//  msg_enc[16] = ~msg_enc[16];
+
+    msg_enc[ 1] = ~msg_enc[ 1];
+//  msg_enc[ 5] = ~msg_enc[ 5];
+    msg_enc[ 9] = ~msg_enc[ 9];
+//  msg_enc[13] = ~msg_enc[13];
+//  msg_enc[17] = ~msg_enc[17];
+
+//  msg_enc[ 2] = ~msg_enc[ 2];
+//  msg_enc[ 6] = ~msg_enc[ 6];
+    msg_enc[10] = ~msg_enc[10];
+    msg_enc[14] = ~msg_enc[14];
+//  msg_enc[18] = ~msg_enc[18];
+
+    msg_enc[ 3] = ~msg_enc[ 3];
+//  msg_enc[ 7] = ~msg_enc[ 7];
+//  msg_enc[11] = ~msg_enc[11];
+//  msg_enc[15] = ~msg_enc[15];
+    msg_enc[19] = ~msg_enc[19];
+
+    // decode message
+    fec_decode(q, n, msg_enc, msg_dec);
+
+    // validate data are the same
+    CONTEND_SAME_DATA(msg, msg_dec, n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
diff --git a/src/fec/tests/fec_secded2216_autotest.c b/src/fec/tests/fec_secded2216_autotest.c
new file mode 100644
index 0000000..aee3305
--- /dev/null
+++ b/src/fec/tests/fec_secded2216_autotest.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: SEC-DEC (22,16) codec (no errors)
+//
+void autotest_secded2216_codec_e0()
+{
+    // generate symbol
+    unsigned char sym_org[2];
+    sym_org[0] = rand() & 0xffff;
+    sym_org[1] = rand() & 0xffff;
+
+    // encoded symbol
+    unsigned char sym_enc[3];
+    fec_secded2216_encode_symbol(sym_org, sym_enc);
+
+    // decoded symbol
+    unsigned char sym_dec[2];
+    fec_secded2216_decode_symbol(sym_enc, sym_dec);
+
+    // validate data are the same
+    CONTEND_EQUALITY(sym_org[0], sym_dec[0]);
+    CONTEND_EQUALITY(sym_org[1], sym_dec[1]);
+}
+
+//
+// AUTOTEST: SEC-DEC (22,16) codec (single error)
+//
+void autotest_secded2216_codec_e1()
+{
+    unsigned int k; // error location
+
+    for (k=0; k<22; k++) {
+        // generate symbol
+        unsigned char sym_org[2];
+        sym_org[0] = rand() & 0xffff;
+        sym_org[1] = rand() & 0xffff;
+
+        // encoded symbol
+        unsigned char sym_enc[3];
+        fec_secded2216_encode_symbol(sym_org, sym_enc);
+
+        // generate error vector (single error)
+        unsigned char e[3] = {0,0,0};
+        div_t d = div(k,8);
+        e[3-d.quot-1] = 1 << d.rem;
+
+        // received symbol
+        unsigned char sym_rec[3];
+        sym_rec[0] = sym_enc[0] ^ e[0];
+        sym_rec[1] = sym_enc[1] ^ e[1];
+        sym_rec[2] = sym_enc[2] ^ e[2];
+
+        // decoded symbol
+        unsigned char sym_dec[2];
+        fec_secded2216_decode_symbol(sym_rec, sym_dec);
+
+        // validate data are the same
+        CONTEND_EQUALITY(sym_org[0], sym_dec[0]);
+        CONTEND_EQUALITY(sym_org[1], sym_dec[1]);
+    }
+}
+
+//
+// AUTOTEST: SEC-DEC (22,16) codec (double error detection)
+//
+void autotest_secded2216_codec_e2()
+{
+    // total combinations of double errors: nchoosek(22,2) = 231
+
+    unsigned int j;
+    unsigned int k;
+
+    for (j=0; j<21; j++) {
+        if (liquid_autotest_verbose)
+            printf("***** %2u *****\n", j);
+        
+        for (k=0; k<22-j-1; k++) {
+            // generate symbol
+            unsigned char sym_org[2];
+            sym_org[0] = rand() & 0xffff;
+            sym_org[1] = rand() & 0xffff;
+
+            // encoded symbol
+            unsigned char sym_enc[3];
+            fec_secded2216_encode_symbol(sym_org, sym_enc);
+
+            // generate error vector (single error)
+            unsigned char e[3] = {0,0,0};
+
+            div_t dj = div(j,8);
+            e[3-dj.quot-1] |= 1 << dj.rem;
+
+            div_t dk = div(k+j+1,8);
+            e[3-dk.quot-1] |= 1 << dk.rem;
+
+            // received symbol
+            unsigned char sym_rec[3];
+            sym_rec[0] = sym_enc[0] ^ e[0];
+            sym_rec[1] = sym_enc[1] ^ e[1];
+            sym_rec[2] = sym_enc[2] ^ e[2];
+
+            // decoded symbol
+            unsigned char sym_dec[2];
+            int syndrome_flag = fec_secded2216_decode_symbol(sym_rec, sym_dec);
+
+
+            if (liquid_autotest_verbose) {
+                // print error vector
+                printf("%3u, e = ", k);
+                liquid_print_bitstring(e[0], 6);
+                liquid_print_bitstring(e[1], 8);
+                liquid_print_bitstring(e[2], 8);
+                printf(" flag=%2d\n", syndrome_flag);
+            }
+
+            // validate syndrome flag is '2'
+            CONTEND_EQUALITY(syndrome_flag, 2);
+        }
+    }
+}
+
diff --git a/src/fec/tests/fec_secded3932_autotest.c b/src/fec/tests/fec_secded3932_autotest.c
new file mode 100644
index 0000000..d2ced93
--- /dev/null
+++ b/src/fec/tests/fec_secded3932_autotest.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: SEC-DEC (39,32) codec (no errors)
+//
+void autotest_secded3932_codec_e0()
+{
+    // generate symbol
+    unsigned char sym_org[4];
+    sym_org[0] = rand() & 0xffff;
+    sym_org[1] = rand() & 0xffff;
+    sym_org[2] = rand() & 0xffff;
+    sym_org[3] = rand() & 0xffff;
+
+    // encoded symbol
+    unsigned char sym_enc[5];
+    fec_secded3932_encode_symbol(sym_org, sym_enc);
+
+    // decoded symbol
+    unsigned char sym_dec[4];
+    fec_secded3932_decode_symbol(sym_enc, sym_dec);
+
+    // validate data are the same
+    CONTEND_EQUALITY(sym_org[0], sym_dec[0]);
+    CONTEND_EQUALITY(sym_org[1], sym_dec[1]);
+    CONTEND_EQUALITY(sym_org[2], sym_dec[2]);
+    CONTEND_EQUALITY(sym_org[3], sym_dec[3]);
+}
+
+//
+// AUTOTEST: SEC-DEC (39,32) codec (single error)
+//
+void autotest_secded3932_codec_e1()
+{
+    unsigned int k; // error location
+
+    for (k=0; k<39; k++) {
+        // generate symbol
+        unsigned char sym_org[4];
+        sym_org[0] = rand() & 0xffff;
+        sym_org[1] = rand() & 0xffff;
+        sym_org[2] = rand() & 0xffff;
+        sym_org[3] = rand() & 0xffff;
+
+        // encoded symbol
+        unsigned char sym_enc[5];
+        fec_secded3932_encode_symbol(sym_org, sym_enc);
+
+        // generate error vector (single error)
+        unsigned char e[5] = {0,0,0,0,0};
+        div_t d = div(k,8);
+        e[5-d.quot-1] = 1 << d.rem;
+
+        // received symbol
+        unsigned char sym_rec[5];
+        sym_rec[0] = sym_enc[0] ^ e[0];
+        sym_rec[1] = sym_enc[1] ^ e[1];
+        sym_rec[2] = sym_enc[2] ^ e[2];
+        sym_rec[3] = sym_enc[3] ^ e[3];
+        sym_rec[4] = sym_enc[4] ^ e[4];
+
+        // decoded symbol
+        unsigned char sym_dec[4];
+        fec_secded3932_decode_symbol(sym_rec, sym_dec);
+
+        // validate data are the same
+        CONTEND_EQUALITY(sym_org[0], sym_dec[0]);
+        CONTEND_EQUALITY(sym_org[1], sym_dec[1]);
+        CONTEND_EQUALITY(sym_org[2], sym_dec[2]);
+        CONTEND_EQUALITY(sym_org[3], sym_dec[3]);
+    }
+}
+
+//
+// AUTOTEST: SEC-DEC (39,32) codec (double error detection)
+//
+void autotest_secded3932_codec_e2()
+{
+    // total combinations of double errors: nchoosek(39,2) = 741
+
+    unsigned int j;
+    unsigned int k;
+
+    for (j=0; j<39-1; j++) {
+        if (liquid_autotest_verbose)
+            printf("***** %2u *****\n", j);
+        
+        for (k=0; k<39-1-j; k++) {
+            // generate symbol
+            unsigned char sym_org[4];
+            sym_org[0] = rand() & 0xffff;
+            sym_org[1] = rand() & 0xffff;
+            sym_org[2] = rand() & 0xffff;
+            sym_org[3] = rand() & 0xffff;
+
+            // encoded symbol
+            unsigned char sym_enc[5];
+            fec_secded3932_encode_symbol(sym_org, sym_enc);
+
+            // generate error vector (single error)
+            unsigned char e[5] = {0,0,0,0,0};
+
+            div_t dj = div(j,8);
+            e[5-dj.quot-1] |= 1 << dj.rem;
+
+            div_t dk = div(k+j+1,8);
+            e[5-dk.quot-1] |= 1 << dk.rem;
+
+            // received symbol
+            unsigned char sym_rec[5];
+            sym_rec[0] = sym_enc[0] ^ e[0];
+            sym_rec[1] = sym_enc[1] ^ e[1];
+            sym_rec[2] = sym_enc[2] ^ e[2];
+            sym_rec[3] = sym_enc[3] ^ e[3];
+            sym_rec[4] = sym_enc[4] ^ e[4];
+
+            // decoded symbol
+            unsigned char sym_dec[4];
+            int syndrome_flag = fec_secded3932_decode_symbol(sym_rec, sym_dec);
+
+
+            if (liquid_autotest_verbose) {
+                // print error vector
+                printf("%3u, e = ", k);
+                liquid_print_bitstring(e[0], 7);
+                liquid_print_bitstring(e[1], 8);
+                liquid_print_bitstring(e[2], 8);
+                liquid_print_bitstring(e[3], 8);
+                liquid_print_bitstring(e[4], 8);
+                printf(" flag=%2d\n", syndrome_flag);
+            }
+
+            // validate syndrome flag is '2'
+            CONTEND_EQUALITY(syndrome_flag, 2);
+        }
+    }
+}
+
diff --git a/src/fec/tests/fec_secded7264_autotest.c b/src/fec/tests/fec_secded7264_autotest.c
new file mode 100644
index 0000000..2292512
--- /dev/null
+++ b/src/fec/tests/fec_secded7264_autotest.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST: SEC-DEC (72,64) codec (no errors)
+//
+void autotest_secded7264_codec_e0()
+{
+    // arrays
+    unsigned char sym_org[8];   // original symbol
+    unsigned char sym_enc[9];   // encoded symbol
+    unsigned char sym_dec[8];   // decoded symbol
+
+    // generate symbol
+    sym_org[0] = rand() & 0xff;
+    sym_org[1] = rand() & 0xff;
+    sym_org[2] = rand() & 0xff;
+    sym_org[3] = rand() & 0xff;
+    sym_org[4] = rand() & 0xff;
+    sym_org[5] = rand() & 0xff;
+    sym_org[6] = rand() & 0xff;
+    sym_org[7] = rand() & 0xff;
+
+    // encoded symbol
+    fec_secded7264_encode_symbol(sym_org, sym_enc);
+
+    // decoded symbol
+    fec_secded7264_decode_symbol(sym_enc, sym_dec);
+
+    // validate data are the same
+    CONTEND_EQUALITY(sym_org[0], sym_dec[0]);
+    CONTEND_EQUALITY(sym_org[1], sym_dec[1]);
+    CONTEND_EQUALITY(sym_org[2], sym_dec[2]);
+    CONTEND_EQUALITY(sym_org[3], sym_dec[3]);
+    CONTEND_EQUALITY(sym_org[4], sym_dec[4]);
+    CONTEND_EQUALITY(sym_org[5], sym_dec[5]);
+    CONTEND_EQUALITY(sym_org[6], sym_dec[6]);
+    CONTEND_EQUALITY(sym_org[7], sym_dec[7]);
+}
+
+//
+// AUTOTEST: SEC-DEC (72,64) codec (single error)
+//
+void autotest_secded7264_codec_e1()
+{
+    // arrays
+    unsigned char sym_org[8];   // original symbol
+    unsigned char sym_enc[9];   // encoded symbol
+    unsigned char e[9];         // error vector
+    unsigned char sym_rec[9];   // received symbol
+    unsigned char sym_dec[8];   // decoded symbol
+
+    unsigned int i;
+    unsigned int k; // error location
+
+    for (k=0; k<72; k++) {
+        // generate symbol
+        for (i=0; i<8; i++)
+            sym_org[i] = rand() & 0xff;
+
+        // encoded symbol
+        fec_secded7264_encode_symbol(sym_org, sym_enc);
+
+        // generate error vector (single error)
+        for (i=0; i<9; i++)
+            e[i] = 0;
+        div_t d = div(k,8);
+        e[9-d.quot-1] = 1 << d.rem;   // flip bit at index k
+
+        // received symbol
+        for (i=0; i<9; i++)
+            sym_rec[i] = sym_enc[i] ^ e[i];
+
+        // decoded symbol
+        fec_secded7264_decode_symbol(sym_rec, sym_dec);
+
+        // validate data are the same
+        for (i=0; i<8; i++)
+            CONTEND_EQUALITY(sym_org[i], sym_dec[i]);
+    }
+}
+
+//
+// AUTOTEST: SEC-DEC (72,64) codec (double error detection)
+//
+void autotest_secded7264_codec_e2()
+{
+    // total combinations of double errors: nchoosek(72,2) = 2556
+
+    // arrays
+    unsigned char sym_org[8];   // original symbol
+    unsigned char sym_enc[9];   // encoded symbol
+    unsigned char e[9];         // error vector
+    unsigned char sym_rec[9];   // received symbol
+    unsigned char sym_dec[8];   // decoded symbol
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    for (j=0; j<72-1; j++) {
+#if 0
+        if (liquid_autotest_verbose)
+            printf("***** %2u *****\n", j);
+#endif
+        
+        for (k=0; k<72-1-j; k++) {
+            // generate symbol
+            for (i=0; i<8; i++)
+                sym_org[i] = rand() & 0xff;
+
+            // encoded symbol
+            fec_secded7264_encode_symbol(sym_org, sym_enc);
+
+            // generate error vector (single error)
+            for (i=0; i<9; i++)
+                e[i] = 0;
+
+            div_t dj = div(j,8);
+            e[9-dj.quot-1] |= 1 << dj.rem;
+
+            div_t dk = div(k+j+1,8);
+            e[9-dk.quot-1] |= 1 << dk.rem;
+
+            // received symbol
+            for (i=0; i<9; i++)
+                sym_rec[i] = sym_enc[i] ^ e[i];
+
+            // decoded symbol
+            int syndrome_flag = fec_secded7264_decode_symbol(sym_rec, sym_dec);
+
+#if 0
+            if (liquid_autotest_verbose) {
+                // print error vector
+                printf("%3u, e = ", k);
+                liquid_print_bitstring(e[0], 8);
+                liquid_print_bitstring(e[1], 8);
+                liquid_print_bitstring(e[2], 8);
+                liquid_print_bitstring(e[3], 8);
+                liquid_print_bitstring(e[4], 8);
+                liquid_print_bitstring(e[5], 8);
+                liquid_print_bitstring(e[6], 8);
+                liquid_print_bitstring(e[7], 8);
+                liquid_print_bitstring(e[8], 8);
+                printf(" flag=%2d\n", syndrome_flag);
+            }
+#endif
+
+            // validate syndrome flag is '2'
+            CONTEND_EQUALITY(syndrome_flag, 2);
+        }
+    }
+}
+
diff --git a/src/fec/tests/fec_soft_autotest.c b/src/fec/tests/fec_soft_autotest.c
new file mode 100644
index 0000000..098ad38
--- /dev/null
+++ b/src/fec/tests/fec_soft_autotest.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// Test soft-decoding of a particular coding scheme
+// (helper function to keep code base small)
+void fec_test_soft_codec(fec_scheme _fs,
+                         unsigned int _n,
+                         void * _opts)
+{
+#if !LIBFEC_ENABLED
+    if ( _fs == LIQUID_FEC_CONV_V27    ||
+         _fs == LIQUID_FEC_CONV_V29    ||
+         _fs == LIQUID_FEC_CONV_V39    ||
+         _fs == LIQUID_FEC_CONV_V615   ||
+         _fs == LIQUID_FEC_CONV_V27P23 ||
+         _fs == LIQUID_FEC_CONV_V27P34 ||
+         _fs == LIQUID_FEC_CONV_V27P45 ||
+         _fs == LIQUID_FEC_CONV_V27P56 ||
+         _fs == LIQUID_FEC_CONV_V27P67 ||
+         _fs == LIQUID_FEC_CONV_V27P78 ||
+         _fs == LIQUID_FEC_CONV_V29P23 ||
+         _fs == LIQUID_FEC_CONV_V29P34 ||
+         _fs == LIQUID_FEC_CONV_V29P45 ||
+         _fs == LIQUID_FEC_CONV_V29P56 ||
+         _fs == LIQUID_FEC_CONV_V29P67 ||
+         _fs == LIQUID_FEC_CONV_V29P78 ||
+         _fs == LIQUID_FEC_RS_M8)
+    {
+        AUTOTEST_WARN("convolutional, Reed-Solomon codes unavailable (install libfec)\n");
+        return;
+    }
+#endif
+
+    // generate fec object
+    fec q = fec_create(_fs,_opts);
+
+    // create arrays
+    unsigned int n_enc = fec_get_enc_msg_length(_fs,_n);
+    unsigned char msg[_n];              // original message
+    unsigned char msg_enc[n_enc];       // encoded message
+    unsigned char msg_soft[8*n_enc];    // encoded message (soft bits)
+    unsigned char msg_dec[_n];          // decoded message
+
+    // initialze message
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        msg[i] = rand() & 0xff;
+        msg_dec[i] = 0;
+    }
+
+    // encode message
+    fec_encode(q, _n, msg, msg_enc);
+
+    // convert to soft bits
+    for (i=0; i<n_enc; i++) {
+        msg_soft[8*i+0] = (msg_enc[i] & 0x80) ? 255 : 0;
+        msg_soft[8*i+1] = (msg_enc[i] & 0x40) ? 255 : 0;
+        msg_soft[8*i+2] = (msg_enc[i] & 0x20) ? 255 : 0;
+        msg_soft[8*i+3] = (msg_enc[i] & 0x10) ? 255 : 0;
+        msg_soft[8*i+4] = (msg_enc[i] & 0x08) ? 255 : 0;
+        msg_soft[8*i+5] = (msg_enc[i] & 0x04) ? 255 : 0;
+        msg_soft[8*i+6] = (msg_enc[i] & 0x02) ? 255 : 0;
+        msg_soft[8*i+7] = (msg_enc[i] & 0x01) ? 255 : 0;
+    }
+
+    // channel: add single error
+    msg_soft[0] = 255 - msg_soft[0];
+
+    // decode message
+    fec_decode_soft(q, _n, msg_soft, msg_dec);
+
+    // validate output
+    CONTEND_SAME_DATA(msg,msg_dec,_n);
+
+    // clean up objects
+    fec_destroy(q);
+}
+
+// 
+// AUTOTESTS: basic encode/decode functionality
+//
+
+// repeat codes
+void autotest_fecsoft_r3()     { fec_test_soft_codec(LIQUID_FEC_REP3,        64, NULL); }
+void autotest_fecsoft_r5()     { fec_test_soft_codec(LIQUID_FEC_REP5,        64, NULL); }
+
+// Hamming block codes
+void autotest_fecsoft_h74()    { fec_test_soft_codec(LIQUID_FEC_HAMMING74,   64, NULL); }
+void autotest_fecsoft_h84()    { fec_test_soft_codec(LIQUID_FEC_HAMMING84,   64, NULL); }
+void autotest_fecsoft_h128()   { fec_test_soft_codec(LIQUID_FEC_HAMMING128,  64, NULL); }
+
+// convolutional codes
+void autotest_fecsoft_v27()    { fec_test_soft_codec(LIQUID_FEC_CONV_V27,    64, NULL); }
+void autotest_fecsoft_v29()    { fec_test_soft_codec(LIQUID_FEC_CONV_V29,    64, NULL); }
+void autotest_fecsoft_v39()    { fec_test_soft_codec(LIQUID_FEC_CONV_V39,    64, NULL); }
+void autotest_fecsoft_v615()   { fec_test_soft_codec(LIQUID_FEC_CONV_V615,   64, NULL); }
+
+// convolutional codes (punctured)
+void autotest_fecsoft_v27p23() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P23, 64, NULL); }
+void autotest_fecsoft_v27p34() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P34, 64, NULL); }
+void autotest_fecsoft_v27p45() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P45, 64, NULL); }
+void autotest_fecsoft_v27p56() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P56, 64, NULL); }
+void autotest_fecsoft_v27p67() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P67, 64, NULL); }
+void autotest_fecsoft_v27p78() { fec_test_soft_codec(LIQUID_FEC_CONV_V27P78, 64, NULL); }
+
+void autotest_fecsoft_v29p23() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P23, 64, NULL); }
+void autotest_fecsoft_v29p34() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P34, 64, NULL); }
+void autotest_fecsoft_v29p45() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P45, 64, NULL); }
+void autotest_fecsoft_v29p56() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P56, 64, NULL); }
+void autotest_fecsoft_v29p67() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P67, 64, NULL); }
+void autotest_fecsoft_v29p78() { fec_test_soft_codec(LIQUID_FEC_CONV_V29P78, 64, NULL); }
+
+// Reed-Solomon block codes
+void autotest_fecsoft_rs8()    { fec_test_soft_codec(LIQUID_FEC_RS_M8,       64, NULL); }
+
+
diff --git a/src/fec/tests/interleaver_autotest.c b/src/fec/tests/interleaver_autotest.c
new file mode 100644
index 0000000..c6880cc
--- /dev/null
+++ b/src/fec/tests/interleaver_autotest.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTESTS: interleave/deinterleave
+//
+void interleaver_test_hard(unsigned int _n)
+{
+    unsigned int i;
+    unsigned char x[_n];
+    unsigned char y[_n];
+    unsigned char z[_n];
+
+    for (i=0; i<_n; i++)
+        x[i] = rand() & 0xFF;
+
+    // create interleaver object
+    interleaver q = interleaver_create(_n);
+
+    interleaver_encode(q,x,y);
+    interleaver_decode(q,y,z);
+
+    CONTEND_SAME_DATA(x, z, _n);
+
+    // destroy interleaver object
+    interleaver_destroy(q);
+}
+
+// 
+// AUTOTESTS: interleave/deinterleave (soft)
+//
+void interleaver_test_soft(unsigned int _n)
+{
+    unsigned int i;
+    unsigned char x[8*_n];
+    unsigned char y[8*_n];
+    unsigned char z[8*_n];
+
+    for (i=0; i<8*_n; i++)
+        x[i] = rand() & 0xFF;
+
+    // create interleaver object
+    interleaver q = interleaver_create(_n);
+
+    interleaver_encode_soft(q,x,y);
+    interleaver_decode_soft(q,y,z);
+
+    CONTEND_SAME_DATA(x, z, 8*_n);
+    
+    // destroy interleaver object
+    interleaver_destroy(q);
+}
+
+void autotest_interleaver_hard_8()      { interleaver_test_hard(8   ); }
+void autotest_interleaver_hard_16()     { interleaver_test_hard(16  ); }
+void autotest_interleaver_hard_64()     { interleaver_test_hard(64  ); }
+void autotest_interleaver_hard_256()    { interleaver_test_hard(256 ); }
+
+void autotest_interleaver_soft_8()      { interleaver_test_soft(8   ); }
+void autotest_interleaver_soft_16()     { interleaver_test_soft(16  ); }
+void autotest_interleaver_soft_64()     { interleaver_test_soft(64  ); }
+void autotest_interleaver_soft_256()    { interleaver_test_soft(256 ); }
+
diff --git a/src/fec/tests/packetizer_autotest.c b/src/fec/tests/packetizer_autotest.c
new file mode 100644
index 0000000..8af382d
--- /dev/null
+++ b/src/fec/tests/packetizer_autotest.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+void packetizer_test_codec(unsigned int _n,
+                           crc_scheme _crc,
+                           fec_scheme _fec0,
+                           fec_scheme _fec1)
+{
+    unsigned char msg_tx[_n];
+    unsigned char msg_rx[_n];
+    unsigned int pkt_len = packetizer_compute_enc_msg_len(_n,_crc,_fec0,_fec1);
+    unsigned char packet[pkt_len];
+
+    // create object
+    packetizer p = packetizer_create(_n,_crc,_fec0,_fec1);
+
+    if (liquid_autotest_verbose)
+        packetizer_print(p);
+
+    // initialize data
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        msg_tx[i] = i % 256;
+        msg_rx[i] = 0;
+    }
+
+    // encode/decode packet
+    packetizer_encode(p, msg_tx, packet);
+    int crc_pass = packetizer_decode(p, packet, msg_rx);
+
+    CONTEND_SAME_DATA(msg_tx, msg_rx, _n);
+    CONTEND_EQUALITY(crc_pass, 1);
+
+    // clean up objects
+    packetizer_destroy(p);
+}
+
+//
+// AUTOTESTS
+//
+void autotest_packetizer_n16_0_0()  { packetizer_test_codec(16, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_NONE);       }
+void autotest_packetizer_n16_0_1()  { packetizer_test_codec(16, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_REP3);       }
+void autotest_packetizer_n16_0_2()  { packetizer_test_codec(16, LIQUID_CRC_32, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING74);  }
+
diff --git a/src/fft/bench/fft_composite_benchmark.c b/src/fft/bench/fft_composite_benchmark.c
new file mode 100644
index 0000000..29c18d3
--- /dev/null
+++ b/src/fft/bench/fft_composite_benchmark.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_composite_benchmark.c : benchmark FFTs of 'composite' length (not
+//   prime, not of form 2^m)
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#include "src/fft/bench/fft_runbench.h"
+
+// composite numbers
+void benchmark_fft_6      LIQUID_FFT_BENCHMARK_API(     6, LIQUID_FFT_FORWARD)
+void benchmark_fft_9      LIQUID_FFT_BENCHMARK_API(     9, LIQUID_FFT_FORWARD)
+void benchmark_fft_10     LIQUID_FFT_BENCHMARK_API(    10, LIQUID_FFT_FORWARD)
+void benchmark_fft_12     LIQUID_FFT_BENCHMARK_API(    12, LIQUID_FFT_FORWARD)
+void benchmark_fft_14     LIQUID_FFT_BENCHMARK_API(    14, LIQUID_FFT_FORWARD)
+void benchmark_fft_15     LIQUID_FFT_BENCHMARK_API(    15, LIQUID_FFT_FORWARD)
+void benchmark_fft_18     LIQUID_FFT_BENCHMARK_API(    18, LIQUID_FFT_FORWARD)
+void benchmark_fft_20     LIQUID_FFT_BENCHMARK_API(    20, LIQUID_FFT_FORWARD)
+void benchmark_fft_21     LIQUID_FFT_BENCHMARK_API(    21, LIQUID_FFT_FORWARD)
+void benchmark_fft_22     LIQUID_FFT_BENCHMARK_API(    22, LIQUID_FFT_FORWARD)
+void benchmark_fft_24     LIQUID_FFT_BENCHMARK_API(    24, LIQUID_FFT_FORWARD)
+void benchmark_fft_25     LIQUID_FFT_BENCHMARK_API(    25, LIQUID_FFT_FORWARD)
+void benchmark_fft_26     LIQUID_FFT_BENCHMARK_API(    26, LIQUID_FFT_FORWARD)
+void benchmark_fft_27     LIQUID_FFT_BENCHMARK_API(    27, LIQUID_FFT_FORWARD)
+void benchmark_fft_28     LIQUID_FFT_BENCHMARK_API(    28, LIQUID_FFT_FORWARD)
+void benchmark_fft_30     LIQUID_FFT_BENCHMARK_API(    30, LIQUID_FFT_FORWARD)
+void benchmark_fft_33     LIQUID_FFT_BENCHMARK_API(    33, LIQUID_FFT_FORWARD)
+void benchmark_fft_34     LIQUID_FFT_BENCHMARK_API(    34, LIQUID_FFT_FORWARD)
+void benchmark_fft_35     LIQUID_FFT_BENCHMARK_API(    35, LIQUID_FFT_FORWARD)
+void benchmark_fft_36     LIQUID_FFT_BENCHMARK_API(    36, LIQUID_FFT_FORWARD)
+void benchmark_fft_38     LIQUID_FFT_BENCHMARK_API(    38, LIQUID_FFT_FORWARD)
+void benchmark_fft_39     LIQUID_FFT_BENCHMARK_API(    39, LIQUID_FFT_FORWARD)
+void benchmark_fft_40     LIQUID_FFT_BENCHMARK_API(    40, LIQUID_FFT_FORWARD)
+void benchmark_fft_42     LIQUID_FFT_BENCHMARK_API(    42, LIQUID_FFT_FORWARD)
+void benchmark_fft_44     LIQUID_FFT_BENCHMARK_API(    44, LIQUID_FFT_FORWARD)
+void benchmark_fft_45     LIQUID_FFT_BENCHMARK_API(    45, LIQUID_FFT_FORWARD)
+void benchmark_fft_46     LIQUID_FFT_BENCHMARK_API(    46, LIQUID_FFT_FORWARD)
+void benchmark_fft_48     LIQUID_FFT_BENCHMARK_API(    48, LIQUID_FFT_FORWARD)
+void benchmark_fft_49     LIQUID_FFT_BENCHMARK_API(    49, LIQUID_FFT_FORWARD)
+void benchmark_fft_50     LIQUID_FFT_BENCHMARK_API(    50, LIQUID_FFT_FORWARD)
+void benchmark_fft_51     LIQUID_FFT_BENCHMARK_API(    51, LIQUID_FFT_FORWARD)
+void benchmark_fft_52     LIQUID_FFT_BENCHMARK_API(    52, LIQUID_FFT_FORWARD)
+void benchmark_fft_54     LIQUID_FFT_BENCHMARK_API(    54, LIQUID_FFT_FORWARD)
+void benchmark_fft_55     LIQUID_FFT_BENCHMARK_API(    55, LIQUID_FFT_FORWARD)
+void benchmark_fft_56     LIQUID_FFT_BENCHMARK_API(    56, LIQUID_FFT_FORWARD)
+void benchmark_fft_57     LIQUID_FFT_BENCHMARK_API(    57, LIQUID_FFT_FORWARD)
+void benchmark_fft_58     LIQUID_FFT_BENCHMARK_API(    58, LIQUID_FFT_FORWARD)
+void benchmark_fft_60     LIQUID_FFT_BENCHMARK_API(    60, LIQUID_FFT_FORWARD)
+void benchmark_fft_62     LIQUID_FFT_BENCHMARK_API(    62, LIQUID_FFT_FORWARD)
+void benchmark_fft_63     LIQUID_FFT_BENCHMARK_API(    63, LIQUID_FFT_FORWARD)
+void benchmark_fft_65     LIQUID_FFT_BENCHMARK_API(    65, LIQUID_FFT_FORWARD)
+void benchmark_fft_66     LIQUID_FFT_BENCHMARK_API(    66, LIQUID_FFT_FORWARD)
+void benchmark_fft_68     LIQUID_FFT_BENCHMARK_API(    68, LIQUID_FFT_FORWARD)
+void benchmark_fft_69     LIQUID_FFT_BENCHMARK_API(    69, LIQUID_FFT_FORWARD)
+void benchmark_fft_70     LIQUID_FFT_BENCHMARK_API(    70, LIQUID_FFT_FORWARD)
+void benchmark_fft_72     LIQUID_FFT_BENCHMARK_API(    72, LIQUID_FFT_FORWARD)
+void benchmark_fft_74     LIQUID_FFT_BENCHMARK_API(    74, LIQUID_FFT_FORWARD)
+void benchmark_fft_75     LIQUID_FFT_BENCHMARK_API(    75, LIQUID_FFT_FORWARD)
+void benchmark_fft_76     LIQUID_FFT_BENCHMARK_API(    76, LIQUID_FFT_FORWARD)
+void benchmark_fft_77     LIQUID_FFT_BENCHMARK_API(    77, LIQUID_FFT_FORWARD)
+void benchmark_fft_78     LIQUID_FFT_BENCHMARK_API(    78, LIQUID_FFT_FORWARD)
+void benchmark_fft_80     LIQUID_FFT_BENCHMARK_API(    80, LIQUID_FFT_FORWARD)
+void benchmark_fft_81     LIQUID_FFT_BENCHMARK_API(    81, LIQUID_FFT_FORWARD)
+void benchmark_fft_82     LIQUID_FFT_BENCHMARK_API(    82, LIQUID_FFT_FORWARD)
+void benchmark_fft_84     LIQUID_FFT_BENCHMARK_API(    84, LIQUID_FFT_FORWARD)
+void benchmark_fft_85     LIQUID_FFT_BENCHMARK_API(    85, LIQUID_FFT_FORWARD)
+void benchmark_fft_86     LIQUID_FFT_BENCHMARK_API(    86, LIQUID_FFT_FORWARD)
+void benchmark_fft_87     LIQUID_FFT_BENCHMARK_API(    87, LIQUID_FFT_FORWARD)
+void benchmark_fft_88     LIQUID_FFT_BENCHMARK_API(    88, LIQUID_FFT_FORWARD)
+void benchmark_fft_90     LIQUID_FFT_BENCHMARK_API(    90, LIQUID_FFT_FORWARD)
+void benchmark_fft_91     LIQUID_FFT_BENCHMARK_API(    91, LIQUID_FFT_FORWARD)
+void benchmark_fft_92     LIQUID_FFT_BENCHMARK_API(    92, LIQUID_FFT_FORWARD)
+void benchmark_fft_93     LIQUID_FFT_BENCHMARK_API(    93, LIQUID_FFT_FORWARD)
+void benchmark_fft_94     LIQUID_FFT_BENCHMARK_API(    94, LIQUID_FFT_FORWARD)
+void benchmark_fft_95     LIQUID_FFT_BENCHMARK_API(    95, LIQUID_FFT_FORWARD)
+void benchmark_fft_96     LIQUID_FFT_BENCHMARK_API(    96, LIQUID_FFT_FORWARD)
+void benchmark_fft_98     LIQUID_FFT_BENCHMARK_API(    98, LIQUID_FFT_FORWARD)
+void benchmark_fft_99     LIQUID_FFT_BENCHMARK_API(    99, LIQUID_FFT_FORWARD)
+void benchmark_fft_100    LIQUID_FFT_BENCHMARK_API(   100, LIQUID_FFT_FORWARD)
+void benchmark_fft_102    LIQUID_FFT_BENCHMARK_API(   102, LIQUID_FFT_FORWARD)
+void benchmark_fft_104    LIQUID_FFT_BENCHMARK_API(   104, LIQUID_FFT_FORWARD)
+void benchmark_fft_105    LIQUID_FFT_BENCHMARK_API(   105, LIQUID_FFT_FORWARD)
+void benchmark_fft_106    LIQUID_FFT_BENCHMARK_API(   106, LIQUID_FFT_FORWARD)
+void benchmark_fft_108    LIQUID_FFT_BENCHMARK_API(   108, LIQUID_FFT_FORWARD)
+void benchmark_fft_110    LIQUID_FFT_BENCHMARK_API(   110, LIQUID_FFT_FORWARD)
+void benchmark_fft_111    LIQUID_FFT_BENCHMARK_API(   111, LIQUID_FFT_FORWARD)
+void benchmark_fft_112    LIQUID_FFT_BENCHMARK_API(   112, LIQUID_FFT_FORWARD)
+void benchmark_fft_114    LIQUID_FFT_BENCHMARK_API(   114, LIQUID_FFT_FORWARD)
+void benchmark_fft_115    LIQUID_FFT_BENCHMARK_API(   115, LIQUID_FFT_FORWARD)
+void benchmark_fft_116    LIQUID_FFT_BENCHMARK_API(   116, LIQUID_FFT_FORWARD)
+void benchmark_fft_117    LIQUID_FFT_BENCHMARK_API(   117, LIQUID_FFT_FORWARD)
+void benchmark_fft_118    LIQUID_FFT_BENCHMARK_API(   118, LIQUID_FFT_FORWARD)
+void benchmark_fft_119    LIQUID_FFT_BENCHMARK_API(   119, LIQUID_FFT_FORWARD)
+void benchmark_fft_120    LIQUID_FFT_BENCHMARK_API(   120, LIQUID_FFT_FORWARD)
+void benchmark_fft_121    LIQUID_FFT_BENCHMARK_API(   121, LIQUID_FFT_FORWARD)
+void benchmark_fft_122    LIQUID_FFT_BENCHMARK_API(   122, LIQUID_FFT_FORWARD)
+void benchmark_fft_123    LIQUID_FFT_BENCHMARK_API(   123, LIQUID_FFT_FORWARD)
+void benchmark_fft_124    LIQUID_FFT_BENCHMARK_API(   124, LIQUID_FFT_FORWARD)
+void benchmark_fft_125    LIQUID_FFT_BENCHMARK_API(   125, LIQUID_FFT_FORWARD)
+void benchmark_fft_126    LIQUID_FFT_BENCHMARK_API(   126, LIQUID_FFT_FORWARD)
+void benchmark_fft_129    LIQUID_FFT_BENCHMARK_API(   129, LIQUID_FFT_FORWARD)
+void benchmark_fft_130    LIQUID_FFT_BENCHMARK_API(   130, LIQUID_FFT_FORWARD)
+void benchmark_fft_132    LIQUID_FFT_BENCHMARK_API(   132, LIQUID_FFT_FORWARD)
+void benchmark_fft_133    LIQUID_FFT_BENCHMARK_API(   133, LIQUID_FFT_FORWARD)
+void benchmark_fft_134    LIQUID_FFT_BENCHMARK_API(   134, LIQUID_FFT_FORWARD)
+void benchmark_fft_135    LIQUID_FFT_BENCHMARK_API(   135, LIQUID_FFT_FORWARD)
+void benchmark_fft_136    LIQUID_FFT_BENCHMARK_API(   136, LIQUID_FFT_FORWARD)
+void benchmark_fft_138    LIQUID_FFT_BENCHMARK_API(   138, LIQUID_FFT_FORWARD)
+void benchmark_fft_140    LIQUID_FFT_BENCHMARK_API(   140, LIQUID_FFT_FORWARD)
+void benchmark_fft_141    LIQUID_FFT_BENCHMARK_API(   141, LIQUID_FFT_FORWARD)
+void benchmark_fft_142    LIQUID_FFT_BENCHMARK_API(   142, LIQUID_FFT_FORWARD)
+void benchmark_fft_143    LIQUID_FFT_BENCHMARK_API(   143, LIQUID_FFT_FORWARD)
+void benchmark_fft_144    LIQUID_FFT_BENCHMARK_API(   144, LIQUID_FFT_FORWARD)
+void benchmark_fft_145    LIQUID_FFT_BENCHMARK_API(   145, LIQUID_FFT_FORWARD)
+void benchmark_fft_146    LIQUID_FFT_BENCHMARK_API(   146, LIQUID_FFT_FORWARD)
+void benchmark_fft_147    LIQUID_FFT_BENCHMARK_API(   147, LIQUID_FFT_FORWARD)
+void benchmark_fft_148    LIQUID_FFT_BENCHMARK_API(   148, LIQUID_FFT_FORWARD)
+void benchmark_fft_150    LIQUID_FFT_BENCHMARK_API(   150, LIQUID_FFT_FORWARD)
+void benchmark_fft_152    LIQUID_FFT_BENCHMARK_API(   152, LIQUID_FFT_FORWARD)
+void benchmark_fft_153    LIQUID_FFT_BENCHMARK_API(   153, LIQUID_FFT_FORWARD)
+void benchmark_fft_154    LIQUID_FFT_BENCHMARK_API(   154, LIQUID_FFT_FORWARD)
+void benchmark_fft_155    LIQUID_FFT_BENCHMARK_API(   155, LIQUID_FFT_FORWARD)
+void benchmark_fft_156    LIQUID_FFT_BENCHMARK_API(   156, LIQUID_FFT_FORWARD)
+void benchmark_fft_158    LIQUID_FFT_BENCHMARK_API(   158, LIQUID_FFT_FORWARD)
+void benchmark_fft_159    LIQUID_FFT_BENCHMARK_API(   159, LIQUID_FFT_FORWARD)
+void benchmark_fft_160    LIQUID_FFT_BENCHMARK_API(   160, LIQUID_FFT_FORWARD)
+void benchmark_fft_161    LIQUID_FFT_BENCHMARK_API(   161, LIQUID_FFT_FORWARD)
+void benchmark_fft_162    LIQUID_FFT_BENCHMARK_API(   162, LIQUID_FFT_FORWARD)
+void benchmark_fft_164    LIQUID_FFT_BENCHMARK_API(   164, LIQUID_FFT_FORWARD)
+void benchmark_fft_165    LIQUID_FFT_BENCHMARK_API(   165, LIQUID_FFT_FORWARD)
+void benchmark_fft_166    LIQUID_FFT_BENCHMARK_API(   166, LIQUID_FFT_FORWARD)
+void benchmark_fft_168    LIQUID_FFT_BENCHMARK_API(   168, LIQUID_FFT_FORWARD)
+void benchmark_fft_169    LIQUID_FFT_BENCHMARK_API(   169, LIQUID_FFT_FORWARD)
+void benchmark_fft_170    LIQUID_FFT_BENCHMARK_API(   170, LIQUID_FFT_FORWARD)
+void benchmark_fft_171    LIQUID_FFT_BENCHMARK_API(   171, LIQUID_FFT_FORWARD)
+void benchmark_fft_172    LIQUID_FFT_BENCHMARK_API(   172, LIQUID_FFT_FORWARD)
+void benchmark_fft_174    LIQUID_FFT_BENCHMARK_API(   174, LIQUID_FFT_FORWARD)
+void benchmark_fft_175    LIQUID_FFT_BENCHMARK_API(   175, LIQUID_FFT_FORWARD)
+void benchmark_fft_176    LIQUID_FFT_BENCHMARK_API(   176, LIQUID_FFT_FORWARD)
+void benchmark_fft_177    LIQUID_FFT_BENCHMARK_API(   177, LIQUID_FFT_FORWARD)
+void benchmark_fft_178    LIQUID_FFT_BENCHMARK_API(   178, LIQUID_FFT_FORWARD)
+void benchmark_fft_180    LIQUID_FFT_BENCHMARK_API(   180, LIQUID_FFT_FORWARD)
+void benchmark_fft_182    LIQUID_FFT_BENCHMARK_API(   182, LIQUID_FFT_FORWARD)
+void benchmark_fft_183    LIQUID_FFT_BENCHMARK_API(   183, LIQUID_FFT_FORWARD)
+void benchmark_fft_184    LIQUID_FFT_BENCHMARK_API(   184, LIQUID_FFT_FORWARD)
+void benchmark_fft_185    LIQUID_FFT_BENCHMARK_API(   185, LIQUID_FFT_FORWARD)
+void benchmark_fft_186    LIQUID_FFT_BENCHMARK_API(   186, LIQUID_FFT_FORWARD)
+void benchmark_fft_187    LIQUID_FFT_BENCHMARK_API(   187, LIQUID_FFT_FORWARD)
+void benchmark_fft_188    LIQUID_FFT_BENCHMARK_API(   188, LIQUID_FFT_FORWARD)
+void benchmark_fft_189    LIQUID_FFT_BENCHMARK_API(   189, LIQUID_FFT_FORWARD)
+void benchmark_fft_190    LIQUID_FFT_BENCHMARK_API(   190, LIQUID_FFT_FORWARD)
+void benchmark_fft_192    LIQUID_FFT_BENCHMARK_API(   192, LIQUID_FFT_FORWARD)
+void benchmark_fft_194    LIQUID_FFT_BENCHMARK_API(   194, LIQUID_FFT_FORWARD)
+void benchmark_fft_195    LIQUID_FFT_BENCHMARK_API(   195, LIQUID_FFT_FORWARD)
+void benchmark_fft_196    LIQUID_FFT_BENCHMARK_API(   196, LIQUID_FFT_FORWARD)
+void benchmark_fft_198    LIQUID_FFT_BENCHMARK_API(   198, LIQUID_FFT_FORWARD)
+void benchmark_fft_200    LIQUID_FFT_BENCHMARK_API(   200, LIQUID_FFT_FORWARD)
+void benchmark_fft_201    LIQUID_FFT_BENCHMARK_API(   201, LIQUID_FFT_FORWARD)
+void benchmark_fft_202    LIQUID_FFT_BENCHMARK_API(   202, LIQUID_FFT_FORWARD)
+void benchmark_fft_203    LIQUID_FFT_BENCHMARK_API(   203, LIQUID_FFT_FORWARD)
+void benchmark_fft_204    LIQUID_FFT_BENCHMARK_API(   204, LIQUID_FFT_FORWARD)
+void benchmark_fft_205    LIQUID_FFT_BENCHMARK_API(   205, LIQUID_FFT_FORWARD)
+void benchmark_fft_206    LIQUID_FFT_BENCHMARK_API(   206, LIQUID_FFT_FORWARD)
+void benchmark_fft_207    LIQUID_FFT_BENCHMARK_API(   207, LIQUID_FFT_FORWARD)
+void benchmark_fft_208    LIQUID_FFT_BENCHMARK_API(   208, LIQUID_FFT_FORWARD)
+void benchmark_fft_209    LIQUID_FFT_BENCHMARK_API(   209, LIQUID_FFT_FORWARD)
+void benchmark_fft_210    LIQUID_FFT_BENCHMARK_API(   210, LIQUID_FFT_FORWARD)
+void benchmark_fft_212    LIQUID_FFT_BENCHMARK_API(   212, LIQUID_FFT_FORWARD)
+void benchmark_fft_213    LIQUID_FFT_BENCHMARK_API(   213, LIQUID_FFT_FORWARD)
+void benchmark_fft_214    LIQUID_FFT_BENCHMARK_API(   214, LIQUID_FFT_FORWARD)
+void benchmark_fft_215    LIQUID_FFT_BENCHMARK_API(   215, LIQUID_FFT_FORWARD)
+void benchmark_fft_216    LIQUID_FFT_BENCHMARK_API(   216, LIQUID_FFT_FORWARD)
+void benchmark_fft_217    LIQUID_FFT_BENCHMARK_API(   217, LIQUID_FFT_FORWARD)
+void benchmark_fft_218    LIQUID_FFT_BENCHMARK_API(   218, LIQUID_FFT_FORWARD)
+void benchmark_fft_219    LIQUID_FFT_BENCHMARK_API(   219, LIQUID_FFT_FORWARD)
+void benchmark_fft_220    LIQUID_FFT_BENCHMARK_API(   220, LIQUID_FFT_FORWARD)
+void benchmark_fft_221    LIQUID_FFT_BENCHMARK_API(   221, LIQUID_FFT_FORWARD)
+void benchmark_fft_222    LIQUID_FFT_BENCHMARK_API(   222, LIQUID_FFT_FORWARD)
+void benchmark_fft_224    LIQUID_FFT_BENCHMARK_API(   224, LIQUID_FFT_FORWARD)
+void benchmark_fft_225    LIQUID_FFT_BENCHMARK_API(   225, LIQUID_FFT_FORWARD)
+void benchmark_fft_226    LIQUID_FFT_BENCHMARK_API(   226, LIQUID_FFT_FORWARD)
+void benchmark_fft_228    LIQUID_FFT_BENCHMARK_API(   228, LIQUID_FFT_FORWARD)
+void benchmark_fft_230    LIQUID_FFT_BENCHMARK_API(   230, LIQUID_FFT_FORWARD)
+void benchmark_fft_231    LIQUID_FFT_BENCHMARK_API(   231, LIQUID_FFT_FORWARD)
+void benchmark_fft_232    LIQUID_FFT_BENCHMARK_API(   232, LIQUID_FFT_FORWARD)
+void benchmark_fft_234    LIQUID_FFT_BENCHMARK_API(   234, LIQUID_FFT_FORWARD)
+void benchmark_fft_235    LIQUID_FFT_BENCHMARK_API(   235, LIQUID_FFT_FORWARD)
+void benchmark_fft_236    LIQUID_FFT_BENCHMARK_API(   236, LIQUID_FFT_FORWARD)
+void benchmark_fft_237    LIQUID_FFT_BENCHMARK_API(   237, LIQUID_FFT_FORWARD)
+void benchmark_fft_238    LIQUID_FFT_BENCHMARK_API(   238, LIQUID_FFT_FORWARD)
+void benchmark_fft_240    LIQUID_FFT_BENCHMARK_API(   240, LIQUID_FFT_FORWARD)
+void benchmark_fft_242    LIQUID_FFT_BENCHMARK_API(   242, LIQUID_FFT_FORWARD)
+void benchmark_fft_243    LIQUID_FFT_BENCHMARK_API(   243, LIQUID_FFT_FORWARD)
+void benchmark_fft_244    LIQUID_FFT_BENCHMARK_API(   244, LIQUID_FFT_FORWARD)
+void benchmark_fft_245    LIQUID_FFT_BENCHMARK_API(   245, LIQUID_FFT_FORWARD)
+void benchmark_fft_246    LIQUID_FFT_BENCHMARK_API(   246, LIQUID_FFT_FORWARD)
+void benchmark_fft_247    LIQUID_FFT_BENCHMARK_API(   247, LIQUID_FFT_FORWARD)
+void benchmark_fft_248    LIQUID_FFT_BENCHMARK_API(   248, LIQUID_FFT_FORWARD)
+void benchmark_fft_249    LIQUID_FFT_BENCHMARK_API(   249, LIQUID_FFT_FORWARD)
+void benchmark_fft_250    LIQUID_FFT_BENCHMARK_API(   250, LIQUID_FFT_FORWARD)
+void benchmark_fft_252    LIQUID_FFT_BENCHMARK_API(   252, LIQUID_FFT_FORWARD)
+void benchmark_fft_253    LIQUID_FFT_BENCHMARK_API(   253, LIQUID_FFT_FORWARD)
+void benchmark_fft_254    LIQUID_FFT_BENCHMARK_API(   254, LIQUID_FFT_FORWARD)
+void benchmark_fft_255    LIQUID_FFT_BENCHMARK_API(   255, LIQUID_FFT_FORWARD)
diff --git a/src/fft/bench/fft_prime_benchmark.c b/src/fft/bench/fft_prime_benchmark.c
new file mode 100644
index 0000000..76135d1
--- /dev/null
+++ b/src/fft/bench/fft_prime_benchmark.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_prime_benchmark.c : benchmark FFTs of prime length
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#include "src/fft/bench/fft_runbench.h"
+
+// prime numbers
+void benchmark_fft_3      LIQUID_FFT_BENCHMARK_API(     3, LIQUID_FFT_FORWARD)
+void benchmark_fft_5      LIQUID_FFT_BENCHMARK_API(     5, LIQUID_FFT_FORWARD)
+void benchmark_fft_7      LIQUID_FFT_BENCHMARK_API(     7, LIQUID_FFT_FORWARD)
+void benchmark_fft_11     LIQUID_FFT_BENCHMARK_API(    11, LIQUID_FFT_FORWARD)
+void benchmark_fft_13     LIQUID_FFT_BENCHMARK_API(    13, LIQUID_FFT_FORWARD)
+void benchmark_fft_17     LIQUID_FFT_BENCHMARK_API(    17, LIQUID_FFT_FORWARD)
+void benchmark_fft_19     LIQUID_FFT_BENCHMARK_API(    19, LIQUID_FFT_FORWARD)
+void benchmark_fft_23     LIQUID_FFT_BENCHMARK_API(    23, LIQUID_FFT_FORWARD)
+void benchmark_fft_29     LIQUID_FFT_BENCHMARK_API(    29, LIQUID_FFT_FORWARD)
+void benchmark_fft_31     LIQUID_FFT_BENCHMARK_API(    31, LIQUID_FFT_FORWARD)
+void benchmark_fft_37     LIQUID_FFT_BENCHMARK_API(    37, LIQUID_FFT_FORWARD)
+void benchmark_fft_41     LIQUID_FFT_BENCHMARK_API(    41, LIQUID_FFT_FORWARD)
+void benchmark_fft_43     LIQUID_FFT_BENCHMARK_API(    43, LIQUID_FFT_FORWARD)
+void benchmark_fft_47     LIQUID_FFT_BENCHMARK_API(    47, LIQUID_FFT_FORWARD)
+void benchmark_fft_53     LIQUID_FFT_BENCHMARK_API(    53, LIQUID_FFT_FORWARD)
+void benchmark_fft_59     LIQUID_FFT_BENCHMARK_API(    59, LIQUID_FFT_FORWARD)
+void benchmark_fft_61     LIQUID_FFT_BENCHMARK_API(    61, LIQUID_FFT_FORWARD)
+void benchmark_fft_67     LIQUID_FFT_BENCHMARK_API(    67, LIQUID_FFT_FORWARD)
+void benchmark_fft_71     LIQUID_FFT_BENCHMARK_API(    71, LIQUID_FFT_FORWARD)
+void benchmark_fft_73     LIQUID_FFT_BENCHMARK_API(    73, LIQUID_FFT_FORWARD)
+void benchmark_fft_79     LIQUID_FFT_BENCHMARK_API(    79, LIQUID_FFT_FORWARD)
+void benchmark_fft_83     LIQUID_FFT_BENCHMARK_API(    83, LIQUID_FFT_FORWARD)
+void benchmark_fft_89     LIQUID_FFT_BENCHMARK_API(    89, LIQUID_FFT_FORWARD)
+void benchmark_fft_97     LIQUID_FFT_BENCHMARK_API(    97, LIQUID_FFT_FORWARD)
+void benchmark_fft_101    LIQUID_FFT_BENCHMARK_API(   101, LIQUID_FFT_FORWARD)
+void benchmark_fft_103    LIQUID_FFT_BENCHMARK_API(   103, LIQUID_FFT_FORWARD)
+void benchmark_fft_107    LIQUID_FFT_BENCHMARK_API(   107, LIQUID_FFT_FORWARD)
+void benchmark_fft_109    LIQUID_FFT_BENCHMARK_API(   109, LIQUID_FFT_FORWARD)
+void benchmark_fft_113    LIQUID_FFT_BENCHMARK_API(   113, LIQUID_FFT_FORWARD)
+void benchmark_fft_127    LIQUID_FFT_BENCHMARK_API(   127, LIQUID_FFT_FORWARD)
+void benchmark_fft_131    LIQUID_FFT_BENCHMARK_API(   131, LIQUID_FFT_FORWARD)
+void benchmark_fft_137    LIQUID_FFT_BENCHMARK_API(   137, LIQUID_FFT_FORWARD)
+void benchmark_fft_139    LIQUID_FFT_BENCHMARK_API(   139, LIQUID_FFT_FORWARD)
+void benchmark_fft_149    LIQUID_FFT_BENCHMARK_API(   149, LIQUID_FFT_FORWARD)
+void benchmark_fft_151    LIQUID_FFT_BENCHMARK_API(   151, LIQUID_FFT_FORWARD)
+void benchmark_fft_157    LIQUID_FFT_BENCHMARK_API(   157, LIQUID_FFT_FORWARD)
+void benchmark_fft_163    LIQUID_FFT_BENCHMARK_API(   163, LIQUID_FFT_FORWARD)
+void benchmark_fft_167    LIQUID_FFT_BENCHMARK_API(   167, LIQUID_FFT_FORWARD)
+void benchmark_fft_173    LIQUID_FFT_BENCHMARK_API(   173, LIQUID_FFT_FORWARD)
+void benchmark_fft_179    LIQUID_FFT_BENCHMARK_API(   179, LIQUID_FFT_FORWARD)
+void benchmark_fft_181    LIQUID_FFT_BENCHMARK_API(   181, LIQUID_FFT_FORWARD)
+void benchmark_fft_191    LIQUID_FFT_BENCHMARK_API(   191, LIQUID_FFT_FORWARD)
+void benchmark_fft_193    LIQUID_FFT_BENCHMARK_API(   193, LIQUID_FFT_FORWARD)
+void benchmark_fft_197    LIQUID_FFT_BENCHMARK_API(   197, LIQUID_FFT_FORWARD)
+void benchmark_fft_199    LIQUID_FFT_BENCHMARK_API(   199, LIQUID_FFT_FORWARD)
+void benchmark_fft_211    LIQUID_FFT_BENCHMARK_API(   211, LIQUID_FFT_FORWARD)
+void benchmark_fft_223    LIQUID_FFT_BENCHMARK_API(   223, LIQUID_FFT_FORWARD)
+void benchmark_fft_227    LIQUID_FFT_BENCHMARK_API(   227, LIQUID_FFT_FORWARD)
+void benchmark_fft_229    LIQUID_FFT_BENCHMARK_API(   229, LIQUID_FFT_FORWARD)
+void benchmark_fft_233    LIQUID_FFT_BENCHMARK_API(   233, LIQUID_FFT_FORWARD)
+void benchmark_fft_239    LIQUID_FFT_BENCHMARK_API(   239, LIQUID_FFT_FORWARD)
+void benchmark_fft_241    LIQUID_FFT_BENCHMARK_API(   241, LIQUID_FFT_FORWARD)
+void benchmark_fft_251    LIQUID_FFT_BENCHMARK_API(   251, LIQUID_FFT_FORWARD)
+void benchmark_fft_257    LIQUID_FFT_BENCHMARK_API(   257, LIQUID_FFT_FORWARD)
+void benchmark_fft_263    LIQUID_FFT_BENCHMARK_API(   263, LIQUID_FFT_FORWARD)
+void benchmark_fft_269    LIQUID_FFT_BENCHMARK_API(   269, LIQUID_FFT_FORWARD)
+void benchmark_fft_271    LIQUID_FFT_BENCHMARK_API(   271, LIQUID_FFT_FORWARD)
+void benchmark_fft_277    LIQUID_FFT_BENCHMARK_API(   277, LIQUID_FFT_FORWARD)
+void benchmark_fft_281    LIQUID_FFT_BENCHMARK_API(   281, LIQUID_FFT_FORWARD)
+void benchmark_fft_283    LIQUID_FFT_BENCHMARK_API(   283, LIQUID_FFT_FORWARD)
+void benchmark_fft_293    LIQUID_FFT_BENCHMARK_API(   293, LIQUID_FFT_FORWARD)
+void benchmark_fft_307    LIQUID_FFT_BENCHMARK_API(   307, LIQUID_FFT_FORWARD)
+void benchmark_fft_311    LIQUID_FFT_BENCHMARK_API(   311, LIQUID_FFT_FORWARD)
+void benchmark_fft_313    LIQUID_FFT_BENCHMARK_API(   313, LIQUID_FFT_FORWARD)
+void benchmark_fft_317    LIQUID_FFT_BENCHMARK_API(   317, LIQUID_FFT_FORWARD)
+void benchmark_fft_331    LIQUID_FFT_BENCHMARK_API(   331, LIQUID_FFT_FORWARD)
+void benchmark_fft_337    LIQUID_FFT_BENCHMARK_API(   337, LIQUID_FFT_FORWARD)
+void benchmark_fft_347    LIQUID_FFT_BENCHMARK_API(   347, LIQUID_FFT_FORWARD)
+void benchmark_fft_349    LIQUID_FFT_BENCHMARK_API(   349, LIQUID_FFT_FORWARD)
+void benchmark_fft_353    LIQUID_FFT_BENCHMARK_API(   353, LIQUID_FFT_FORWARD)
+void benchmark_fft_359    LIQUID_FFT_BENCHMARK_API(   359, LIQUID_FFT_FORWARD)
+void benchmark_fft_367    LIQUID_FFT_BENCHMARK_API(   367, LIQUID_FFT_FORWARD)
+void benchmark_fft_373    LIQUID_FFT_BENCHMARK_API(   373, LIQUID_FFT_FORWARD)
+void benchmark_fft_379    LIQUID_FFT_BENCHMARK_API(   379, LIQUID_FFT_FORWARD)
+void benchmark_fft_383    LIQUID_FFT_BENCHMARK_API(   383, LIQUID_FFT_FORWARD)
+void benchmark_fft_389    LIQUID_FFT_BENCHMARK_API(   389, LIQUID_FFT_FORWARD)
+void benchmark_fft_397    LIQUID_FFT_BENCHMARK_API(   397, LIQUID_FFT_FORWARD)
+void benchmark_fft_401    LIQUID_FFT_BENCHMARK_API(   401, LIQUID_FFT_FORWARD)
+void benchmark_fft_409    LIQUID_FFT_BENCHMARK_API(   409, LIQUID_FFT_FORWARD)
+void benchmark_fft_419    LIQUID_FFT_BENCHMARK_API(   419, LIQUID_FFT_FORWARD)
+void benchmark_fft_421    LIQUID_FFT_BENCHMARK_API(   421, LIQUID_FFT_FORWARD)
+void benchmark_fft_431    LIQUID_FFT_BENCHMARK_API(   431, LIQUID_FFT_FORWARD)
+void benchmark_fft_433    LIQUID_FFT_BENCHMARK_API(   433, LIQUID_FFT_FORWARD)
+void benchmark_fft_439    LIQUID_FFT_BENCHMARK_API(   439, LIQUID_FFT_FORWARD)
+void benchmark_fft_443    LIQUID_FFT_BENCHMARK_API(   443, LIQUID_FFT_FORWARD)
+void benchmark_fft_449    LIQUID_FFT_BENCHMARK_API(   449, LIQUID_FFT_FORWARD)
+void benchmark_fft_457    LIQUID_FFT_BENCHMARK_API(   457, LIQUID_FFT_FORWARD)
+void benchmark_fft_461    LIQUID_FFT_BENCHMARK_API(   461, LIQUID_FFT_FORWARD)
+void benchmark_fft_463    LIQUID_FFT_BENCHMARK_API(   463, LIQUID_FFT_FORWARD)
+void benchmark_fft_467    LIQUID_FFT_BENCHMARK_API(   467, LIQUID_FFT_FORWARD)
+void benchmark_fft_479    LIQUID_FFT_BENCHMARK_API(   479, LIQUID_FFT_FORWARD)
+void benchmark_fft_487    LIQUID_FFT_BENCHMARK_API(   487, LIQUID_FFT_FORWARD)
+void benchmark_fft_491    LIQUID_FFT_BENCHMARK_API(   491, LIQUID_FFT_FORWARD)
+void benchmark_fft_499    LIQUID_FFT_BENCHMARK_API(   499, LIQUID_FFT_FORWARD)
+void benchmark_fft_503    LIQUID_FFT_BENCHMARK_API(   503, LIQUID_FFT_FORWARD)
+void benchmark_fft_509    LIQUID_FFT_BENCHMARK_API(   509, LIQUID_FFT_FORWARD)
+
diff --git a/src/fft/bench/fft_r2r_benchmark.c b/src/fft/bench/fft_r2r_benchmark.c
new file mode 100644
index 0000000..ab3cb7c
--- /dev/null
+++ b/src/fft/bench/fft_r2r_benchmark.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_r2r_benchmark.h
+//
+// Real even/odd FFT benchmarks (discrete cosine/sine transforms)
+//
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define LIQUID_FFT_R2R_BENCH_API(N,K)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ fft_r2r_bench(_start, _finish, _num_iterations, N, K); }
+
+// Helper function to keep code base small
+void fft_r2r_bench(struct rusage *_start,
+                   struct rusage *_finish,
+                   unsigned long int *_num_iterations,
+                   unsigned int _n,
+                   int _kind)
+{
+    // initialize arrays, plan
+    float x[_n], y[_n];
+    int _flags = 0;
+    fftplan p = fft_create_plan_r2r_1d(_n, x, y, _kind, _flags);
+    
+    unsigned long int i;
+
+    // initialize input with random values
+    for (i=0; i<_n; i++)
+        x[i] = randnf();
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    *_num_iterations /= _n * _n;
+    *_num_iterations *= 10;
+    *_num_iterations += 1;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fft_execute(p);
+        fft_execute(p);
+        fft_execute(p);
+        fft_execute(p);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    fft_destroy_plan(p);
+}
+
+// Radix-2
+
+void benchmark_fft_REDFT00_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_REDFT00)
+void benchmark_fft_REDFT01_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_REDFT01)
+void benchmark_fft_REDFT10_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_REDFT10)
+void benchmark_fft_REDFT11_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_REDFT11)
+
+void benchmark_fft_RODFT00_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_RODFT00)
+void benchmark_fft_RODFT01_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_RODFT01)
+void benchmark_fft_RODFT10_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_RODFT10)
+void benchmark_fft_RODFT11_128  LIQUID_FFT_R2R_BENCH_API(128,  LIQUID_FFT_RODFT11)
+
+
+// prime number
+
+void benchmark_fft_REDFT00_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_REDFT00)
+void benchmark_fft_REDFT01_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_REDFT01)
+void benchmark_fft_REDFT10_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_REDFT10)
+void benchmark_fft_REDFT11_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_REDFT11)
+
+void benchmark_fft_RODFT00_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_RODFT00)
+void benchmark_fft_RODFT01_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_RODFT01)
+void benchmark_fft_RODFT10_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_RODFT10)
+void benchmark_fft_RODFT11_127  LIQUID_FFT_R2R_BENCH_API(127,  LIQUID_FFT_RODFT11)
+
diff --git a/src/fft/bench/fft_radix2_benchmark.c b/src/fft/bench/fft_radix2_benchmark.c
new file mode 100644
index 0000000..6612096
--- /dev/null
+++ b/src/fft/bench/fft_radix2_benchmark.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_radix2_benchmark.c : benchmark FFTs of length 2^m
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#include "src/fft/bench/fft_runbench.h"
+
+// power-of-two transforms
+void benchmark_fft_2      LIQUID_FFT_BENCHMARK_API(2,       LIQUID_FFT_FORWARD)
+void benchmark_fft_4      LIQUID_FFT_BENCHMARK_API(4,       LIQUID_FFT_FORWARD)
+void benchmark_fft_8      LIQUID_FFT_BENCHMARK_API(8,       LIQUID_FFT_FORWARD)
+void benchmark_fft_16     LIQUID_FFT_BENCHMARK_API(16,      LIQUID_FFT_FORWARD)
+void benchmark_fft_32     LIQUID_FFT_BENCHMARK_API(32,      LIQUID_FFT_FORWARD)
+void benchmark_fft_64     LIQUID_FFT_BENCHMARK_API(64,      LIQUID_FFT_FORWARD)
+void benchmark_fft_128    LIQUID_FFT_BENCHMARK_API(128,     LIQUID_FFT_FORWARD)
+void benchmark_fft_256    LIQUID_FFT_BENCHMARK_API(256,     LIQUID_FFT_FORWARD)
+void benchmark_fft_512    LIQUID_FFT_BENCHMARK_API(512,     LIQUID_FFT_FORWARD)
+void benchmark_fft_1024   LIQUID_FFT_BENCHMARK_API(1024,    LIQUID_FFT_FORWARD)
+void benchmark_fft_2048   LIQUID_FFT_BENCHMARK_API(2048,    LIQUID_FFT_FORWARD)
+void benchmark_fft_4096   LIQUID_FFT_BENCHMARK_API(4096,    LIQUID_FFT_FORWARD)
+void benchmark_fft_8192   LIQUID_FFT_BENCHMARK_API(8192,    LIQUID_FFT_FORWARD)
+void benchmark_fft_16384  LIQUID_FFT_BENCHMARK_API(16384,   LIQUID_FFT_FORWARD)
+void benchmark_fft_32768  LIQUID_FFT_BENCHMARK_API(32768,   LIQUID_FFT_FORWARD)
+
diff --git a/src/fft/bench/fft_runbench.c b/src/fft/bench/fft_runbench.c
new file mode 100644
index 0000000..bb4f9f7
--- /dev/null
+++ b/src/fft/bench/fft_runbench.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_runbench.c : benchmark execution program
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void fft_runbench(struct rusage *     _start,
+                  struct rusage *     _finish,
+                  unsigned long int * _num_iterations,
+                  unsigned int        _nfft,
+                  int                 _direction)
+{
+    // initialize arrays, plan
+    float complex * x = (float complex *) malloc(_nfft*sizeof(float complex));
+    float complex * y = (float complex *) malloc(_nfft*sizeof(float complex));
+    int _method = 0;
+    fftplan q = fft_create_plan(_nfft, x, y, _direction, _method);
+    
+    unsigned long int i;
+
+    // initialize input with random values
+    for (i=0; i<_nfft; i++)
+        x[i] = randnf() + randnf()*_Complex_I;
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    *_num_iterations /= _nfft;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fft_execute(q);
+        fft_execute(q);
+        fft_execute(q);
+        fft_execute(q);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    fft_destroy_plan(q);
+    free(x);
+    free(y);
+}
+
diff --git a/src/fft/bench/fft_runbench.h b/src/fft/bench/fft_runbench.h
new file mode 100644
index 0000000..65a4a0f
--- /dev/null
+++ b/src/fft/bench/fft_runbench.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_runbench.h : benchmark execution program declaration
+//
+
+#ifndef __FFT_RUNBENCH_H__
+#define __FFT_RUNBENCH_H__
+
+#include <sys/resource.h>
+
+#define LIQUID_FFT_BENCHMARK_API(NFFT,D)    \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ fft_runbench(_start, _finish, _num_iterations, NFFT, D); }
+
+// Helper function to keep code base small
+void fft_runbench(struct rusage *     _start,
+                  struct rusage *     _finish,
+                  unsigned long int * _num_iterations,
+                  unsigned int        _nfft,
+                  int                 _direction);
+
+#endif // __FFT_RUNBENCH_H__
+
diff --git a/src/fft/src/asgram.c b/src/fft/src/asgram.c
new file mode 100644
index 0000000..6f641fd
--- /dev/null
+++ b/src/fft/src/asgram.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// asgram (ASCII spectrogram)
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include <complex.h>
+#include "liquid.internal.h"
+
+struct ASGRAM(_s) {
+    unsigned int nfft;          // transform size
+    SPGRAM()     periodogram;   // spectral periodogram object
+    TC *         X;             // spectral periodogram output
+    float *      psd;           // power spectral density
+
+    float        levels[10];    // threshold for signal levels
+    char         levelchar[10]; // characters representing levels
+    unsigned int num_levels;    // number of levels
+    float        scale;         // dB per division
+    float        offset;        // dB offset (max)
+};
+
+// create asgram object with size _nfft
+ASGRAM() ASGRAM(_create)(unsigned int _nfft)
+{
+    // validate input
+    if (_nfft < 2) {
+        fprintf(stderr,"error: asgram%s_create(), fft size must be at least 2\n", EXTENSION);
+        exit(1);
+    }
+
+    // create main object
+    ASGRAM() q = (ASGRAM()) malloc(sizeof(struct ASGRAM(_s)));
+
+    q->nfft = _nfft;
+
+    // allocate memory for PSD estimate
+    q->X   = (TC *   ) malloc((q->nfft)*sizeof(TC)   );
+    q->psd = (float *) malloc((q->nfft)*sizeof(float));
+
+    // create spectral periodogram object
+    q->periodogram = SPGRAM(_create_default)(q->nfft);
+
+    // power spectral density levels
+    q->num_levels = 10;
+    q->levelchar[9] = '#';
+    q->levelchar[8] = 'M';
+    q->levelchar[7] = 'N';
+    q->levelchar[6] = '&';
+    q->levelchar[5] = '*';
+    q->levelchar[4] = '+';
+    q->levelchar[3] = '-';
+    q->levelchar[2] = ',';
+    q->levelchar[1] = '.';
+    q->levelchar[0] = ' ';
+
+    ASGRAM(_set_scale)(q, 0.0f, 10.0f);
+
+    return q;
+}
+
+// destroy asgram object
+void ASGRAM(_destroy)(ASGRAM() _q)
+{
+    // destroy spectral periodogram object
+    SPGRAM(_destroy)(_q->periodogram);
+
+    // free PSD estimate array
+    free(_q->X);
+    free(_q->psd);
+
+    // free main object memory
+    free(_q);
+}
+
+// resets the internal state of the asgram object
+void ASGRAM(_reset)(ASGRAM() _q)
+{
+    SPGRAM(_reset)(_q->periodogram);
+}
+
+// set scale and offset for spectrogram
+//  _q      :   asgram object
+//  _offset :   signal offset level [dB]
+//  _scale  :   signal scale [dB]
+void ASGRAM(_set_scale)(ASGRAM() _q,
+                        float    _offset,
+                        float    _scale)
+{
+    if (_scale <= 0.0f) {
+        fprintf(stderr,"ASGRAM(_set_scale)(), scale must be greater than zero\n");
+        exit(1);
+    }
+
+    _q->offset = _offset;
+    _q->scale  = _scale;
+
+    unsigned int i;
+    for (i=0; i<_q->num_levels; i++)
+        _q->levels[i] = _q->offset + i*_q->scale;
+}
+
+// push a single sample into the asgram object
+//  _q      :   asgram object
+//  _x      :   input buffer [size: _n x 1]
+//  _n      :   input buffer length
+void ASGRAM(_push)(ASGRAM() _q,
+                   TI       _x)
+{
+    // push sample into internal spectral periodogram
+    SPGRAM(_push)(_q->periodogram, _x);
+}
+
+// write a block of samples to the asgram object
+//  _q      :   asgram object
+//  _x      :   input buffer [size: _n x 1]
+//  _n      :   input buffer length
+void ASGRAM(_write)(ASGRAM()     _q,
+                    TI *         _x,
+                    unsigned int _n)
+{
+    // write samples to internal spectral periodogram
+    SPGRAM(_write)(_q->periodogram, _x, _n);
+}
+
+// compute spectral periodogram output from current buffer contents
+//  _q          :   ascii spectrogram object
+//  _ascii      :   character buffer [size: 1 x n]
+//  _peakval    :   value at peak (returned value)
+//  _peakfreq   :   frequency at peak (returned value)
+void ASGRAM(_execute)(ASGRAM()  _q,
+                      char *    _ascii,
+                      float *   _peakval,
+                      float *   _peakfreq)
+{
+#if 0
+    // execute spectral periodogram
+    SPGRAM(_execute)(_q->periodogram, _q->X);
+
+    // compute PSD magnitude and apply FFT shift
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++)
+        _q->psd[i] = 10*log10f(cabsf(_q->X[(i + _q->nfft/2)%_q->nfft]));
+#else
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++)
+        _q->psd[i] = 0.0f;
+#endif
+
+    unsigned int j;
+    for (i=0; i<_q->nfft; i++) {
+        // find peak
+        if (i==0 || _q->psd[i] > *_peakval) {
+            *_peakval = _q->psd[i];
+            *_peakfreq = (float)(i) / (float)(_q->nfft) - 0.5f;
+        }
+
+        // determine ascii level (which character to use)
+#if 0
+        for (j=0; j<_q->num_levels-1; j++) {
+            if ( _q->psd[i] > ( _q->offset - j*(_q->scale)) )
+                break;
+        }
+        _ascii[i] = _q->levelchar[j];
+#else
+        _ascii[i] = _q->levelchar[0];
+        for (j=0; j<_q->num_levels; j++) {
+            if ( _q->psd[i] > _q->levels[j] )
+                _ascii[i] = _q->levelchar[j];
+        }
+#endif
+    }
+
+    // append null character to end of string
+    //_ascii[i] = '\0';
+}
+
+// compute spectral periodogram output from current buffer
+// contents and print standard format to stdout
+void ASGRAM(_print)(ASGRAM() _q)
+{
+    float maxval;
+    float maxfreq;
+    char ascii[_q->nfft+1];
+    ascii[_q->nfft] = '\0'; // append null character to end of string
+        
+    // execute the spectrogram
+    ASGRAM(_execute)(_q, ascii, &maxval, &maxfreq);
+
+    // print the spectrogram to stdout
+    printf(" > %s < pk%5.1f dB [%5.2f]\n", ascii, maxval, maxfreq);
+}
+
diff --git a/src/fft/src/dct_execute.c b/src/fft/src/dct_execute.c
new file mode 100644
index 0000000..0f4aafe
--- /dev/null
+++ b/src/fft/src/dct_execute.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// DCT : Discrete Cosine Transforms
+//
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// DCT-I
+void FFT(_execute_REDFT00)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft-1);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        T x0 = _q->xr[0];       // first element
+        T xn = _q->xr[_q->nfft-1]; // last element
+        _q->yr[i] = 0.5f*( x0 + (i%2 ? -xn : xn));
+        for (k=1; k<_q->nfft-1; k++) {
+            phi = M_PI*n_inv*((float)k)*((float)i);
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-II (regular 'dct')
+void FFT(_execute_REDFT10)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)_q->nfft;
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k + 0.5f)*i;
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-III (regular 'idct')
+void FFT(_execute_REDFT01)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)_q->nfft;
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = _q->xr[0]*0.5f;
+        for (k=1; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)i + 0.5f)*k;
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-IV
+void FFT(_execute_REDFT11)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k+0.5f)*((float)i+0.5f);
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
diff --git a/src/fft/src/fct_execute.c b/src/fft/src/fct_execute.c
new file mode 100644
index 0000000..3087d4d
--- /dev/null
+++ b/src/fft/src/fct_execute.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fct_exeucte.c
+//
+// FCT : Fast (Discrete) Cosine Transforms
+//
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// DCT-I
+//
+
+// 
+// DCT-II (regular 'dct')
+//
+void FFT(_execute_REDFT10_fftn)(FFT(plan) _p)
+{
+    unsigned int i;
+    unsigned int r = _p->n % 2;
+    unsigned int L = (_p->n - r)/2;
+
+    // precondition fft
+    for (i=0; i<L; i++) {
+        _p->xc[i]         = _p->xr[2*i+0];
+        _p->xc[_p->n-i-1] = _p->xr[2*i+1];
+    }
+    // check for odd condition
+    if (r==1)
+        _p->xc[L] = _p->xr[2*L];
+
+    // execute fft, storing result in _p->yc
+    FFT(_execute)(_p->internal_plan);
+
+    // post-condition output
+    for (i=0; i<_p->n; i++)
+        _p->yr[i] = 2.0f*crealf(_p->yc[i]*cexpf(-_Complex_I*0.5f*M_PI*i/((float)_p->n)));
+}
+
+// 
+// DCT-III (regular 'idct')
+//
+void FFT(_execute_REDFT01_fftn)(FFT(plan) _p)
+{
+    unsigned int i;
+    unsigned int r = _p->n % 2;
+    unsigned int L = (_p->n - r)/2;
+
+    // precondition fft
+    for (i=0; i<_p->n; i++)
+        _p->xc[i] = _p->xr[i] * cexpf(_Complex_I*0.5f*M_PI*i/((float)_p->n));
+
+    // execute fft, storing result in _p->yc
+    FFT(_execute)(_p->internal_plan);
+
+    // post-condition output
+    for (i=0; i<L; i++) {
+        _p->yr[2*i  ] = 2.0f * crealf( _p->yc[i] );
+        _p->yr[2*i+1] = 2.0f * crealf( _p->yc[_p->n-i-1] );
+    }
+    // check for odd condition
+    if (r==1)
+        _p->yr[2*L] = 2.0f * crealf( _p->yc[L] );
+}
+
+// 
+// DCT-IV
+//
+void FFT(_execute_REDFT11_fft4n)(FFT(plan) _p)
+{
+    // NOTE: this method is only faster if n >= 64 and is radix-2
+
+    unsigned int i;
+    // precondition fft
+    for (i=0; i<_p->n; i++) {
+        _p->xc[0*(_p->n) + i] =  _p->xr[i];
+        _p->xc[1*(_p->n) + i] = -_p->xr[_p->n-i-1];
+        _p->xc[2*(_p->n) + i] = -_p->xr[i];
+        _p->xc[3*(_p->n) + i] =  _p->xr[_p->n-i-1];
+    }
+
+    // execute fft, storing result in _p->yc
+    FFT(_execute)(_p->internal_plan);
+
+    // post-condition output
+    for (i=0; i<_p->n; i++) {
+        float theta = 0.5 * M_PI * ((float)i + 0.5) / ((float)_p->n);
+        _p->yr[i] = 0.5f*crealf(_p->yc[2*i+1]*cexpf(-_Complex_I*theta));
+    }
+}
diff --git a/src/fft/src/fft_common.c b/src/fft/src/fft_common.c
new file mode 100644
index 0000000..4ed5429
--- /dev/null
+++ b/src/fft/src/fft_common.c
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_common.c : common utilities specific to precision
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "liquid.internal.h"
+
+struct FFT(plan_s)
+{
+    // common data
+    unsigned int nfft;  // fft size
+    TC * x;             // input array pointer (not allocated)
+    TC * y;             // output array pointer (not allocated)
+    int direction;      // forward/reverse
+    int flags;
+    liquid_fft_type   type;     // type of transform
+    liquid_fft_method method;   // transform method
+
+    // 'execute' function pointer
+    FFT(_execute_t) * execute;
+
+    // real even/odd DFT parameters (DCT/DST)
+    T * xr; // input array (real)
+    T * yr; // output array (real)
+
+    // common data structure shared between specific FFT algorithms
+    union {
+        // DFT
+        struct {
+            TC * twiddle;               // twiddle factors
+            DOTPROD() * dotprod;        // inner dot products
+        } dft;
+
+        // radix-2 transform data
+        struct {
+            unsigned int m;             // log2(nfft)
+            unsigned int * index_rev;   // reversed indices
+            TC * twiddle;               // twiddle factors
+        } radix2;
+
+        // recursive mixed-radix transform data:
+        //  - compute 'Q' FFTs of size 'P'
+        //  - apply twiddle factors
+        //  - compute 'P' FFTs of size 'Q'
+        //  - transpose result
+        struct {
+            unsigned int P;     // first FFT size
+            unsigned int Q;     // second FFT size
+            TC * x;             // input buffer (copied)
+            TC * t0;            // temporary buffer (small FFT input)
+            TC * t1;            // temporary buffer (small FFT output)
+            TC * twiddle;       // twiddle factors
+            FFT(plan) fft_P;    // sub-transform of size P
+            FFT(plan) fft_Q;    // sub-transform of size Q
+        } mixedradix;
+
+        // Rader's algorithm for computing FFTs of prime length
+        struct {
+            unsigned int * seq; // transformation sequence, size: nfft-1
+            TC * R;             // DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft-1
+            TC * x_prime;       // sub-transform time-domain buffer
+            TC * X_prime;       // sub-transform freq-domain buffer
+            FFT(plan) fft;      // sub-FFT of size nfft-1
+            FFT(plan) ifft;     // sub-IFFT of size nfft-1
+        } rader;
+
+        // Rader's alternate algorithm for computing FFTs of prime length
+        struct {
+            unsigned int nfft_prime;
+            unsigned int * seq; // transformation sequence, size: nfft_prime
+            TC * R;             // DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft_prime
+            TC * x_prime;       // sub-transform time-domain buffer
+            TC * X_prime;       // sub-transform freq-domain buffer
+            FFT(plan) fft;      // sub-FFT of size nfft_prime
+            FFT(plan) ifft;     // sub-IFFT of size nfft_prime
+        } rader2;
+    } data;
+};
+
+// create FFT plan, regular complex one-dimensional transform
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _flags  :   fft method
+FFT(plan) FFT(_create_plan)(unsigned int _nfft,
+                            TC *         _x,
+                            TC *         _y,
+                            int          _dir,
+                            int          _flags)
+{
+    // determine best method for execution
+    // TODO : check flags and allow user override
+    liquid_fft_method method = liquid_fft_estimate_method(_nfft);
+
+    // initialize fft based on method
+    switch (method) {
+    case LIQUID_FFT_METHOD_RADIX2:
+        // use radix-2 decimation-in-time method
+        return FFT(_create_plan_radix2)(_nfft, _x, _y, _dir, _flags);
+
+    case LIQUID_FFT_METHOD_MIXED_RADIX:
+        // use Cooley-Tukey mixed-radix algorithm
+        return FFT(_create_plan_mixed_radix)(_nfft, _x, _y, _dir, _flags);
+
+    case LIQUID_FFT_METHOD_RADER:
+        // use Rader's algorithm for FFTs of prime length
+        return FFT(_create_plan_rader)(_nfft, _x, _y, _dir, _flags);
+
+    case LIQUID_FFT_METHOD_RADER2:
+        // use Rader's algorithm for FFTs of prime length
+        return FFT(_create_plan_rader2)(_nfft, _x, _y, _dir, _flags);
+
+    case LIQUID_FFT_METHOD_DFT:
+        // use slow DFT
+        return FFT(_create_plan_dft)(_nfft, _x, _y, _dir, _flags);
+
+    case LIQUID_FFT_METHOD_UNKNOWN:
+    default:
+        fprintf(stderr,"error: fft_create_plan(), unknown/invalid fft method\n");
+        exit(1);
+    }
+
+    return NULL;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan)(FFT(plan) _q)
+{
+    switch (_q->type) {
+    // complex one-dimensional transforms
+    case LIQUID_FFT_FORWARD:
+    case LIQUID_FFT_BACKWARD:
+        switch (_q->method) {
+        case LIQUID_FFT_METHOD_DFT:         FFT(_destroy_plan_dft)(_q);         return;
+        case LIQUID_FFT_METHOD_RADIX2:      FFT(_destroy_plan_radix2)(_q);      return;
+        case LIQUID_FFT_METHOD_MIXED_RADIX: FFT(_destroy_plan_mixed_radix)(_q); return;
+        case LIQUID_FFT_METHOD_RADER:       FFT(_destroy_plan_rader)(_q);       return;
+        case LIQUID_FFT_METHOD_RADER2:      FFT(_destroy_plan_rader2)(_q);      return;
+        case LIQUID_FFT_METHOD_UNKNOWN:
+        default:
+            fprintf(stderr,"error: fft_destroy_plan(), unknown/invalid fft method\n");
+            exit(1);
+        }
+        break;
+    // discrete cosine transforms
+    case LIQUID_FFT_REDFT00:
+    case LIQUID_FFT_REDFT10:
+    case LIQUID_FFT_REDFT01:
+    case LIQUID_FFT_REDFT11:
+    // discrete sine transforms
+    case LIQUID_FFT_RODFT00:
+    case LIQUID_FFT_RODFT10:
+    case LIQUID_FFT_RODFT01:
+    case LIQUID_FFT_RODFT11:
+        FFT(_destroy_plan_r2r_1d)(_q);
+        break;
+
+    // modified discrete cosine transform
+    case LIQUID_FFT_MDCT:   break;
+    case LIQUID_FFT_IMDCT:  break;
+
+    case LIQUID_FFT_UNKNOWN:
+    default:
+        fprintf(stderr,"error: fft_destroy_plan(), unknown/invalid fft type\n");
+        exit(1);
+    }
+}
+
+// print FFT plan
+void FFT(_print_plan)(FFT(plan) _q)
+{
+    switch (_q->type) {
+    // complex one-dimensional transforms
+    case LIQUID_FFT_FORWARD:
+    case LIQUID_FFT_BACKWARD:
+        printf("fft plan [%s], n=%u, ",
+                _q->direction == LIQUID_FFT_FORWARD ? "forward" : "reverse",
+                _q->nfft);
+        switch (_q->method) {
+        case LIQUID_FFT_METHOD_DFT:         printf("DFT\n");                break;
+        case LIQUID_FFT_METHOD_RADIX2:      printf("Radix-2\n");            break;
+        case LIQUID_FFT_METHOD_MIXED_RADIX: printf("Cooley-Tukey\n");       break;
+        case LIQUID_FFT_METHOD_RADER:       printf("Rader (Type I)\n");     break;
+        case LIQUID_FFT_METHOD_RADER2:      printf("Rader (Type II)\n");    break;
+        case LIQUID_FFT_METHOD_UNKNOWN:
+        default:
+            fprintf(stderr,"error: fft_destroy_plan(), unknown/invalid fft method\n");
+            exit(1);
+        }
+        // print recursive plan
+        FFT(_print_plan_recursive)(_q, 0);
+        break;
+    // discrete cosine transforms
+    case LIQUID_FFT_REDFT00:
+    case LIQUID_FFT_REDFT10:
+    case LIQUID_FFT_REDFT01:
+    case LIQUID_FFT_REDFT11:
+    // discrete sine transforms
+    case LIQUID_FFT_RODFT00:
+    case LIQUID_FFT_RODFT10:
+    case LIQUID_FFT_RODFT01:
+    case LIQUID_FFT_RODFT11:
+        FFT(_print_plan)(_q);
+        break;
+
+    // modified discrete cosine transform
+    case LIQUID_FFT_MDCT:   break;
+    case LIQUID_FFT_IMDCT:  break;
+
+    case LIQUID_FFT_UNKNOWN:
+    default:
+        fprintf(stderr,"error: fft_print_plan(), unknown/invalid fft type\n");
+        exit(1);
+    }
+}
+
+// print FFT plan (recursively)
+void FFT(_print_plan_recursive)(FFT(plan)    _q,
+                                unsigned int _level)
+{
+    // print indentation based on recursion level
+    unsigned int i;
+    for (i=0; i<_level; i++)
+        printf("  ");
+    printf("%u, ", _q->nfft);
+
+    switch (_q->method) {
+    case LIQUID_FFT_METHOD_DFT:
+        printf("DFT\n");
+        break;
+
+    case LIQUID_FFT_METHOD_RADIX2:
+        printf("Radix-2\n");
+        break;
+
+    case LIQUID_FFT_METHOD_MIXED_RADIX:
+        // two internal transforms
+        printf("Cooley-Tukey mixed radix, Q=%u, P=%u\n",
+                _q->data.mixedradix.Q,
+                _q->data.mixedradix.P);
+        FFT(_print_plan_recursive)(_q->data.mixedradix.fft_Q, _level+1);
+        FFT(_print_plan_recursive)(_q->data.mixedradix.fft_P, _level+1);
+        break;
+
+    case LIQUID_FFT_METHOD_RADER:
+        printf("Rader (Type-II), nfft-prime=%u\n", _q->nfft-1);
+        FFT(_print_plan_recursive)(_q->data.rader.fft, _level+1);
+        break;
+
+    case LIQUID_FFT_METHOD_RADER2:
+        printf("Rader (Type-II), nfft-prime=%u\n", _q->data.rader2.nfft_prime);
+        FFT(_print_plan_recursive)(_q->data.rader2.fft, _level+1);
+        break;
+
+    case LIQUID_FFT_METHOD_UNKNOWN:     printf("(unknown)\n");      break;
+    default:                            printf("(unknown)\n");      break;
+    }
+}
+
+// execute fft
+void FFT(_execute)(FFT(plan) _q)
+{
+    // invoke internal function pointer
+    _q->execute(_q);
+}
+
+// perform n-point FFT allocating plan internally
+//  _nfft   :   fft size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: LIQUID_FFT_{FORWARD,BACKWARD}
+//  _flags  :   fft flags
+void FFT(_run)(unsigned int _nfft,
+               TC *         _x,
+               TC *         _y,
+               int          _dir,
+               int          _flags)
+{
+    // create plan
+    FFT(plan) plan = FFT(_create_plan)(_nfft, _x, _y, _dir, _flags);
+
+    // execute fft
+    FFT(_execute)(plan);
+
+    // destroy plan
+    FFT(_destroy_plan)(plan);
+}
+
+// perform real n-point FFT allocating plan internally
+//  _nfft   : fft size
+//  _x      : input array [size: _nfft x 1]
+//  _y      : output array [size: _nfft x 1]
+//  _type   : fft type, e.g. LIQUID_FFT_REDFT10
+//  _flags  : fft flags
+void FFT(_r2r_1d_run)(unsigned int _nfft,
+                      T *          _x,
+                      T *          _y,
+                      int          _type,
+                      int          _flags)
+{
+    // create plan
+    FFT(plan) plan = FFT(_create_plan_r2r_1d)(_nfft, _x, _y, _type, _flags);
+
+    // execute fft
+    FFT(_execute)(plan);
+
+    // destroy plan
+    FFT(_destroy_plan)(plan);
+}
+
+// perform _n-point FFT shift
+void FFT(_shift)(TC *_x, unsigned int _n)
+{
+    unsigned int i, n2;
+    if (_n%2)
+        n2 = (_n-1)/2;
+    else
+        n2 = _n/2;
+
+    TC tmp;
+    for (i=0; i<n2; i++) {
+        tmp = _x[i];
+        _x[i] = _x[i+n2];
+        _x[i+n2] = tmp;
+    }
+}
+
diff --git a/src/fft/src/fft_dft.c b/src/fft/src/fft_dft.c
new file mode 100644
index 0000000..70589ed
--- /dev/null
+++ b/src/fft/src/fft_dft.c
@@ -0,0 +1,523 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_dft.c : definitions for regular, slow DFTs
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+// create FFT plan for regular DFT
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_dft)(unsigned int _nfft,
+                                TC *         _x,
+                                TC *         _y,
+                                int          _dir,
+                                int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft      = _nfft;
+    q->x         = _x;
+    q->y         = _y;
+    q->flags     = _flags;
+    q->type      = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->direction = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->method    = LIQUID_FFT_METHOD_DFT;
+        
+    q->data.dft.twiddle = NULL;
+    q->data.dft.dotprod = NULL;
+
+    // check size, use specific codelet for small DFTs
+    if      (q->nfft == 2) q->execute = FFT(_execute_dft_2);
+    else if (q->nfft == 3) q->execute = FFT(_execute_dft_3);
+    else if (q->nfft == 4) q->execute = FFT(_execute_dft_4);
+    else if (q->nfft == 5) q->execute = FFT(_execute_dft_5);
+    else if (q->nfft == 6) q->execute = FFT(_execute_dft_6);
+    else if (q->nfft == 7) q->execute = FFT(_execute_dft_7);
+    else if (q->nfft == 8) q->execute = FFT(_execute_dft_8);
+    else if (q->nfft == 16) q->execute = FFT(_execute_dft_16);
+    else {
+        q->execute = FFT(_execute_dft);
+
+        // initialize twiddle factors
+        q->data.dft.twiddle = (TC *) malloc(q->nfft * sizeof(TC));
+
+        // create dotprod objects
+        q->data.dft.dotprod = (DOTPROD()*) malloc(q->nfft * sizeof(DOTPROD()));
+        
+        // create dotprod objects
+        // twiddles: exp(-j*2*pi*W/n), W=
+        //  0   0   0   0   0...
+        //  0   1   2   3   4...
+        //  0   2   4   6   8...
+        //  0   3   6   9   12...
+        //  ...
+        // Note that first row/column is zero, no multiplication necessary.
+        // Create dotprod for first row anyway because it's still faster...
+        unsigned int i;
+        unsigned int k;
+        T d = (q->direction == LIQUID_FFT_FORWARD) ? -1.0 : 1.0;
+        for (i=0; i<q->nfft; i++) {
+            // initialize twiddle factors
+            // NOTE: no need to compute first twiddle because exp(-j*2*pi*0) = 1
+            for (k=1; k<q->nfft; k++)
+                q->data.dft.twiddle[k-1] = cexpf(_Complex_I*d*2*M_PI*(T)(k*i) / (T)(q->nfft));
+
+            // create dotprod object
+            q->data.dft.dotprod[i] = DOTPROD(_create)(q->data.dft.twiddle, q->nfft-1);
+        }
+    }
+
+    return q;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan_dft)(FFT(plan) _q)
+{
+    // free twiddle factors
+    if (_q->data.dft.twiddle != NULL)
+        free(_q->data.dft.twiddle);
+
+    // free dotprod objects
+    if (_q->data.dft.dotprod != NULL) {
+        unsigned int i;
+        for (i=0; i<_q->nfft; i++)
+            DOTPROD(_destroy)(_q->data.dft.dotprod[i]);
+
+        // free dotprod array
+        free(_q->data.dft.dotprod);
+    }
+
+    // free main object memory
+    free(_q);
+}
+
+// execute DFT (slow but functionally correct)
+void FFT(_execute_dft)(FFT(plan) _q)
+{
+    unsigned int i;
+    unsigned int nfft = _q->nfft;
+
+#if 0
+    // DC value is sum of input
+    _q->y[0] = _q->x[0];
+    for (i=1; i<nfft; i++)
+        _q->y[0] += _q->x[i];
+    
+    // compute remaining DFT values
+    unsigned int k;
+    for (i=1; i<nfft; i++) {
+        _q->y[i] = _q->x[0];
+        for (k=1; k<nfft; k++) {
+            _q->y[i] += _q->x[k] * _q->data.dft.twiddle[(i*k)%_q->nfft];
+        }
+    }
+#else
+    // use vector dot products
+    // NOTE: no need to compute first multiplication because exp(-j*2*pi*0) = 1
+    for (i=0; i<nfft; i++) {
+        DOTPROD(_execute)(_q->data.dft.dotprod[i], &_q->x[1], &_q->y[i]);
+        _q->y[i] += _q->x[0];
+    }
+#endif
+}
+
+// 
+// codelets for small DFTs
+//
+
+// 
+void FFT(_execute_dft_2)(FFT(plan) _q)
+{
+    _q->y[0] = _q->x[0] + _q->x[1];
+    _q->y[1] = _q->x[0] - _q->x[1];
+}
+
+//
+void FFT(_execute_dft_3)(FFT(plan) _q)
+{
+#if 0
+    // NOTE: not as fast as other method, but perhaps useful for
+    // fixed-point algorithm
+    //  x = a + jb
+    //  y = c + jd
+    // We want to compute both x*y and x*conj(y) with as few
+    // multiplications as possible. If we define
+    //  k1 = a*(c+d);
+    //  k2 = d*(a+b);
+    //  k3 = c*(b-a);
+    //  k4 = b*(c+d);
+    // then
+    //  x*y       = (k1-k2) + j(k1+k3)
+    //  x*conj(y) = (k4-k3) + j(k4-k2)
+    T a,  b; // c=real(g)=-0.5, d=imag(g)=-sqrt(3)/2
+    T k1, k2, k3, k4;
+
+    // compute both _q->x[1]*g and _q->x[1]*conj(g) with only 4 real multiplications
+    a = crealf(_q->x[1]);
+    b = cimagf(_q->x[1]);
+    //k1 = a*(-0.5f + -0.866025403784439f);
+    k1 = -1.36602540378444f*a;
+    k2 = -0.866025403784439f*(    a + b);
+    k3 =               -0.5f*(    b - a);
+    //k4 =                   b*(-0.5f + -0.866025403784439f);
+    k4 = -1.36602540378444f*b;
+
+    TC ta1 = (k1-k2) + _Complex_I*(k1+k3);   // 
+    TC tb1 = (k4-k3) + _Complex_I*(k4-k2);   // 
+    
+    // compute both _q->x[2]*g and _q->x[2]*conj(g) with only 4 real multiplications
+    a = crealf(_q->x[2]);
+    b = cimagf(_q->x[2]);
+    //k1 = a*(-0.5f + -0.866025403784439f);
+    k1 = -1.36602540378444f*a;
+    k2 = -0.866025403784439f*(    a + b);
+    k3 =               -0.5f*(    b - a);
+    //k4 =                   b*(-0.5f + -0.866025403784439f);
+    k4 = -1.36602540378444f*b;
+
+    TC ta2 = (k1-k2) + _Complex_I*(k1+k3);   // 
+    TC tb2 = (k4-k3) + _Complex_I*(k4-k2);   // 
+    
+    // set return values
+    _q->y[0] = _q->x[0] + _q->x[1] + _q->x[2];
+    if (_q->direction == LIQUID_FFT_FORWARD) {
+        _q->y[1] = _q->x[0] + ta1 + tb2;
+        _q->y[2] = _q->x[0] + tb1 + ta2;
+    } else {
+        _q->y[1] = _q->x[0] + tb1 + ta2;
+        _q->y[2] = _q->x[0] + ta1 + tb2;
+    }
+#else
+    TC g  = -0.5f - _Complex_I*0.866025403784439; // sqrt(3)/2
+
+    _q->y[0] = _q->x[0] + _q->x[1]          + _q->x[2];
+    TC ta    = _q->x[0] + _q->x[1]*g        + _q->x[2]*conjf(g);
+    TC tb    = _q->x[0] + _q->x[1]*conjf(g) + _q->x[2]*g;
+
+    // set return values
+    if (_q->direction == LIQUID_FFT_FORWARD) {
+        _q->y[1] = ta;
+        _q->y[2] = tb;
+    } else {
+        _q->y[1] = tb;
+        _q->y[2] = ta;
+    }
+#endif
+}
+
+//
+void FFT(_execute_dft_4)(FFT(plan) _q)
+{
+    TC yp;
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // index reversal
+    y[0] = x[0];
+    y[1] = x[2];
+    y[2] = x[1];
+    y[3] = x[3];
+
+    // k0 = 0, k1=1
+    yp = y[1];
+    y[1] = y[0] - yp;
+    y[0] = y[0] + yp;
+
+    // k0 = 2, k1=3
+    yp = y[3];
+    y[3] = y[2] - yp;
+    y[2] = y[2] + yp;
+
+    // k0 = 0, k1=2
+    yp = y[2];
+    y[2] = y[0] - yp;
+    y[0] = y[0] + yp;
+
+    // k0 = 1, k1=3
+    yp = cimagf(y[3]) - _Complex_I*crealf(y[3]);
+    if (_q->direction == LIQUID_FFT_BACKWARD)
+        yp = -yp;
+    y[3] = y[1] - yp;
+    y[1] = y[1] + yp;
+}
+
+//
+void FFT(_execute_dft_5)(FFT(plan) _q)
+{
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // DC value is sum of inputs
+    y[0] = x[0] + x[1] + x[2] + x[3] + x[4];
+
+    // exp(-j*2*pi*1/5)
+    TC g0 =  0.309016994374947 - 0.951056516295154*_Complex_I;
+
+    // exp(-j*2*pi*2/5)
+    TC g1 = -0.809016994374947 - 0.587785252292473*_Complex_I;
+
+    if (_q->direction == LIQUID_FFT_BACKWARD) {
+        g0 = conjf(g0);
+        g1 = conjf(g1);
+    }
+    TC g0_conj = conjf(g0);
+    TC g1_conj = conjf(g1);
+
+    y[1] = x[0] + x[1]*g0      + x[2]*g1      + x[3]*g1_conj + x[4]*g0_conj;
+    y[2] = x[0] + x[1]*g1      + x[2]*g0_conj + x[3]*g0      + x[4]*g1_conj;
+    y[3] = x[0] + x[1]*g1_conj + x[2]*g0      + x[3]*g0_conj + x[4]*g1;
+    y[4] = x[0] + x[1]*g0_conj + x[2]*g1_conj + x[3]*g1      + x[4]*g0;
+}
+
+//
+void FFT(_execute_dft_6)(FFT(plan) _q)
+{
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // DC value is sum of inputs
+    y[0] = x[0] + x[1] + x[2] + x[3] + x[4] + x[5];
+
+    // exp(-j*2*pi*1/6) = 1/2 - j*sqrt(3)/2
+    TC g = 0.5 - 0.866025403784439*_Complex_I;
+
+    TC g1, g2, g4, g5;
+
+    if (_q->direction == LIQUID_FFT_FORWARD) {
+        g1 =        g;  // exp(-j*2*pi*1/6)
+        g2 = -conjf(g); // exp(-j*2*pi*2/6)
+        g4 =       -g;  // exp(-j*2*pi*4/6)
+        g5 =  conjf(g); // exp(-j*2*pi*5/6)
+    } else {
+        g1 =  conjf(g); // exp( j*2*pi*1/6)
+        g2 =       -g;  // exp( j*2*pi*2/6)
+        g4 = -conjf(g); // exp( j*2*pi*4/6)
+        g5 =        g;  // exp( j*2*pi*5/6)
+    }
+
+    y[1] = x[0] + x[1]*g1 + x[2]*g2 - x[3] + x[4]*g4 + x[5]*g5;
+    y[2] = x[0] + x[1]*g2 + x[2]*g4 + x[3] + x[4]*g2 + x[5]*g4;
+    y[3] = x[0] - x[1]    + x[2]    - x[3] + x[4]    - x[5];
+    y[4] = x[0] + x[1]*g4 + x[2]*g2 + x[3] + x[4]*g4 + x[5]*g2;
+    y[5] = x[0] + x[1]*g5 + x[2]*g4 - x[3] + x[4]*g2 + x[5]*g1;
+}
+
+//
+void FFT(_execute_dft_7)(FFT(plan) _q)
+{
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // DC value is sum of inputs
+    y[0] = x[0] + x[1] + x[2] + x[3] + x[4] + x[5] + x[6];
+
+    // initialize twiddle factors
+    TC g1 =  0.623489801858734 - 0.781831482468030 * _Complex_I; // exp(-j*2*pi*1/7)
+    TC g2 = -0.222520933956314 - 0.974927912181824 * _Complex_I; // exp(-j*2*pi*2/7)
+    TC g3 = -0.900968867902419 - 0.433883739117558 * _Complex_I; // exp(-j*2*pi*3/7)
+
+    if (_q->direction == LIQUID_FFT_FORWARD) {
+    } else {
+        g1 = conjf(g1); // exp(+j*2*pi*1/7)
+        g2 = conjf(g2); // exp(+j*2*pi*2/7)
+        g3 = conjf(g3); // exp(+j*2*pi*3/7)
+    }
+
+    TC g4 = conjf(g3);
+    TC g5 = conjf(g2);
+    TC g6 = conjf(g1);
+
+    y[1] = x[0] + x[1]*g1 + x[2]*g2 + x[3]*g3 + x[4]*g4 + x[5]*g5 + x[6]*g6;
+    y[2] = x[0] + x[1]*g2 + x[2]*g4 + x[3]*g6 + x[4]*g1 + x[5]*g3 + x[6]*g5;
+    y[3] = x[0] + x[1]*g3 + x[2]*g6 + x[3]*g2 + x[4]*g5 + x[5]*g1 + x[6]*g4;
+    y[4] = x[0] + x[1]*g4 + x[2]*g1 + x[3]*g5 + x[4]*g2 + x[5]*g6 + x[6]*g3;
+    y[5] = x[0] + x[1]*g5 + x[2]*g3 + x[3]*g1 + x[4]*g6 + x[5]*g4 + x[6]*g2;
+    y[6] = x[0] + x[1]*g6 + x[2]*g5 + x[3]*g4 + x[4]*g3 + x[5]*g2 + x[6]*g1;
+}
+
+//
+void FFT(_execute_dft_8)(FFT(plan) _q)
+{
+    TC yp;
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // fft or ifft?
+    int fft = _q->direction == LIQUID_FFT_FORWARD ? 1 : 0;
+
+    // index reversal
+    y[0] = x[0];
+    y[1] = x[4];
+    y[2] = x[2];
+    y[3] = x[6];
+    y[4] = x[1];
+    y[5] = x[5];
+    y[6] = x[3];
+    y[7] = x[7];
+
+    // i=0
+    yp = y[1];  y[1] = y[0]-yp;     y[0] += yp;
+    yp = y[3];  y[3] = y[2]-yp;     y[2] += yp;
+    yp = y[5];  y[5] = y[4]-yp;     y[4] += yp;
+    yp = y[7];  y[7] = y[6]-yp;     y[6] += yp;
+
+
+    // i=1
+    yp = y[2];  y[2] = y[0]-yp;     y[0] += yp;
+    yp = y[6];  y[6] = y[4]-yp;     y[4] += yp;
+
+    if (fft) yp =  cimagf(y[3]) - crealf(y[3])*_Complex_I;
+    else     yp = -cimagf(y[3]) + crealf(y[3])*_Complex_I;
+    y[3] = y[1]-yp;
+    y[1] += yp;
+
+    if (fft) yp =  cimagf(y[7]) - crealf(y[7])*_Complex_I;
+    else     yp = -cimagf(y[7]) + crealf(y[7])*_Complex_I;
+    y[7] = y[5]-yp;
+    y[5] += yp;
+
+
+    // i=2
+    yp = y[4];  y[4] = y[0]-yp;     y[0] += yp;
+
+    if (fft) yp = y[5]*(M_SQRT1_2 - M_SQRT1_2*_Complex_I);
+    else     yp = y[5]*(M_SQRT1_2 + M_SQRT1_2*_Complex_I);
+    y[5] = y[1]-yp;
+    y[1] += yp;
+
+    if (fft) yp =  cimagf(y[6]) - crealf(y[6])*_Complex_I;
+    else     yp = -cimagf(y[6]) + crealf(y[6])*_Complex_I;
+    y[6] = y[2]-yp;
+    y[2] += yp;
+
+    if (fft) yp = y[7]*(-M_SQRT1_2 - M_SQRT1_2*_Complex_I);
+    else     yp = y[7]*(-M_SQRT1_2 + M_SQRT1_2*_Complex_I);
+    y[7] = y[3]-yp;
+    y[3] += yp;
+}
+
+
+//
+void FFT(_execute_dft_16)(FFT(plan) _q)
+{
+    TC yp;
+    TC * x = _q->x;
+    TC * y = _q->y;
+
+    // fft or ifft?
+    int fft = _q->direction == LIQUID_FFT_FORWARD ? 1 : 0;
+
+    // index reversal
+    y[ 0] = x[ 0];
+    y[ 1] = x[ 8];
+    y[ 2] = x[ 4];
+    y[ 3] = x[12];
+    y[ 4] = x[ 2];
+    y[ 5] = x[10];
+    y[ 6] = x[ 6];
+    y[ 7] = x[14];
+    y[ 8] = x[ 1];
+    y[ 9] = x[ 9];
+    y[10] = x[ 5];
+    y[11] = x[13];
+    y[12] = x[ 3];
+    y[13] = x[11];
+    y[14] = x[ 7];
+    y[15] = x[15];
+
+    // i=0
+    yp =  y[ 1];    y[ 1]  = y[ 0] - yp;    y[ 0] += yp;
+    yp =  y[ 3];    y[ 3]  = y[ 2] - yp;    y[ 2] += yp;
+    yp =  y[ 5];    y[ 5]  = y[ 4] - yp;    y[ 4] += yp;
+    yp =  y[ 7];    y[ 7]  = y[ 6] - yp;    y[ 6] += yp;
+    yp =  y[ 9];    y[ 9]  = y[ 8] - yp;    y[ 8] += yp;
+    yp =  y[11];    y[11]  = y[10] - yp;    y[10] += yp;
+    yp =  y[13];    y[13]  = y[12] - yp;    y[12] += yp;
+    yp =  y[15];    y[15]  = y[14] - yp;    y[14] += yp;
+
+    // i=1
+    yp =  y[ 2];    y[ 2]  = y[ 0] - yp;    y[ 0] += yp;
+    yp =  y[ 6];    y[ 6]  = y[ 4] - yp;    y[ 4] += yp;
+    yp =  y[10];    y[10]  = y[ 8] - yp;    y[ 8] += yp;
+    yp =  y[14];    y[14]  = y[12] - yp;    y[12] += yp;
+    if (fft) {
+        yp = -y[ 3]*_Complex_I;    y[ 3]  = y[ 1] - yp;    y[ 1] += yp;
+        yp = -y[ 7]*_Complex_I;    y[ 7]  = y[ 5] - yp;    y[ 5] += yp;
+        yp = -y[11]*_Complex_I;    y[11]  = y[ 9] - yp;    y[ 9] += yp;
+        yp = -y[15]*_Complex_I;    y[15]  = y[13] - yp;    y[13] += yp;
+    } else {
+        yp  =  y[ 3]*_Complex_I;    y[ 3]  = y[ 1] - yp;   y[ 1] += yp;
+        yp  =  y[ 7]*_Complex_I;    y[ 7]  = y[ 5] - yp;   y[ 5] += yp;
+        yp  =  y[11]*_Complex_I;    y[11]  = y[ 9] - yp;   y[ 9] += yp;
+        yp  =  y[15]*_Complex_I;    y[15]  = y[13] - yp;   y[13] += yp;
+    }
+
+    // i=2
+    yp =  y[ 4];    y[ 4]  = y[ 0] - yp;    y[ 0] += yp;
+    yp =  y[12];    y[12]  = y[ 8] - yp;    y[ 8] += yp;
+    if (fft) {
+        yp =  y[ 5]*(  0.70710677 + _Complex_I* -0.70710677);  y[ 5]  = y[ 1] - yp;  y[ 1] += yp;
+        yp =  y[13]*(  0.70710677 + _Complex_I* -0.70710677);  y[13]  = y[ 9] - yp;  y[ 9] += yp;
+        yp = -y[ 6]*_Complex_I;                                y[ 6]  = y[ 2] - yp;  y[ 2] += yp;
+        yp = -y[14]*_Complex_I;                                y[14]  = y[10] - yp;  y[10] += yp;
+        yp =  y[ 7]*( -0.70710677 + _Complex_I* -0.70710677);  y[ 7]  = y[ 3] - yp;  y[ 3] += yp;
+        yp =  y[15]*( -0.70710677 + _Complex_I* -0.70710677);  y[15]  = y[11] - yp;  y[11] += yp;
+    } else {
+        yp =  y[ 5]*(  0.70710677 - _Complex_I* -0.70710677);  y[ 5]  = y[ 1] - yp;  y[ 1] += yp;
+        yp =  y[13]*(  0.70710677 - _Complex_I* -0.70710677);  y[13]  = y[ 9] - yp;  y[ 9] += yp;
+        yp  =  y[ 6]*_Complex_I;                               y[ 6]  = y[ 2] - yp;  y[ 2] += yp;
+        yp  =  y[14]*_Complex_I;                               y[14]  = y[10] - yp;  y[10] += yp;
+        yp =  y[ 7]*( -0.70710677 - _Complex_I* -0.70710677);  y[ 7]  = y[ 3] - yp;  y[ 3] += yp;
+        yp =  y[15]*( -0.70710677 - _Complex_I* -0.70710677);  y[15]  = y[11] - yp;  y[11] += yp;
+    }
+
+    // i=3
+    yp =  y[ 8];    y[ 8]  = y[ 0] - yp;    y[ 0] += yp;
+    if (fft) {
+        yp =  y[ 9]*(  0.92387950 + _Complex_I* -0.38268346);  y[ 9]  = y[ 1] - yp;  y[ 1] += yp;
+        yp =  y[10]*(  0.70710677 + _Complex_I* -0.70710677);  y[10]  = y[ 2] - yp;  y[ 2] += yp;
+        yp =  y[11]*(  0.38268343 + _Complex_I* -0.92387950);  y[11]  = y[ 3] - yp;  y[ 3] += yp;
+        yp = -y[12]*_Complex_I;                                y[12]  = y[ 4] - yp;  y[ 4] += yp;
+        yp =  y[13]*( -0.38268340 + _Complex_I* -0.92387956);  y[13]  = y[ 5] - yp;  y[ 5] += yp;
+        yp =  y[14]*( -0.70710677 + _Complex_I* -0.70710677);  y[14]  = y[ 6] - yp;  y[ 6] += yp;
+        yp =  y[15]*( -0.92387950 + _Complex_I* -0.38268349);  y[15]  = y[ 7] - yp;  y[ 7] += yp;
+    } else {
+        yp =  y[ 9]*(  0.92387950 - _Complex_I* -0.38268346);  y[ 9]  = y[ 1] - yp;  y[ 1] += yp;
+        yp =  y[10]*(  0.70710677 - _Complex_I* -0.70710677);  y[10]  = y[ 2] - yp;  y[ 2] += yp;
+        yp =  y[11]*(  0.38268343 - _Complex_I* -0.92387950);  y[11]  = y[ 3] - yp;  y[ 3] += yp;
+        yp =  y[12]*_Complex_I;                                y[12]  = y[ 4] - yp;  y[ 4] += yp;
+        yp =  y[13]*( -0.38268340 - _Complex_I* -0.92387956);  y[13]  = y[ 5] - yp;  y[ 5] += yp;
+        yp =  y[14]*( -0.70710677 - _Complex_I* -0.70710677);  y[14]  = y[ 6] - yp;  y[ 6] += yp;
+        yp =  y[15]*( -0.92387950 - _Complex_I* -0.38268349);  y[15]  = y[ 7] - yp;  y[ 7] += yp;
+    }
+}
+
diff --git a/src/fft/src/fft_mixed_radix.c b/src/fft/src/fft_mixed_radix.c
new file mode 100644
index 0000000..f27ac8c
--- /dev/null
+++ b/src/fft/src/fft_mixed_radix.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_mixed_radix.c : definitions for mixed-radix transforms using
+//                     the Cooley-Tukey algorithm
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+#define FFT_DEBUG_MIXED_RADIX 0
+
+// create FFT plan for regular DFT
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_mixed_radix)(unsigned int _nfft,
+                                        TC *         _x,
+                                        TC *         _y,
+                                        int          _dir,
+                                        int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft      = _nfft;
+    q->x         = _x;
+    q->y         = _y;
+    q->flags     = _flags;
+    q->type      = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->direction = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->method    = LIQUID_FFT_METHOD_MIXED_RADIX;
+
+    q->execute   = FFT(_execute_mixed_radix);
+
+    // find first 'prime' factor of _nfft
+    unsigned int i;
+    unsigned int Q = FFT(_estimate_mixed_radix)(_nfft);
+    if (Q==0) {
+        fprintf(stderr,"error: fft_create_plan_mixed_radix(), _nfft=%u is prime\n", _nfft);
+        exit(1);
+    } else if ( (_nfft % Q) != 0 ) {
+        fprintf(stderr,"error: fft_create_plan_mixed_radix(), _nfft=%u is not divisible by Q=%u\n", _nfft, Q);
+        exit(1);
+    }
+
+    // set mixed-radix data
+    unsigned int P = q->nfft / Q;
+    q->data.mixedradix.Q = Q;
+    q->data.mixedradix.P = P;
+
+    // allocate memory for buffers
+    unsigned int t_len = Q > P ? Q : P;
+    q->data.mixedradix.t0 = (TC *) malloc(t_len * sizeof(TC));
+    q->data.mixedradix.t1 = (TC *) malloc(t_len * sizeof(TC));
+
+    // allocate memory for input buffers
+    q->data.mixedradix.x = (TC *) malloc(q->nfft * sizeof(TC));
+
+    // create P-point FFT plan
+    q->data.mixedradix.fft_P = FFT(_create_plan)(q->data.mixedradix.P,
+                                                 q->data.mixedradix.t0,
+                                                 q->data.mixedradix.t1,
+                                                 q->direction,
+                                                 q->flags);
+
+    // create Q-point FFT plan
+    q->data.mixedradix.fft_Q = FFT(_create_plan)(q->data.mixedradix.Q,
+                                                 q->data.mixedradix.t0,
+                                                 q->data.mixedradix.t1,
+                                                 q->direction,
+                                                 q->flags);
+
+    // initialize twiddle factors, indices for mixed-radix transforms
+    // TODO : only allocate necessary twiddle factors
+    q->data.mixedradix.twiddle = (TC *) malloc(q->nfft * sizeof(TC));
+    
+    T d = (q->direction == LIQUID_FFT_FORWARD) ? -1.0 : 1.0;
+    for (i=0; i<q->nfft; i++)
+        q->data.mixedradix.twiddle[i] = cexpf(_Complex_I*d*2*M_PI*(T)i / (T)(q->nfft));
+
+    return q;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan_mixed_radix)(FFT(plan) _q)
+{
+    // destroy sub-plans
+    FFT(_destroy_plan)(_q->data.mixedradix.fft_P);
+    FFT(_destroy_plan)(_q->data.mixedradix.fft_Q);
+
+    // free data specific to mixed-radix transforms
+    free(_q->data.mixedradix.t0);
+    free(_q->data.mixedradix.t1);
+    free(_q->data.mixedradix.x);
+    free(_q->data.mixedradix.twiddle);
+
+    // free main object memory
+    free(_q);
+}
+
+// execute mixed-radix FFT
+void FFT(_execute_mixed_radix)(FFT(plan) _q)
+{
+    // set internal constants
+    unsigned int P = _q->data.mixedradix.P; // first FFT size
+    unsigned int Q = _q->data.mixedradix.Q; // second FFT size
+
+    // set pointers
+    TC * t0      = _q->data.mixedradix.t0;  // small FFT input buffer
+    TC * t1      = _q->data.mixedradix.t1;  // small FFT output buffer
+    TC * x       = _q->data.mixedradix.x;   // full input buffer (data copied)
+    TC * twiddle = _q->data.mixedradix.twiddle; // twiddle factors
+
+    // copy input to internal buffer
+    memmove(x, _q->x, _q->nfft*sizeof(TC));
+
+    unsigned int i;
+    unsigned int k;
+
+    // compute 'Q' DFTs of size 'P'
+#if FFT_DEBUG_MIXED_RADIX
+    printf("computing %u DFTs of size %u\n", Q, P);
+#endif
+    for (i=0; i<Q; i++) {
+        // copy to temporary buffer
+        for (k=0; k<P; k++)
+            t0[k] = x[Q*k+i];
+
+        // run internal P-point DFT
+        FFT(_execute)(_q->data.mixedradix.fft_P);
+
+        // copy back to input, applying twiddle factors
+        for (k=0; k<P; k++)
+            x[Q*k+i] = t1[k] * twiddle[i*k];
+
+#if FFT_DEBUG_MIXED_RADIX
+        printf("i=%3u/%3u\n", i, Q);
+        for (k=0; k<P; k++)
+            printf("  %12.6f %12.6f\n", crealf(x[Q*k+i]), cimagf(x[Q*k+i]));
+#endif
+    }
+
+    // compute 'P' DFTs of size 'Q' and transpose
+#if DEBUG
+    printf("computing %u DFTs of size %u\n", P, Q);
+#endif
+    for (i=0; i<P; i++) {
+        // copy to temporary buffer
+        for (k=0; k<Q; k++)
+            t0[k] = x[Q*i+k];
+
+        // run internal Q-point DFT
+        FFT(_execute)(_q->data.mixedradix.fft_Q);
+
+        // copy and transpose
+        for (k=0; k<Q; k++)
+            _q->y[k*P+i] = t1[k];
+        
+#if DEBUG
+        printf("i=%3u/%3u\n", i, P);
+        for (k=0; k<Q; k++)
+            printf("  %12.6f %12.6f\n", crealf(_q->y[k*P+i]), cimagf(_q->y[k*P+i]));
+#endif
+    }
+}
+
+// strategize as to best radix to use
+unsigned int FFT(_estimate_mixed_radix)(unsigned int _nfft)
+{
+    // compute factors of _nfft
+    unsigned int factors[LIQUID_MAX_FACTORS];
+    unsigned int num_factors;
+    liquid_factor(_nfft, factors, &num_factors);
+
+    // check if _nfft is prime
+    if (num_factors < 2) {
+        fprintf(stderr,"warning: fft_estimate_mixed_radix(), %u is prime\n", _nfft);
+        return 0;
+    }
+
+    // if _nfft has many factors of 2, retain for later in favor of
+    // radix2 sub-fft method
+    unsigned int num_factors_2 = 0;
+    unsigned int i;
+    for (i=0; i<num_factors; i++) {
+        if (factors[i] != 2)
+            break;
+    }
+    num_factors_2 = i;
+    //printf("nfft: %u / 2^%u = %u\n", _nfft, num_factors_2, _nfft / (1<<num_factors_2));
+
+    // prefer aggregate radix-2 form if possible
+    if (num_factors_2 > 0) {
+#if 0
+        // check if there are _only_ factors of 2
+        if (num_factors_2 == num_factors) {
+            // return Q = 2^(ceil(num_factors_2 / 2))
+            // example: nfft = 128 = 2^7, return Q=2^4 = 16
+            return 1 << ((num_factors_2 + (num_factors_2%2))/2);
+        }
+
+        // return 2^num_factors_2
+        return 1 << num_factors_2;
+#else
+        // use codelets
+        if      ( (_nfft%16)==0 ) return 16;
+        if      ( (_nfft% 8)==0 ) return  8;
+        else if ( (_nfft% 4)==0 ) return  4;
+        else                      return  2;
+#endif
+    }
+
+    // return next largest prime factor
+    return factors[0];
+}
+
diff --git a/src/fft/src/fft_r2r_1d.c b/src/fft/src/fft_r2r_1d.c
new file mode 100644
index 0000000..07ccb3e
--- /dev/null
+++ b/src/fft/src/fft_r2r_1d.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_r2r_1d.c : real-to-real methods (DCT/DST)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+// create DCT/DST plan
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _type   :   type (e.g. LIQUID_FFT_REDFT00)
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_r2r_1d)(unsigned int _nfft,
+                                   T *          _x,
+                                   T *          _y,
+                                   int          _type,
+                                   int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft   = _nfft;
+    q->xr     = _x;
+    q->yr     = _y;
+    q->type   = _type;
+    q->flags  = _flags;
+
+    // TODO : use separate 'method' for real-to-real types
+    //q->method = LIQUID_FFT_METHOD_NONE;
+
+    switch (q->type) {
+    case LIQUID_FFT_REDFT00:  q->execute = &FFT(_execute_REDFT00);  break;  // DCT-I
+    case LIQUID_FFT_REDFT10:  q->execute = &FFT(_execute_REDFT10);  break;  // DCT-II
+    case LIQUID_FFT_REDFT01:  q->execute = &FFT(_execute_REDFT01);  break;  // DCT-III
+    case LIQUID_FFT_REDFT11:  q->execute = &FFT(_execute_REDFT11);  break;  // DCT-IV
+
+    case LIQUID_FFT_RODFT00:  q->execute = &FFT(_execute_RODFT00);  break;  // DST-I
+    case LIQUID_FFT_RODFT10:  q->execute = &FFT(_execute_RODFT10);  break;  // DST-II
+    case LIQUID_FFT_RODFT01:  q->execute = &FFT(_execute_RODFT01);  break;  // DST-III
+    case LIQUID_FFT_RODFT11:  q->execute = &FFT(_execute_RODFT11);  break;  // DST-IV
+    default:
+        fprintf(stderr,"error: fft_create_plan_r2r_1d(), invalid type, %d\n", q->type);
+        exit(1);
+    }
+
+    return q;
+}
+
+// destroy real-to-real transform plan
+void FFT(_destroy_plan_r2r_1d)(FFT(plan) _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+// print real-to-real transform plan
+void FFT(_print_plan_r2r_1d)(FFT(plan) _q)
+{
+    printf("real-to-real transform...\n");
+    // TODO: print actual transform type
+}
+
+//
+// DCT : Discrete Cosine Transforms
+//
+
+// DCT-I
+void FFT(_execute_REDFT00)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft-1);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        T x0 = _q->xr[0];       // first element
+        T xn = _q->xr[_q->nfft-1]; // last element
+        _q->yr[i] = 0.5f*( x0 + (i%2 ? -xn : xn));
+        for (k=1; k<_q->nfft-1; k++) {
+            phi = M_PI*n_inv*((float)k)*((float)i);
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-II (regular 'dct')
+void FFT(_execute_REDFT10)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)_q->nfft;
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k + 0.5f)*i;
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-III (regular 'idct')
+void FFT(_execute_REDFT01)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)_q->nfft;
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = _q->xr[0]*0.5f;
+        for (k=1; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)i + 0.5f)*k;
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DCT-IV
+void FFT(_execute_REDFT11)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k+0.5f)*((float)i+0.5f);
+            _q->yr[i] += _q->xr[k]*cosf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+//
+// DST : Discrete Sine Transforms
+//
+
+// DST-I
+void FFT(_execute_RODFT00)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft+1);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*(float)((k+1)*(i+1));
+            _q->yr[i] += _q->xr[k]*sinf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DST-II
+void FFT(_execute_RODFT10)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k+0.5f)*(i+1);
+            _q->yr[i] += _q->xr[k]*sinf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DST-III
+void FFT(_execute_RODFT01)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = ((i%2)==0 ? 0.5f : -0.5f) * _q->xr[_q->nfft-1];
+        for (k=0; k<_q->nfft-1; k++) {
+            phi = M_PI*n_inv*((float)k+1)*((float)i+0.5f);
+            _q->yr[i] += _q->xr[k]*sinf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
+// DST-IV
+void FFT(_execute_RODFT11)(FFT(plan) _q)
+{
+    // ugly, slow method
+    unsigned int i,k;
+    float n_inv = 1.0f / (float)(_q->nfft);
+    float phi;
+    for (i=0; i<_q->nfft; i++) {
+        _q->yr[i] = 0.0f;
+        for (k=0; k<_q->nfft; k++) {
+            phi = M_PI*n_inv*((float)k+0.5f)*((float)i+0.5f);
+            _q->yr[i] += _q->xr[k]*sinf(phi);
+        }
+
+        // compensate for discrepancy
+        _q->yr[i] *= 2.0f;
+    }
+}
+
diff --git a/src/fft/src/fft_rader.c b/src/fft/src/fft_rader.c
new file mode 100644
index 0000000..ae21d8e
--- /dev/null
+++ b/src/fft/src/fft_rader.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_rader.c : definitions for transforms of prime length using
+//               Rader's algorithm
+//
+// References:
+//  [Rader:1968] Charles M. Rader, "Discrete Fourier Transforms When
+//      the Number of Data Samples Is Prime," Proceedings of the IEEE,
+//      vol. 56, number 6, pp. 1107--1108, June 1968
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+#define FFT_DEBUG_RADER 0
+
+// create FFT plan for regular DFT
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_rader)(unsigned int _nfft,
+                                  TC *         _x,
+                                  TC *         _y,
+                                  int          _dir,
+                                  int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft      = _nfft;
+    q->x         = _x;
+    q->y         = _y;
+    q->flags     = _flags;
+    q->type      = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->direction = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->method    = LIQUID_FFT_METHOD_RADER;
+
+    q->execute   = FFT(_execute_rader);
+
+    // allocate memory for sub-transforms
+    q->data.rader.x_prime = (TC*)malloc((q->nfft-1)*sizeof(TC));
+    q->data.rader.X_prime = (TC*)malloc((q->nfft-1)*sizeof(TC));
+
+    // create sub-FFT of size nfft-1
+    q->data.rader.fft = FFT(_create_plan)(q->nfft-1,
+                                          q->data.rader.x_prime,
+                                          q->data.rader.X_prime,
+                                          LIQUID_FFT_FORWARD,
+                                          q->flags);
+
+    // create sub-IFFT of size nfft-1
+    q->data.rader.ifft = FFT(_create_plan)(q->nfft-1,
+                                           q->data.rader.X_prime,
+                                           q->data.rader.x_prime,
+                                           LIQUID_FFT_BACKWARD,
+                                           q->flags);
+
+    // compute primitive root of nfft
+    unsigned int g = liquid_primitive_root_prime(q->nfft);
+
+    // create and initialize sequence
+    q->data.rader.seq = (unsigned int *)malloc((q->nfft-1)*sizeof(unsigned int));
+    unsigned int i;
+    for (i=0; i<q->nfft-1; i++)
+        q->data.rader.seq[i] = liquid_modpow(g, i+1, q->nfft);
+    
+    // compute DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft-1
+    // NOTE: R[0] = -1, |R[k]| = sqrt(nfft) for k != 0
+    // (use newly-created FFT plan of length nfft-1)
+    T d = (q->direction == LIQUID_FFT_FORWARD) ? -1.0 : 1.0;
+    for (i=0; i<q->nfft-1; i++)
+        q->data.rader.x_prime[i] = cexpf(_Complex_I*d*2*M_PI*q->data.rader.seq[i]/(T)(q->nfft));
+    FFT(_execute)(q->data.rader.fft);
+
+    // copy result to R
+    q->data.rader.R = (TC*)malloc((q->nfft-1)*sizeof(TC));
+    memmove(q->data.rader.R, q->data.rader.X_prime, (q->nfft-1)*sizeof(TC));
+    
+    // return main object
+    return q;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan_rader)(FFT(plan) _q)
+{
+    // free data specific to Rader's algorithm
+    free(_q->data.rader.seq);       // sequence
+    free(_q->data.rader.R);         // pre-computed transform of exp(j*2*pi*seq)
+    free(_q->data.rader.x_prime);   // sub-transform input array
+    free(_q->data.rader.X_prime);   // sub-transform output array
+
+    FFT(_destroy_plan)(_q->data.rader.fft);
+    FFT(_destroy_plan)(_q->data.rader.ifft);
+
+    // free main object memory
+    free(_q);
+}
+
+// execute Rader's algorithm
+void FFT(_execute_rader)(FFT(plan) _q)
+{
+    unsigned int i;
+
+    // compute DFT of permuted sequence, size: nfft-1
+    for (i=0; i<_q->nfft-1; i++) {
+        // reverse sequence
+        unsigned int k = _q->data.rader.seq[_q->nfft-1-i-1];
+        _q->data.rader.x_prime[i] = _q->x[k];
+    }
+    // compute sub-FFT
+    // equivalent to: FFT(_run)(_q->nfft-1, xp, Xp, LIQUID_FFT_FORWARD, 0);
+    FFT(_execute)(_q->data.rader.fft);
+
+    // compute inverse FFT of product
+    for (i=0; i<_q->nfft-1; i++)
+        _q->data.rader.X_prime[i] *= _q->data.rader.R[i];
+
+    // compute sub-IFFT
+    // equivalent to: FFT(_run)(_q->nfft-1, Xp, xp, LIQUID_FFT_BACKWARD, 0);
+    FFT(_execute)(_q->data.rader.ifft);
+
+    // set DC value
+    _q->y[0] = 0.0f;
+    for (i=0; i<_q->nfft; i++)
+        _q->y[0] += _q->x[i];
+
+    // reverse permute result, scale, and add offset x[0]
+    for (i=0; i<_q->nfft-1; i++) {
+        unsigned int k = _q->data.rader.seq[i];
+
+        _q->y[k] = _q->data.rader.x_prime[i] / (T)(_q->nfft-1) + _q->x[0];
+    }
+}
+
diff --git a/src/fft/src/fft_rader2.c b/src/fft/src/fft_rader2.c
new file mode 100644
index 0000000..5b074da
--- /dev/null
+++ b/src/fft/src/fft_rader2.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_rader2.c : definitions for transforms of prime length using
+//                Rader's alternate algorithm
+//
+// References:
+//  [Rader:1968] Charles M. Rader, "Discrete Fourier Transforms When
+//      the Number of Data Samples Is Prime," Proceedings of the IEEE,
+//      vol. 56, number 6, pp. 1107--1108, June 1968
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+#define FFT_DEBUG_RADER 0
+
+// create FFT plan
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_rader2)(unsigned int _nfft,
+                                         TC *         _x,
+                                         TC *         _y,
+                                         int          _dir,
+                                         int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft      = _nfft;
+    q->x         = _x;
+    q->y         = _y;
+    q->flags     = _flags;
+    q->type      = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->direction = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->method    = LIQUID_FFT_METHOD_RADER2;
+
+    q->execute   = FFT(_execute_rader2);
+
+    unsigned int i;
+
+    // compute primitive root of nfft
+    unsigned int g = liquid_primitive_root_prime(q->nfft);
+
+    // create and initialize sequence
+    q->data.rader2.seq = (unsigned int *)malloc((q->nfft-1)*sizeof(unsigned int));
+    for (i=0; i<q->nfft-1; i++)
+        q->data.rader2.seq[i] = liquid_modpow(g, i+1, q->nfft);
+
+#if 0
+    // compute larger FFT length greater than 2*nfft-4
+    // NOTE: while any length greater than 2*nfft-4 will work, use
+    //       nfft_prime as smallest 'simple' FFT (mostly small factors)
+    //
+    // TODO: devise better score (fewer factors is better)
+    //       score(n) = n / sum(factors(n).^2)
+    float gamma_max = 0.0f; // score
+    unsigned int nfft_prime_opt = 0;
+    unsigned int num_steps = 10;// + q->nfft;
+    for (i=1; i<=num_steps; i++) {
+        unsigned int n_hat = 2*q->nfft - 4 + i;
+
+        // compute factors
+        unsigned int k;
+        unsigned int num_factors = 0;
+        unsigned int m = n_hat;
+        float gamma = 0.0f;
+        do {
+            for (k=2; k<=m; k++) {
+                if ( (m % k) == 0) {
+                    m /= k;
+                    num_factors++;
+                    gamma += k*k;
+                    break;
+                }
+            }
+        } while (m > 1);
+
+        // compute score:
+        //float gamma = (float)n_hat / (float)num_factors;
+        //float gamma = 1e3f * (float)num_factors / (float)n_hat;
+        gamma = (float)n_hat / gamma;
+
+        if (gamma > gamma_max) {
+            gamma_max = gamma;
+            nfft_prime_opt = n_hat;
+        }
+    }
+    q->data.rader2.nfft_prime = nfft_prime_opt;
+#else
+    // compute larger FFT length greater than 2*nfft-4
+    // NOTE: while any length greater than 2*nfft-4 will work, use
+    //       nfft_prime = 2 ^ nextpow2( 2*nfft - 4 ) to enable
+    //       radix-2 transform
+    unsigned int m=0;
+    q->data.rader2.nfft_prime = (2*q->nfft-4)-1;
+    while (q->data.rader2.nfft_prime > 0) {
+        q->data.rader2.nfft_prime >>= 1;
+        m++;
+    }
+    q->data.rader2.nfft_prime = 1 << m;
+#endif
+    //printf("nfft_prime = %u\n", q->data.rader2.nfft_prime);
+    // assert(nfft_prime > 2*nfft-4)
+
+    // allocate memory for sub-transforms
+    q->data.rader2.x_prime = (TC*)malloc((q->data.rader2.nfft_prime)*sizeof(TC));
+    q->data.rader2.X_prime = (TC*)malloc((q->data.rader2.nfft_prime)*sizeof(TC));
+
+    // create sub-FFT of size nfft-1
+    q->data.rader2.fft = FFT(_create_plan)(q->data.rader2.nfft_prime,
+                                           q->data.rader2.x_prime,
+                                           q->data.rader2.X_prime,
+                                           LIQUID_FFT_FORWARD,
+                                           q->flags);
+
+    // create sub-IFFT of size nfft-1
+    q->data.rader2.ifft = FFT(_create_plan)(q->data.rader2.nfft_prime,
+                                            q->data.rader2.X_prime,
+                                            q->data.rader2.x_prime,
+                                            LIQUID_FFT_BACKWARD,
+                                            q->flags);
+
+    // compute DFT of sequence { exp(-j*2*pi*g^i/nfft }, size: nfft_prime
+    // NOTE: R[0] = -1, |R[k]| = sqrt(nfft) for k != 0
+    // (use newly-created FFT plan of length nfft_prime)
+    T d = (q->direction == LIQUID_FFT_FORWARD) ? -1.0 : 1.0;
+    for (i=0; i<q->data.rader2.nfft_prime; i++)
+        q->data.rader2.x_prime[i] = cexpf(_Complex_I*d*2*M_PI*q->data.rader2.seq[i%(q->nfft-1)]/(T)(q->nfft));
+    FFT(_execute)(q->data.rader2.fft);
+    
+    // copy result to R
+    q->data.rader2.R = (TC*)malloc(q->data.rader2.nfft_prime*sizeof(TC));
+    memmove(q->data.rader2.R, q->data.rader2.X_prime, q->data.rader2.nfft_prime*sizeof(TC));
+
+    // return main object
+    return q;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan_rader2)(FFT(plan) _q)
+{
+    // free data specific to Rader's algorithm
+    free(_q->data.rader2.seq);      // sequence
+    free(_q->data.rader2.R);        // pre-computed transform of exp(j*2*pi*seq)
+
+    free(_q->data.rader2.x_prime);   // sub-transform input array
+    free(_q->data.rader2.X_prime);   // sub-transform output array
+
+    FFT(_destroy_plan)(_q->data.rader2.fft);
+    FFT(_destroy_plan)(_q->data.rader2.ifft);
+
+    // free main object memory
+    free(_q);
+}
+
+// execute Rader's algorithm
+void FFT(_execute_rader2)(FFT(plan) _q)
+{
+    unsigned int i;
+
+    // set pointers to internal buffers
+    TC * xp = _q->data.rader2.x_prime;
+    TC * Xp = _q->data.rader2.X_prime;
+    TC * R  = _q->data.rader2.R;
+    unsigned int * seq = _q->data.rader2.seq;
+
+    // set constant values
+    unsigned int nfft_prime = _q->data.rader2.nfft_prime;
+
+    // compute nfft_prime-length DFT of permuted sequence with
+    // nfft_prime-nfft+1 zeros inserted after first element
+
+    xp[0] = _q->x[ seq[_q->nfft-2] ];
+    for (i=0; i<nfft_prime-_q->nfft+1; i++)
+        xp[i+1] = 0.0f;
+    for (i=1; i<_q->nfft-1; i++) {
+        // reverse sequence
+        unsigned int k = seq[_q->nfft-1-i-1];
+        xp[i+nfft_prime-_q->nfft+1] = _q->x[k];
+    }
+    FFT(_execute)(_q->data.rader2.fft);
+
+    // compute inverse FFT of product
+    // compute nfft_prime-length inverse FFT of product
+    for (i=0; i<nfft_prime; i++)
+        Xp[i] *= R[i];
+
+    // call radix-2 function (IFFT)
+    FFT(_execute)(_q->data.rader2.ifft);
+
+    // set DC value
+    _q->y[0] = 0.0f;
+    for (i=0; i<_q->nfft; i++)
+        _q->y[0] += _q->x[i];
+
+    // reverse permute result, scale, and add offset x[0]
+    for (i=0; i<_q->nfft-1; i++) {
+        unsigned int k = seq[i];
+
+        _q->y[k] = xp[i] / (T)(nfft_prime) + _q->x[0];
+    }
+}
+
diff --git a/src/fft/src/fft_radix2.c b/src/fft/src/fft_radix2.c
new file mode 100644
index 0000000..ece7cad
--- /dev/null
+++ b/src/fft/src/fft_radix2.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_radix2.c : definitions for transforms of the form 2^m
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+// create FFT plan for regular DFT
+//  _nfft   :   FFT size
+//  _x      :   input array [size: _nfft x 1]
+//  _y      :   output array [size: _nfft x 1]
+//  _dir    :   fft direction: {LIQUID_FFT_FORWARD, LIQUID_FFT_BACKWARD}
+//  _method :   fft method
+FFT(plan) FFT(_create_plan_radix2)(unsigned int _nfft,
+                                   TC *         _x,
+                                   TC *         _y,
+                                   int          _dir,
+                                   int          _flags)
+{
+    // allocate plan and initialize all internal arrays to NULL
+    FFT(plan) q = (FFT(plan)) malloc(sizeof(struct FFT(plan_s)));
+
+    q->nfft      = _nfft;
+    q->x         = _x;
+    q->y         = _y;
+    q->flags     = _flags;
+    q->type      = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->direction = (_dir == LIQUID_FFT_FORWARD) ? LIQUID_FFT_FORWARD : LIQUID_FFT_BACKWARD;
+    q->method    = LIQUID_FFT_METHOD_RADIX2;
+
+    q->execute   = FFT(_execute_radix2);
+
+    // initialize twiddle factors, indices for radix-2 transforms
+    q->data.radix2.m = liquid_msb_index(q->nfft) - 1;  // m = log2(nfft)
+    
+    q->data.radix2.index_rev = (unsigned int *) malloc((q->nfft)*sizeof(unsigned int));
+    unsigned int i;
+    for (i=0; i<q->nfft; i++)
+        q->data.radix2.index_rev[i] = fft_reverse_index(i,q->data.radix2.m);
+
+    // initialize twiddle factors
+    q->data.radix2.twiddle = (TC *) malloc(q->nfft * sizeof(TC));
+    
+    T d = (q->direction == LIQUID_FFT_FORWARD) ? -1.0 : 1.0;
+    for (i=0; i<q->nfft; i++)
+        q->data.radix2.twiddle[i] = cexpf(_Complex_I*d*2*M_PI*(T)i / (T)(q->nfft));
+
+    return q;
+}
+
+// destroy FFT plan
+void FFT(_destroy_plan_radix2)(FFT(plan) _q)
+{
+    // free data specific to radix-2 transforms
+    free(_q->data.radix2.index_rev);
+    free(_q->data.radix2.twiddle);
+
+    // free main object memory
+    free(_q);
+}
+
+// execute radix-2 FFT
+void FFT(_execute_radix2)(FFT(plan) _q)
+{
+    // swap values
+    unsigned int i,j,k;
+
+    // unroll loop
+    unsigned int nfft4 = (_q->nfft>>2)<<2;  // floor(_nfft/4)
+    for (i=0; i<nfft4; i+=4) {
+        _q->y[i  ] = _q->x[ _q->data.radix2.index_rev[i  ] ];
+        _q->y[i+1] = _q->x[ _q->data.radix2.index_rev[i+1] ];
+        _q->y[i+2] = _q->x[ _q->data.radix2.index_rev[i+2] ];
+        _q->y[i+3] = _q->x[ _q->data.radix2.index_rev[i+3] ];
+    }
+
+#if 0
+    // clean up remaining
+    // NOTE : this only happens when _nfft=2 because we know (_nfft%4)==0 otherwise
+    for ( ; i<_q->nfft; i++)
+        _q->y[i] = _q->x[ _q->data.radix2.index_rev[i] ];
+#endif
+
+    TC yp;
+    TC *y=_q->y;
+    unsigned int n1 = 0;
+    unsigned int n2 = 1;
+
+    TC t;   // twiddle value
+    unsigned int stride = _q->nfft;
+    unsigned int twiddle_index;
+
+    for (i=0; i<_q->data.radix2.m; i++) {
+        n1 = n2;
+        n2 *= 2;
+        stride >>= 1;
+
+        twiddle_index = 0;
+    
+        for (j=0; j<n1; j++) {
+            t = _q->data.radix2.twiddle[twiddle_index];
+            twiddle_index = (twiddle_index + stride) % _q->nfft;
+
+            for (k=j; k<_q->nfft; k+=n2) {
+                // NOTE: most computation is with the multiplication in next line
+                yp      =  y[k+n1]*t;
+                y[k+n1] =  y[k] - yp;
+                y[k]    += yp;
+            }
+        }
+    }
+}
+
diff --git a/src/fft/src/fft_utilities.c b/src/fft/src/fft_utilities.c
new file mode 100644
index 0000000..a8499be
--- /dev/null
+++ b/src/fft/src/fft_utilities.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_utilities.c : common utilities not specific to precision
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// determine best FFT method based on size
+liquid_fft_method liquid_fft_estimate_method(unsigned int _nfft)
+{
+    if (_nfft == 0) {
+        // invalid length
+        fprintf(stderr,"error: liquid_fft_estimate_method(), fft size must be > 0\n");
+        return LIQUID_FFT_METHOD_UNKNOWN;
+
+    } else if (_nfft <= 8 || _nfft==11 || _nfft==13 || _nfft==16 || _nfft==17) {
+        // use simple DFT
+        return LIQUID_FFT_METHOD_DFT;
+
+    } else if (fft_is_radix2(_nfft)) {
+        // transform is of the form 2^m
+#if 0
+        // use radix-2 algorithm
+        return LIQUID_FFT_METHOD_RADIX2;
+#else
+        // acutally, prefer Cooley-Tukey algorithm
+        return LIQUID_FFT_METHOD_MIXED_RADIX;
+#endif
+
+    } else if (liquid_is_prime(_nfft)) {
+        // prefer Rader's alternate method (using radix-2 transform)
+        // unless _nfft-1 is also radix2
+        // TODO : also prefer Rader-I if _nfft-1 is mostly factors of 2
+        if ( fft_is_radix2(_nfft-1) )
+            return LIQUID_FFT_METHOD_RADER;
+        else
+            return LIQUID_FFT_METHOD_RADER2;
+    }
+
+    // last resort
+    //return LIQUID_FFT_METHOD_DFT;         // use slow DFT method
+    return LIQUID_FFT_METHOD_MIXED_RADIX;   // use mixed radix method
+}
+
+// is input radix-2?
+int fft_is_radix2(unsigned int _n)
+{
+    // check to see if _n is radix 2
+    unsigned int i;
+    unsigned int d=0;
+    unsigned int m=0;
+    unsigned int t=_n;
+    for (i=0; i<8*sizeof(unsigned int); i++) {
+        d += (t & 1);           // count bits, radix-2 if d==1
+        if (!m && (t&1)) m = i; // count lagging zeros, effectively log2(n)
+        t >>= 1;
+    }
+
+    return (d == 1) ? 1 : 0;
+}
+
+
+// reverse _n-bit index _i
+unsigned int fft_reverse_index(unsigned int _i, unsigned int _n)
+{
+    unsigned int j=0, k;
+    for (k=0; k<_n; k++) {
+        j <<= 1;
+        j |= ( _i & 1 );
+        _i >>= 1;
+    }
+
+    return j;
+}
+
+
diff --git a/src/fft/src/fftf.c b/src/fft/src/fftf.c
new file mode 100644
index 0000000..eb5b02e
--- /dev/null
+++ b/src/fft/src/fftf.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// FFT API: floating-point (single precision)
+//
+
+#include "liquid.internal.h"
+
+// Macro definitions
+#define FFT(name)           LIQUID_CONCAT(fft,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_cccf,name)
+
+#define T                   float           /* primitive type */
+#define TC                  float complex   /* primitive type (complex) */
+
+#define PRINTVAL_T(X,F)     PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// include main files
+#include "fft_common.c"         // common source must come first (object definition)
+#include "fft_dft.c"            // FFT definitions for DFT
+#include "fft_radix2.c"         // FFT definitions for radix-2 transforms
+#include "fft_mixed_radix.c"    // FFT definitions for mixed-radix transforms (Cooley-Tukey)
+#include "fft_rader.c"          // FFT definitions for transforms of prime length (Rader's algorithm)
+#include "fft_rader2.c"         // FFT definitions for transforms of prime length (Rader's alternate algorithm)
+#include "fft_r2r_1d.c"         // real-to-real definitions (DCT/DST)
+
diff --git a/src/fft/src/spgram.c b/src/fft/src/spgram.c
new file mode 100644
index 0000000..50c34db
--- /dev/null
+++ b/src/fft/src/spgram.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// spgram (spectral periodogram)
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include <complex.h>
+#include "liquid.internal.h"
+
+struct SPGRAM(_s) {
+    // options
+    unsigned int    nfft;           // FFT length
+    int             wtype;          // window type
+    unsigned int    window_len;     // window length
+    unsigned int    delay;          // delay between transforms [samples]
+    float           alpha;          // spectrum smoothing filter: feedforward parameter
+    float           gamma;          // spectrum smoothing filter: feedback parameter
+    int             accumulate;     // accumulate? or use time-average
+
+    WINDOW()        buffer;         // input buffer
+    TC *            buf_time;       // pointer to input array (allocated)
+    TC *            buf_freq;       // output fft (allocated)
+    T  *            w;              // tapering window [size: window_len x 1]
+    FFT_PLAN        fft;            // FFT plan
+
+    // psd accumulation
+    T *             psd;                    // accumulated power spectral density estimate (linear)
+    unsigned int    sample_timer;           // countdown to transform
+    uint64_t        num_samples;            // total number of samples since reset
+    uint64_t        num_samples_total;      // total number of samples since start
+    uint64_t        num_transforms;         // total number of transforms since reset
+    uint64_t        num_transforms_total;   // total number of transforms since start
+};
+
+//
+// internal methods
+//
+
+// compute spectral periodogram output (complex values)
+// from current buffer contents
+void SPGRAM(_step)(SPGRAM() _q);
+
+// create spgram object
+//  _nfft       : FFT size
+//  _window     : window coefficients [size: _window_len x 1]
+//  _window_len : window length
+//  _delay      : delay between transforms, _delay > 0
+SPGRAM() SPGRAM(_create)(unsigned int _nfft,
+                         int          _wtype,
+                         unsigned int _window_len,
+                         unsigned int _delay)
+{
+    // validate input
+    if (_nfft < 2) {
+        fprintf(stderr,"error: spgram%s_create(), fft size must be at least 2\n", EXTENSION);
+        exit(1);
+    } else if (_window_len > _nfft) {
+        fprintf(stderr,"error: spgram%s_create(), window size cannot exceed fft size\n", EXTENSION);
+        exit(1);
+    } else if (_window_len == 0) {
+        fprintf(stderr,"error: spgram%s_create(), window size must be greater than zero\n", EXTENSION);
+        exit(1);
+    } else if (_wtype == LIQUID_WINDOW_KBD && _window_len % 2) {
+        fprintf(stderr,"error: spgram%s_create(), KBD window length must be even\n", EXTENSION);
+        exit(1);
+    } else if (_delay == 0) {
+        fprintf(stderr,"error: spgram%s_create(), delay must be greater than 0\n", EXTENSION);
+        exit(1);
+    }
+
+    // allocate memory for main object
+    SPGRAM() q = (SPGRAM()) malloc(sizeof(struct SPGRAM(_s)));
+
+    // set input parameters
+    q->nfft       = _nfft;
+    q->wtype      = _wtype;
+    q->window_len = _window_len;
+    q->delay      = _delay;
+
+    // set object for full accumulation
+    SPGRAM(_set_alpha)(q, -1.0f);
+
+    // create FFT arrays, object
+    q->buf_time = (TC*) malloc((q->nfft)*sizeof(TC));
+    q->buf_freq = (TC*) malloc((q->nfft)*sizeof(TC));
+    q->psd      = (T *) malloc((q->nfft)*sizeof(T ));
+    q->fft      = FFT_CREATE_PLAN(q->nfft, q->buf_time, q->buf_freq, FFT_DIR_FORWARD, FFT_METHOD);
+
+    // create buffer
+    q->buffer = WINDOW(_create)(q->window_len);
+
+    // create window
+    q->w = (T*) malloc((q->window_len)*sizeof(T));
+    unsigned int i;
+    unsigned int n = q->window_len;
+    float beta = 10.0f;
+    float zeta =  3.0f;
+    for (i=0; i<n; i++) {
+        switch (q->wtype) {
+        case LIQUID_WINDOW_HAMMING:         q->w[i] = hamming(i,n);         break;
+        case LIQUID_WINDOW_HANN:            q->w[i] = hann(i,n);            break;
+        case LIQUID_WINDOW_BLACKMANHARRIS:  q->w[i] = blackmanharris(i,n);  break;
+        case LIQUID_WINDOW_BLACKMANHARRIS7: q->w[i] = blackmanharris7(i,n); break;
+        case LIQUID_WINDOW_KAISER:          q->w[i] = kaiser(i,n,beta,0);   break;
+        case LIQUID_WINDOW_FLATTOP:         q->w[i] = flattop(i,n);         break;
+        case LIQUID_WINDOW_TRIANGULAR:      q->w[i] = triangular(i,n,n);    break;
+        case LIQUID_WINDOW_RCOSTAPER:       q->w[i] = liquid_rcostaper_windowf(i,n/3,n); break;
+        case LIQUID_WINDOW_KBD:             q->w[i] = liquid_kbd(i,n,zeta); break;
+        default:
+            fprintf(stderr,"error: spgram%s_create(), invalid window\n", EXTENSION);
+            exit(1);
+        }
+    }
+
+    // scale by window magnitude, FFT size
+    float g = 0.0f;
+    for (i=0; i<q->window_len; i++)
+        g += q->w[i] * q->w[i];
+    g = M_SQRT2 / ( sqrtf(g / q->window_len) * sqrtf((float)(q->nfft)) );
+
+    // scale window and copy
+    for (i=0; i<q->window_len; i++)
+        q->w[i] = g * q->w[i];
+    
+    // reset the spgram object
+    q->num_samples_total    = 0;
+    q->num_transforms_total = 0;
+    SPGRAM(_reset)(q);
+
+    // return new object
+    return q;
+}
+
+// create default spgram object (Kaiser-Bessel window)
+SPGRAM() SPGRAM(_create_default)(unsigned int _nfft)
+{
+    // validate input
+    if (_nfft < 2) {
+        fprintf(stderr,"error: spgram%s_create_default(), fft size must be at least 2\n", EXTENSION);
+        exit(1);
+    }
+
+    return SPGRAM(_create)(_nfft, LIQUID_WINDOW_KAISER, _nfft/2, _nfft/4);
+}
+
+// destroy spgram object
+void SPGRAM(_destroy)(SPGRAM() _q)
+{
+    // free allocated memory
+    free(_q->buf_time);
+    free(_q->buf_freq);
+    free(_q->w);
+    free(_q->psd);
+    WINDOW(_destroy)(_q->buffer);
+    FFT_DESTROY_PLAN(_q->fft);
+
+    // free main object
+    free(_q);
+}
+
+// resets the internal state of the spgram object
+void SPGRAM(_reset)(SPGRAM() _q)
+{
+    // clear the window buffer
+    //WINDOW(_clear)(_q->buffer);
+
+    // clear FFT input
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++)
+        _q->buf_time[i] = 0.0f;
+
+    // reset counters
+    _q->sample_timer   = _q->delay;
+    _q->num_transforms = 0;
+    _q->num_samples    = 0;
+
+    // clear PSD accumulation
+    for (i=0; i<_q->nfft; i++)
+        _q->psd[i] = 0.0f;
+}
+
+// prints the spgram object's parameters
+void SPGRAM(_print)(SPGRAM() _q)
+{
+    printf("spgram%s: nfft=%u, window=%u, delay=%u\n",
+            EXTENSION, _q->nfft, _q->window_len, _q->delay);
+}
+
+// set forgetting factor
+int SPGRAM(_set_alpha)(SPGRAM() _q,
+                       float    _alpha)
+{
+    // validate input
+    if (_alpha != -1 && (_alpha < 0.0f || _alpha > 1.0f)) {
+        fprintf(stderr,"warning: spgram%s_set_alpha(), alpha must be in {-1,[0,1]}\n", EXTENSION);
+        return -1;
+    }
+
+    // set accumulation flag appropriately
+    _q->accumulate = (_alpha == -1.0f) ? 1 : 0;
+
+    if (_q->accumulate) {
+        _q->alpha = 1.0f;
+        _q->gamma = 1.0f;
+    } else {
+        _q->alpha = _alpha;
+        _q->gamma = 1.0f - _q->alpha;
+    }
+    return 0;
+}
+
+// get FFT size
+unsigned int SPGRAM(_get_nfft)(SPGRAM() _q)
+{
+    return _q->nfft;
+}
+
+// get window length
+unsigned int SPGRAM(_get_window_len)(SPGRAM() _q)
+{
+    return _q->window_len;
+}
+
+// get delay between transforms
+unsigned int SPGRAM(_get_delay)(SPGRAM() _q)
+{
+    return _q->delay;
+}
+
+// get number of samples processed since reset
+uint64_t SPGRAM(_get_num_samples)(SPGRAM() _q)
+{
+    return _q->num_samples;
+}
+
+// get number of samples processed since start
+uint64_t SPGRAM(_get_num_samples_total)(SPGRAM() _q)
+{
+    return _q->num_samples_total;
+}
+
+// get number of transforms processed since reset
+uint64_t SPGRAM(_get_num_transforms)(SPGRAM() _q)
+{
+    return _q->num_transforms;
+}
+
+// get number of transforms processed since start
+uint64_t SPGRAM(_get_num_transforms_total)(SPGRAM() _q)
+{
+    return _q->num_transforms_total;
+}
+
+// push a single sample into the spgram object
+//  _q      :   spgram object
+//  _x      :   input sample
+void SPGRAM(_push)(SPGRAM() _q,
+                   TI       _x)
+{
+    // push sample into internal window
+    WINDOW(_push)(_q->buffer, _x);
+
+    // update counters
+    _q->num_samples++;
+    _q->num_samples_total++;
+
+    // adjust timer
+    _q->sample_timer--;
+
+    if (_q->sample_timer)
+        return;
+
+    // reset timer and step through computation
+    _q->sample_timer = _q->delay;
+    SPGRAM(_step)(_q);
+}
+
+// write a block of samples to the spgram object
+//  _q      :   spgram object
+//  _x      :   input buffer [size: _n x 1]
+//  _n      :   input buffer length
+void SPGRAM(_write)(SPGRAM()     _q,
+                    TI *         _x,
+                    unsigned int _n)
+{
+#if 0
+    // write a block of samples to the internal window
+    WINDOW(_write)(_q->buffer, _x, _n);
+#else
+    // TODO: be smarter about how to write and execute samples
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        SPGRAM(_push)(_q, _x[i]);
+#endif
+}
+
+
+// compute spectral periodogram output from current buffer contents
+//  _q      :   spgram object
+void SPGRAM(_step)(SPGRAM() _q)
+{
+    unsigned int i;
+
+    // read buffer, copy to FFT input (applying window)
+    // TODO: use SIMD extensions to speed this up
+    TI * rc;
+    WINDOW(_read)(_q->buffer, &rc);
+    for (i=0; i<_q->window_len; i++)
+        _q->buf_time[i] = rc[i] * _q->w[i];
+
+    // execute fft on _q->buf_time and store result in _q->buf_freq
+    FFT_EXECUTE(_q->fft);
+
+    // accumulate output
+    for (i=0; i<_q->nfft; i++) {
+        T v = crealf( _q->buf_freq[i] * conjf(_q->buf_freq[i]) );
+        if (_q->num_transforms == 0)
+            _q->psd[i] = v;
+        else
+            _q->psd[i] = _q->gamma*_q->psd[i] + _q->alpha*v;
+    }
+
+    _q->num_transforms++;
+    _q->num_transforms_total++;
+}
+
+// compute spectral periodogram output (fft-shifted values
+// in dB) from current buffer contents
+//  _q      :   spgram object
+//  _X      :   output spectrum [size: _nfft x 1]
+void SPGRAM(_get_psd)(SPGRAM() _q,
+                      T *      _X)
+{
+    // compute magnitude in dB and run FFT shift
+    unsigned int i;
+    unsigned int nfft_2 = _q->nfft / 2;
+    T scale = _q->accumulate ? -10*log10f(_q->num_transforms) : 0.0f;
+    // TODO: adjust scale if infinite integration
+    for (i=0; i<_q->nfft; i++) {
+        unsigned int k = (i + nfft_2) % _q->nfft;
+        _X[i] = 10*log10f(_q->psd[k]+1e-6f) + scale;
+    }
+}
+
+// export gnuplot file
+//  _q        : spgram object
+//  _filename : input buffer [size: _n x 1]
+int SPGRAM(_export_gnuplot)(SPGRAM()     _q,
+                            const char * _filename)
+{
+    FILE * fid = fopen(_filename,"w");
+    if (fid == NULL) {
+        fprintf(stderr,"error: spgram%s_export_gnuplot(), could not open '%s' for writing\n",
+                EXTENSION, _filename);
+        return -1;
+    }
+    fprintf(fid,"# %s : auto-generated file\n", _filename);
+    fprintf(fid,"reset\n");
+    fprintf(fid,"set terminal png size 1200,800 enhanced font 'Verdana,10'\n");
+    fprintf(fid,"set output '%s.png'\n", _filename);
+    fprintf(fid,"set xrange [-0.5:0.5]\n");
+    fprintf(fid,"set autoscale y\n");
+    fprintf(fid,"set xlabel 'Noramlized Frequency'\n");
+    fprintf(fid,"set ylabel 'Power Spectral Density'\n");
+    fprintf(fid,"set style line 12 lc rgb '#404040' lt 0 lw 1\n");
+    fprintf(fid,"set grid xtics ytics\n");
+    fprintf(fid,"set grid front ls 12\n");
+    fprintf(fid,"set style fill transparent solid 0.2\n");
+    fprintf(fid,"set nokey\n");
+    fprintf(fid,"plot '-' w filledcurves x1 lt 1 lw 2 lc rgb '#004080'\n");
+
+    // export spectrum data
+    T * psd = (T*) malloc(_q->nfft * sizeof(T));
+    SPGRAM(_get_psd)(_q, psd);
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++)
+        fprintf(fid,"  %12.8f %12.8f\n", (float)i/(float)(_q->nfft)-0.5f, (float)(psd[i]));
+    free(psd);
+    fprintf(fid,"e\n");
+
+    // close it up
+    fclose(fid);
+
+    return 0;
+}
+
+//
+// object-independent methods
+//
+
+// estimate spectrum on input signal
+//  _nfft   :   FFT size
+//  _x      :   input signal [size: _n x 1]
+//  _n      :   input signal length
+//  _psd    :   output spectrum, [size: _nfft x 1]
+void SPGRAM(_estimate_psd)(unsigned int _nfft,
+                           TI *         _x,
+                           unsigned int _n,
+                           T *          _psd)
+{
+    // create object
+    SPGRAM() q = SPGRAM(_create_default)(_nfft);
+
+    // run spectral estimate on entire sequence
+    SPGRAM(_write)(q, _x, _n);
+
+    // get PSD estimate
+    SPGRAM(_get_psd)(q, _psd);
+
+    // destroy object
+    SPGRAM(_destroy)(q);
+}
+
diff --git a/src/fft/src/spgramcf.c b/src/fft/src/spgramcf.c
new file mode 100644
index 0000000..d25fccc
--- /dev/null
+++ b/src/fft/src/spgramcf.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// spectral periodogram API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION           "cf"
+
+// name-mangling macros
+#define ASGRAM(name)        LIQUID_CONCAT(asgramcf,name)
+#define SPGRAM(name)        LIQUID_CONCAT(spgramcf,name)
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define FFT(name)           LIQUID_CONCAT(fft,name)
+
+#define T                   float           // primitive type (real)
+#define TC                  float complex   // primitive type (complex)
+#define TI                  float complex   // input type
+
+#define TI_COMPLEX          1
+
+#define PRINTVAL_T(X,F)     PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// source files
+#include "asgram.c"
+#include "spgram.c"
+
diff --git a/src/fft/src/spgramf.c b/src/fft/src/spgramf.c
new file mode 100644
index 0000000..2255035
--- /dev/null
+++ b/src/fft/src/spgramf.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// spectral periodogram API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION           "f"
+
+// name-mangling macros
+#define ASGRAM(name)        LIQUID_CONCAT(asgramf,name)
+#define SPGRAM(name)        LIQUID_CONCAT(spgramf,name)
+#define WINDOW(name)        LIQUID_CONCAT(windowf,name)
+#define FFT(name)           LIQUID_CONCAT(fft,name)
+
+#define T                   float           // primitive type (real)
+#define TC                  float complex   // primitive type (complex)
+#define TI                  float           // input type
+
+#define TI_COMPLEX          0
+
+#define PRINTVAL_T(X,F)     PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_FLOAT(X,F)
+
+// source files
+#include "asgram.c"
+#include "spgram.c"
+
diff --git a/src/fft/tests/data/fft_data_10.c b/src/fft/tests/data/fft_data_10.c
new file mode 100644
index 0000000..26e0afc
--- /dev/null
+++ b/src/fft/tests/data/fft_data_10.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 10-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x10[] = {
+   -0.380648737020 +   1.003981780953*_Complex_I,
+    1.031511152163 +  -2.625896014009*_Complex_I,
+   -1.083239396623 +   1.646877001105*_Complex_I,
+    0.951587457487 +  -0.004983138281*_Complex_I,
+    0.407589360084 +   0.345698641918*_Complex_I,
+    0.549291472049 +   0.542579734652*_Complex_I,
+   -0.911825526748 +   1.282009726257*_Complex_I,
+   -0.617849040964 +   0.696673367751*_Complex_I,
+    1.097501043733 +   1.373947311009*_Complex_I,
+    0.848713422957 +  -0.738252787172*_Complex_I};
+
+float complex fft_test_y10[] = {
+    1.892631207117 +   3.522635624182*_Complex_I,
+   -1.167216826866 +  -3.158947047615*_Complex_I,
+   -0.019614668329 +   1.291770408491*_Complex_I,
+   -3.842057814631 +  -1.668342848977*_Complex_I,
+   -2.323082893679 +   1.200058008683*_Complex_I,
+   -3.633877720265 +   7.782393298301*_Complex_I,
+    0.649048218143 +   6.997144832999*_Complex_I,
+    1.025423884758 +   1.907666229150*_Complex_I,
+    0.644231811894 +  -5.278801296330*_Complex_I,
+    2.968027431661 +  -2.555759399357*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_120.c b/src/fft/tests/data/fft_data_120.c
new file mode 100644
index 0000000..c8f39a3
--- /dev/null
+++ b/src/fft/tests/data/fft_data_120.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 120-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x120[] = {
+   -2.933561116248 +   0.585802030432*_Complex_I,
+    0.996149474765 +  -0.621353657548*_Complex_I,
+   -0.262138535383 +   0.974643272654*_Complex_I,
+   -1.483709823026 +  -1.654326811798*_Complex_I,
+    0.009105914718 +  -0.801453728867*_Complex_I,
+    1.480357059658 +  -0.231135073122*_Complex_I,
+   -0.580765210290 +   1.288460832776*_Complex_I,
+    0.485467815383 +   0.439565460988*_Complex_I,
+   -1.010673740947 +  -0.691639979516*_Complex_I,
+   -0.525003222625 +  -0.639606200048*_Complex_I,
+   -0.806916915589 +   0.103762356730*_Complex_I,
+   -0.399336315292 +   0.323161793208*_Complex_I,
+    0.111455618955 +   0.487552495249*_Complex_I,
+    0.523592233718 +   0.565527718910*_Complex_I,
+   -0.181781072708 +   0.927375893986*_Complex_I,
+   -0.427811932504 +  -1.730760015952*_Complex_I,
+    0.722234655868 +  -0.528518032448*_Complex_I,
+    0.327894303730 +   1.078490369290*_Complex_I,
+   -0.365985637484 +  -0.829110156759*_Complex_I,
+   -1.230443935235 +   1.168892009209*_Complex_I,
+   -0.497547601907 +  -1.884812736460*_Complex_I,
+   -0.188693666275 +  -0.388189056130*_Complex_I,
+    1.581490442848 +  -0.773206675201*_Complex_I,
+   -0.276904037052 +  -1.607191981281*_Complex_I,
+    0.869586527848 +   2.279865113156*_Complex_I,
+    2.280976663440 +  -0.491932963519*_Complex_I,
+   -0.972146414270 +   1.317390598922*_Complex_I,
+    0.752226877706 +   0.307325301979*_Complex_I,
+   -2.176688443840 +  -0.178238348418*_Complex_I,
+    0.603808804599 +  -1.024869177311*_Complex_I,
+    1.637709885855 +   1.283852424020*_Complex_I,
+    0.766867282671 +  -2.390532794299*_Complex_I,
+   -0.803164170514 +  -0.738964307326*_Complex_I,
+   -0.217928168834 +  -1.591138647698*_Complex_I,
+    0.044558930462 +  -0.585819999208*_Complex_I,
+    0.185461763498 +   0.190876001378*_Complex_I,
+   -0.887233966458 +   0.932582089649*_Complex_I,
+   -1.388038826042 +   0.114934886174*_Complex_I,
+    0.079732100731 +   0.150414346304*_Complex_I,
+    1.215962906658 +   0.864185821675*_Complex_I,
+   -0.889642609373 +  -0.510424427491*_Complex_I,
+    0.243053087540 +  -1.438563895787*_Complex_I,
+   -0.409793881375 +   0.157176601902*_Complex_I,
+   -1.193700035506 +  -0.866457731187*_Complex_I,
+    1.192127022305 +  -0.584612994505*_Complex_I,
+   -0.387515835212 +   1.536461365714*_Complex_I,
+   -0.853256362306 +  -0.633564682282*_Complex_I,
+   -0.152836605296 +  -0.219510310627*_Complex_I,
+   -1.151934152005 +   0.171485405414*_Complex_I,
+   -0.010116218440 +   0.914918482762*_Complex_I,
+   -1.686537878154 +   0.779331814618*_Complex_I,
+    0.673793112643 +   0.566118503412*_Complex_I,
+   -0.143701669515 +  -0.556780150872*_Complex_I,
+   -0.845847069767 +   0.686668013963*_Complex_I,
+   -1.147722980640 +  -0.455850082513*_Complex_I,
+    1.310965019946 +  -0.268939336441*_Complex_I,
+    0.641191445468 +   0.065635531845*_Complex_I,
+    0.790297755545 +   0.898978586598*_Complex_I,
+   -1.539239983789 +   1.569958337837*_Complex_I,
+   -1.042534751831 +  -1.615908992141*_Complex_I,
+    0.848219334777 +   1.570278962754*_Complex_I,
+    0.768312422101 +   1.168572048009*_Complex_I,
+   -0.119617114473 +   0.018922768020*_Complex_I,
+    0.463083556040 +  -0.289189219121*_Complex_I,
+   -2.334868450076 +   0.543186130761*_Complex_I,
+   -0.859810016516 +  -0.073915244925*_Complex_I,
+   -0.986571365561 +   0.653098182477*_Complex_I,
+    0.522451913274 +  -0.042046367028*_Complex_I,
+    0.106640796179 +  -0.069187996590*_Complex_I,
+    1.052892113567 +   0.678469397559*_Complex_I,
+   -0.698384198090 +   0.402405190110*_Complex_I,
+   -0.274412699197 +   0.194991340203*_Complex_I,
+    0.154775651261 +  -1.748234046267*_Complex_I,
+   -0.373611252309 +   0.759957440236*_Complex_I,
+    0.307971942775 +   0.177025819034*_Complex_I,
+    1.127535219986 +  -0.133866569607*_Complex_I,
+   -0.780872981180 +  -1.914809677751*_Complex_I,
+    0.097809503845 +  -0.311527508403*_Complex_I,
+   -1.338663250568 +  -0.272131094279*_Complex_I,
+   -1.774506466862 +   0.189947566984*_Complex_I,
+   -0.108910232947 +  -1.104077803690*_Complex_I,
+    0.700684038217 +   0.489503255191*_Complex_I,
+    1.723390943656 +  -0.653061888020*_Complex_I,
+   -0.307392508631 +  -0.487335518704*_Complex_I,
+   -1.351222246370 +  -0.276340698500*_Complex_I,
+    0.554657638847 +   1.969266502927*_Complex_I,
+   -0.693153982211 +  -0.117977636798*_Complex_I,
+   -0.639852935555 +   0.539350932011*_Complex_I,
+    0.774200449083 +   1.123041486424*_Complex_I,
+   -2.046058231062 +  -0.457644959516*_Complex_I,
+   -1.606346330196 +  -1.560038496690*_Complex_I,
+   -1.701087998893 +   1.670050065045*_Complex_I,
+    0.443897868781 +   0.379995716985*_Complex_I,
+    0.193958078518 +   0.643773547931*_Complex_I,
+   -1.527640156621 +   1.266495146511*_Complex_I,
+    0.259186234049 +  -0.465035554366*_Complex_I,
+   -0.385562205417 +  -1.015469771575*_Complex_I,
+   -1.871226440588 +   0.185126256408*_Complex_I,
+    0.542445571049 +  -0.431681611434*_Complex_I,
+    0.214440313475 +  -0.338588753334*_Complex_I,
+    0.163385540462 +  -0.222892296796*_Complex_I,
+    0.477872198487 +  -0.578917341564*_Complex_I,
+   -1.258436301493 +  -0.335313659938*_Complex_I,
+    0.310183724816 +  -1.538340991002*_Complex_I,
+    0.315711034668 +   0.372755872975*_Complex_I,
+   -0.832316134028 +   0.846715050479*_Complex_I,
+    1.810572502100 +  -0.747188365112*_Complex_I,
+   -1.238997433684 +   2.921109879602*_Complex_I,
+   -1.359334420871 +   1.137914515456*_Complex_I,
+   -1.735544412609 +   0.353972290274*_Complex_I,
+   -1.967071931759 +   1.720943408177*_Complex_I,
+    0.175659653421 +   0.673882179212*_Complex_I,
+    0.566435305275 +   1.086122584518*_Complex_I,
+   -0.685557294027 +   0.440265086848*_Complex_I,
+    1.232868086689 +  -0.376035944159*_Complex_I,
+    0.031017508722 +  -1.091694970471*_Complex_I,
+    0.153454665192 +  -0.387812357268*_Complex_I,
+    0.055087132749 +   0.121934454858*_Complex_I,
+   -1.641857455950 +  -0.680302798653*_Complex_I,
+   -0.235668947578 +  -0.276093078030*_Complex_I};
+
+float complex fft_test_y120[] = {
+  -26.130540532441 +   2.500301372380*_Complex_I,
+   -8.384737785882 + -11.493299680300*_Complex_I,
+  -10.815843993705 +   1.373214442809*_Complex_I,
+   -3.468316057471 +  -0.197988490875*_Complex_I,
+   -8.468015203881 +   4.172305025792*_Complex_I,
+   -6.972140924378 + -19.059978062591*_Complex_I,
+   12.894909733921 + -14.674650174719*_Complex_I,
+    6.217174828182 +  -4.198857679111*_Complex_I,
+   -3.038713671007 +  11.416582830167*_Complex_I,
+   -5.889009032363 +   2.623087027225*_Complex_I,
+    4.415856321129 +  20.520368038723*_Complex_I,
+   -7.596161829888 +  -9.011449139900*_Complex_I,
+  -22.967094572900 + -14.281559182333*_Complex_I,
+  -13.103802451651 +   2.098170225144*_Complex_I,
+   -5.815320588822 +  10.518428726478*_Complex_I,
+   -1.290859552698 +  -5.007381379220*_Complex_I,
+   10.503332312095 + -13.128075179264*_Complex_I,
+   -0.995307687739 +   6.404055459139*_Complex_I,
+   -7.098602985708 +   2.406450947955*_Complex_I,
+   -5.425368507411 +  31.439384831310*_Complex_I,
+   -4.531419053887 +  -0.173481154479*_Complex_I,
+    9.402659099609 +   3.570607998710*_Complex_I,
+   10.130149004652 + -16.694138931142*_Complex_I,
+   -2.197851988961 +   2.938436935104*_Complex_I,
+    5.864606545918 + -13.199247801155*_Complex_I,
+   12.427931476763 + -14.442851189707*_Complex_I,
+   -0.346878049148 +  -2.649522091233*_Complex_I,
+   -6.110432016865 +  10.576069646496*_Complex_I,
+   29.337845632961 +  -5.226538341924*_Complex_I,
+   -5.530434871933 +  -6.791491806236*_Complex_I,
+   12.511893544960 +  -9.883054566598*_Complex_I,
+  -13.522823191342 +   3.402811573476*_Complex_I,
+   -6.949602446599 +  -2.976747287959*_Complex_I,
+  -13.604769865716 + -21.208579208314*_Complex_I,
+   -2.667739313157 +   7.095560898932*_Complex_I,
+   -4.030948890524 +  -4.275911410965*_Complex_I,
+   -1.538455879764 +   0.859913764271*_Complex_I,
+   -1.597640929072 +   9.198642244462*_Complex_I,
+    3.258927941252 +   3.681214115496*_Complex_I,
+  -27.177542272720 +  -9.835919044876*_Complex_I,
+    2.137018426012 +   5.951425737520*_Complex_I,
+   -9.328564597213 +  -8.189268926052*_Complex_I,
+  -15.202874868646 + -10.830493804699*_Complex_I,
+  -14.141307626573 +   6.069950543455*_Complex_I,
+   23.789715337183 +  -0.149207303988*_Complex_I,
+    1.018819274801 +  23.160092205105*_Complex_I,
+    0.160728240887 +  25.021802134125*_Complex_I,
+  -27.292399750004 + -12.724069599967*_Complex_I,
+  -11.090997281400 +   7.909805227447*_Complex_I,
+  -12.632686116172 +   4.297788877667*_Complex_I,
+  -10.947166654332 +  -7.342984066688*_Complex_I,
+  -12.526588806117 +   6.326089996957*_Complex_I,
+    7.848075855545 +   5.822833656652*_Complex_I,
+    4.463550473312 +  -2.355918199764*_Complex_I,
+   -3.359730942688 +  -1.811340237346*_Complex_I,
+  -16.045827623056 + -11.927425635813*_Complex_I,
+  -17.191945449780 +  14.640840260710*_Complex_I,
+    5.723503979540 +  -3.333396073843*_Complex_I,
+   -7.212762063453 +  -0.426026153122*_Complex_I,
+   -8.371988589537 +  -1.588815189476*_Complex_I,
+  -16.721024926709 +   1.223543596227*_Complex_I,
+   21.500278405653 +  15.014414278563*_Complex_I,
+  -12.998260008435 +   6.823922119636*_Complex_I,
+  -11.969924059222 +  -6.475357643023*_Complex_I,
+   -3.880671177744 +  23.295158190426*_Complex_I,
+   -1.615932730740 +   9.161241055173*_Complex_I,
+    6.743423357908 +   4.077889764270*_Complex_I,
+   -8.491295100234 + -14.590167909493*_Complex_I,
+   -4.050564794383 +  -0.332576254078*_Complex_I,
+   -7.563666582981 +  -0.491929778422*_Complex_I,
+    5.325292014900 +  13.120067752469*_Complex_I,
+   17.266746589948 +  -1.749739091662*_Complex_I,
+    9.016685746645 +  -6.656805505959*_Complex_I,
+   -8.457355300299 +  -4.003222916857*_Complex_I,
+  -31.407914776214 +  -9.344158206903*_Complex_I,
+   -8.003020240767 +  -1.945349879704*_Complex_I,
+   10.679124047221 +   1.579265510278*_Complex_I,
+    7.576096581517 + -20.019004345583*_Complex_I,
+    5.425463900123 +  -6.332000571240*_Complex_I,
+  -11.713907656105 +   7.443366839954*_Complex_I,
+   -0.593664650946 +   6.790006366701*_Complex_I,
+   10.381068180108 +  -7.397393197217*_Complex_I,
+    0.455908842424 +   7.373964657268*_Complex_I,
+   -8.378432107508 +  18.256672106713*_Complex_I,
+  -24.128536613056 +   5.444870431684*_Complex_I,
+    8.421822254234 +  -9.806814459804*_Complex_I,
+    7.173520639058 +   9.565811687718*_Complex_I,
+   -6.236143277340 +   6.409295846945*_Complex_I,
+   -5.379127157610 +  -7.097598602556*_Complex_I,
+    0.992816214134 +   1.184456111668*_Complex_I,
+   -8.630312793127 +  -3.752996076907*_Complex_I,
+   -0.458097520531 +  10.469419461305*_Complex_I,
+    3.184993888854 +   4.049136044816*_Complex_I,
+    2.080876935294 +  -9.778155908324*_Complex_I,
+  -12.941818037027 +   3.242798433849*_Complex_I,
+   -6.524522778987 +   2.497157598131*_Complex_I,
+    3.225741204857 +  15.546794796319*_Complex_I,
+   -6.079247610512 + -13.728424650306*_Complex_I,
+   -4.960232908834 +  10.931039387444*_Complex_I,
+   -2.558877649510 +  10.755632383919*_Complex_I,
+  -19.611477019565 +   5.785472297267*_Complex_I,
+   21.866745387027 + -15.392995894737*_Complex_I,
+   16.349317592240 +  12.469152896354*_Complex_I,
+   -9.294214354347 +   3.922833779556*_Complex_I,
+   13.879970160908 +   7.328114738105*_Complex_I,
+    2.651592361533 +  -7.170628973597*_Complex_I,
+   -5.253905486354 +   2.891157258399*_Complex_I,
+   -8.491348558042 +  11.767371163610*_Complex_I,
+   -0.986424222839 +  12.293687558243*_Complex_I,
+  -16.973656701850 +  -4.692292471582*_Complex_I,
+  -10.242186370330 +  14.766563852584*_Complex_I,
+   -5.961461862786 +  15.202716060356*_Complex_I,
+   -8.983395642462 + -16.343883875645*_Complex_I,
+  -10.094160152160 +  -8.605909997569*_Complex_I,
+   -2.538579712014 + -14.632787859230*_Complex_I,
+   10.260131998695 +  -4.124613468367*_Complex_I,
+    5.155214364436 +  -0.647489300204*_Complex_I,
+   -7.821333192327 + -15.606994193249*_Complex_I,
+    4.093578304649 +  15.462757600992*_Complex_I,
+    4.765467299654 +   7.963215307045*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_130.c b/src/fft/tests/data/fft_data_130.c
new file mode 100644
index 0000000..c017fa1
--- /dev/null
+++ b/src/fft/tests/data/fft_data_130.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 130-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x130[] = {
+    2.295593731681 +   0.411874155977*_Complex_I,
+    0.867233010936 +   2.391238924231*_Complex_I,
+   -0.979511815183 +   0.113140414722*_Complex_I,
+    0.077578424280 +   1.186610948031*_Complex_I,
+   -1.071528198235 +   0.804300526324*_Complex_I,
+    2.564018851452 +  -0.310561947705*_Complex_I,
+   -0.950972074792 +   0.436022224496*_Complex_I,
+    0.329631038192 +  -0.396044027241*_Complex_I,
+   -0.862183775613 +   0.927159525327*_Complex_I,
+   -1.059508389407 +   0.941689902943*_Complex_I,
+    0.561615143764 +   0.096377911775*_Complex_I,
+    0.534738747717 +   0.825200677825*_Complex_I,
+    0.532672479322 +  -1.976151133815*_Complex_I,
+   -1.310581230339 +   0.002609415320*_Complex_I,
+   -0.093941505596 +   0.905920914198*_Complex_I,
+    0.925525275182 +   1.564605784264*_Complex_I,
+   -0.117943104365 +  -0.166904851246*_Complex_I,
+   -0.009619069574 +   1.676338431134*_Complex_I,
+   -1.367286699092 +   1.175301759986*_Complex_I,
+   -0.053699588910 +   1.515076774609*_Complex_I,
+   -0.070846548150 +  -1.323601968188*_Complex_I,
+    1.796443691981 +  -2.011926920619*_Complex_I,
+    0.493444077774 +  -0.231926417128*_Complex_I,
+    0.426102034457 +  -0.966613330823*_Complex_I,
+   -0.403283890205 +   0.570936153832*_Complex_I,
+    0.339532677572 +  -2.038175992612*_Complex_I,
+    0.202689295315 +   0.573201095278*_Complex_I,
+   -0.571885221166 +  -1.275661165797*_Complex_I,
+   -0.794196427625 +   0.423266616492*_Complex_I,
+   -1.259472882356 +   0.532748103823*_Complex_I,
+    0.578465509624 +   0.826666605971*_Complex_I,
+   -0.185287664847 +   1.746937914937*_Complex_I,
+    0.763423434636 +   2.020182432630*_Complex_I,
+    0.086623435989 +   0.431914895390*_Complex_I,
+    1.507900753669 +   0.056290417021*_Complex_I,
+   -0.032809671097 +   0.098620827398*_Complex_I,
+    1.816125083125 +   0.081663531528*_Complex_I,
+    0.214492964742 +  -0.162240129055*_Complex_I,
+    0.189647728207 +  -1.547086817248*_Complex_I,
+   -0.273499165641 +  -0.552833918252*_Complex_I,
+    0.118280846537 +   2.716796709548*_Complex_I,
+    0.771040737403 +   2.763544783033*_Complex_I,
+   -1.225097272428 +   0.386568340878*_Complex_I,
+   -0.106529445935 +   2.317399662638*_Complex_I,
+   -0.309752877128 +   0.781864233638*_Complex_I,
+   -0.382242026751 +   0.530778535695*_Complex_I,
+    2.187686178145 +  -1.629696212455*_Complex_I,
+   -0.796121964339 +   1.212447992147*_Complex_I,
+    0.483902967137 +  -1.761611437465*_Complex_I,
+    0.078193155045 +  -0.695682313346*_Complex_I,
+   -0.586826944592 +   0.119088984700*_Complex_I,
+    0.105215960567 +   1.605824117591*_Complex_I,
+   -1.096678506319 +   0.626271388642*_Complex_I,
+   -1.393844336696 +   0.006228875154*_Complex_I,
+   -0.563177155438 +  -0.137367111477*_Complex_I,
+    0.009064659930 +   0.774248879600*_Complex_I,
+    0.116957909967 +  -0.374512247987*_Complex_I,
+    0.139602276134 +   1.196173350261*_Complex_I,
+    2.212769662630 +   0.374920897734*_Complex_I,
+    0.016527615895 +   0.361537040940*_Complex_I,
+   -0.121813458827 +  -1.829691824294*_Complex_I,
+   -1.177075906748 +   0.142539646960*_Complex_I,
+    0.036310389827 +   1.045967306206*_Complex_I,
+   -0.065389592517 +  -1.807892452763*_Complex_I,
+   -1.387880762495 +   0.171082957785*_Complex_I,
+   -0.561065052343 +  -1.260326041796*_Complex_I,
+    0.640813649741 +  -1.777293190718*_Complex_I,
+   -0.854417690416 +   2.237007530372*_Complex_I,
+   -0.547722791713 +  -2.330803396241*_Complex_I,
+    0.241170879004 +  -0.617927403601*_Complex_I,
+    1.149285559687 +  -0.376338314437*_Complex_I,
+   -0.611492918690 +   2.047469301403*_Complex_I,
+    1.187263678013 +  -0.165876393098*_Complex_I,
+    0.165257041186 +   1.672015729392*_Complex_I,
+   -0.994770290926 +  -0.351786586669*_Complex_I,
+   -0.899279932248 +   0.810204064241*_Complex_I,
+   -0.989604008277 +  -0.119746717438*_Complex_I,
+   -0.645537324969 +   0.353174690969*_Complex_I,
+   -0.170329112065 +   0.085003743875*_Complex_I,
+   -0.622581542228 +   0.035852829902*_Complex_I,
+    1.027721243658 +  -1.009164344833*_Complex_I,
+   -0.512899325586 +   1.422707083000*_Complex_I,
+   -1.537474288640 +   0.734758277184*_Complex_I,
+    0.300068603056 +  -0.844600037520*_Complex_I,
+    1.173177814994 +   0.474849978182*_Complex_I,
+    1.994390672833 +   0.274156356539*_Complex_I,
+   -0.395247012804 +   1.046793121497*_Complex_I,
+    0.111209493599 +  -1.063639370387*_Complex_I,
+    0.804028185695 +  -2.330860293474*_Complex_I,
+   -0.790484315366 +  -2.043033932357*_Complex_I,
+   -1.938436910347 +   0.005399210558*_Complex_I,
+   -2.213763088095 +  -0.050657389028*_Complex_I,
+   -1.085606481444 +  -1.672620738363*_Complex_I,
+    0.114374573479 +  -0.314566566070*_Complex_I,
+   -0.334479316133 +  -0.269357185023*_Complex_I,
+   -0.405597270856 +  -1.024425491739*_Complex_I,
+   -0.268446228006 +   0.625273024277*_Complex_I,
+    0.114305720338 +   0.091667406850*_Complex_I,
+    0.458271491171 +   0.893733272049*_Complex_I,
+    2.164470970876 +  -0.008479990723*_Complex_I,
+    2.534318741148 +  -0.513177256373*_Complex_I,
+    1.349837294176 +   0.730739449071*_Complex_I,
+    0.178600395004 +   0.598903569881*_Complex_I,
+    0.999820793703 +   0.023280883440*_Complex_I,
+    2.567157231145 +   2.857783095653*_Complex_I,
+    1.121937084722 +  -0.127318755949*_Complex_I,
+   -1.476481512156 +   0.438739152692*_Complex_I,
+    0.102675413849 +  -1.072173994008*_Complex_I,
+    1.753108769750 +  -1.363923993462*_Complex_I,
+   -0.329713403173 +  -0.836197629494*_Complex_I,
+    0.053237332369 +  -0.316007225300*_Complex_I,
+    1.094272758407 +   0.154099319830*_Complex_I,
+    1.120647617053 +   2.290453172159*_Complex_I,
+   -0.909112485707 +  -0.663394681024*_Complex_I,
+    0.427832898254 +   0.948020241699*_Complex_I,
+    0.346684378724 +   1.412486035318*_Complex_I,
+   -0.142628142360 +   0.034555947923*_Complex_I,
+    0.329645857287 +   2.194959848150*_Complex_I,
+   -0.914476449793 +   0.031998423787*_Complex_I,
+    3.135765246395 +  -1.497430797101*_Complex_I,
+    1.209028626447 +  -0.686517697463*_Complex_I,
+    0.379503693068 +  -0.842990842120*_Complex_I,
+   -0.660923599405 +  -0.501650971018*_Complex_I,
+    0.299565764767 +   1.443185673085*_Complex_I,
+   -0.436332705510 +  -0.166601489288*_Complex_I,
+    0.671417018091 +   1.996886835639*_Complex_I,
+   -0.357759632151 +  -0.399361979644*_Complex_I,
+    1.113330373199 +  -0.350135780628*_Complex_I,
+   -0.281958991560 +  -0.356378430727*_Complex_I,
+    1.134108928078 +  -2.092860871008*_Complex_I};
+
+float complex fft_test_y130[] = {
+   14.378246546427 +  16.821529859589*_Complex_I,
+   27.857066272618 +  13.571899136472*_Complex_I,
+  -15.456704775735 +  12.690273582209*_Complex_I,
+   -8.753035425840 +   7.321557750269*_Complex_I,
+   28.113754727138 + -12.547913966687*_Complex_I,
+   10.563681172036 +   7.127854603059*_Complex_I,
+   -6.710153682806 +   1.534074734434*_Complex_I,
+   -0.152583781138 +   1.608751427720*_Complex_I,
+    9.516727413961 + -11.938945268968*_Complex_I,
+   11.591596283751 +  29.464218232945*_Complex_I,
+   -1.861083239337 +   0.048807386609*_Complex_I,
+   13.530879033363 + -18.257581820108*_Complex_I,
+  -11.904116745072 + -21.233551918127*_Complex_I,
+   25.195023350250 +  -2.967265398356*_Complex_I,
+   11.563020359283 +  -8.938209578954*_Complex_I,
+   -2.212982769038 +  20.141015933481*_Complex_I,
+    9.560556874374 +  25.805352474019*_Complex_I,
+    9.352588131212 +  -7.076490204727*_Complex_I,
+   19.915109002940 +   6.825454682895*_Complex_I,
+    9.736225779794 +  -4.099017628979*_Complex_I,
+   10.036798782148 +   4.863204800610*_Complex_I,
+   -5.027402703494 +   4.645951171953*_Complex_I,
+    0.722099014183 +   4.901713974497*_Complex_I,
+   12.341913244180 +   4.734214019087*_Complex_I,
+    6.490076890583 + -14.694265064166*_Complex_I,
+   18.468325287177 +  -3.754877637942*_Complex_I,
+   30.687354000703 +   1.981540291860*_Complex_I,
+  -17.305129028905 +   1.869860221279*_Complex_I,
+    3.696020771673 +  -4.871064988434*_Complex_I,
+   15.580947342376 +  22.519564496626*_Complex_I,
+    3.848144651945 +  -1.781612409800*_Complex_I,
+    8.942942784269 +  -3.602301409947*_Complex_I,
+   -7.765663555408 +  -0.625063385554*_Complex_I,
+  -19.365854088060 +  -1.898384733722*_Complex_I,
+   22.654736737697 + -31.009432894736*_Complex_I,
+    8.366067634071 +  17.695248714554*_Complex_I,
+   13.661573039778 +   5.489335650118*_Complex_I,
+    7.965893046432 +   0.920878507501*_Complex_I,
+   -6.773323972099 +  -8.838093482259*_Complex_I,
+   18.008424231247 +   0.970453153687*_Complex_I,
+    3.616219743282 +  -7.106399136439*_Complex_I,
+    4.354625182855 +   6.303312903302*_Complex_I,
+    2.152940232565 +  10.856400632985*_Complex_I,
+    9.549358562936 +   3.736001577178*_Complex_I,
+  -23.393358100356 +  -6.204075948816*_Complex_I,
+   15.508680212133 +   8.577949641991*_Complex_I,
+   -7.063921889490 +  -9.211630834511*_Complex_I,
+   21.009705406653 +  -6.517205812361*_Complex_I,
+   15.423888702704 +   0.981999687774*_Complex_I,
+   28.573348594219 +   5.383290022507*_Complex_I,
+   15.304347003446 +  27.118709596573*_Complex_I,
+   14.142070126013 +   6.277279353863*_Complex_I,
+    1.625323780309 +  -9.187451801090*_Complex_I,
+    8.830870090341 +  18.810210753448*_Complex_I,
+   -0.345015176149 +  -9.619976818810*_Complex_I,
+    7.341484437606 +  -0.732292583861*_Complex_I,
+    8.958852700478 + -10.587428450375*_Complex_I,
+    3.882707974052 + -17.745844755612*_Complex_I,
+   -8.873303252125 + -15.211822737393*_Complex_I,
+   -6.167534400705 +  10.214589494545*_Complex_I,
+  -20.228656724261 +  17.585629744635*_Complex_I,
+   10.933318198559 +  14.370328231006*_Complex_I,
+   -8.461977401717 + -11.180604276190*_Complex_I,
+   -9.616742566226 + -24.903826036989*_Complex_I,
+  -11.411472927768 +  17.575007330620*_Complex_I,
+   -2.685486674198 + -14.771303637127*_Complex_I,
+  -25.251618764170 +   6.826824013717*_Complex_I,
+   -6.299724034225 + -16.396112316751*_Complex_I,
+   -7.265214784934 +  -5.565409121926*_Complex_I,
+   -6.640524692135 +   0.521019036698*_Complex_I,
+   16.214262242728 +   2.209865277239*_Complex_I,
+   -5.045705495784 +   7.246063716170*_Complex_I,
+   12.635656398329 +   5.953408206671*_Complex_I,
+    7.826253258739 +  10.349167227222*_Complex_I,
+    2.088937948255 +  -5.121963022405*_Complex_I,
+  -11.873961874446 +  14.994764484717*_Complex_I,
+   22.398422820899 +   6.844715327780*_Complex_I,
+  -12.730924558399 + -11.992318329822*_Complex_I,
+   13.524168525742 +  -4.729670042115*_Complex_I,
+    1.202484810688 +  -1.663460844124*_Complex_I,
+   -1.947874132965 +   1.302757622556*_Complex_I,
+  -20.543601027337 +  12.258518226726*_Complex_I,
+   -3.137767302170 +  -2.981480633371*_Complex_I,
+   -7.975513140256 + -16.485317151074*_Complex_I,
+    8.810596349659 +   6.332990185486*_Complex_I,
+   -6.161246346457 +   5.235039382005*_Complex_I,
+    2.843534766863 +  -4.617409607122*_Complex_I,
+   -4.073520680077 +  -5.879651303740*_Complex_I,
+   -2.171553918002 +  -2.554595714543*_Complex_I,
+   -6.693230033855 +  -4.317974300454*_Complex_I,
+    2.510552087942 +   5.009469541979*_Complex_I,
+  -19.083210256655 +   5.125864403228*_Complex_I,
+    0.411198384867 +   2.018760355265*_Complex_I,
+   -9.315948091880 + -15.753105210356*_Complex_I,
+    7.665007791183 +  19.339077441185*_Complex_I,
+   17.992904291137 +   1.608531544860*_Complex_I,
+    7.252549051948 +   5.185390374966*_Complex_I,
+   -3.402294497131 +  15.472029335175*_Complex_I,
+  -10.304002604604 +  -3.412309568194*_Complex_I,
+    3.576347909571 + -16.980789801741*_Complex_I,
+  -14.889487424902 +  -0.293460269814*_Complex_I,
+   -2.329133331489 +  15.570197654593*_Complex_I,
+   -4.321477736813 +   4.956074608145*_Complex_I,
+    3.632694634916 +  11.871283520762*_Complex_I,
+    2.290904837875 +  -0.771528125573*_Complex_I,
+   36.996035166517 +  -1.073727284586*_Complex_I,
+    6.227709813318 +  -9.761528406472*_Complex_I,
+   -2.515872122268 +   0.034234272997*_Complex_I,
+    4.552755682518 +  -9.370352932646*_Complex_I,
+    9.004603455113 +   4.753018490255*_Complex_I,
+    0.440587766505 +  16.114787576668*_Complex_I,
+  -28.634077387281 +  -5.605910818928*_Complex_I,
+   -5.160190484618 + -11.779239644678*_Complex_I,
+  -17.819659704355 +   0.062857325389*_Complex_I,
+  -14.769191642874 +  -0.223091433159*_Complex_I,
+    2.070190617098 +  -3.834976376660*_Complex_I,
+   -3.825400452939 +  -5.056722468813*_Complex_I,
+  -15.844519611706 + -11.255119227692*_Complex_I,
+   25.546454083988 +   2.033450780777*_Complex_I,
+   25.208964212648 + -18.608412069091*_Complex_I,
+  -30.422847814837 + -18.350779470779*_Complex_I,
+   15.095434571838 +  20.530536309828*_Complex_I,
+    1.789608009331 +  28.248536173054*_Complex_I,
+  -12.951533049636 +   6.950006423363*_Complex_I,
+    8.717234868692 + -32.148462751388*_Complex_I,
+    7.386347991900 +   3.651733560911*_Complex_I,
+  -12.562312227509 +   3.070991373115*_Complex_I,
+   -0.069804711953 +   1.802309211709*_Complex_I,
+    1.176122484348 + -14.149959691947*_Complex_I,
+    5.353573748142 +   0.564676076929*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_157.c b/src/fft/tests/data/fft_data_157.c
new file mode 100644
index 0000000..4506819
--- /dev/null
+++ b/src/fft/tests/data/fft_data_157.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 157-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x157[] = {
+    0.257182749425 +   0.221924222343*_Complex_I,
+    1.678933476985 +   1.690438801569*_Complex_I,
+   -0.370453539748 +  -0.279443271284*_Complex_I,
+   -0.425568170028 +  -0.346884537719*_Complex_I,
+    0.025131704106 +   0.368626953376*_Complex_I,
+   -0.013250700538 +   0.876875327290*_Complex_I,
+    0.935964221688 +  -0.003996500003*_Complex_I,
+    0.697482794970 +   0.169890786221*_Complex_I,
+   -0.672758751419 +  -0.529033743727*_Complex_I,
+   -0.103638614252 +   0.221443696028*_Complex_I,
+   -0.365208618659 +  -0.660189374984*_Complex_I,
+    0.148416173585 +  -0.919145673704*_Complex_I,
+   -0.015366679279 +  -0.943544155661*_Complex_I,
+    1.622323455798 +   0.481254671673*_Complex_I,
+   -0.438594792675 +   0.487727000762*_Complex_I,
+    1.590594341984 +  -0.549613288902*_Complex_I,
+   -0.917184623744 +  -2.221026339765*_Complex_I,
+   -1.617744928538 +   1.593484682047*_Complex_I,
+   -0.989508283152 +  -0.094108203222*_Complex_I,
+    0.852090994091 +  -2.231080010685*_Complex_I,
+   -0.400903430450 +  -0.366248303051*_Complex_I,
+   -0.125211983197 +  -1.455734382818*_Complex_I,
+    1.947225389836 +   0.980175860892*_Complex_I,
+   -1.398014729095 +   0.014318572276*_Complex_I,
+   -0.771114386583 +   0.544211573481*_Complex_I,
+    0.126434853618 +  -0.465319924891*_Complex_I,
+    0.952552119580 +   2.799409645314*_Complex_I,
+    0.745539772546 +   0.038782344125*_Complex_I,
+    0.629464449611 +  -1.544746925900*_Complex_I,
+   -1.430974620188 +  -1.148543279148*_Complex_I,
+   -0.550191215098 +  -1.044145048363*_Complex_I,
+   -0.616171694097 +  -1.252745889547*_Complex_I,
+    0.988808988108 +  -1.602725943935*_Complex_I,
+   -0.607710796313 +   0.518971475013*_Complex_I,
+    0.771090806997 +  -0.385515422626*_Complex_I,
+    0.825512088357 +   0.474459769075*_Complex_I,
+   -0.291745809631 +  -0.149808656805*_Complex_I,
+   -1.958415654055 +   0.651147959844*_Complex_I,
+   -0.778182389479 +  -0.780881485990*_Complex_I,
+   -2.015821535479 +  -0.050499463828*_Complex_I,
+    0.887353255055 +  -1.214230826942*_Complex_I,
+   -0.096317891587 +   0.761206139060*_Complex_I,
+   -1.062009284735 +  -0.271266211836*_Complex_I,
+    1.423990266183 +   1.335814455222*_Complex_I,
+   -0.847479948365 +   2.671530186606*_Complex_I,
+   -1.345728311726 +  -0.634481496676*_Complex_I,
+   -0.797816703427 +   0.502757855534*_Complex_I,
+   -0.464673266498 +   0.116721201943*_Complex_I,
+    0.203062847770 +   0.561494795746*_Complex_I,
+    0.033909266352 +  -0.261380019453*_Complex_I,
+   -0.658056423237 +   1.281296865326*_Complex_I,
+    0.941736081602 +   1.987602839317*_Complex_I,
+   -0.423846356775 +  -1.241587960307*_Complex_I,
+   -0.393324168712 +   0.737043249313*_Complex_I,
+   -1.944403782443 +  -0.943905358441*_Complex_I,
+   -0.528242253913 +   1.908236422266*_Complex_I,
+    1.452295322067 +  -0.869099055081*_Complex_I,
+   -1.120053624869 +   0.850800785935*_Complex_I,
+    0.365006117743 +  -0.347073388222*_Complex_I,
+   -1.980724188697 +   1.747266666168*_Complex_I,
+    1.558965406397 +   0.725116188824*_Complex_I,
+   -1.050340476177 +   0.477386287145*_Complex_I,
+    0.078299549695 +  -1.166209184146*_Complex_I,
+    0.666177598254 +  -1.881539760701*_Complex_I,
+   -0.553008717374 +   0.112063233224*_Complex_I,
+   -1.542438669400 +  -0.905459946121*_Complex_I,
+   -0.540183077595 +  -0.653405677927*_Complex_I,
+    0.093096056538 +   1.658838310544*_Complex_I,
+   -0.070743442866 +  -0.474238998458*_Complex_I,
+   -1.057268947675 +   0.965366634327*_Complex_I,
+   -0.084404481306 +   0.516458044167*_Complex_I,
+    0.415540467796 +  -0.571894574983*_Complex_I,
+   -0.495234089660 +   0.103235766156*_Complex_I,
+    0.464768365021 +   0.651070663181*_Complex_I,
+   -1.478301866530 +  -1.246265840883*_Complex_I,
+   -0.810080291321 +  -0.090687751615*_Complex_I,
+    1.597727768491 +  -1.303542260713*_Complex_I,
+    0.899193648643 +   0.101515082173*_Complex_I,
+   -1.275261212066 +  -1.670150745367*_Complex_I,
+   -0.067477528818 +   0.082676448878*_Complex_I,
+   -0.593487795772 +   1.015383261914*_Complex_I,
+   -0.195143706875 +   0.197795648282*_Complex_I,
+    1.470101318427 +  -1.090920995443*_Complex_I,
+    3.099635934834 +   0.418783664178*_Complex_I,
+    0.517928899798 +  -0.515604023095*_Complex_I,
+   -0.699637334030 +   0.411968031541*_Complex_I,
+    0.238527504044 +  -1.024932943805*_Complex_I,
+    1.067350706566 +  -1.185403362126*_Complex_I,
+    1.013271318338 +   0.881471326676*_Complex_I,
+    0.004430604182 +  -0.799775043761*_Complex_I,
+    0.351524389294 +   0.780435439706*_Complex_I,
+    0.106334238498 +   0.458736063427*_Complex_I,
+   -0.008723581305 +   2.334855897709*_Complex_I,
+   -0.486030470423 +  -0.237085023857*_Complex_I,
+   -0.752315645429 +   0.905584767839*_Complex_I,
+   -0.578103821311 +   1.125629982729*_Complex_I,
+   -0.948197773007 +  -0.457632138151*_Complex_I,
+    0.742229639059 +   0.271160961106*_Complex_I,
+   -0.773931821655 +  -0.314444247010*_Complex_I,
+   -1.490298210738 +   0.035980837847*_Complex_I,
+    0.199618338445 +   0.632691460449*_Complex_I,
+   -0.234261424994 +  -1.443456519632*_Complex_I,
+    0.463236279099 +   0.303140551137*_Complex_I,
+    0.292524617190 +  -0.043026971072*_Complex_I,
+    0.046692677937 +   1.116783220186*_Complex_I,
+   -1.861132210698 +   0.261380583838*_Complex_I,
+   -0.341148975896 +  -0.470343064889*_Complex_I,
+   -0.671470989909 +   0.007947245970*_Complex_I,
+   -0.534160841208 +  -0.124549058301*_Complex_I,
+    0.487094311512 +  -0.314353890072*_Complex_I,
+   -0.403442115448 +  -0.857412221358*_Complex_I,
+    1.085529198882 +  -0.386892824664*_Complex_I,
+    0.591555113309 +   0.384947041491*_Complex_I,
+   -0.516471791207 +  -0.646903850947*_Complex_I,
+   -0.216099520865 +   0.030923700983*_Complex_I,
+   -2.362722198570 +   2.187609191074*_Complex_I,
+   -2.996234948353 +   1.169329942024*_Complex_I,
+    0.398987017180 +   1.432369958192*_Complex_I,
+   -0.321543239403 +  -1.079202616493*_Complex_I,
+    0.155932087142 +   0.283226593654*_Complex_I,
+   -0.149566717019 +   2.000037448099*_Complex_I,
+   -0.505275327391 +  -0.592114971901*_Complex_I,
+    0.712863240336 +  -0.919212613392*_Complex_I,
+    2.561694455976 +  -0.315857229465*_Complex_I,
+   -0.401098664528 +  -1.414850199386*_Complex_I,
+    1.068811040362 +  -0.520713203914*_Complex_I,
+   -0.928756706401 +  -1.036472849545*_Complex_I,
+    0.731918541008 +   2.246056257734*_Complex_I,
+   -1.087622423230 +  -1.271134991425*_Complex_I,
+    0.747682565630 +   1.167376255945*_Complex_I,
+    0.823139516666 +   0.417274248065*_Complex_I,
+    0.488604293527 +  -0.768657162242*_Complex_I,
+   -1.566207330521 +  -0.520510590335*_Complex_I,
+    0.286432330277 +  -0.603250643993*_Complex_I,
+    0.685025047143 +  -0.939995698866*_Complex_I,
+    0.955471389243 +  -0.323428744209*_Complex_I,
+   -1.169027438782 +  -0.175653814544*_Complex_I,
+   -0.284005159687 +   0.460772145706*_Complex_I,
+   -0.310159212214 +  -1.720418494846*_Complex_I,
+    0.305232638909 +  -0.090314073904*_Complex_I,
+    0.190795117789 +  -0.467619419555*_Complex_I,
+   -0.905744042954 +  -1.143116395802*_Complex_I,
+    0.392128587570 +   2.286798561832*_Complex_I,
+   -1.315880319405 +  -1.432434182558*_Complex_I,
+    1.007683317172 +  -0.154461360928*_Complex_I,
+    0.474642520199 +   0.729416858430*_Complex_I,
+   -0.679375823140 +  -2.856682284668*_Complex_I,
+   -0.687426016158 +  -1.023213289303*_Complex_I,
+   -0.022785229613 +   0.698739170753*_Complex_I,
+   -0.643221050022 +   1.444057111513*_Complex_I,
+    0.149585673645 +  -0.602773170360*_Complex_I,
+    0.389853409146 +  -0.949003645210*_Complex_I,
+   -1.115251553217 +  -0.405311422912*_Complex_I,
+    0.228569355271 +  -0.486849085232*_Complex_I,
+   -0.140823347089 +  -0.328067722810*_Complex_I,
+    1.388787822068 +  -2.267147899079*_Complex_I,
+    0.939124099904 +  -0.563609820499*_Complex_I};
+
+float complex fft_test_y157[] = {
+  -13.721520175468 +  -9.568907767571*_Complex_I,
+    8.721468524756 + -10.198063416643*_Complex_I,
+   15.060716782723 + -14.846901712846*_Complex_I,
+  -11.623905596583 +  16.650835292293*_Complex_I,
+   19.695072317931 + -11.298461808236*_Complex_I,
+    1.838122765692 +   7.179001252206*_Complex_I,
+    8.145195639028 + -10.634659386087*_Complex_I,
+   13.033783220459 +  19.061682407788*_Complex_I,
+   -3.959970595745 +  -7.333783752376*_Complex_I,
+   17.086993536942 +  -6.079044767515*_Complex_I,
+   10.491053572778 +  -4.514192461667*_Complex_I,
+    6.064025221921 +  10.332579887094*_Complex_I,
+   -3.762638600928 +  10.624543304629*_Complex_I,
+   -1.847856432273 +  -8.006182372781*_Complex_I,
+   41.710087074031 +   2.242012124979*_Complex_I,
+    8.362552928082 +  -0.431571425998*_Complex_I,
+   -2.684280607792 + -19.646530227003*_Complex_I,
+   -5.355855356382 +  -4.457469415514*_Complex_I,
+   -7.411106913072 +  24.950544746191*_Complex_I,
+   16.058416814462 +   7.398904353701*_Complex_I,
+    4.287192732400 + -11.227843680234*_Complex_I,
+   -1.117925486982 +  24.330611594547*_Complex_I,
+    3.444591991920 +  32.326680862100*_Complex_I,
+   17.351475510592 +  -7.023399927552*_Complex_I,
+   -0.243462815765 +   5.525094059061*_Complex_I,
+    1.824464191173 + -10.437625350692*_Complex_I,
+    0.863195259259 +   8.286510583962*_Complex_I,
+   -2.958971801726 + -14.975362935027*_Complex_I,
+    9.706578069666 +   2.274491450388*_Complex_I,
+   18.904225667531 +   9.156322125243*_Complex_I,
+    1.847919083204 +  22.188095024380*_Complex_I,
+   -4.136288117963 + -22.092021747680*_Complex_I,
+    4.703234992916 +   0.532501901615*_Complex_I,
+   -3.030532632664 + -11.313921121067*_Complex_I,
+    1.091146775351 +   7.577427645200*_Complex_I,
+    3.975634530789 +  10.604839429746*_Complex_I,
+   12.752600166339 +   7.600696307234*_Complex_I,
+   15.977675567158 +  16.362460714763*_Complex_I,
+   -0.020172843559 +   3.858168994446*_Complex_I,
+  -14.397449821557 +  12.915383581435*_Complex_I,
+   15.101543348628 + -23.907576345828*_Complex_I,
+   -1.954273280892 +  13.597214533352*_Complex_I,
+   -8.025138226552 +   3.693906127107*_Complex_I,
+  -10.663001711599 +  11.720999651550*_Complex_I,
+   -1.917368345264 + -19.036649221044*_Complex_I,
+    4.152821868650 +  -8.488773546805*_Complex_I,
+  -24.343188706355 + -10.538612575861*_Complex_I,
+   12.414685926300 +  16.068084980361*_Complex_I,
+    2.673733474235 +  -8.293379374130*_Complex_I,
+    9.339757855123 +   0.779105093141*_Complex_I,
+   34.385020257627 +  -8.910778839680*_Complex_I,
+   13.686080719584 +  -2.468581114278*_Complex_I,
+   -0.105352104096 +  -7.123485160682*_Complex_I,
+  -26.981218401390 +  13.727259016390*_Complex_I,
+   -6.504207057579 +   3.268522185903*_Complex_I,
+    7.577558748754 +   2.965812787302*_Complex_I,
+  -22.926267985360 +   4.630017422388*_Complex_I,
+   -8.347022143339 +   1.975527870103*_Complex_I,
+    3.060029390078 + -21.656262254959*_Complex_I,
+    9.637126253972 +  -7.538708825218*_Complex_I,
+  -13.130853679756 + -18.805264312027*_Complex_I,
+  -16.821204438708 +   4.950740394033*_Complex_I,
+   22.988677066759 +  -1.571260161915*_Complex_I,
+    1.406205876954 +  12.062599478139*_Complex_I,
+  -28.514716646160 +   7.993271146497*_Complex_I,
+   -0.907689305572 +   0.456303536916*_Complex_I,
+   -3.571030267866 +  -4.518597998420*_Complex_I,
+  -11.957457146313 + -19.766437913007*_Complex_I,
+   -5.413654003898 +  -1.754632161022*_Complex_I,
+   -9.175989023330 +  -7.046783981905*_Complex_I,
+   -4.258428526498 + -16.319730685668*_Complex_I,
+   -8.132160224937 +  -1.537755723079*_Complex_I,
+   15.156793468505 +  10.343087291076*_Complex_I,
+   25.761505476002 +   3.887909686471*_Complex_I,
+   21.853951697775 + -12.770771455381*_Complex_I,
+  -14.242421455518 +   2.407693112009*_Complex_I,
+  -13.467445303389 + -29.036488998230*_Complex_I,
+   -9.305071893547 +   2.506212475888*_Complex_I,
+   22.323867850705 +  -4.075769716056*_Complex_I,
+   -5.679643000649 +  -3.670620943265*_Complex_I,
+  -13.769169134408 +  13.834529429033*_Complex_I,
+  -12.414956903750 + -16.194735699494*_Complex_I,
+   -2.005403979995 + -11.362528318332*_Complex_I,
+    7.730155179299 +   9.255875770335*_Complex_I,
+   15.051941677947 +  -3.828126836044*_Complex_I,
+  -21.086873877673 +  12.717384691065*_Complex_I,
+   22.428970474249 +  14.741806815196*_Complex_I,
+    2.480130753861 + -23.039122194478*_Complex_I,
+  -10.903771391849 + -13.412743738585*_Complex_I,
+    6.147006915635 +  13.690721935985*_Complex_I,
+  -20.616357511746 + -17.845769868478*_Complex_I,
+   -8.402987902554 +   3.410410123270*_Complex_I,
+   10.582061652995 +   4.348210154826*_Complex_I,
+   15.024788596168 +  -5.782083339001*_Complex_I,
+    8.689851678442 +  35.535383742654*_Complex_I,
+   -4.132601952228 +  -0.321739745032*_Complex_I,
+  -19.197163106392 +   3.413027621621*_Complex_I,
+   -5.794327856223 +  -7.331102283615*_Complex_I,
+   -6.543417534351 +  -1.677934031582*_Complex_I,
+    7.329352169488 +  -4.324029036611*_Complex_I,
+  -25.477673544286 + -10.789721951286*_Complex_I,
+   -0.912644707297 +  -9.637940446228*_Complex_I,
+    4.037691841333 +   3.096945310047*_Complex_I,
+   -5.847674330905 +  13.938407689472*_Complex_I,
+   10.812159378022 +   2.497873869651*_Complex_I,
+    5.034733833406 +  20.614345199846*_Complex_I,
+    9.197163204775 +   3.499527690970*_Complex_I,
+  -11.061382406800 +   3.269075618163*_Complex_I,
+   -2.869327440190 + -17.824719429904*_Complex_I,
+  -16.473833296088 +  -7.487202778068*_Complex_I,
+    6.310496423938 +   5.004751989728*_Complex_I,
+   -6.079010956757 +  18.024756582815*_Complex_I,
+   -4.356220575254 +  -2.205381751597*_Complex_I,
+   -8.493558121870 +  -3.869308590719*_Complex_I,
+    7.159973070917 +   6.849733506562*_Complex_I,
+   17.405411913875 +  -3.643915205170*_Complex_I,
+    1.342183355448 +   0.252403128806*_Complex_I,
+    2.470554720768 +   3.325864954849*_Complex_I,
+    3.304540368971 +  -7.701906570289*_Complex_I,
+  -41.203455184368 +  12.817592975767*_Complex_I,
+  -20.058954711820 +  19.878752053751*_Complex_I,
+    9.046175416813 +  11.732938261288*_Complex_I,
+   -0.786044861883 +  17.615815760165*_Complex_I,
+   -3.027566374297 +  11.824558467687*_Complex_I,
+   -3.793394817088 +   4.309384254386*_Complex_I,
+  -15.395453276032 +  -6.476272377854*_Complex_I,
+    2.993318695808 +  20.818567190739*_Complex_I,
+  -21.109827170023 + -18.433989642597*_Complex_I,
+   -7.427193578999 +  -4.496310454720*_Complex_I,
+   13.462176439125 + -14.456554646582*_Complex_I,
+    2.860309865551 +   0.813195406900*_Complex_I,
+  -16.705039250487 +   5.416257334069*_Complex_I,
+   12.149530796231 +   5.677739145239*_Complex_I,
+   10.883522142352 +  -2.048976821145*_Complex_I,
+   33.919769198703 +  14.546697989253*_Complex_I,
+   -0.080428049883 +  -5.729314964055*_Complex_I,
+   -1.382967825489 +  -0.843240827862*_Complex_I,
+  -14.258739687335 +  -4.164415134916*_Complex_I,
+  -11.232537602109 +  13.450368521399*_Complex_I,
+    6.475674985073 +  -6.229640995099*_Complex_I,
+    9.211806090883 +  -7.267971548561*_Complex_I,
+  -14.365301190661 +   8.987964911736*_Complex_I,
+  -11.266093935597 + -15.954781116216*_Complex_I,
+  -18.499836430748 +  -8.923649916614*_Complex_I,
+   31.090884321227 +   2.260100309627*_Complex_I,
+   -7.383545212516 +  10.553116472713*_Complex_I,
+    1.165337529929 +  -1.083672004639*_Complex_I,
+    1.026784219611 + -16.203749347621*_Complex_I,
+   -0.529416311921 +  -8.707703034115*_Complex_I,
+   -9.657893357311 +  12.096083541637*_Complex_I,
+  -15.285477594802 +   4.224467463110*_Complex_I,
+   16.595393096965 +   1.232758856522*_Complex_I,
+   10.380238903621 +  -5.473695910884*_Complex_I,
+   -5.199124871644 +  14.427071924858*_Complex_I,
+  -10.919624558299 +  16.105413828801*_Complex_I,
+   15.081167155045 +  -6.737986374676*_Complex_I,
+    5.602666437487 + -19.846630500695*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_16.c b/src/fft/tests/data/fft_data_16.c
new file mode 100644
index 0000000..5cafff5
--- /dev/null
+++ b/src/fft/tests/data/fft_data_16.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 16-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x16[] = {
+   -1.772146047027 +   0.295934658602*_Complex_I,
+   -1.433777343858 +  -0.874041962217*_Complex_I,
+   -0.388629405392 +   0.611030474954*_Complex_I,
+   -0.492539890742 +   1.007726724574*_Complex_I,
+    0.494699992643 +  -1.725668238103*_Complex_I,
+    0.572982289851 +   0.061642401846*_Complex_I,
+   -0.574974496567 +   0.909843544187*_Complex_I,
+    0.733687565510 +   0.447433079732*_Complex_I,
+    0.308242485351 +  -1.532252262483*_Complex_I,
+    1.207949830231 +  -0.953543898451*_Complex_I,
+    0.640048909719 +  -1.022371047059*_Complex_I,
+   -0.241879356643 +  -0.462432765300*_Complex_I,
+   -0.435900183311 +   0.856847254979*_Complex_I,
+    0.577243720893 +   0.220786650383*_Complex_I,
+    1.263302572543 +   1.444493924498*_Complex_I,
+    1.911070541506 +  -1.906912076526*_Complex_I};
+
+float complex fft_test_y16[] = {
+    2.369381184706 +  -2.621483536381*_Complex_I,
+   -2.618054253504 +   4.676728363894*_Complex_I,
+   -2.946376269367 +   0.522273546089*_Complex_I,
+    2.120729100677 +   4.308004588255*_Complex_I,
+   -2.975823103566 +  -3.062195121072*_Complex_I,
+   -3.932696300262 +   3.143967424649*_Complex_I,
+   -0.247865075327 +   5.467645213372*_Complex_I,
+    3.887448941709 +   5.439788616063*_Complex_I,
+   -3.300093528791 +   2.297200155534*_Complex_I,
+   -4.008939493163 +   4.239020717646*_Complex_I,
+   -5.630386554230 +  -0.383449644305*_Complex_I,
+   -0.707551950987 +   2.197968388714*_Complex_I,
+   -1.713879561730 +  -5.034075846097*_Complex_I,
+   -8.091926054909 +  -8.469369525668*_Complex_I,
+    2.733814414891 +  -7.076455598181*_Complex_I,
+   -3.292118248586 +  -0.910613204873*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_17.c b/src/fft/tests/data/fft_data_17.c
new file mode 100644
index 0000000..6f5373a
--- /dev/null
+++ b/src/fft/tests/data/fft_data_17.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 17-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x17[] = {
+   -0.655587921776 +  -0.863886550358*_Complex_I,
+   -1.070139919529 +  -2.370656605854*_Complex_I,
+    1.167384281127 +   0.116747569575*_Complex_I,
+    1.105640858428 +  -1.226141009276*_Complex_I,
+   -0.349562744440 +  -0.005953723423*_Complex_I,
+    0.655312446998 +   1.594642817084*_Complex_I,
+   -0.230495501721 +  -0.272806523691*_Complex_I,
+    0.731764183318 +  -0.732380509571*_Complex_I,
+   -0.953609619255 +   1.102615917395*_Complex_I,
+   -0.496898865182 +  -1.822920499315*_Complex_I,
+   -2.052948327746 +  -0.382334768518*_Complex_I,
+    1.062783811112 +   0.673250838256*_Complex_I,
+   -0.142175503751 +   0.542432325176*_Complex_I,
+    1.108087524611 +  -0.029888173999*_Complex_I,
+   -0.164259605567 +  -1.184782100304*_Complex_I,
+   -0.236452649938 +  -0.998080495482*_Complex_I,
+   -0.197908967423 +  -0.229518503722*_Complex_I};
+
+float complex fft_test_y17[] = {
+   -0.719066520735 +  -6.089659996026*_Complex_I,
+    1.821427336790 +  -5.621144218242*_Complex_I,
+   -6.173678834923 +  -4.525807530343*_Complex_I,
+   -1.549626337288 +   0.314087328771*_Complex_I,
+   -5.136370472539 +   4.327372264632*_Complex_I,
+   -0.105170126419 +  -2.207231016009*_Complex_I,
+   -5.750228396460 +   3.094152945701*_Complex_I,
+    0.618725077794 +   3.278602823446*_Complex_I,
+   -0.888646366224 +  -3.887677672776*_Complex_I,
+    3.905822192802 +   7.933139073811*_Complex_I,
+   -1.522365359136 +   0.172605279098*_Complex_I,
+    8.552784050577 +  -3.105361200836*_Complex_I,
+   -1.598265822441 +  -5.532288727983*_Complex_I,
+    1.151099028128 +  -2.046469788513*_Complex_I,
+    0.253410312032 +   6.386316111258*_Complex_I,
+   -4.676752932337 +  -4.421634395132*_Complex_I,
+    0.671908500186 +  -2.755072636947*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_192.c b/src/fft/tests/data/fft_data_192.c
new file mode 100644
index 0000000..402d803
--- /dev/null
+++ b/src/fft/tests/data/fft_data_192.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 192-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x192[] = {
+   -0.570749076040 +   1.623496753738*_Complex_I,
+   -0.801441214588 +  -0.237768079755*_Complex_I,
+   -1.136144657269 +   0.367681411439*_Complex_I,
+   -0.021881616373 +  -1.310767380444*_Complex_I,
+   -1.897076967315 +  -0.151633764169*_Complex_I,
+   -0.065089420065 +   0.179240807290*_Complex_I,
+   -0.459561140285 +   0.186362705450*_Complex_I,
+   -1.459184572831 +  -0.172507317719*_Complex_I,
+    0.768770035801 +  -0.603405455907*_Complex_I,
+   -0.445592790811 +  -0.545504111400*_Complex_I,
+   -1.133095461256 +   0.347109313928*_Complex_I,
+    0.680343842697 +   0.984380778227*_Complex_I,
+    1.434274246060 +  -0.455280274247*_Complex_I,
+    1.367719846389 +  -0.017753112833*_Complex_I,
+   -1.036796157162 +   0.202926093122*_Complex_I,
+   -0.608495290895 +   1.377505175242*_Complex_I,
+    0.677352983170 +   0.522913747818*_Complex_I,
+    0.829598919707 +  -0.238489026179*_Complex_I,
+    1.658006740638 +   1.321959544651*_Complex_I,
+   -1.813019197848 +  -1.379543844064*_Complex_I,
+   -0.105360926209 +   0.770250719958*_Complex_I,
+    0.950326938828 +  -0.839058539588*_Complex_I,
+    1.158809729841 +  -0.647245275247*_Complex_I,
+   -0.360439558788 +  -0.056522073112*_Complex_I,
+   -0.729658576111 +  -0.230389217591*_Complex_I,
+   -0.147264114233 +  -0.844995936147*_Complex_I,
+    3.006061814772 +   0.324561093094*_Complex_I,
+    0.421672171136 +  -1.045335631843*_Complex_I,
+    1.450421276223 +  -1.809661507608*_Complex_I,
+    0.112589736618 +  -0.680553655537*_Complex_I,
+   -0.962866997971 +   0.051225992580*_Complex_I,
+   -0.069473575656 +   0.412572221634*_Complex_I,
+    0.807441882459 +  -0.390366883673*_Complex_I,
+    0.494922478167 +   0.666273714397*_Complex_I,
+    0.534063543949 +   0.216473899855*_Complex_I,
+    0.115686515954 +  -1.150016410968*_Complex_I,
+   -0.911950154175 +  -1.906708901788*_Complex_I,
+    0.035013270272 +   1.560677880077*_Complex_I,
+   -0.689521651706 +  -0.963904151064*_Complex_I,
+   -1.140202975221 +  -0.076069898430*_Complex_I,
+   -0.246077937761 +   0.616853881528*_Complex_I,
+   -0.251792408901 +  -0.385289165959*_Complex_I,
+    0.144732608298 +  -0.544232656456*_Complex_I,
+    0.716079834835 +   0.154716467884*_Complex_I,
+   -0.454600091317 +  -0.778823717821*_Complex_I,
+   -0.004009391933 +   0.239803231190*_Complex_I,
+    0.128608781578 +   0.252750485610*_Complex_I,
+   -0.104121384913 +  -1.189000295448*_Complex_I,
+    0.242846644961 +   0.081301007736*_Complex_I,
+    1.450965500668 +   0.361778385840*_Complex_I,
+   -0.190106534680 +   0.071804478293*_Complex_I,
+    0.420793349874 +   1.491145034929*_Complex_I,
+    2.460108883286 +   0.757779280601*_Complex_I,
+   -0.825692579645 +  -0.596573882891*_Complex_I,
+    0.671589132843 +   1.011087599399*_Complex_I,
+    1.284648906257 +  -0.045814861627*_Complex_I,
+   -0.760224555847 +  -0.722324468953*_Complex_I,
+   -1.322063665211 +  -2.308468943382*_Complex_I,
+    2.293407378555 +   0.010203054084*_Complex_I,
+    0.055649061121 +  -2.002674185757*_Complex_I,
+   -0.282775998354 +  -1.935193634195*_Complex_I,
+    1.737107472308 +  -1.107578165456*_Complex_I,
+   -0.235741548678 +  -0.486998315145*_Complex_I,
+   -0.270131100153 +  -2.340452003512*_Complex_I,
+   -1.145976320980 +   1.671478612533*_Complex_I,
+    1.046314822557 +   0.182117686556*_Complex_I,
+   -0.745497599782 +  -0.723373842151*_Complex_I,
+   -0.511430521736 +  -0.669552224448*_Complex_I,
+    0.619398807961 +  -0.051926233859*_Complex_I,
+    0.650768664429 +  -0.054337533084*_Complex_I,
+   -0.377066273366 +   0.748578490695*_Complex_I,
+    0.868445500849 +  -1.916678793529*_Complex_I,
+   -0.745634363305 +   2.968322096526*_Complex_I,
+    1.601642204548 +  -1.815842464572*_Complex_I,
+   -0.110280900762 +  -0.375012568050*_Complex_I,
+    1.082989689805 +   0.615940836645*_Complex_I,
+    0.285506402216 +   0.273198983697*_Complex_I,
+    0.277214797147 +   1.771337438831*_Complex_I,
+    0.165028606876 +  -0.704451808040*_Complex_I,
+    0.317894159247 +   0.492665087928*_Complex_I,
+    0.601574563612 +  -0.904521376193*_Complex_I,
+   -0.642798244158 +   0.327271328047*_Complex_I,
+    0.011482342139 +  -0.787115819239*_Complex_I,
+    0.244096373806 +   0.152569770967*_Complex_I,
+    0.178980665283 +   0.813856965670*_Complex_I,
+    1.492787787643 +  -0.413137630323*_Complex_I,
+   -1.122949408956 +  -0.730572761342*_Complex_I,
+    0.096736426284 +  -0.623125567869*_Complex_I,
+   -0.054054998356 +   0.102181208006*_Complex_I,
+    0.016144101863 +  -0.139709667178*_Complex_I,
+    1.323423360215 +  -1.218385808067*_Complex_I,
+    1.225801036168 +   1.825795241395*_Complex_I,
+    1.773877495557 +   0.364293281834*_Complex_I,
+   -1.266134039437 +   0.099487732018*_Complex_I,
+   -0.417581296163 +   0.343715046363*_Complex_I,
+    0.505757022167 +  -0.500849213570*_Complex_I,
+    0.968334906502 +  -0.526257684744*_Complex_I,
+    0.333143774360 +   0.396107354791*_Complex_I,
+   -0.506358896777 +  -0.087644984012*_Complex_I,
+    1.324525846569 +   1.494077333636*_Complex_I,
+   -0.951951460363 +   0.994211347018*_Complex_I,
+   -2.692462733023 +  -0.697601652243*_Complex_I,
+   -1.072637853655 +   1.879429345687*_Complex_I,
+   -0.193163794476 +   1.344807479696*_Complex_I,
+   -0.036468538711 +  -2.246890819337*_Complex_I,
+    0.123890932473 +  -1.171359056597*_Complex_I,
+    1.614167761632 +  -0.084861961160*_Complex_I,
+    0.813475153805 +  -1.363244160675*_Complex_I,
+   -0.751043379055 +  -0.001003534113*_Complex_I,
+   -1.127244933655 +   0.033479567244*_Complex_I,
+    0.120947403072 +   0.172712059551*_Complex_I,
+   -0.650772252959 +   1.321513948454*_Complex_I,
+    1.305164416475 +  -0.787832375788*_Complex_I,
+   -0.312608725354 +  -0.233463073219*_Complex_I,
+   -0.754485246213 +  -0.132782409884*_Complex_I,
+    1.522953329225 +   0.109222954313*_Complex_I,
+   -0.495952414190 +   0.544687157445*_Complex_I,
+    0.506640354547 +  -1.676292292327*_Complex_I,
+    0.495984603346 +   0.705582611126*_Complex_I,
+    0.555362258234 +   1.175515543993*_Complex_I,
+   -1.380515094250 +   0.427692852149*_Complex_I,
+    0.003538138035 +   1.425908489255*_Complex_I,
+    1.062943493718 +   1.079328766594*_Complex_I,
+    0.035769210794 +   0.546944179639*_Complex_I,
+   -1.009393498146 +  -0.836699998466*_Complex_I,
+   -1.299667858035 +  -1.876603335383*_Complex_I,
+    1.400213634255 +  -1.094331065468*_Complex_I,
+   -0.978532602360 +  -1.133768146514*_Complex_I,
+   -0.126022453004 +  -0.548218141207*_Complex_I,
+   -0.459386774097 +  -1.142543752432*_Complex_I,
+   -0.634282978000 +   0.401478043588*_Complex_I,
+    0.536619631169 +  -0.637851111890*_Complex_I,
+   -2.177417556650 +  -0.582755642600*_Complex_I,
+    1.956859485192 +   0.756502126757*_Complex_I,
+   -1.033248608338 +   0.083674820018*_Complex_I,
+    0.361227297522 +   0.246987978580*_Complex_I,
+    0.200321461901 +   0.997822511718*_Complex_I,
+   -1.130355402825 +  -0.097176654080*_Complex_I,
+    1.384768698791 +  -0.772817911874*_Complex_I,
+    0.926525539860 +   0.145277765291*_Complex_I,
+   -0.540081956252 +   0.372734113022*_Complex_I,
+    0.327046375183 +   1.452638689757*_Complex_I,
+   -0.402162597503 +  -0.571759031309*_Complex_I,
+    1.213886497238 +   0.862378057918*_Complex_I,
+   -0.024799349445 +  -0.640880433466*_Complex_I,
+    0.605895117723 +   1.040856615204*_Complex_I,
+    0.875424648817 +  -1.747168830682*_Complex_I,
+    1.792252094005 +   1.449436965826*_Complex_I,
+   -0.058686051876 +   0.024079540685*_Complex_I,
+   -1.197793455172 +   0.076528126740*_Complex_I,
+   -1.451475019825 +   2.506331331519*_Complex_I,
+    0.115177207244 +   0.581887944455*_Complex_I,
+   -0.894760295667 +   0.941285480086*_Complex_I,
+    0.167847546335 +   0.113650691315*_Complex_I,
+    0.580138910850 +   1.482400230136*_Complex_I,
+   -0.638693285492 +   0.706252192544*_Complex_I,
+   -0.679198255352 +  -0.864392741247*_Complex_I,
+    0.309224287292 +   0.802306837308*_Complex_I,
+    0.089371278986 +  -1.059876744659*_Complex_I,
+    1.352756771804 +  -0.303374747952*_Complex_I,
+    0.142478845463 +   1.679379378038*_Complex_I,
+   -0.307104681980 +  -1.525241010787*_Complex_I,
+    0.947136026136 +  -0.176290475474*_Complex_I,
+   -0.191221819400 +   0.872369793925*_Complex_I,
+   -0.694209420142 +   0.251537117440*_Complex_I,
+    1.193931121456 +   0.364438121717*_Complex_I,
+    0.364530798418 +   0.953151437736*_Complex_I,
+   -2.204969193002 +   0.591100538765*_Complex_I,
+    1.152638435048 +   0.423855010290*_Complex_I,
+   -0.190087319373 +  -0.236280077579*_Complex_I,
+   -0.959025701481 +  -1.046604614844*_Complex_I,
+   -0.059036610250 +   0.757806508323*_Complex_I,
+    1.193828263334 +   0.991816149312*_Complex_I,
+   -0.644717162130 +   0.685130498159*_Complex_I,
+    0.386929662859 +   0.246269623980*_Complex_I,
+    0.747830541986 +  -0.853860497506*_Complex_I,
+    0.563215051840 +  -1.553462734011*_Complex_I,
+   -0.411175328612 +   0.338710437150*_Complex_I,
+   -1.017739241999 +  -0.234632452129*_Complex_I,
+   -2.270318833952 +   0.624001888473*_Complex_I,
+    0.530109233459 +  -3.021054334701*_Complex_I,
+   -0.733212401282 +   0.337380481817*_Complex_I,
+    0.686400469100 +   1.434988023046*_Complex_I,
+   -1.537557685024 +   0.266727612837*_Complex_I,
+    2.377907246279 +  -0.647849557308*_Complex_I,
+    1.588677766399 +  -0.611004042543*_Complex_I,
+    0.547569751159 +  -0.942144527428*_Complex_I,
+   -0.489770632777 +  -1.190329237689*_Complex_I,
+   -0.588811442748 +   1.025069940138*_Complex_I,
+   -0.380574882230 +  -1.193424193517*_Complex_I,
+   -2.719012193329 +  -0.412270955448*_Complex_I,
+    1.447794971270 +   0.160429136252*_Complex_I};
+
+float complex fft_test_y192[] = {
+   11.637079481157 +  -8.771861743960*_Complex_I,
+  -26.395324212554 + -12.523034709229*_Complex_I,
+  -13.199274560613 +   4.197128353434*_Complex_I,
+    2.770319150971 + -22.367461652042*_Complex_I,
+    0.642915300037 +   6.363791329048*_Complex_I,
+   -1.448465724858 +  11.100404880196*_Complex_I,
+   -6.231101627932 +  -0.711426629573*_Complex_I,
+    6.572370237538 +  -9.008077010635*_Complex_I,
+    7.543400860435 +  28.757508480923*_Complex_I,
+  -17.693058117854 + -14.497870123296*_Complex_I,
+  -18.350699894294 +  19.700720310742*_Complex_I,
+  -22.388250522169 +   7.667843634853*_Complex_I,
+   14.220360999832 +  11.088627215515*_Complex_I,
+   14.686986747880 +   4.769183572180*_Complex_I,
+  -16.224161466491 +  18.657410968166*_Complex_I,
+   -5.561225898629 +   0.059313803898*_Complex_I,
+   13.221567254519 +  -8.899219750894*_Complex_I,
+  -20.776362788232 + -12.895144971233*_Complex_I,
+   16.702761314442 +  -1.206621684382*_Complex_I,
+    1.152218624052 +  10.429943215921*_Complex_I,
+    1.406407090905 + -25.487932735618*_Complex_I,
+    5.058858691359 +  14.007031493308*_Complex_I,
+    3.915927218401 +  -3.143885706619*_Complex_I,
+    0.511679691204 +   7.640849778701*_Complex_I,
+   -9.519895102966 + -18.415287292132*_Complex_I,
+    2.876304518596 +  20.969442045845*_Complex_I,
+    7.342107433201 + -12.347059316472*_Complex_I,
+   -0.618433952859 +  25.041216941337*_Complex_I,
+    0.580421597502 + -14.237985250543*_Complex_I,
+    8.771852508251 +   0.733917062704*_Complex_I,
+    3.782207555074 +  16.248301038041*_Complex_I,
+   -8.524579539709 +   9.295327847229*_Complex_I,
+   -4.768100090762 + -10.116200145201*_Complex_I,
+    5.053774488844 +  -2.415715341537*_Complex_I,
+   14.309532012096 +  -0.424419688667*_Complex_I,
+   12.756379146573 +   5.032080306349*_Complex_I,
+   34.330424118154 +  10.117580842288*_Complex_I,
+   -5.753681073486 +  -1.411149381440*_Complex_I,
+    1.264859359146 +   4.528836925970*_Complex_I,
+   -2.586666114372 +  13.851391335293*_Complex_I,
+   -1.713098457415 +  20.890928009489*_Complex_I,
+  -13.434363693601 +  24.394077211392*_Complex_I,
+  -10.022671324009 +  17.664609245348*_Complex_I,
+   -1.725934813257 +   1.891913667017*_Complex_I,
+    9.110355361482 +  -0.179266402064*_Complex_I,
+   -6.803970453739 +   1.078079859350*_Complex_I,
+   15.245357404619 + -16.002907380848*_Complex_I,
+   18.798658922559 +  -8.909897678113*_Complex_I,
+   -9.340116247154 +  -1.125951004947*_Complex_I,
+    3.688509570515 + -32.689986273951*_Complex_I,
+    0.407888797988 +  12.869443175340*_Complex_I,
+   11.996274048535 +  -2.662751606922*_Complex_I,
+    7.018158515976 +  28.262862824837*_Complex_I,
+    7.922722654625 +  14.250007388651*_Complex_I,
+    2.975237590008 + -10.603333565612*_Complex_I,
+    0.455653895495 +   1.453939650391*_Complex_I,
+   -9.066906825176 +  25.796872875146*_Complex_I,
+   -0.918183225634 + -10.468224002255*_Complex_I,
+   -0.142851335912 +  22.780663054656*_Complex_I,
+    2.282086213671 +  29.891813625626*_Complex_I,
+    7.221536839959 +   7.770574674805*_Complex_I,
+  -37.398067728397 +  -1.105557754066*_Complex_I,
+  -17.142633714292 +   6.118620955162*_Complex_I,
+   -6.477664567284 +  -7.690396365101*_Complex_I,
+   16.982767357512 + -18.131798098394*_Complex_I,
+    1.080785945291 +  16.906857138491*_Complex_I,
+    0.599451587747 +  26.081775902564*_Complex_I,
+   -0.978816302785 +   5.925677319285*_Complex_I,
+   10.210787997091 +  -9.910196007357*_Complex_I,
+  -19.147402098057 +  -2.498686289732*_Complex_I,
+   21.966256137493 + -22.859882129197*_Complex_I,
+  -15.265643040019 +  -4.426471861527*_Complex_I,
+   12.929479693820 +  22.330642231752*_Complex_I,
+    2.740221347659 +  24.061774049828*_Complex_I,
+  -14.562401185320 + -19.628678597306*_Complex_I,
+   20.997689192967 +   5.792733814018*_Complex_I,
+    5.119546713501 +   3.964059886246*_Complex_I,
+   -1.928221405609 +  -6.512384434671*_Complex_I,
+   -2.156293329162 +   7.753329775994*_Complex_I,
+   -3.573953149890 +  -9.239991326571*_Complex_I,
+  -13.630949945730 +  -8.106005746278*_Complex_I,
+   12.825262070728 +  -4.660266138913*_Complex_I,
+   -1.891349427851 +   9.400617516536*_Complex_I,
+  -18.076150879307 +  22.373408216149*_Complex_I,
+  -15.956268983268 + -21.975317374526*_Complex_I,
+    0.164201332756 +  24.388112510894*_Complex_I,
+   -4.597078387934 +   7.039522987551*_Complex_I,
+  -12.697120797033 +   9.467372419639*_Complex_I,
+    0.666493946055 +  16.398714904237*_Complex_I,
+   14.501434995333 +  -1.201151824518*_Complex_I,
+   -5.674281705139 +   5.888174371162*_Complex_I,
+  -20.163201241601 +  -1.818438322864*_Complex_I,
+  -30.219423076971 + -13.719313314399*_Complex_I,
+  -13.990403694826 +   2.714807784298*_Complex_I,
+   11.250768761115 +   3.628822569150*_Complex_I,
+   -0.733728572049 +   2.948828784375*_Complex_I,
+   -3.826671819253 +   4.571602216696*_Complex_I,
+   23.522991447544 +  -2.821207413423*_Complex_I,
+    5.182638636295 +  -2.280239804828*_Complex_I,
+  -14.978530389737 +  34.007674915178*_Complex_I,
+   12.067946114665 +  -7.231592873495*_Complex_I,
+  -18.036308190888 +  -1.167944458385*_Complex_I,
+  -14.570445828147 +  33.703221498009*_Complex_I,
+  -14.355007896334 +   8.624163479423*_Complex_I,
+  -12.522417307559 +   2.964999959389*_Complex_I,
+    1.285831266251 +  17.486913574970*_Complex_I,
+  -18.400444537057 +  10.275105944079*_Complex_I,
+  -10.798107010917 +  -9.398928074828*_Complex_I,
+   -3.735870933695 +  -3.928195596167*_Complex_I,
+  -28.347786532915 +  -4.087804201121*_Complex_I,
+   22.446246894740 +  -3.314141727023*_Complex_I,
+  -12.595391479166 + -20.525839403734*_Complex_I,
+  -28.712938572605 +  -6.003936755853*_Complex_I,
+   28.019514492138 + -30.082449455145*_Complex_I,
+  -11.209466775921 +  26.443062244454*_Complex_I,
+    1.207579305184 +  15.519647647646*_Complex_I,
+   -4.037012632720 +  -7.499501325179*_Complex_I,
+   10.741743378262 +  10.026834979238*_Complex_I,
+   18.870849049565 +  11.960677218574*_Complex_I,
+    5.853418449079 +  -7.462260554939*_Complex_I,
+    4.335224360674 +   3.129860437677*_Complex_I,
+   12.605242126520 +   4.554248036599*_Complex_I,
+  -16.969702130369 + -29.534520407430*_Complex_I,
+   -6.120705955107 +  -4.482591730189*_Complex_I,
+  -12.926535006552 +  -1.874171675105*_Complex_I,
+   14.665201057893 +  -1.128035925962*_Complex_I,
+   24.051538767781 + -15.958257482748*_Complex_I,
+   19.820057240718 + -22.152759047970*_Complex_I,
+  -24.058042894391 +  13.729789221856*_Complex_I,
+   19.182753493650 +  -1.045899761274*_Complex_I,
+    7.427878004795 + -14.645722900400*_Complex_I,
+    0.354829774210 + -28.351378771571*_Complex_I,
+   -0.079617808212 +   3.973765307882*_Complex_I,
+   -0.956500599215 + -15.489797280577*_Complex_I,
+    0.762491009085 + -25.453122941666*_Complex_I,
+  -11.840853996701 +  12.962212817110*_Complex_I,
+   35.495866538641 +   9.686565151838*_Complex_I,
+   -0.038469688004 +   4.392742866312*_Complex_I,
+  -19.123338054473 +   2.775599118392*_Complex_I,
+  -11.096274610813 +   9.852185716742*_Complex_I,
+   15.297635548840 + -11.871123303123*_Complex_I,
+  -20.554975892285 +   3.876667437210*_Complex_I,
+   35.623881203832 +  -7.111604836972*_Complex_I,
+   -6.116783490647 +   6.858127980009*_Complex_I,
+    7.214111046309 +  -6.503534651808*_Complex_I,
+   13.222866709925 +  14.222914198476*_Complex_I,
+    7.177684846410 +   4.449199979110*_Complex_I,
+    8.704887882540 +  13.440615520893*_Complex_I,
+    3.880333256992 + -21.379125009121*_Complex_I,
+    7.048477459126 +  -5.798390953651*_Complex_I,
+   -2.887754540526 +   0.926387706089*_Complex_I,
+   -4.738508647891 +   1.592531834771*_Complex_I,
+  -29.140442148645 +   1.326408413819*_Complex_I,
+  -17.686809708344 +   9.306299214767*_Complex_I,
+  -11.287941537086 +  18.573964519700*_Complex_I,
+  -14.430331062949 +  20.500648002089*_Complex_I,
+    5.833514963456 +  -0.787459203485*_Complex_I,
+    6.996874871331 +   4.372973087460*_Complex_I,
+   10.606258817386 + -15.371196814824*_Complex_I,
+    7.173199494850 +  -3.739254103514*_Complex_I,
+    0.650868123207 +   1.129252104053*_Complex_I,
+    2.934818232673 + -15.452697731691*_Complex_I,
+    5.965230433753 +  25.063178506013*_Complex_I,
+   23.622843424435 +  -9.618416072703*_Complex_I,
+    7.156185469219 +   3.585963809070*_Complex_I,
+   -5.732158086481 + -13.452361329340*_Complex_I,
+   -9.590479206366 +  -9.053169128213*_Complex_I,
+    2.000269872570 +  -9.513605072784*_Complex_I,
+   11.315627899677 +  22.817996942644*_Complex_I,
+   12.413894814140 +  13.626175038709*_Complex_I,
+   29.172874942325 +   1.484706508481*_Complex_I,
+    0.015766090270 +   4.368571218197*_Complex_I,
+   15.563774953007 +   4.894512943893*_Complex_I,
+  -11.842691536265 +  35.592965762670*_Complex_I,
+  -12.994473369362 +   2.698888313132*_Complex_I,
+    8.754669228150 +  11.235443005907*_Complex_I,
+  -10.328265749913 +   2.323163803747*_Complex_I,
+  -13.439572075998 +  13.889640848681*_Complex_I,
+   -3.977518922953 +   0.690204512797*_Complex_I,
+  -10.800653995412 +  -6.554357023667*_Complex_I,
+  -11.525124988298 + -20.660565069742*_Complex_I,
+   19.162721444200 +   9.408214211732*_Complex_I,
+   -0.088361438150 +  -0.665881998403*_Complex_I,
+  -18.557501816859 + -12.065202621962*_Complex_I,
+    8.534483256201 +   9.270859667546*_Complex_I,
+   10.095451307996 + -11.066538969502*_Complex_I,
+  -21.844145113512 + -28.094981179422*_Complex_I,
+  -19.202996015641 +   7.054377546211*_Complex_I,
+  -15.529186020599 +   7.476299951687*_Complex_I,
+  -18.157842430552 +  -5.316780213393*_Complex_I,
+   -5.293208686973 +  -3.663831551237*_Complex_I,
+   10.661425533836 +  11.243445036012*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_2.c b/src/fft/tests/data/fft_data_2.c
new file mode 100644
index 0000000..0881fa8
--- /dev/null
+++ b/src/fft/tests/data/fft_data_2.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 2-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x2[] = {
+   -0.442695266914 +  -1.176601139920*_Complex_I,
+    0.164300702210 +   0.941269951205*_Complex_I};
+
+float complex fft_test_y2[] = {
+   -0.278394564704 +  -0.235331188715*_Complex_I,
+   -0.606995969123 +  -2.117871091124*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_20.c b/src/fft/tests/data/fft_data_20.c
new file mode 100644
index 0000000..41cc574
--- /dev/null
+++ b/src/fft/tests/data/fft_data_20.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 20-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x20[] = {
+   -0.138174469322 +  -0.274748504122*_Complex_I,
+   -1.227476709426 +   0.905279873773*_Complex_I,
+   -0.940584818722 +  -0.463678927507*_Complex_I,
+   -0.327372793730 +   0.441537277752*_Complex_I,
+    0.086812488375 +   1.122322318862*_Complex_I,
+   -0.100645730164 +  -0.142102548176*_Complex_I,
+   -0.339607323410 +  -0.632982556339*_Complex_I,
+   -1.122342071583 +  -0.361529677196*_Complex_I,
+   -0.394764118785 +   1.317868322379*_Complex_I,
+    0.885025645922 +   0.277662260330*_Complex_I,
+   -0.937809434341 +  -0.464711897028*_Complex_I,
+   -0.316998490158 +   0.688777215432*_Complex_I,
+    0.649526607014 +   0.181558179235*_Complex_I,
+    0.780299241568 +  -0.070192359053*_Complex_I,
+    1.932600474030 +  -0.136400805574*_Complex_I,
+   -0.216121187682 +  -0.461249755715*_Complex_I,
+    0.926223942818 +  -0.028529590209*_Complex_I,
+    1.602135289481 +   1.434596371731*_Complex_I,
+    1.623836928491 +  -1.265873017618*_Complex_I,
+    1.568843067308 +  -0.364404259258*_Complex_I};
+
+float complex fft_test_y20[] = {
+    3.993406537683 +   1.703197921701*_Complex_I,
+    3.008874212687 +   7.663142996950*_Complex_I,
+   -1.159042365700 +   3.282695683450*_Complex_I,
+    0.903124379379 +   1.428002755716*_Complex_I,
+   -1.572075523393 +   4.283883169775*_Complex_I,
+    2.253301421641 +   2.928788716985*_Complex_I,
+    3.534976344610 +   2.383761115026*_Complex_I,
+    1.326813143543 +   3.111834244207*_Complex_I,
+   -3.988701280111 +  -0.677308408606*_Complex_I,
+   -1.984004176890 +  -4.401380983625*_Complex_I,
+    0.942714014611 +  -2.993550877540*_Complex_I,
+    2.448617718798 +  -1.195359461640*_Complex_I,
+   -3.328697057289 +  -4.221639306000*_Complex_I,
+    4.287827550431 +   1.139111429335*_Complex_I,
+   -4.013398939947 +  -0.677595378939*_Complex_I,
+   -2.670924173537 +   7.635447143438*_Complex_I,
+   -2.067686784436 +  -7.802196902076*_Complex_I,
+   -1.972088145081 +  -6.957222482710*_Complex_I,
+   -3.101333982661 +  -2.675851028288*_Complex_I,
+    0.394807719219 +  -9.452730429599*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_21.c b/src/fft/tests/data/fft_data_21.c
new file mode 100644
index 0000000..13db4a9
--- /dev/null
+++ b/src/fft/tests/data/fft_data_21.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 21-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x21[] = {
+   -0.292459060095 +  -0.918931582987*_Complex_I,
+   -1.517033866291 +  -0.323416256164*_Complex_I,
+   -1.482752904272 +   0.244611310478*_Complex_I,
+    0.319915998225 +  -0.315947516079*_Complex_I,
+   -0.304653265396 +   1.887596128705*_Complex_I,
+    0.411043591735 +  -0.374178478358*_Complex_I,
+    0.462233294747 +   0.181705549666*_Complex_I,
+   -0.053584030277 +  -1.645170622140*_Complex_I,
+   -0.503521485275 +   0.734911625473*_Complex_I,
+   -0.425967471739 +   0.316306288806*_Complex_I,
+    0.824866046500 +  -0.830071277629*_Complex_I,
+    1.806337286696 +  -0.766132673126*_Complex_I,
+    0.620721751355 +   0.965491952308*_Complex_I,
+   -2.440640280723 +  -0.836336433610*_Complex_I,
+   -0.620347474921 +  -1.385103078296*_Complex_I,
+   -0.195316065403 +   0.051333260777*_Complex_I,
+   -0.448853815719 +   1.473820485114*_Complex_I,
+    0.468718174491 +  -0.133593056210*_Complex_I,
+   -0.912579674279 +   0.093239456860*_Complex_I,
+   -0.060107647935 +  -1.182219308121*_Complex_I,
+   -1.630393758159 +  -1.763218511036*_Complex_I};
+
+float complex fft_test_y21[] = {
+   -5.974374656733 +  -4.525302735571*_Complex_I,
+   -3.602445825195 +  -3.324518378407*_Complex_I,
+    1.072414517043 +  -2.680697729601*_Complex_I,
+   -2.544321267300 +  -3.718686381182*_Complex_I,
+    7.073007677928 +  -0.865187973747*_Complex_I,
+   -4.293690988707 +   8.225140969655*_Complex_I,
+    6.571053045547 +  -6.857090522238*_Complex_I,
+    4.072721192387 +   4.943421889232*_Complex_I,
+    0.156147391740 +  -4.066681194313*_Complex_I,
+   -0.307481517626 +  -5.479113972476*_Complex_I,
+   -3.514186056563 +   2.187768615743*_Complex_I,
+    6.761786041122 +   2.434424093260*_Complex_I,
+    6.161936098710 +   2.932845658178*_Complex_I,
+   -0.983132976608 +   2.475188613552*_Complex_I,
+    0.631299782782 +   0.701473074392*_Complex_I,
+   -0.667672994573 +  -2.853889888582*_Complex_I,
+   -1.447661658880 +   2.115483689466*_Complex_I,
+    4.415160634353 +   1.955631737971*_Complex_I,
+  -10.003872665072 +  -7.143199142087*_Complex_I,
+   -2.863331203221 +  -6.794540372208*_Complex_I,
+   -6.854994833120 +   1.039966706231*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_22.c b/src/fft/tests/data/fft_data_22.c
new file mode 100644
index 0000000..85728ed
--- /dev/null
+++ b/src/fft/tests/data/fft_data_22.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 22-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x22[] = {
+    1.005393906711 +   1.012308570546*_Complex_I,
+    1.688071880070 +   1.150240841419*_Complex_I,
+    0.683345289428 +  -0.062623351865*_Complex_I,
+    0.155361503274 +  -0.795786088580*_Complex_I,
+   -1.820284248422 +   1.218801819999*_Complex_I,
+    0.628547514320 +  -0.973049954879*_Complex_I,
+    1.065280228237 +   0.036573157841*_Complex_I,
+   -2.060385212897 +   0.046192542979*_Complex_I,
+   -1.013657698873 +   0.781474907362*_Complex_I,
+    0.375646814842 +   1.554324770487*_Complex_I,
+    0.891369658302 +  -0.161096019710*_Complex_I,
+   -1.068796361214 +   0.437936260633*_Complex_I,
+    2.551818984217 +  -0.855823653619*_Complex_I,
+    1.902986665335 +   0.173617409071*_Complex_I,
+    0.782297326663 +   1.157416587734*_Complex_I,
+    1.408853663828 +  -0.360396487397*_Complex_I,
+   -1.133068299545 +   0.494376224047*_Complex_I,
+   -0.644483632405 +  -0.024946520734*_Complex_I,
+   -1.906331676937 +   0.114276645380*_Complex_I,
+   -0.607040125764 +   0.926527519920*_Complex_I,
+    0.405414441208 +   0.272172965895*_Complex_I,
+    0.522244149054 +   0.024648904750*_Complex_I};
+
+float complex fft_test_y22[] = {
+    3.812584769431 +   6.167167051279*_Complex_I,
+   -2.048551145080 +   1.920025824226*_Complex_I,
+    6.865239600362 +  -6.254294174522*_Complex_I,
+    6.680188302485 +   7.175301698042*_Complex_I,
+    0.336985685391 +  -2.472221525673*_Complex_I,
+    2.704578846644 +  -1.072297553521*_Complex_I,
+   -5.869735761695 +   0.406925054472*_Complex_I,
+    7.260505946731 +   4.415111182242*_Complex_I,
+    1.284910248555 +  -1.935024971881*_Complex_I,
+    1.066939615916 +  -8.084240769565*_Complex_I,
+   -5.103441669518 +   4.459170611366*_Complex_I,
+   -0.789428947455 +   1.848548655941*_Complex_I,
+   -2.925771543223 +  -0.333175332942*_Complex_I,
+    8.231473045218 +  -1.322435498403*_Complex_I,
+   -2.591866662060 +   5.216079561352*_Complex_I,
+   -0.164183264077 +  -4.464934113704*_Complex_I,
+  -10.152302601435 +   2.776060476179*_Complex_I,
+   -1.546651996340 +   8.030006620400*_Complex_I,
+    2.238990051464 +  -2.081025590453*_Complex_I,
+    2.420796442681 +  -1.572310054104*_Complex_I,
+   11.406980883199 +  10.003031983787*_Complex_I,
+   -0.999573899550 +  -0.554680582506*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_24.c b/src/fft/tests/data/fft_data_24.c
new file mode 100644
index 0000000..8736235
--- /dev/null
+++ b/src/fft/tests/data/fft_data_24.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 24-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x24[] = {
+   -1.420307293620 +   0.936847943063*_Complex_I,
+   -0.496969771953 +   0.590714565032*_Complex_I,
+   -1.354677361467 +  -2.010032405560*_Complex_I,
+   -0.345483299359 +   0.404926987051*_Complex_I,
+    0.689486222452 +  -0.768259750193*_Complex_I,
+   -1.549641684121 +  -0.858118594125*_Complex_I,
+    0.420175234777 +  -0.280408837681*_Complex_I,
+   -2.217262854791 +  -0.375004631542*_Complex_I,
+   -1.198653374297 +   0.900261122326*_Complex_I,
+    0.329725819477 +   0.014746627154*_Complex_I,
+    0.045545673584 +  -0.712071131515*_Complex_I,
+   -0.495265874931 +  -0.922857927267*_Complex_I,
+    0.628733326629 +   0.643883091672*_Complex_I,
+    0.331696680042 +  -1.105791987558*_Complex_I,
+    0.515399107713 +  -0.438177945625*_Complex_I,
+    0.183533661425 +  -0.031372217136*_Complex_I,
+   -0.276753781687 +   0.187288341292*_Complex_I,
+    0.177067067896 +  -0.017072068523*_Complex_I,
+   -0.269294774259 +   0.299567125253*_Complex_I,
+    0.691280106543 +  -0.285429769347*_Complex_I,
+   -0.478959763653 +   0.592471601549*_Complex_I,
+    0.731469600041 +  -1.116061252131*_Complex_I,
+    0.506952261982 +  -1.310440154883*_Complex_I,
+   -0.131823537621 +   1.440176198054*_Complex_I};
+
+float complex fft_test_y24[] = {
+   -4.984028609200 +  -4.220215070640*_Complex_I,
+   -4.250507506099 +   6.724208699822*_Complex_I,
+    0.057360770043 +   0.512716211712*_Complex_I,
+   -3.908279516708 +   6.693724296623*_Complex_I,
+   -1.890450979255 +   6.088953815148*_Complex_I,
+   -1.373512942118 +   4.739496879303*_Complex_I,
+   -4.642576156472 +   5.105686189601*_Complex_I,
+   -2.584328440882 +   1.728098263528*_Complex_I,
+    0.744531943959 +   0.135606499920*_Complex_I,
+   -0.971959807211 +  -7.134164325953*_Complex_I,
+    0.738417588646 +  -0.940988007706*_Complex_I,
+   -0.528146358843 +  -0.272559882858*_Complex_I,
+    0.599319565507 +   0.302073070033*_Complex_I,
+   -3.856916042162 + -10.635041786616*_Complex_I,
+   -5.293262572847 +  -3.982011344991*_Complex_I,
+   -4.348687900619 +   2.460786763911*_Complex_I,
+    5.015153490569 +   6.700996972453*_Complex_I,
+    0.389766514080 +  -0.278393532462*_Complex_I,
+    0.801466543459 +   8.782425209839*_Complex_I,
+    3.623555226700 +  -0.026142755609*_Complex_I,
+   -3.328686450422 +   0.591920646925*_Complex_I,
+   -5.710969715584 +   4.204863120029*_Complex_I,
+    2.683867262111 +  -0.108391775481*_Complex_I,
+   -1.068500953539 +  -4.689297523031*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_26.c b/src/fft/tests/data/fft_data_26.c
new file mode 100644
index 0000000..4b9866a
--- /dev/null
+++ b/src/fft/tests/data/fft_data_26.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 26-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x26[] = {
+   -1.513790990128 +   0.450104660529*_Complex_I,
+   -0.609587704775 +   0.200963343771*_Complex_I,
+    1.150854971928 +  -0.979670346844*_Complex_I,
+    0.676761753784 +  -0.390760850862*_Complex_I,
+    0.025326431025 +   0.226613394038*_Complex_I,
+   -0.877894422758 +   0.377687762743*_Complex_I,
+    0.016945667503 +  -0.414424826825*_Complex_I,
+    0.671396901344 +   1.014597796222*_Complex_I,
+    1.620562100771 +  -0.445584464270*_Complex_I,
+   -0.621912682345 +   0.523347355420*_Complex_I,
+   -1.722706628967 +  -1.473722873869*_Complex_I,
+    1.604125850850 +  -0.595523792175*_Complex_I,
+    2.695969244871 +  -0.740444785313*_Complex_I,
+   -1.837539349404 +  -0.402987576873*_Complex_I,
+    0.643703593669 +  -0.530984627964*_Complex_I,
+    0.745985203740 +  -1.158124796569*_Complex_I,
+    0.492860315079 +   0.183477887101*_Complex_I,
+   -0.715219690752 +  -0.979086251385*_Complex_I,
+    0.179883358483 +   0.236135674483*_Complex_I,
+    0.196402574786 +  -1.059380996958*_Complex_I,
+    0.069933652344 +   0.829344522775*_Complex_I,
+    0.325146685501 +  -1.266467132602*_Complex_I,
+   -1.839777223485 +  -0.327473446299*_Complex_I,
+    1.778506841624 +  -1.571433253340*_Complex_I,
+    1.002599293378 +   0.297630901673*_Complex_I,
+   -1.821267785996 +   1.027493831629*_Complex_I};
+
+float complex fft_test_y26[] = {
+    2.337267962069 +  -6.968672891765*_Complex_I,
+   -1.374144569280 +   2.440460452235*_Complex_I,
+    0.293963382503 +  -1.014603415840*_Complex_I,
+   -5.808647515741 +   2.932080281412*_Complex_I,
+   -3.954391977726 +   4.050421232706*_Complex_I,
+  -10.434029005073 +  -2.430379478773*_Complex_I,
+  -14.396971442054 +   7.561455425624*_Complex_I,
+    4.801073402095 +  -7.297832464880*_Complex_I,
+   -6.451827528493 +  -1.778154888801*_Complex_I,
+    7.694740702469 +  -3.586296872724*_Complex_I,
+    0.830361317715 +  -4.000025563805*_Complex_I,
+    2.193823341792 +   2.627715132519*_Complex_I,
+    5.557050661872 +   3.023112572307*_Complex_I,
+    3.307459610871 +   1.590676230192*_Complex_I,
+  -12.118932653799 +  -2.054113664628*_Complex_I,
+    5.893155083845 +  -6.130479717222*_Complex_I,
+    1.236890091413 +  -1.250300895885*_Complex_I,
+    6.703265906214 +   0.834044438577*_Complex_I,
+   -7.565202099594 +   2.208332250508*_Complex_I,
+    3.230245599909 +   6.555949348851*_Complex_I,
+   -7.293883944894 +  -3.228615872305*_Complex_I,
+   -4.814526866453 +   4.783901741180*_Complex_I,
+   -0.579907911716 +   5.262229129419*_Complex_I,
+   -0.768605942699 +   3.726121287503*_Complex_I,
+   -1.461710271209 +  -1.198541330006*_Complex_I,
+   -6.415081077369 +   5.044238707360*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_3.c b/src/fft/tests/data/fft_data_3.c
new file mode 100644
index 0000000..aed8a24
--- /dev/null
+++ b/src/fft/tests/data/fft_data_3.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 3-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x3[] = {
+   -0.757661328095 +   0.655250200055*_Complex_I,
+   -1.262964116539 +   0.804288531547*_Complex_I,
+    0.075727215016 +  -0.825503865060*_Complex_I};
+
+float complex fft_test_y3[] = {
+   -1.944898229617 +   0.634034866542*_Complex_I,
+    1.247398741022 +   1.825198567765*_Complex_I,
+   -1.575484495691 +  -0.493482834141*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_30.c b/src/fft/tests/data/fft_data_30.c
new file mode 100644
index 0000000..497b4db
--- /dev/null
+++ b/src/fft/tests/data/fft_data_30.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 30-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x30[] = {
+    1.383958311928 +   1.009063372834*_Complex_I,
+    0.656943839727 +  -0.201492142788*_Complex_I,
+   -1.263185461543 +  -0.762235271683*_Complex_I,
+   -0.279286295800 +   0.737833309276*_Complex_I,
+    0.061984731638 +   0.232640465778*_Complex_I,
+    0.078974781535 +   0.151163978562*_Complex_I,
+   -0.067264854607 +  -1.701011326832*_Complex_I,
+   -2.367250035232 +  -0.675654408115*_Complex_I,
+    0.901831068347 +  -2.291740807507*_Complex_I,
+   -0.685180193104 +   0.640309251420*_Complex_I,
+   -0.865870302577 +  -0.234993063029*_Complex_I,
+    0.394411867502 +  -1.629554965527*_Complex_I,
+   -0.163205564173 +   0.199656642882*_Complex_I,
+    0.561129126184 +   2.875025531619*_Complex_I,
+   -1.191762025326 +   0.544473782291*_Complex_I,
+    0.808768695645 +   1.055445062240*_Complex_I,
+   -0.145391531392 +  -0.095884441741*_Complex_I,
+    1.006538762836 +  -0.724775764669*_Complex_I,
+    2.116364610835 +  -2.412759658708*_Complex_I,
+    0.210526780948 +   1.338479312378*_Complex_I,
+    0.722177277886 +   1.447742766425*_Complex_I,
+    0.148533332865 +   0.262381546261*_Complex_I,
+   -1.337190861421 +   1.251733062365*_Complex_I,
+   -0.346555962201 +  -0.012481387193*_Complex_I,
+    1.400932749094 +  -0.360556660035*_Complex_I,
+    0.479524006659 +  -0.777208683834*_Complex_I,
+   -0.525976158853 +  -0.155146817266*_Complex_I,
+   -1.237887495307 +   0.159614688084*_Complex_I,
+    0.526429110861 +  -1.368405705104*_Complex_I,
+   -0.688995272873 +   0.833548127008*_Complex_I};
+
+float complex fft_test_y30[] = {
+    0.294027040082 +  -0.664790204608*_Complex_I,
+   -7.251086654153 +   1.930700457878*_Complex_I,
+    4.192769230897 +   1.789725983362*_Complex_I,
+   11.197367573831 +   3.254783235859*_Complex_I,
+  -10.053118996304 +  -1.739286933243*_Complex_I,
+   10.012405959748 +  -4.383419562391*_Complex_I,
+   -1.293134786584 +   8.917417869363*_Complex_I,
+    3.828158834339 +   7.240756714477*_Complex_I,
+    2.213781017086 +  -3.491740260928*_Complex_I,
+  -11.803330439118 +   1.771364969478*_Complex_I,
+    9.272563287802 +   0.848929030951*_Complex_I,
+   -0.497098653141 +  -0.996109950789*_Complex_I,
+   -3.419412966566 +  -7.252273855897*_Complex_I,
+   -3.013334143526 +  -1.690385014079*_Complex_I,
+    8.340586193744 +   1.433397080247*_Complex_I,
+    2.813635161314 +  -8.730057114052*_Complex_I,
+    0.460908379865 +   1.518681481090*_Complex_I,
+   -5.717851959043 +  -2.820174393000*_Complex_I,
+    8.168927986272 +  11.157813415293*_Complex_I,
+    2.879730739941 +   1.551007915618*_Complex_I,
+    0.710609564242 +  -1.414210144075*_Complex_I,
+    5.664928404839 +  12.624792285877*_Complex_I,
+   -4.559435692794 +   5.128817112052*_Complex_I,
+    3.020061485600 +   2.317084746039*_Complex_I,
+    9.287256582183 +   1.097899941841*_Complex_I,
+    4.921470505274 +  -5.250097784974*_Complex_I,
+    5.775306053035 +   5.396588308077*_Complex_I,
+   -8.507611683876 +   0.041180219148*_Complex_I,
+    3.499272220645 +   8.240657702592*_Complex_I,
+    1.080399112219 +  -7.557152066173*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_317.c b/src/fft/tests/data/fft_data_317.c
new file mode 100644
index 0000000..b1c23ea
--- /dev/null
+++ b/src/fft/tests/data/fft_data_317.c
@@ -0,0 +1,666 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 317-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x317[] = {
+    0.554097588235 +   0.674837317416*_Complex_I,
+    0.530737753317 +  -0.079409691311*_Complex_I,
+    0.132225792019 +  -2.683942671459*_Complex_I,
+   -0.433831796385 +  -0.644436889896*_Complex_I,
+    0.531193579547 +  -0.234132120154*_Complex_I,
+    0.372970050685 +   0.234590738463*_Complex_I,
+    0.469189910453 +   1.347839137560*_Complex_I,
+    1.407357556116 +  -0.732654574714*_Complex_I,
+   -0.892419998715 +   1.232301421325*_Complex_I,
+   -2.097695896055 +   0.374300022370*_Complex_I,
+    0.841647143326 +   1.509643189344*_Complex_I,
+    2.033126977287 +  -0.280107016121*_Complex_I,
+    0.436605233661 +  -0.542582266432*_Complex_I,
+   -0.564143476179 +  -1.316474379263*_Complex_I,
+    1.205813790177 +   0.314460438693*_Complex_I,
+   -0.290370012573 +  -0.142559201647*_Complex_I,
+    0.389754456864 +   0.846145131479*_Complex_I,
+    1.072733277575 +   0.217097105341*_Complex_I,
+   -0.530701360675 +   0.621669741612*_Complex_I,
+   -0.747465971012 +  -1.217065085995*_Complex_I,
+    0.285811246522 +  -0.575506757253*_Complex_I,
+    1.731868270921 +  -1.695420723201*_Complex_I,
+    2.062026927380 +  -0.467312976619*_Complex_I,
+   -0.238522083883 +   0.733481847031*_Complex_I,
+    0.962605683108 +  -0.932743731262*_Complex_I,
+   -0.244109153061 +   0.048909057472*_Complex_I,
+   -0.521908701077 +  -0.335290348825*_Complex_I,
+   -0.224377634061 +   0.812682493510*_Complex_I,
+   -0.808168093175 +  -2.606743537297*_Complex_I,
+   -0.383229234780 +   0.204386340920*_Complex_I,
+   -1.604057984500 +   0.282386325516*_Complex_I,
+    1.123689375175 +   0.294576201783*_Complex_I,
+    1.045506847212 +   0.558518419936*_Complex_I,
+    0.444874000589 +   0.832776075751*_Complex_I,
+    0.381774427538 +   0.783400026120*_Complex_I,
+    0.591805517099 +   0.899143948342*_Complex_I,
+    0.995508244414 +  -0.835798382940*_Complex_I,
+   -2.173146523457 +   0.954135191444*_Complex_I,
+    2.429109028004 +   0.404032043829*_Complex_I,
+   -0.642851155627 +  -0.253033059825*_Complex_I,
+   -0.960201285491 +  -0.932974674369*_Complex_I,
+    0.052794955676 +   0.070183835184*_Complex_I,
+   -0.122786042744 +   0.417387287207*_Complex_I,
+    0.200201640049 +  -0.342343750496*_Complex_I,
+    0.152601120150 +   0.746938165561*_Complex_I,
+   -0.720582709452 +   1.464296507060*_Complex_I,
+    0.286243449065 +  -0.356130710199*_Complex_I,
+   -0.855441109470 +   0.260173089323*_Complex_I,
+    1.241960323905 +  -1.220424835146*_Complex_I,
+    0.410436718259 +  -0.915784426815*_Complex_I,
+   -0.608754986761 +   1.196797063902*_Complex_I,
+    0.842595164718 +   1.776375320466*_Complex_I,
+    0.461313891161 +   0.132552548247*_Complex_I,
+   -1.038934516657 +  -0.503009712801*_Complex_I,
+   -0.174516309894 +  -1.414963943166*_Complex_I,
+   -1.312041533200 +   0.350893580013*_Complex_I,
+    0.208956667743 +  -0.958786287939*_Complex_I,
+    0.803660420194 +   1.011070131954*_Complex_I,
+    2.165017913712 +  -1.468093310734*_Complex_I,
+    1.129645943719 +   0.537365498647*_Complex_I,
+   -1.595984272966 +  -0.203122161061*_Complex_I,
+    1.001865195124 +  -1.388137201018*_Complex_I,
+    0.824523455412 +   0.399895307714*_Complex_I,
+    1.390174301291 +   2.631574565038*_Complex_I,
+   -0.950205283612 +  -1.131255194597*_Complex_I,
+    1.905487211032 +  -0.077934597969*_Complex_I,
+    1.003876696894 +  -0.060334922974*_Complex_I,
+   -0.739566795906 +  -0.125966238551*_Complex_I,
+    0.638862295532 +   0.312440320520*_Complex_I,
+    0.812688518156 +  -0.687832384174*_Complex_I,
+   -0.247460071939 +   1.506637100983*_Complex_I,
+    1.119835012306 +   1.284530437377*_Complex_I,
+    0.514075655596 +   0.315432575412*_Complex_I,
+   -0.556581507109 +  -2.240098527759*_Complex_I,
+    0.249592136667 +   0.505876736625*_Complex_I,
+    0.138808728752 +  -0.362938299441*_Complex_I,
+   -1.295018476090 +  -0.498162870507*_Complex_I,
+    0.633165144610 +  -1.395172629954*_Complex_I,
+    0.932960550573 +  -0.237180627122*_Complex_I,
+    1.661895463725 +   0.690503082347*_Complex_I,
+    0.333824445072 +  -1.788741666587*_Complex_I,
+    0.489887999458 +  -0.706548054495*_Complex_I,
+   -0.548551848905 +   0.898884888429*_Complex_I,
+   -0.608101416118 +  -0.201679435156*_Complex_I,
+    0.890886957534 +  -0.480205878554*_Complex_I,
+   -0.000052987921 +   0.753051843488*_Complex_I,
+   -2.008688335321 +  -0.531405355010*_Complex_I,
+    0.178224821311 +   0.093166214057*_Complex_I,
+   -1.028365245165 +  -0.852475963885*_Complex_I,
+    0.686303677542 +   1.543109809415*_Complex_I,
+    1.711982713732 +  -0.058899888612*_Complex_I,
+    1.120881384941 +   0.023773370634*_Complex_I,
+    0.452547007139 +  -0.345550830853*_Complex_I,
+    0.851642145828 +   0.918449585078*_Complex_I,
+    1.354411836149 +   0.053840956523*_Complex_I,
+    0.186505302140 +   0.455350659118*_Complex_I,
+    1.178867803924 +   0.561242592990*_Complex_I,
+   -0.867274007830 +   0.778369347385*_Complex_I,
+   -0.763210306046 +  -0.056553236256*_Complex_I,
+   -0.925493602469 +   1.802123360882*_Complex_I,
+   -0.381880717193 +   0.836790955039*_Complex_I,
+    0.740584915503 +   0.410451383495*_Complex_I,
+   -1.739773756173 +  -0.008392421431*_Complex_I,
+   -0.927812449353 +   0.225621894111*_Complex_I,
+    0.291685537968 +  -0.631710150727*_Complex_I,
+    1.044752342554 +  -0.921336822198*_Complex_I,
+    0.379926674636 +   0.104269280376*_Complex_I,
+   -0.747794397492 +  -1.586409810217*_Complex_I,
+   -0.707471806222 +  -3.451349626151*_Complex_I,
+   -0.610242217287 +  -0.050520690817*_Complex_I,
+   -1.141609802996 +  -0.450890109498*_Complex_I,
+   -0.110600652376 +   0.711135784835*_Complex_I,
+   -2.154140310955 +  -0.464833145767*_Complex_I,
+    2.215673027150 +   0.378163057373*_Complex_I,
+   -0.654346983910 +  -0.446467309237*_Complex_I,
+    0.054883600089 +  -0.699447750344*_Complex_I,
+    0.806739839269 +   0.085857123252*_Complex_I,
+   -1.606523541279 +   0.816575059374*_Complex_I,
+    0.592874861350 +  -0.160053556472*_Complex_I,
+   -0.487687814535 +   2.062004006591*_Complex_I,
+   -0.494854876646 +   1.222171460716*_Complex_I,
+    1.143598578272 +  -0.256116050124*_Complex_I,
+    2.400451006261 +  -0.615247709498*_Complex_I,
+    1.794972603823 +   0.061326347413*_Complex_I,
+   -1.785033202582 +   1.285030637182*_Complex_I,
+    0.952408789671 +  -0.061111017884*_Complex_I,
+    0.045375503932 +  -0.337506966204*_Complex_I,
+    0.098933506757 +   1.552870310423*_Complex_I,
+   -0.114562876521 +   0.289943289341*_Complex_I,
+   -0.283731751338 +  -0.946113439136*_Complex_I,
+    0.461130821204 +   0.048895395259*_Complex_I,
+   -1.174095369155 +   0.023986190137*_Complex_I,
+   -0.098711272023 +   0.155133236840*_Complex_I,
+    1.644296831488 +  -0.763890180558*_Complex_I,
+   -0.598417564337 +  -0.286264760137*_Complex_I,
+    0.526726895394 +   1.073518895905*_Complex_I,
+   -0.853228628205 +  -1.282488324989*_Complex_I,
+   -1.020706122357 +   0.248901790873*_Complex_I,
+    1.313386656293 +   1.883626375861*_Complex_I,
+   -0.596968080006 +   1.198886796626*_Complex_I,
+   -1.090527229732 +   1.322486318068*_Complex_I,
+    0.055643681240 +  -1.476648993263*_Complex_I,
+   -0.134166277120 +   0.538780165850*_Complex_I,
+   -0.394194728658 +   0.287017836253*_Complex_I,
+    0.451499019863 +   0.196444291722*_Complex_I,
+   -1.227900659262 +   0.066951464777*_Complex_I,
+    0.281655958102 +   0.477082252845*_Complex_I,
+    1.584578121348 +   2.263167026855*_Complex_I,
+   -0.337702889133 +  -1.675522983729*_Complex_I,
+   -1.262293072370 +   0.295093464675*_Complex_I,
+   -1.466903736895 +   0.492057686013*_Complex_I,
+   -1.485463891249 +   1.082857775981*_Complex_I,
+   -0.275569696975 +  -0.635382067043*_Complex_I,
+    1.705596337300 +   2.104558859516*_Complex_I,
+    0.704228406430 +   1.560694307633*_Complex_I,
+    0.940747890984 +   0.080075753674*_Complex_I,
+   -0.671195049979 +  -0.305225546505*_Complex_I,
+    1.351213152915 +  -0.159424965469*_Complex_I,
+   -0.168781019368 +   0.642571757925*_Complex_I,
+   -0.431378015911 +  -0.017150397606*_Complex_I,
+    0.084141441624 +   0.021065037620*_Complex_I,
+   -0.464340239256 +  -0.192995496761*_Complex_I,
+   -1.333718595841 +  -0.017151207176*_Complex_I,
+    1.699781888517 +   1.611185583580*_Complex_I,
+   -0.413570253776 +  -0.044257707528*_Complex_I,
+    2.258631378215 +  -0.166004542094*_Complex_I,
+   -0.465389665786 +   0.871749883624*_Complex_I,
+    1.029189363291 +   0.713498194652*_Complex_I,
+   -0.094915760832 +  -1.625030317385*_Complex_I,
+   -1.232066038521 +  -0.607872994844*_Complex_I,
+    1.502005331699 +  -0.020525271340*_Complex_I,
+   -0.683321223012 +  -0.293225147764*_Complex_I,
+   -0.907670613167 +  -0.107969897144*_Complex_I,
+    0.462429724200 +  -0.775625444256*_Complex_I,
+   -1.205101490966 +  -0.132384994859*_Complex_I,
+   -1.179628062446 +   2.189754291494*_Complex_I,
+   -0.631509862810 +   1.222985676196*_Complex_I,
+    0.789563515020 +   0.698677701769*_Complex_I,
+    1.687609752565 +   0.400827580780*_Complex_I,
+   -0.645610608306 +  -0.014606261491*_Complex_I,
+   -1.361740989948 +  -0.117827247124*_Complex_I,
+    1.314229682964 +  -0.603800894199*_Complex_I,
+   -1.956380418062 +   1.043229034913*_Complex_I,
+   -0.801537986631 +   2.532651178906*_Complex_I,
+   -2.386565797106 +  -1.098115563132*_Complex_I,
+    0.220407348326 +  -1.279188578233*_Complex_I,
+   -0.692180379497 +   0.560515689944*_Complex_I,
+   -0.356604504521 +   0.793303202093*_Complex_I,
+   -0.159496968171 +  -1.294766371488*_Complex_I,
+    1.198119217621 +  -1.994862046372*_Complex_I,
+   -0.568527073111 +   0.401071277388*_Complex_I,
+    2.987226058138 +  -0.073884662975*_Complex_I,
+    1.799586418676 +  -0.574490253290*_Complex_I,
+    0.011347684988 +  -0.745307885407*_Complex_I,
+    0.729683796427 +  -1.060721735894*_Complex_I,
+   -1.966760785708 +  -0.605669511192*_Complex_I,
+    1.142741187156 +   0.019297308388*_Complex_I,
+   -1.561621247893 +  -0.095007722087*_Complex_I,
+    0.336572557763 +  -0.651058510390*_Complex_I,
+    0.040766936719 +   1.217219328749*_Complex_I,
+    0.200903662048 +   0.393373958513*_Complex_I,
+   -1.236464108144 +   0.541280738924*_Complex_I,
+    0.117909377974 +  -0.607773139351*_Complex_I,
+    0.379616825601 +  -0.643186918605*_Complex_I,
+    0.643537081891 +   0.275436892658*_Complex_I,
+    0.225873382688 +  -0.158194256053*_Complex_I,
+    0.053176096756 +   1.458472268368*_Complex_I,
+    1.414145044178 +  -0.733638770750*_Complex_I,
+    1.347289531912 +   0.169321860659*_Complex_I,
+   -0.840384203303 +   0.763005504852*_Complex_I,
+    1.214072735988 +   1.485891717688*_Complex_I,
+   -0.348223438412 +   1.020459174171*_Complex_I,
+    0.073882541886 +  -0.905791314139*_Complex_I,
+   -0.705446913782 +   0.056186574618*_Complex_I,
+   -0.960863041873 +  -0.196833332985*_Complex_I,
+    1.006218030516 +  -0.467774405167*_Complex_I,
+    0.602372049969 +  -2.674593305127*_Complex_I,
+   -0.934722056514 +  -0.016922494007*_Complex_I,
+    0.257679561629 +  -0.885684235693*_Complex_I,
+    0.044866947149 +   0.936359293985*_Complex_I,
+    0.950842198317 +   1.868559062575*_Complex_I,
+    1.498943799171 +  -0.019107821973*_Complex_I,
+   -1.868507034816 +   0.197524971623*_Complex_I,
+    0.970832401725 +  -0.074109462719*_Complex_I,
+   -2.886469419266 +   0.142475981345*_Complex_I,
+    1.240964551237 +   0.153090872958*_Complex_I,
+   -0.000396756859 +  -1.046593294557*_Complex_I,
+    1.117655103664 +   1.647657701398*_Complex_I,
+    1.162930856009 +  -0.975104959968*_Complex_I,
+   -0.934535360620 +   0.307953402722*_Complex_I,
+   -0.573780565126 +  -0.375508524005*_Complex_I,
+   -0.103208834185 +   2.105768262189*_Complex_I,
+   -1.379507684252 +  -0.280894989725*_Complex_I,
+    0.859242654654 +   0.201801911064*_Complex_I,
+    0.214128192305 +  -0.427748749718*_Complex_I,
+    1.559388133754 +  -2.233121573297*_Complex_I,
+    0.019639327733 +   0.360300645061*_Complex_I,
+   -0.133932495128 +  -0.722680847651*_Complex_I,
+    0.863036773382 +  -2.922284619456*_Complex_I,
+    0.334598761557 +  -0.983050027547*_Complex_I,
+   -1.077563482460 +   0.417379502415*_Complex_I,
+   -0.198656810153 +   0.569701104618*_Complex_I,
+    0.913145816840 +   2.052526155977*_Complex_I,
+    0.854066622796 +  -0.413714533361*_Complex_I,
+   -0.414380668765 +  -1.405489197548*_Complex_I,
+   -1.110627741502 +  -1.820107908459*_Complex_I,
+   -0.613940194705 +   1.779435308312*_Complex_I,
+   -2.105251634607 +   0.525272681631*_Complex_I,
+    0.609098015750 +  -0.895402748194*_Complex_I,
+    1.934336563550 +   0.543860167180*_Complex_I,
+   -0.782446497605 +   0.641226314552*_Complex_I,
+   -0.955134630524 +  -0.077175268361*_Complex_I,
+   -0.210873574493 +  -1.715168716896*_Complex_I,
+    1.628059930554 +  -1.533301168339*_Complex_I,
+   -1.421933758706 +  -0.241187651806*_Complex_I,
+    2.187891215143 +  -0.023407875710*_Complex_I,
+    0.548885995861 +   0.285435244871*_Complex_I,
+    0.674430997216 +  -0.481078429799*_Complex_I,
+    0.397489182610 +  -0.446320110483*_Complex_I,
+   -0.865204055951 +  -0.020571209344*_Complex_I,
+   -0.155857632710 +  -1.473838050536*_Complex_I,
+   -0.825650581473 +   0.501060420785*_Complex_I,
+    0.506220809313 +   1.124359364738*_Complex_I,
+    0.199383983129 +   0.552775919340*_Complex_I,
+   -0.675785644869 +  -0.015515289402*_Complex_I,
+    0.347119071389 +  -0.046308125867*_Complex_I,
+   -0.580411900444 +   0.473043358519*_Complex_I,
+   -1.073929642005 +  -1.146130174539*_Complex_I,
+   -1.137495899690 +  -1.309784871827*_Complex_I,
+    1.171486989096 +   1.823857109273*_Complex_I,
+   -0.742946853968 +   0.049703242746*_Complex_I,
+   -0.160432289828 +   1.724103887278*_Complex_I,
+   -0.546035353551 +   0.386968779514*_Complex_I,
+   -1.213047883956 +  -0.537389928129*_Complex_I,
+   -1.339236442910 +  -1.248701576327*_Complex_I,
+   -0.815367922604 +   0.085728302177*_Complex_I,
+   -2.349193819868 +   0.427831727302*_Complex_I,
+   -0.276194199790 +   1.234485714534*_Complex_I,
+   -0.355231570763 +  -0.995816467388*_Complex_I,
+    1.137208262430 +   0.216271557004*_Complex_I,
+    0.962040804090 +  -1.065610794498*_Complex_I,
+   -0.668324382362 +   0.822590130460*_Complex_I,
+   -1.202015945307 +  -1.480043875470*_Complex_I,
+    1.179225549720 +   1.767048172045*_Complex_I,
+   -0.684123959923 +  -0.862865878982*_Complex_I,
+   -0.174319873616 +   0.167752215701*_Complex_I,
+   -1.446536704664 +   1.172516467428*_Complex_I,
+    0.858114162859 +   2.295181364008*_Complex_I,
+    0.124270375245 +   0.997986406329*_Complex_I,
+    0.394906672949 +   1.154862665198*_Complex_I,
+    1.053903228740 +   1.779817113028*_Complex_I,
+   -0.230950556520 +  -0.175979989611*_Complex_I,
+   -1.265383803223 +  -0.026310311336*_Complex_I,
+    0.762972265227 +   1.429231386216*_Complex_I,
+    1.162409662447 +  -1.112322698285*_Complex_I,
+    0.775539802471 +  -1.323064642721*_Complex_I,
+    0.666613966503 +  -0.129946108336*_Complex_I,
+    1.930414467502 +   0.436695033239*_Complex_I,
+    0.885068429064 +   0.115321831962*_Complex_I,
+    1.551024374006 +   0.416463074882*_Complex_I,
+    0.247974255137 +   0.627306468118*_Complex_I,
+    0.781609286510 +   0.378212834621*_Complex_I,
+    0.332343800329 +   0.539122959506*_Complex_I,
+    0.519709930109 +  -1.064947224271*_Complex_I,
+   -0.635850608190 +   1.257862397877*_Complex_I,
+    1.628515077193 +  -1.103666746889*_Complex_I,
+   -0.849325965524 +  -1.767990899186*_Complex_I,
+    0.743956616560 +  -2.505409755963*_Complex_I,
+    0.830498883146 +   0.057323145873*_Complex_I,
+   -1.470980958211 +  -1.558079958147*_Complex_I,
+   -0.088063460329 +   0.426467567974*_Complex_I,
+   -0.219298752127 +  -0.711676070690*_Complex_I,
+   -0.283866135348 +   1.254036523517*_Complex_I,
+   -0.254391988290 +   0.215410095075*_Complex_I,
+   -0.590092311740 +  -1.507631350024*_Complex_I,
+   -1.535360519901 +  -0.437319427483*_Complex_I,
+    1.026878345705 +  -0.182042614613*_Complex_I};
+
+float complex fft_test_y317[] = {
+   19.891894947904 +   5.655090929781*_Complex_I,
+   18.838569316919 + -27.216365987549*_Complex_I,
+  -11.140787864820 +  -1.700693291107*_Complex_I,
+    9.248191980131 + -11.880625755527*_Complex_I,
+    0.877433246927 +   5.210710512463*_Complex_I,
+   12.613756002695 + -22.914829471885*_Complex_I,
+   -7.522954808149 +   2.580642036625*_Complex_I,
+  -24.806396396859 +  14.114587974426*_Complex_I,
+    9.875249841730 + -22.004495722041*_Complex_I,
+  -18.222931551932 +   2.572927350772*_Complex_I,
+   20.867583356509 +  -1.458843546852*_Complex_I,
+   38.916192529134 +  -7.826129128685*_Complex_I,
+  -19.618271263978 + -21.217111192894*_Complex_I,
+    0.549362128526 +  18.584275483073*_Complex_I,
+   -5.258299327834 +  -0.446452119776*_Complex_I,
+   -6.830532675831 + -35.800356213084*_Complex_I,
+   16.124101912097 +  -2.387751210287*_Complex_I,
+   19.602770799308 + -18.449258115561*_Complex_I,
+   -4.933124103618 +   8.281766183665*_Complex_I,
+  -13.560383009984 +   0.970618524872*_Complex_I,
+   41.610229013366 +   5.152249379736*_Complex_I,
+    1.415309104711 +  11.210809107531*_Complex_I,
+  -40.441003535705 + -12.405906077032*_Complex_I,
+  -12.530216678354 +  22.864679881966*_Complex_I,
+  -29.573667819723 + -22.154070735316*_Complex_I,
+   -8.933348019353 + -25.720405793092*_Complex_I,
+   -3.072833505067 +  16.874820762270*_Complex_I,
+   -8.751051523099 +  -9.544252802963*_Complex_I,
+   19.619902222112 + -22.765558912475*_Complex_I,
+    3.204939906092 + -25.426020065167*_Complex_I,
+  -18.600493227238 +   2.762236432333*_Complex_I,
+   10.833867918922 + -38.070397977737*_Complex_I,
+   25.869914191964 + -19.192002503531*_Complex_I,
+   12.052348942398 +  -1.338816803484*_Complex_I,
+  -38.656067918699 + -24.135381967263*_Complex_I,
+    5.700781235700 +  11.219859126000*_Complex_I,
+  -23.338619501120 +  32.607593688928*_Complex_I,
+  -10.686035441793 +  16.337999228208*_Complex_I,
+   -9.517260831300 +  17.469953582495*_Complex_I,
+   11.452433021422 +  -2.244848000851*_Complex_I,
+    3.355159985201 +  -0.950021809091*_Complex_I,
+   11.960645612996 +   6.893274528846*_Complex_I,
+    9.585251410761 +  10.141135223678*_Complex_I,
+   -7.050051336892 +  27.613088292755*_Complex_I,
+    7.367552651770 +  -6.006047409882*_Complex_I,
+   19.902478672101 + -16.730550563777*_Complex_I,
+   32.759288191850 +   1.162079163303*_Complex_I,
+  -10.679222098466 +  -0.414496389670*_Complex_I,
+  -17.167648316558 +  13.231819142930*_Complex_I,
+  -15.270670234876 +  12.750590771914*_Complex_I,
+    7.808635317834 +  -1.976327148026*_Complex_I,
+    3.659364354893 +  12.967348190608*_Complex_I,
+    8.516191035487 +  -3.814776057042*_Complex_I,
+   -2.967686164798 +  -2.759276437723*_Complex_I,
+  -10.217022770046 +  22.446524490020*_Complex_I,
+  -13.759629399132 +   0.428077429397*_Complex_I,
+   12.108694472460 +  17.906136235301*_Complex_I,
+    0.500310973628 +  10.379459504754*_Complex_I,
+   22.170113416067 + -17.601071337137*_Complex_I,
+   -4.547207763897 +  13.535378900333*_Complex_I,
+   35.073898289289 +  -3.750408416097*_Complex_I,
+   12.960162045071 + -10.329236328497*_Complex_I,
+    5.155047938611 +  11.233862622741*_Complex_I,
+  -19.366076595616 +   3.290152177759*_Complex_I,
+   -2.547983907745 +  -4.318523679571*_Complex_I,
+   13.079902614417 + -31.788319997011*_Complex_I,
+   -0.294946700028 +  20.014494355305*_Complex_I,
+   18.602791112148 +  -1.875890460773*_Complex_I,
+  -13.396328282272 +   9.288327370031*_Complex_I,
+   -3.117158916891 +   2.544747297232*_Complex_I,
+   14.226131115223 +  17.462152594662*_Complex_I,
+  -19.578473387664 +  34.411911283259*_Complex_I,
+   -6.579019542175 +   1.022001674347*_Complex_I,
+   30.972194959263 +  -8.350211186911*_Complex_I,
+  -23.959086942588 +  -4.745490152344*_Complex_I,
+   18.433629972547 +  33.672506545950*_Complex_I,
+   -3.583025093477 +  29.006005065761*_Complex_I,
+   -3.421689832344 +  -6.516619591564*_Complex_I,
+   17.468290088683 +  14.351378688148*_Complex_I,
+  -20.170011170114 + -44.394817543067*_Complex_I,
+  -13.150516148634 +  45.749703126153*_Complex_I,
+   -6.345962437499 +   5.896690541701*_Complex_I,
+   -3.508737698320 +  25.459472943049*_Complex_I,
+   -9.590464636095 + -37.162638669309*_Complex_I,
+   -7.707872774343 +  -4.065042502767*_Complex_I,
+    4.310656075828 +  55.425516805330*_Complex_I,
+   -8.841697395726 +   7.827822742296*_Complex_I,
+    6.538079696486 +  18.790190957680*_Complex_I,
+    3.052383346750 + -35.409567370640*_Complex_I,
+    6.790218518127 +   6.485107042363*_Complex_I,
+   -1.939520400173 +   9.749487460014*_Complex_I,
+   27.335153513982 + -28.544990200718*_Complex_I,
+    1.310416901033 +  -3.384377012660*_Complex_I,
+   39.882940481275 +  -2.284359214074*_Complex_I,
+   10.547528262920 + -12.560415251324*_Complex_I,
+  -39.735255177309 +  13.592975359455*_Complex_I,
+  -15.339971072116 + -16.475578099072*_Complex_I,
+   17.033232877918 +   3.802866771597*_Complex_I,
+   28.013870016133 +   2.419611488016*_Complex_I,
+   -2.265805112454 +   0.312114751003*_Complex_I,
+  -33.149667339304 + -13.689695655857*_Complex_I,
+   17.388116065076 +   0.542000357078*_Complex_I,
+   24.899620116312 + -12.668755714836*_Complex_I,
+    4.917415611451 +  -9.932194848538*_Complex_I,
+  -11.738607295144 +  -8.914945728892*_Complex_I,
+  -19.712014537454 +  22.399807137247*_Complex_I,
+    5.053485084584 +  17.298193889941*_Complex_I,
+   -7.079518754159 +  17.581889100321*_Complex_I,
+  -10.281582568059 +   8.089169898931*_Complex_I,
+    1.422571832929 +   3.080323599470*_Complex_I,
+    8.605408612244 +  -3.487094722779*_Complex_I,
+  -11.890704936363 +   9.663054150701*_Complex_I,
+    9.768366151498 +  15.851583190991*_Complex_I,
+   15.643908708786 +  -4.022026320351*_Complex_I,
+  -23.153672099048 +  27.718041721476*_Complex_I,
+  -10.673968698682 +  -2.656942822276*_Complex_I,
+   -3.084198946373 +   5.011470019270*_Complex_I,
+   30.294417286976 +  -0.649680240512*_Complex_I,
+    9.336689517258 + -15.513350621571*_Complex_I,
+   -0.332156127181 +   9.028518696767*_Complex_I,
+  -18.028373273009 +   0.865624445339*_Complex_I,
+   18.478442253114 +  -4.603304072176*_Complex_I,
+  -23.690660760663 +  23.776870282847*_Complex_I,
+   15.985913856353 +  -2.839961425057*_Complex_I,
+    1.572606311668 + -19.612416936799*_Complex_I,
+   16.810087235424 +   0.199177019879*_Complex_I,
+  -15.975160985564 +   6.532163069799*_Complex_I,
+  -32.272173936218 + -31.217876380070*_Complex_I,
+   15.826696571154 +  18.609590632164*_Complex_I,
+   47.902645387609 +  17.126787301492*_Complex_I,
+   -2.541073313860 +  14.418504375704*_Complex_I,
+   14.540095561207 +  20.121389424019*_Complex_I,
+   26.277876001597 +  -3.644634538637*_Complex_I,
+   -2.512888880479 + -30.538233732588*_Complex_I,
+   16.529338657188 +  22.345368961768*_Complex_I,
+   -1.812032422371 + -34.161479691113*_Complex_I,
+   -0.453702679159 +  -6.380888555304*_Complex_I,
+   -3.152766309700 + -11.666085457234*_Complex_I,
+  -11.122245781125 + -11.927016012323*_Complex_I,
+   36.608207324428 +  15.883213693451*_Complex_I,
+   -7.991006509076 +  50.985368963626*_Complex_I,
+    4.226949261924 +   1.424207754420*_Complex_I,
+   17.463446429822 +   6.779880329142*_Complex_I,
+   -3.489655564559 +   0.384578586369*_Complex_I,
+   -5.888919363255 +  -5.109974862115*_Complex_I,
+   -7.227459554733 +  29.423681452522*_Complex_I,
+   -5.599219759541 + -23.600754528481*_Complex_I,
+   -1.449537808914 +  -5.756622299277*_Complex_I,
+  -19.475712102355 + -23.900197536654*_Complex_I,
+  -21.448274996924 +  26.188122613033*_Complex_I,
+  -29.449414779539 +   4.514251604734*_Complex_I,
+    0.711130838156 +   9.386706325613*_Complex_I,
+   -4.964574769823 +   3.355702966205*_Complex_I,
+  -69.341381991121 + -15.186662242307*_Complex_I,
+   -9.570125543135 + -19.565859787554*_Complex_I,
+  -10.294086283004 +   8.398169381304*_Complex_I,
+   -1.885058572468 +  -6.759464252726*_Complex_I,
+   27.070270898380 +  -0.178665040288*_Complex_I,
+   47.866947417745 + -31.409630574884*_Complex_I,
+   -8.921385543048 +  26.098382682234*_Complex_I,
+   -2.790785416910 +   9.787980555385*_Complex_I,
+  -11.714395450184 +   2.331009478742*_Complex_I,
+  -15.110851454222 + -16.495034888485*_Complex_I,
+    4.191147002221 +  12.009574674807*_Complex_I,
+   13.441299656087 +  -6.566453769009*_Complex_I,
+   37.589069138284 +  -3.493855465671*_Complex_I,
+   12.583644012597 +  15.635478070296*_Complex_I,
+  -10.233844300260 +  -2.537026190645*_Complex_I,
+   32.544552127969 +  -9.371296682693*_Complex_I,
+    5.840669998388 +  -0.465346273045*_Complex_I,
+  -32.889192462170 + -18.684446717050*_Complex_I,
+  -15.105348929262 +   9.854947012189*_Complex_I,
+    2.561152455963 + -17.413141080242*_Complex_I,
+   13.746092833724 +  -1.220057764138*_Complex_I,
+  -23.803933926504 +  19.459415272545*_Complex_I,
+   13.809596770429 + -10.104944386386*_Complex_I,
+   -8.303454415515 +   8.117739395766*_Complex_I,
+    6.133097727305 +  -1.569418595484*_Complex_I,
+   -8.760751262525 + -27.389777662710*_Complex_I,
+   10.131599125171 +   6.687127259801*_Complex_I,
+  -25.519018101480 +  18.199522992831*_Complex_I,
+  -13.233035001671 +  -6.395819934405*_Complex_I,
+   11.700095411690 + -15.584918347062*_Complex_I,
+   18.906883235021 +  -7.460982609320*_Complex_I,
+  -17.459921203210 +   1.804228941706*_Complex_I,
+   12.317579017635 +  10.356522098016*_Complex_I,
+   26.023982632040 +  -7.991255808237*_Complex_I,
+  -37.779207166301 +  -2.527619516503*_Complex_I,
+  -16.084376241401 + -20.612256188135*_Complex_I,
+   -6.114349663136 + -23.064645190863*_Complex_I,
+   -2.116498608238 +  50.021918384600*_Complex_I,
+   -7.117672685723 +  -8.176883387504*_Complex_I,
+   -5.412565357343 +  22.014215119309*_Complex_I,
+    6.088100003823 + -14.490088332571*_Complex_I,
+  -23.952484213391 +  -6.311185058243*_Complex_I,
+  -41.692266879065 + -16.754070143984*_Complex_I,
+   15.044401210058 +   3.664913601471*_Complex_I,
+  -33.750320820834 +  36.019579795022*_Complex_I,
+   13.347039200739 + -32.112552097269*_Complex_I,
+  -15.461626675606 +  23.718105709185*_Complex_I,
+  -12.296412987482 +  11.603712083795*_Complex_I,
+   -5.811459938628 +  -7.925008172416*_Complex_I,
+   -1.192572683531 + -17.231195267270*_Complex_I,
+  -20.865176875222 + -31.216074954599*_Complex_I,
+  -28.168417257904 +  -6.162171521961*_Complex_I,
+   14.071723863194 + -13.359093220787*_Complex_I,
+  -10.855751735127 +  -2.119685075188*_Complex_I,
+   10.100698435956 +  -0.047663624125*_Complex_I,
+  -16.742422653794 + -24.530696829578*_Complex_I,
+    4.000721417241 +  -9.851748070066*_Complex_I,
+   13.784754826511 +  11.502447326859*_Complex_I,
+   24.102575046761 +   1.552286673184*_Complex_I,
+    6.070752306643 +  -4.812888456253*_Complex_I,
+   -5.300318786685 +  -5.773884122203*_Complex_I,
+    4.177402715108 +   4.644324663894*_Complex_I,
+  -34.005084599492 +  24.853411330269*_Complex_I,
+   -4.077672998994 +   2.001774715846*_Complex_I,
+  -21.978658258889 + -16.888801388499*_Complex_I,
+   16.217687213434 +  24.308462530919*_Complex_I,
+    9.698665151728 + -11.152518442318*_Complex_I,
+   -0.713578550754 +  -0.408153703877*_Complex_I,
+  -13.404489453311 + -13.338812872852*_Complex_I,
+   12.438700016349 +  34.570234225070*_Complex_I,
+   -9.926721954129 +   6.906926926113*_Complex_I,
+   34.152883393921 +  32.217622455379*_Complex_I,
+   18.232286791872 +  37.874687752978*_Complex_I,
+   12.000459208850 +  -4.948870303603*_Complex_I,
+  -32.687697613521 + -14.030824660001*_Complex_I,
+    9.549264725873 + -11.955689902942*_Complex_I,
+   14.839058751293 + -31.933475859189*_Complex_I,
+  -14.754140603346 + -24.878350781691*_Complex_I,
+   10.712939318393 +  -5.996862342613*_Complex_I,
+  -17.907934788390 +   6.090055602127*_Complex_I,
+   36.124946787489 +  -5.830186488894*_Complex_I,
+    8.800236613602 +  -2.072057513781*_Complex_I,
+    3.437540103841 +   0.664749616099*_Complex_I,
+   31.950715457487 +  -0.220151294656*_Complex_I,
+   -0.045173525280 +  19.196581987850*_Complex_I,
+    1.870859081457 + -31.599244764015*_Complex_I,
+    0.209529336033 +   0.187035118051*_Complex_I,
+  -20.188770724804 +  -1.836423923991*_Complex_I,
+   -0.878467583840 +  20.234622217282*_Complex_I,
+   -2.102494717199 +  -2.913862105328*_Complex_I,
+  -12.424351209312 +  16.006770035694*_Complex_I,
+    4.029466210003 +  -4.407643696893*_Complex_I,
+    5.945482525641 +   6.202857230453*_Complex_I,
+  -10.003725089675 +  12.495974119472*_Complex_I,
+  -30.517926092302 + -12.650897630264*_Complex_I,
+    4.362955303417 + -12.091129699738*_Complex_I,
+   16.312617526910 +  33.224889607577*_Complex_I,
+   -7.191052604298 +  20.407651857657*_Complex_I,
+   20.796924916343 +  -6.364968926767*_Complex_I,
+  -12.625716653882 +  13.205109334295*_Complex_I,
+    8.088234917787 +   7.824060551408*_Complex_I,
+    1.922368898588 +  47.121922176668*_Complex_I,
+  -22.856735196688 +  24.367218386007*_Complex_I,
+   -7.383851526948 +  16.988073661561*_Complex_I,
+   44.161092935665 +  -5.907735807977*_Complex_I,
+   21.472541358807 + -13.144261767738*_Complex_I,
+  -11.542368964090 +  15.544417175199*_Complex_I,
+   27.503706468722 + -11.522885641815*_Complex_I,
+   47.558932357808 +   7.448305631246*_Complex_I,
+   11.766347399663 +  19.611745304561*_Complex_I,
+    9.190381176578 +  15.613955528574*_Complex_I,
+    4.579716108010 + -19.097122607568*_Complex_I,
+    8.259625237826 +   5.225672702106*_Complex_I,
+  -29.407167921996 +   2.730538989362*_Complex_I,
+   28.823600196176 +  29.176827923246*_Complex_I,
+    7.192706095305 + -13.060381105412*_Complex_I,
+   25.710478454828 +   0.151963358956*_Complex_I,
+    2.912008664120 + -30.634293042262*_Complex_I,
+  -18.887195071024 +  24.458778149068*_Complex_I,
+  -11.270981360031 +  36.740254138082*_Complex_I,
+  -27.910677769149 + -46.983604943585*_Complex_I,
+   28.541376587410 + -11.613642790751*_Complex_I,
+    4.477974452509 +  19.232597353019*_Complex_I,
+   -4.293520064043 +  21.064976071666*_Complex_I,
+  -35.598030786598 +  22.993609231670*_Complex_I,
+   15.465979612020 +  -7.083850274044*_Complex_I,
+  -20.864435551224 + -11.252291347107*_Complex_I,
+   -8.098484900823 +  15.454651546399*_Complex_I,
+    7.922777952587 + -23.004986313444*_Complex_I,
+    5.362453523073 +  -0.810615362821*_Complex_I,
+   23.691538716715 +   5.018112888735*_Complex_I,
+   -0.840977210667 +   5.291230447587*_Complex_I,
+  -14.732575553595 +   5.007945078325*_Complex_I,
+   11.153523811020 +   9.041741420055*_Complex_I,
+   10.332014628129 + -19.924730861837*_Complex_I,
+  -14.993931961657 +  31.489106362903*_Complex_I,
+   18.595068458053 + -35.418285443670*_Complex_I,
+   39.450078173463 +   1.995244884889*_Complex_I,
+   -1.199515997336 + -39.429463141214*_Complex_I,
+   32.594746366756 +  11.337568560792*_Complex_I,
+   12.403678050012 + -21.176744666160*_Complex_I,
+    3.346525369966 +  -1.001631596928*_Complex_I,
+  -19.825849167391 +  26.742894636469*_Complex_I,
+    4.915352470909 +   3.753914873323*_Complex_I,
+    8.679746038045 +  -3.972380637551*_Complex_I,
+   -3.287311421377 +  20.200996274143*_Complex_I,
+   22.907843859952 +   3.994757951688*_Complex_I,
+    2.844877291610 +  -4.339174940613*_Complex_I,
+    4.103872158917 +  -8.872001060599*_Complex_I,
+   28.989825837444 + -23.300936570044*_Complex_I,
+   -7.955654262813 +  31.438036806524*_Complex_I,
+   -6.389510821525 +  -2.868211291181*_Complex_I,
+    2.583272449361 + -13.352995160958*_Complex_I,
+  -38.239266883463 +  23.653429024344*_Complex_I,
+    8.079210774309 +  23.697861354779*_Complex_I,
+  -37.713434701568 +   2.799035443775*_Complex_I,
+  -26.751551315717 +   8.673836408974*_Complex_I,
+  -33.613333707208 +  -0.367929968698*_Complex_I,
+  -14.541161127911 +  -5.708211292132*_Complex_I,
+   -3.393160511476 + -24.278587890684*_Complex_I,
+   26.111359371252 + -28.934022170518*_Complex_I,
+   14.009362482345 + -20.358022264859*_Complex_I,
+    7.316431660011 +  26.887415857021*_Complex_I,
+    8.199534997352 +  -2.435205163079*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_32.c b/src/fft/tests/data/fft_data_32.c
new file mode 100644
index 0000000..c097111
--- /dev/null
+++ b/src/fft/tests/data/fft_data_32.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 32-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x32[] = {
+    0.010014623512 +   0.557635892111*_Complex_I,
+   -0.206536736342 +  -0.166543150147*_Complex_I,
+    0.538145349965 +  -1.725439447290*_Complex_I,
+    0.302742823470 +  -1.371600938217*_Complex_I,
+    1.002711341702 +   0.326114543577*_Complex_I,
+    0.141075699526 +   0.155158151624*_Complex_I,
+   -0.607068327122 +   1.319516119806*_Complex_I,
+    0.368752062157 +  -0.923715410356*_Complex_I,
+    2.103742061281 +  -0.015112313666*_Complex_I,
+    0.161141569742 +   0.602136525051*_Complex_I,
+   -0.450622712484 +   1.781653187693*_Complex_I,
+   -0.135385448029 +   0.118136375039*_Complex_I,
+   -0.881476360789 +   0.743248990846*_Complex_I,
+   -0.470865375669 +   0.297577338949*_Complex_I,
+    1.578293870903 +   0.150586285410*_Complex_I,
+   -0.582548890310 +  -0.090131349592*_Complex_I,
+   -0.728216825909 +  -0.280660553441*_Complex_I,
+   -0.405960077281 +  -0.823236356122*_Complex_I,
+   -0.689782453002 +  -0.111721138015*_Complex_I,
+   -0.483036608523 +  -2.255409787531*_Complex_I,
+   -1.773638052539 +   0.276871847058*_Complex_I,
+    1.768143481866 +   0.929411559777*_Complex_I,
+    1.598162436054 +   0.336924712326*_Complex_I,
+    0.025313208364 +   0.101634388542*_Complex_I,
+    1.295932058649 +  -0.792267243573*_Complex_I,
+    0.197302929571 +  -1.198246830820*_Complex_I,
+   -0.674567746661 +   0.782836390216*_Complex_I,
+   -1.155843266715 +  -2.060346979162*_Complex_I,
+   -1.174296668808 +  -1.018450093532*_Complex_I,
+   -2.374237969002 +   0.374087797223*_Complex_I,
+    0.773269391236 +  -0.854101525457*_Complex_I,
+   -0.320046719055 +  -0.112754382318*_Complex_I};
+
+float complex fft_test_y32[] = {
+   -1.249387330241 +  -4.946207393990*_Complex_I,
+    6.246388763884 +  -6.773507691880*_Complex_I,
+   -8.084921469264 + -10.845914953315*_Complex_I,
+    4.357263742580 +  -0.359518611967*_Complex_I,
+    0.869129033150 +   4.810140005791*_Complex_I,
+   -4.320755060606 +  -2.990777115902*_Complex_I,
+   -6.040916675986 +  11.391912365593*_Complex_I,
+    1.334419178501 +   8.684519829396*_Complex_I,
+    4.553475487339 +  -2.672989876360*_Complex_I,
+    5.980003650433 +   0.810448601904*_Complex_I,
+   -6.393705093395 +   4.226375095002*_Complex_I,
+   -2.975901497257 +   6.587514214591*_Complex_I,
+   -1.005810190439 +  -6.128898292417*_Complex_I,
+    0.016624592858 +  -1.973370861210*_Complex_I,
+    0.024605667140 +  -7.634608092684*_Complex_I,
+    2.682037461215 +  -1.379684965569*_Complex_I,
+    5.090591302217 +   7.901478702130*_Complex_I,
+    4.974928932755 +  -2.673377557945*_Complex_I,
+    2.018958625049 +   1.429758433848*_Complex_I,
+   -3.215930811783 +  -5.513827987988*_Complex_I,
+    9.696021083826 +   2.712450847683*_Complex_I,
+   -6.175824724853 +   6.515234684364*_Complex_I,
+  -10.139408716624 +   3.456026873018*_Complex_I,
+    3.811736373367 +   3.481961808174*_Complex_I,
+   -8.975590750922 +  -1.092757154258*_Complex_I,
+    1.006822057237 +  -4.766098514087*_Complex_I,
+   -0.499087378413 +   4.387815733060*_Complex_I,
+   -0.223489533627 +   2.032893439147*_Complex_I,
+   12.473346705334 +  -4.826450587127*_Complex_I,
+    4.394902822920 +  12.095339998118*_Complex_I,
+   -3.828535537115 +   2.263473712749*_Complex_I,
+   -6.081522756885 +  -0.365006140312*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_35.c b/src/fft/tests/data/fft_data_35.c
new file mode 100644
index 0000000..347eea7
--- /dev/null
+++ b/src/fft/tests/data/fft_data_35.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 35-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x35[] = {
+    0.621203985274 +  -1.170486588571*_Complex_I,
+    0.001346936460 +   0.911992162865*_Complex_I,
+    0.852266167932 +  -0.485602417107*_Complex_I,
+   -1.440614617555 +  -0.143907395580*_Complex_I,
+   -0.886824740774 +  -0.735649970983*_Complex_I,
+    0.822316647558 +  -2.190416870881*_Complex_I,
+   -0.848542045588 +   0.265512380995*_Complex_I,
+    0.224076450439 +  -2.302360677412*_Complex_I,
+   -0.510056035858 +  -1.331941821231*_Complex_I,
+    1.682894720322 +   1.515017086970*_Complex_I,
+   -0.293255187957 +   0.559255597549*_Complex_I,
+    1.692889886331 +  -1.640844999342*_Complex_I,
+   -1.032973350809 +   0.405524091118*_Complex_I,
+    0.177413388869 +  -1.166118195604*_Complex_I,
+   -0.077664879368 +  -1.471655833896*_Complex_I,
+   -0.713927870525 +   1.118966977478*_Complex_I,
+    3.112942596938 +   0.183067680138*_Complex_I,
+   -2.132342418783 +   0.888305824658*_Complex_I,
+    0.570015793758 +  -1.075297345387*_Complex_I,
+    0.029232277101 +  -0.052772048068*_Complex_I,
+    0.021810229730 +   1.026136624357*_Complex_I,
+    0.183820954153 +   0.359011138378*_Complex_I,
+   -0.646415125847 +   0.878859460005*_Complex_I,
+    0.379503797426 +   0.690555809274*_Complex_I,
+   -1.205501520591 +  -1.281704908973*_Complex_I,
+   -0.671296057445 +  -1.202380495393*_Complex_I,
+    1.000730854392 +   1.337218974137*_Complex_I,
+   -0.507823699969 +   0.667041823900*_Complex_I,
+    0.786493220706 +   0.711624105805*_Complex_I,
+   -0.147343975279 +  -0.421666316903*_Complex_I,
+    1.000992786069 +   0.072055493934*_Complex_I,
+   -0.638324259500 +   0.503386775311*_Complex_I,
+    1.518863022405 +  -0.447779056699*_Complex_I,
+   -0.690987494413 +   0.744128091375*_Complex_I,
+    0.074647158829 +  -1.048901976585*_Complex_I};
+
+float complex fft_test_y35[] = {
+    2.309567594431 +  -5.331826820366*_Complex_I,
+   -7.442846930878 +  -4.725553620071*_Complex_I,
+   -2.183297818919 +   6.404493143269*_Complex_I,
+   -2.751014847029 +   2.658941664250*_Complex_I,
+    8.330534466967 +   0.919074147759*_Complex_I,
+    6.588325473188 +  -2.237962814214*_Complex_I,
+   -7.983324045653 +  -4.751388462518*_Complex_I,
+    9.842334426677 +  -4.886656356639*_Complex_I,
+    2.723677138226 +  -1.864201873505*_Complex_I,
+    3.314550864796 +  -2.486087207928*_Complex_I,
+   -9.968089532702 + -11.225031015883*_Complex_I,
+    3.348282695100 +   3.685146610266*_Complex_I,
+    3.650591794608 +   7.864477419325*_Complex_I,
+   12.324040428213 +  -2.521426915911*_Complex_I,
+   -1.118920460046 +  -5.025187742730*_Complex_I,
+   14.513400880941 +  11.251977643133*_Complex_I,
+   -2.482556277234 +  -3.095357211645*_Complex_I,
+   -7.549326283090 +   3.844289159351*_Complex_I,
+   -6.554611159279 +  -1.426517057076*_Complex_I,
+    6.564157092619 +   4.071022052298*_Complex_I,
+    5.022588868136 + -15.817689097768*_Complex_I,
+   -5.251727726400 +   2.789037545358*_Complex_I,
+   -2.128113345135 + -15.921584799030*_Complex_I,
+   -6.717549051974 +   1.209151038219*_Complex_I,
+    3.222807461576 +   5.205190352829*_Complex_I,
+   -2.420694516532 +  -6.628232249491*_Complex_I,
+   -0.210938110900 +   1.317502940068*_Complex_I,
+   -4.284810675111 +   4.379050426632*_Complex_I,
+   -1.842031171135 +   3.520287066742*_Complex_I,
+    5.259391106033 +  -9.692983760094*_Complex_I,
+   -3.879590649031 +   2.871689364722*_Complex_I,
+   -1.641216184114 +   4.267406466810*_Complex_I,
+    1.294145378923 +  -2.702480168973*_Complex_I,
+    1.886124883788 +  -3.741139781213*_Complex_I,
+    7.958277715545 +  -3.144460685955*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_36.c b/src/fft/tests/data/fft_data_36.c
new file mode 100644
index 0000000..7e624d0
--- /dev/null
+++ b/src/fft/tests/data/fft_data_36.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 36-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x36[] = {
+   -0.515260084522 +   1.287529717076*_Complex_I,
+    0.587117158481 +  -0.148572876243*_Complex_I,
+   -0.782947562463 +   1.641852621518*_Complex_I,
+   -0.526927266439 +   0.584647073200*_Complex_I,
+   -1.531349139822 +  -1.069623628897*_Complex_I,
+    1.018192400002 +  -0.793147230271*_Complex_I,
+   -1.074504882712 +  -0.091149144200*_Complex_I,
+   -0.401657438434 +   0.004317962559*_Complex_I,
+    1.259620132258 +  -0.104377362194*_Complex_I,
+   -0.102194404981 +  -0.973411762169*_Complex_I,
+   -0.895434561911 +  -1.050093649832*_Complex_I,
+    1.026577410282 +   1.080478300358*_Complex_I,
+    0.691470386469 +  -0.436083438367*_Complex_I,
+   -0.230801075525 +   1.635643257519*_Complex_I,
+   -0.121589188789 +   0.070584124184*_Complex_I,
+    0.332245123083 +   0.603463018191*_Complex_I,
+    0.098342650657 +   0.713292188052*_Complex_I,
+   -0.653525164870 +   0.226711651388*_Complex_I,
+   -0.486468735347 +   0.247902880597*_Complex_I,
+   -0.092383856045 +   0.443156295025*_Complex_I,
+   -1.207432550343 +  -0.503252568552*_Complex_I,
+    0.680629730441 +   0.210743200774*_Complex_I,
+   -1.655855919610 +   0.985314552623*_Complex_I,
+   -0.635578415199 +   1.126805769525*_Complex_I,
+    0.429626094959 +  -0.591166406548*_Complex_I,
+    0.843172830682 +  -0.010918445865*_Complex_I,
+   -0.130407009132 +   0.920489918178*_Complex_I,
+    0.879595565427 +   0.076478701355*_Complex_I,
+    0.407356105015 +   0.244802856694*_Complex_I,
+   -0.247029167369 +   1.416537545584*_Complex_I,
+    0.001447109646 +  -0.855321598060*_Complex_I,
+   -1.005160244372 +   0.880697474487*_Complex_I,
+   -1.506889600501 +  -0.376866773437*_Complex_I,
+    0.595335431863 +   0.874899791600*_Complex_I,
+   -1.720946712489 +  -0.251050064927*_Complex_I,
+   -0.304668621370 +  -0.437688673914*_Complex_I};
+
+float complex fft_test_y36[] = {
+   -6.978283472983 +   7.583625277009*_Complex_I,
+   -6.674217696450 +  -3.304059977181*_Complex_I,
+   -9.133615960691 +   4.553754806959*_Complex_I,
+    7.138280833022 +  -0.461421293427*_Complex_I,
+    6.376807409683 +  -0.306551799085*_Complex_I,
+    2.594166782732 +   4.623935739453*_Complex_I,
+    2.991096252215 +  -1.709441742997*_Complex_I,
+    7.042547687256 +  -5.210100129931*_Complex_I,
+   -3.291195373643 +  -2.837550873581*_Complex_I,
+    3.068320668506 +  -5.673130538045*_Complex_I,
+    1.959771299385 +  -2.235257672510*_Complex_I,
+  -10.609066858652 +   0.928096828820*_Complex_I,
+    3.208839022246 +   0.484194748895*_Complex_I,
+    2.548602732505 +   9.743720342785*_Complex_I,
+    6.240917395617 +   6.571681544646*_Complex_I,
+   -4.101181674885 +   4.039956075121*_Complex_I,
+   -9.685452992487 +   0.380605286598*_Complex_I,
+   -3.555255829985 +   3.488301753752*_Complex_I,
+  -10.504163464296 +  -6.018056829194*_Complex_I,
+   -2.111740241301 +   1.705549685664*_Complex_I,
+    2.140088966360 +   7.541207884663*_Complex_I,
+    0.985036473018 +  -0.558056384811*_Complex_I,
+   -1.305685512500 +   8.210384283174*_Complex_I,
+    4.783233947768 +  -2.666144508286*_Complex_I,
+    6.484426654393 +  -5.252223925558*_Complex_I,
+   -2.313461997972 +   5.743487698749*_Complex_I,
+   -1.086778704978 +   8.029162089587*_Complex_I,
+    6.916062245443 +   0.764580425536*_Complex_I,
+   -2.415195588075 +  -0.073706064002*_Complex_I,
+   -5.346986333451 +  -3.512605491284*_Complex_I,
+   -0.924055660625 +   2.282174534826*_Complex_I,
+   -0.470350054245 +   2.031728408857*_Complex_I,
+    2.141016439694 +  -1.773104703203*_Complex_I,
+   -1.014341113192 +   7.641158118573*_Complex_I,
+   -4.249655466965 +   2.206889911886*_Complex_I,
+    0.602106144731 +  -0.611713697716*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_4.c b/src/fft/tests/data/fft_data_4.c
new file mode 100644
index 0000000..fffbc2c
--- /dev/null
+++ b/src/fft/tests/data/fft_data_4.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 4-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x4[] = {
+   -2.218920151449 +  -1.079004048069*_Complex_I,
+    0.045264423484 +   0.426155393025*_Complex_I,
+    0.218614474268 +  -0.334711618319*_Complex_I,
+    2.182538230032 +   1.706944462070*_Complex_I};
+
+float complex fft_test_y4[] = {
+    0.227496976335 +   0.719384188708*_Complex_I,
+   -3.718323694762 +   1.392981376798*_Complex_I,
+   -4.228108330697 +  -3.546815521483*_Complex_I,
+   -1.156745556672 +  -2.881566236299*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_43.c b/src/fft/tests/data/fft_data_43.c
new file mode 100644
index 0000000..057b978
--- /dev/null
+++ b/src/fft/tests/data/fft_data_43.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 43-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x43[] = {
+    0.086214736594 +  -0.374055466225*_Complex_I,
+   -0.398326405553 +   0.064437628903*_Complex_I,
+   -0.086828498048 +  -1.377541893939*_Complex_I,
+   -0.014057206023 +  -1.721581520365*_Complex_I,
+   -1.130661886812 +  -0.823144321334*_Complex_I,
+    0.265330564080 +  -2.139333421737*_Complex_I,
+    0.987278886883 +   0.510016610694*_Complex_I,
+    1.388439583820 +  -0.312226254937*_Complex_I,
+    1.314187441787 +   2.179164255828*_Complex_I,
+   -0.134438842798 +  -2.204082595282*_Complex_I,
+    0.245609009336 +   0.112479617279*_Complex_I,
+   -1.038607185161 +   0.772906423002*_Complex_I,
+    0.816938259734 +  -2.090685529624*_Complex_I,
+    0.975237183551 +  -0.563625353818*_Complex_I,
+   -0.941942184460 +   1.392834491457*_Complex_I,
+    1.369305972621 +   0.035788913101*_Complex_I,
+    0.274358812513 +   0.334025802789*_Complex_I,
+   -0.187955769606 +   0.806622195637*_Complex_I,
+   -0.435359111927 +   0.395863170294*_Complex_I,
+   -1.059982565487 +  -1.108626336147*_Complex_I,
+   -0.085915350568 +  -0.294840267490*_Complex_I,
+   -0.202754288995 +  -0.413065083074*_Complex_I,
+   -0.607968226502 +   1.253761902790*_Complex_I,
+    2.921829936105 +   1.400641655759*_Complex_I,
+    0.493898754300 +   2.597915340403*_Complex_I,
+   -0.939016287203 +   0.999025234458*_Complex_I,
+   -1.213828533796 +  -0.093371812474*_Complex_I,
+    0.049357784794 +  -1.117710210403*_Complex_I,
+    0.291427701377 +   0.619473850471*_Complex_I,
+   -0.297284045241 +   1.471963630624*_Complex_I,
+   -1.204828141785 +   0.357668469775*_Complex_I,
+    1.214147177406 +  -1.053652949447*_Complex_I,
+   -0.669099873019 +   0.645208133613*_Complex_I,
+   -0.669325749409 +   1.526826939113*_Complex_I,
+    1.388489801757 +  -0.663313867864*_Complex_I,
+    1.528893702905 +  -0.300643090523*_Complex_I,
+    0.969651597440 +   0.887262607328*_Complex_I,
+    0.961949407503 +  -1.302265834141*_Complex_I,
+    0.025366344758 +   1.938755581562*_Complex_I,
+    0.012954685257 +  -0.117840651513*_Complex_I,
+    0.422317736906 +   0.454593332125*_Complex_I,
+   -1.225108444904 +  -0.762684444639*_Complex_I,
+    0.510768457794 +   0.200474501150*_Complex_I};
+
+float complex fft_test_y43[] = {
+    5.970664941924 +   2.123419383178*_Complex_I,
+   -4.826258373494 + -10.530035821412*_Complex_I,
+   -6.840438649504 +   2.195697396144*_Complex_I,
+  -15.037444321130 +  -0.498694877295*_Complex_I,
+   -2.324178679783 +  -5.588631770237*_Complex_I,
+   -9.817989632278 +   8.215797271526*_Complex_I,
+   19.440508234356 +   0.725796090385*_Complex_I,
+   -0.273833848905 +   4.924507895251*_Complex_I,
+    5.898110303453 +  -7.584923619025*_Complex_I,
+   -1.491058783404 +   7.983518326557*_Complex_I,
+   -7.459786425320 +  -5.471409031844*_Complex_I,
+    9.458054840383 +  -1.411338639410*_Complex_I,
+   -7.652963176561 +  16.470882260110*_Complex_I,
+    4.933008347551 +   1.752692467372*_Complex_I,
+    3.158349989133 + -10.520004874496*_Complex_I,
+    3.461440707146 +  -1.053485004255*_Complex_I,
+    0.403169282390 +  15.216737475743*_Complex_I,
+   11.005277294499 +   0.924950795722*_Complex_I,
+   -2.459303339406 +  -4.952009870945*_Complex_I,
+  -11.998504221115 +  -4.269332241730*_Complex_I,
+  -10.575401537817 +   0.521491539120*_Complex_I,
+   -8.612771844559 +  -4.765648940500*_Complex_I,
+    7.802134874811 +   3.882271887067*_Complex_I,
+    2.664085789892 +  -5.430064274009*_Complex_I,
+    7.012256247961 +  -6.119235310609*_Complex_I,
+   -0.520208603756 +  -0.845655711277*_Complex_I,
+    0.308220700989 +   3.533138362895*_Complex_I,
+  -10.811305738786 +  -4.238039944052*_Complex_I,
+    6.001274292511 +  -2.592573440494*_Complex_I,
+    5.544361033380 + -11.920579663414*_Complex_I,
+   -2.095258565391 +   0.336151200781*_Complex_I,
+   -5.596706630782 +   7.291207810674*_Complex_I,
+    9.325757124002 +  -3.598787999557*_Complex_I,
+    1.133897756930 +  -1.612056679027*_Complex_I,
+   -2.012305507947 +   5.032886593926*_Complex_I,
+  -10.072407904704 +   5.354603355501*_Complex_I,
+    2.641399130952 +  -1.713592877295*_Complex_I,
+    1.128116547129 +   7.394974387525*_Complex_I,
+   12.210114373933 +  -3.041200993402*_Complex_I,
+   -0.850951175674 +   3.946134783722*_Complex_I,
+   -1.686631417770 +  -4.776809881210*_Complex_I,
+   -2.550902604765 +  -2.082230855079*_Complex_I,
+    9.773642843061 +  -9.294902010297*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_48.c b/src/fft/tests/data/fft_data_48.c
new file mode 100644
index 0000000..15b0bdc
--- /dev/null
+++ b/src/fft/tests/data/fft_data_48.c
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 48-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x48[] = {
+   -1.408383409504 +   0.661990153106*_Complex_I,
+   -0.257850483647 +  -2.392863066952*_Complex_I,
+   -0.321563284081 +   1.408168751420*_Complex_I,
+   -1.963488918325 +  -0.315363008755*_Complex_I,
+    0.872572043811 +  -1.774358495756*_Complex_I,
+   -1.036243090405 +   0.085162186806*_Complex_I,
+   -0.810463329242 +   0.370726012575*_Complex_I,
+    0.814727688017 +  -0.148930236445*_Complex_I,
+   -0.482188504339 +  -0.888806074906*_Complex_I,
+    0.751010250272 +   0.610241893778*_Complex_I,
+    1.562853718365 +   0.338170177897*_Complex_I,
+    0.836367957185 +  -1.685869538958*_Complex_I,
+   -0.678836393610 +  -1.105381292997*_Complex_I,
+    1.313536222418 +  -1.604103598331*_Complex_I,
+    0.652309130012 +  -1.915192986159*_Complex_I,
+   -0.398123599463 +   1.372136473305*_Complex_I,
+    2.555137160099 +   0.715094591241*_Complex_I,
+    1.803926723963 +  -0.470162240275*_Complex_I,
+   -1.033969686967 +  -1.754083599107*_Complex_I,
+    0.345505105620 +   1.869665415226*_Complex_I,
+    0.917886037796 +   1.488788889124*_Complex_I,
+   -0.149819207787 +   0.641387132093*_Complex_I,
+    0.239135545455 +   0.491166352445*_Complex_I,
+    0.571741246274 +   0.056534896965*_Complex_I,
+   -0.131630105163 +  -1.508920032468*_Complex_I,
+    1.209119501276 +  -0.078706912158*_Complex_I,
+   -0.955610498388 +   1.044137820022*_Complex_I,
+    0.106457806412 +  -0.585542024212*_Complex_I,
+   -1.229933443664 +  -0.322249167809*_Complex_I,
+    0.278186189361 +   1.807684972303*_Complex_I,
+   -1.729784506112 +  -0.939519586014*_Complex_I,
+    0.927483189137 +   0.431322133098*_Complex_I,
+    0.141968377392 +  -0.120521833531*_Complex_I,
+    0.747436107175 +  -1.117297332586*_Complex_I,
+    0.321427506892 +   0.604553538042*_Complex_I,
+   -1.310259971951 +   0.301350874871*_Complex_I,
+    0.000616127451 +   1.395956853708*_Complex_I,
+    0.789283665376 +  -0.378417239374*_Complex_I,
+    1.626071060809 +  -0.838198796474*_Complex_I,
+   -0.502170615893 +  -0.371786828507*_Complex_I,
+    0.941560993676 +   0.083132997096*_Complex_I,
+    0.217335920952 +  -0.775367298569*_Complex_I,
+   -1.386726382455 +   1.228033537755*_Complex_I,
+    0.135305964480 +   1.010214926902*_Complex_I,
+   -0.769134001940 +  -1.642182708631*_Complex_I,
+    0.451443995268 +  -1.443277308466*_Complex_I,
+    0.315403114502 +   0.409584212993*_Complex_I,
+    0.777007938735 +   0.033642728423*_Complex_I};
+
+float complex fft_test_y48[] = {
+    5.666636855245 +  -5.718253686246*_Complex_I,
+   -9.318440723038 + -11.045628239935*_Complex_I,
+  -10.484391800934 +  10.258547835119*_Complex_I,
+    5.692975013010 +  -0.079178697546*_Complex_I,
+   -0.031494239726 +  -3.429117974659*_Complex_I,
+   -1.633492827483 +   6.955978741593*_Complex_I,
+    9.741303637543 +   2.061588265899*_Complex_I,
+    1.051018454307 +  11.180284489721*_Complex_I,
+   -8.006452512021 +   0.459314562290*_Complex_I,
+   -7.397134103296 +   6.198088361763*_Complex_I,
+    4.688329496623 +   1.626283668222*_Complex_I,
+   -8.380975402709 +  -3.715659857481*_Complex_I,
+   -4.832542130429 +  -9.241813561210*_Complex_I,
+   -5.444148048877 +  -5.774968307876*_Complex_I,
+   -2.557281197147 +  -8.013068178874*_Complex_I,
+   -2.508606838763 +   3.393130955025*_Complex_I,
+  -13.856615792482 +  -8.279988253172*_Complex_I,
+   -0.275213201833 +   9.234040467214*_Complex_I,
+  -10.548623142987 +   1.461152259705*_Complex_I,
+   -5.454201445127 +   5.015988800202*_Complex_I,
+    3.151148175752 +  16.404964892896*_Complex_I,
+   -2.628471739476 +  12.549705973202*_Complex_I,
+   -5.309278338161 +  -1.496205611507*_Complex_I,
+    8.327510797620 +   9.076602173656*_Complex_I,
+   -7.249202313653 +   0.578432313391*_Complex_I,
+    0.278119672993 +   6.335493150399*_Complex_I,
+   -4.400202487177 +   4.358641062270*_Complex_I,
+    1.825561215029 + -10.742991731336*_Complex_I,
+    2.059618967411 +   5.230483842207*_Complex_I,
+   -0.589368957248 +  -0.568413649725*_Complex_I,
+    5.846114680415 +   3.953722464016*_Complex_I,
+   -4.790203069855 +   5.357335572595*_Complex_I,
+  -16.219316666593 +   5.416145069047*_Complex_I,
+    4.053779640014 +  14.490156426037*_Complex_I,
+   10.343285976607 + -19.941466772140*_Complex_I,
+   -0.179745539698 +  -6.776924924303*_Complex_I,
+    9.333647116859 +   2.311810446775*_Complex_I,
+   -0.778589953827 +  -7.796695480692*_Complex_I,
+   -5.728518422011 +  -0.187241621271*_Complex_I,
+   15.554791252022 +   3.837751365144*_Complex_I,
+   -3.410115684107 +  -2.362837725953*_Complex_I,
+  -14.699080682437 +   4.704009521181*_Complex_I,
+    4.974381394298 +  -3.870880098021*_Complex_I,
+    4.347015481178 +   1.639590273798*_Complex_I,
+    6.775882853829 +  -8.045391749167*_Complex_I,
+   -6.905056488040 +  -1.077414483536*_Complex_I,
+   -6.906638779165 +  -3.861138554296*_Complex_I,
+   -0.790121808651 +  -0.288436445313*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_5.c b/src/fft/tests/data/fft_data_5.c
new file mode 100644
index 0000000..0a8b87d
--- /dev/null
+++ b/src/fft/tests/data/fft_data_5.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 5-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x5[] = {
+    1.043452789296 +  -0.216675780077*_Complex_I,
+   -0.039259154719 +  -0.756503590362*_Complex_I,
+   -1.378329383804 +  -1.629692578129*_Complex_I,
+    0.695728357044 +  -2.639675956000*_Complex_I,
+   -0.019932891052 +   0.123958045411*_Complex_I};
+
+float complex fft_test_y5[] = {
+    0.301659716765 +  -5.118589859158*_Complex_I,
+    1.333681830770 +   4.279329517647*_Complex_I,
+   -0.597668794979 +  -2.985429553632*_Complex_I,
+    2.358478480201 +   0.936943320049*_Complex_I,
+    1.821112713724 +   1.804367674708*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_509.c b/src/fft/tests/data/fft_data_509.c
new file mode 100644
index 0000000..15e52c4
--- /dev/null
+++ b/src/fft/tests/data/fft_data_509.c
@@ -0,0 +1,1050 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 509-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x509[] = {
+    0.359331995889 +  -1.085301108776*_Complex_I,
+   -1.932383412518 +   0.116856180907*_Complex_I,
+    0.496271262268 +   0.731796541816*_Complex_I,
+    0.431573865944 +   0.591703481184*_Complex_I,
+    0.589253369531 +  -0.795072338555*_Complex_I,
+   -0.870697944847 +  -0.497562617185*_Complex_I,
+    1.247223411086 +   0.771429589056*_Complex_I,
+    0.924873577618 +  -1.184353621638*_Complex_I,
+    0.659579079487 +   0.126294704315*_Complex_I,
+    1.647221437368 +  -2.249906156787*_Complex_I,
+   -1.334652205984 +  -0.024425874102*_Complex_I,
+    0.095148747858 +  -0.284583137620*_Complex_I,
+   -2.534039905928 +   1.936731699042*_Complex_I,
+    0.370565605548 +  -0.671454121821*_Complex_I,
+    0.382886168067 +   0.318205203845*_Complex_I,
+   -1.314163630052 +   0.381239207410*_Complex_I,
+   -0.258334295223 +   0.599344826242*_Complex_I,
+    1.542372805791 +  -0.381018806439*_Complex_I,
+   -0.015408662897 +   1.208686378191*_Complex_I,
+   -3.023761214358 +  -0.135651280290*_Complex_I,
+   -0.916610967637 +   0.884088889046*_Complex_I,
+   -2.081046862959 +   2.001230452826*_Complex_I,
+    1.031862595677 +   1.366475262140*_Complex_I,
+   -0.259564882667 +   0.315307538802*_Complex_I,
+    0.161129496712 +  -1.667686892086*_Complex_I,
+   -1.241250123646 +   1.380715267035*_Complex_I,
+    0.027019787139 +   2.068943431414*_Complex_I,
+    0.164488255181 +  -0.688316587237*_Complex_I,
+    1.114819509678 +  -3.008077068837*_Complex_I,
+   -1.374358189256 +   0.000217799371*_Complex_I,
+    1.783814617294 +  -0.127391483103*_Complex_I,
+    1.648034611975 +  -0.251680996856*_Complex_I,
+    1.678583258653 +   1.974165102901*_Complex_I,
+   -0.857815837356 +  -3.739419503346*_Complex_I,
+    1.227762010986 +  -0.496092919671*_Complex_I,
+    0.275130781058 +   1.160971226357*_Complex_I,
+    0.333840127696 +   1.388036870908*_Complex_I,
+   -0.805343489373 +   0.006883877363*_Complex_I,
+    0.113234013309 +   0.031253576243*_Complex_I,
+   -0.610537915176 +   0.630710349348*_Complex_I,
+    1.397240850777 +  -2.014373420814*_Complex_I,
+   -0.255790837573 +   0.916792397001*_Complex_I,
+    0.129924388336 +  -0.537587690895*_Complex_I,
+   -0.926838068677 +   0.308336177664*_Complex_I,
+    3.010372178544 +  -2.369144950446*_Complex_I,
+    1.263300917549 +  -1.660219437257*_Complex_I,
+    0.841656993731 +   1.927041610736*_Complex_I,
+   -0.904792064104 +  -1.173048767457*_Complex_I,
+    0.989814388062 +   1.253071554414*_Complex_I,
+   -0.257249205993 +  -0.959941785447*_Complex_I,
+   -1.205852496010 +   0.196179577313*_Complex_I,
+    1.940909824834 +  -2.078512378665*_Complex_I,
+    0.300600588979 +   0.572506173199*_Complex_I,
+   -0.375271364555 +  -0.773019164981*_Complex_I,
+   -0.858303843109 +  -0.674765891031*_Complex_I,
+   -1.189090659992 +   0.543093004900*_Complex_I,
+    2.165334883903 +   0.001082499227*_Complex_I,
+    2.057289953335 +  -0.916206975165*_Complex_I,
+   -0.161982761480 +  -0.815858087637*_Complex_I,
+   -1.390044285564 +  -0.714076838951*_Complex_I,
+    0.766348194222 +   0.076251375781*_Complex_I,
+   -0.630967707884 +   0.854026208416*_Complex_I,
+   -1.309424118846 +   0.470225939681*_Complex_I,
+    0.664797780343 +  -1.109803637533*_Complex_I,
+    3.563726896705 +   1.370311380835*_Complex_I,
+    0.850424444505 +  -0.770251870088*_Complex_I,
+    0.316880772888 +  -0.509312503865*_Complex_I,
+   -1.439495581618 +  -1.741765587801*_Complex_I,
+   -1.095540513740 +   0.552478238644*_Complex_I,
+   -0.879540480746 +  -1.782154665595*_Complex_I,
+    1.394922475961 +   0.407153930461*_Complex_I,
+   -1.583206834198 +   0.548532742373*_Complex_I,
+   -0.330266310339 +   0.821466818961*_Complex_I,
+   -1.157454328378 +   0.386301239269*_Complex_I,
+    0.823214183923 +   0.993600792956*_Complex_I,
+   -0.665523708393 +  -0.143306700746*_Complex_I,
+   -2.274210663640 +  -0.526858961140*_Complex_I,
+    0.675171584769 +   1.608754308056*_Complex_I,
+    0.067605428754 +   0.840683747814*_Complex_I,
+    0.927563962378 +   0.563153155030*_Complex_I,
+   -0.129920407968 +   0.614536449089*_Complex_I,
+   -0.633778727653 +   0.174141369494*_Complex_I,
+   -0.579622089160 +  -1.179631499150*_Complex_I,
+    3.157266013709 +   0.841577893721*_Complex_I,
+   -1.564968432235 +   0.237536721331*_Complex_I,
+    0.832675154129 +   2.752538330003*_Complex_I,
+   -1.003618718524 +  -0.803502222719*_Complex_I,
+    1.033045390907 +  -0.887091010037*_Complex_I,
+    0.406299848093 +  -0.231522470982*_Complex_I,
+   -1.224877731528 +   0.962620343882*_Complex_I,
+   -0.287856500251 +  -0.684348325534*_Complex_I,
+    0.769551121775 +   1.052171601110*_Complex_I,
+    1.211938362349 +  -0.397315302619*_Complex_I,
+   -1.801171069328 +   0.782481239455*_Complex_I,
+   -0.428774166115 +   0.205654171857*_Complex_I,
+    0.742768169350 +   1.078779525233*_Complex_I,
+   -0.760778783880 +  -0.392123510805*_Complex_I,
+    0.530474251890 +   0.355891481054*_Complex_I,
+    1.455692020840 +   0.275438176816*_Complex_I,
+   -0.256274791959 +  -1.055463927568*_Complex_I,
+    1.397683977888 +  -1.672842861191*_Complex_I,
+    1.059395488056 +  -1.195826863369*_Complex_I,
+    0.535641711388 +  -0.139593755691*_Complex_I,
+    0.767194249054 +   1.159606442946*_Complex_I,
+    0.730248752053 +   0.874096637495*_Complex_I,
+    0.374817248354 +   0.381042720649*_Complex_I,
+   -1.290660859871 +  -1.052408951473*_Complex_I,
+   -0.698710060780 +  -0.404862720632*_Complex_I,
+    0.260762012984 +  -0.586144943477*_Complex_I,
+    0.471825306906 +   0.216309496806*_Complex_I,
+    0.902145392649 +   0.734793058179*_Complex_I,
+    0.970712066057 +   1.203301629983*_Complex_I,
+    0.697414298916 +  -0.091399436653*_Complex_I,
+   -0.763151612464 +  -0.155822503488*_Complex_I,
+    0.571013025643 +  -0.991520969075*_Complex_I,
+    0.488145846303 +   0.518072470835*_Complex_I,
+    0.382903596789 +  -0.570642408749*_Complex_I,
+   -0.068409602398 +  -0.457729632233*_Complex_I,
+    0.328633596900 +  -0.485642431490*_Complex_I,
+    1.463236532852 +  -0.689222532447*_Complex_I,
+    0.843726237994 +   1.027737725483*_Complex_I,
+    0.411174457800 +   1.072703354585*_Complex_I,
+   -0.553638496687 +   0.592560929746*_Complex_I,
+   -0.519820674211 +   0.395335990219*_Complex_I,
+    0.816739721990 +   1.296561601952*_Complex_I,
+    0.839055358910 +   0.038232944058*_Complex_I,
+   -1.321845319423 +   2.461463566622*_Complex_I,
+   -1.244535108317 +  -0.855331739033*_Complex_I,
+    0.174834515541 +  -0.236103345803*_Complex_I,
+   -0.817712897113 +   0.503960726952*_Complex_I,
+   -0.723518973851 +   0.751673530926*_Complex_I,
+   -0.189283180062 +   0.644019002779*_Complex_I,
+    0.237729751553 +   0.335607912526*_Complex_I,
+   -0.969216001025 +  -0.603266829416*_Complex_I,
+   -0.207478679057 +  -0.730339402811*_Complex_I,
+   -0.024191569913 +  -1.259252874185*_Complex_I,
+    1.472785074216 +  -0.491418040669*_Complex_I,
+    0.143580527900 +  -1.186038294726*_Complex_I,
+    0.835958076660 +   1.651209544972*_Complex_I,
+   -1.330070431715 +   0.745655269480*_Complex_I,
+   -2.351132556248 +   1.414206325062*_Complex_I,
+   -0.647960722564 +   0.019824747070*_Complex_I,
+    0.054504089094 +  -0.434505484307*_Complex_I,
+   -1.069009000323 +   0.930859192631*_Complex_I,
+    2.256466805965 +   0.677754214770*_Complex_I,
+   -1.017788464214 +   0.299270915482*_Complex_I,
+   -1.350006249149 +   0.860365863370*_Complex_I,
+    0.554243078961 +   1.630321512911*_Complex_I,
+   -1.709590344454 +  -0.169567705711*_Complex_I,
+    0.650774374311 +   0.064592934208*_Complex_I,
+    1.989755256430 +   0.536482018456*_Complex_I,
+   -0.373249333538 +  -0.106134582591*_Complex_I,
+    0.095351627343 +  -0.279297038594*_Complex_I,
+    0.230789928160 +   0.532796285505*_Complex_I,
+    0.247854968690 +  -2.153358768182*_Complex_I,
+    0.136098798048 +   1.135668579132*_Complex_I,
+   -0.208329927938 +   0.789350627066*_Complex_I,
+    0.039858273209 +  -0.599940553834*_Complex_I,
+   -0.117101531764 +   0.201275502180*_Complex_I,
+    0.817971264476 +  -1.329760648517*_Complex_I,
+    0.035980481979 +  -0.922818951948*_Complex_I,
+   -1.888935382734 +  -1.797450884362*_Complex_I,
+   -0.097016985104 +  -1.168628933267*_Complex_I,
+    0.452048304507 +  -0.675116763083*_Complex_I,
+   -1.209707805549 +   0.172148052410*_Complex_I,
+    1.432472352322 +   0.632406226221*_Complex_I,
+    2.933280640140 +   0.949590441066*_Complex_I,
+   -0.309024453832 +   1.192611727581*_Complex_I,
+   -0.255306372880 +   0.007559617013*_Complex_I,
+    0.081653931435 +  -0.672920464273*_Complex_I,
+    0.405276164696 +  -0.077841212841*_Complex_I,
+   -0.338396874520 +   0.355225122224*_Complex_I,
+    0.109601214660 +   1.392547443330*_Complex_I,
+   -1.871393476572 +   0.310989021924*_Complex_I,
+    0.332233956611 +   1.730122993833*_Complex_I,
+    1.439926026777 +  -1.611064773734*_Complex_I,
+   -1.860778448552 +   0.425384165779*_Complex_I,
+    0.274648666779 +   1.286600320037*_Complex_I,
+    0.762615580678 +  -0.222402755827*_Complex_I,
+    0.426513801822 +   1.965805792967*_Complex_I,
+   -0.103171697634 +  -0.569960476279*_Complex_I,
+    1.562067509000 +  -2.347766792543*_Complex_I,
+    0.467044769684 +   0.321342630659*_Complex_I,
+   -1.416578640685 +  -0.831113210732*_Complex_I,
+   -0.733167805614 +   1.176375168746*_Complex_I,
+   -1.282613845051 +  -0.979773114694*_Complex_I,
+    0.506103347478 +  -1.085270279646*_Complex_I,
+    2.539137831319 +   2.232625718408*_Complex_I,
+   -1.637899545016 +   0.265528606195*_Complex_I,
+   -1.931767882639 +   0.329066225098*_Complex_I,
+    0.127023806083 +   0.504962043815*_Complex_I,
+    0.588353928765 +  -1.463911658760*_Complex_I,
+    0.907010785485 +  -0.108879037057*_Complex_I,
+   -0.951802902717 +  -0.685757383062*_Complex_I,
+    1.723143501154 +  -0.419300229480*_Complex_I,
+   -0.943119843417 +   0.652144368853*_Complex_I,
+   -0.851586780301 +  -1.516257192307*_Complex_I,
+    0.472227887604 +   1.052236419944*_Complex_I,
+    0.113888251161 +  -1.124587148028*_Complex_I,
+    0.051529667469 +  -0.136410527640*_Complex_I,
+   -0.647857223628 +   0.431204980149*_Complex_I,
+    1.349209612218 +  -0.768179692308*_Complex_I,
+   -0.026826971021 +  -0.545234111539*_Complex_I,
+   -0.514071604921 +  -0.664124619949*_Complex_I,
+    0.320778745077 +   1.111997835236*_Complex_I,
+   -0.232562962611 +  -0.669159856913*_Complex_I,
+   -0.896014800467 +   0.082046851667*_Complex_I,
+   -0.167690950519 +  -1.119443226265*_Complex_I,
+   -0.433053084055 +  -0.204133858571*_Complex_I,
+   -1.567141147453 +  -0.972571871430*_Complex_I,
+   -1.040971048402 +   0.473975308345*_Complex_I,
+   -1.551423307502 +  -0.934406696693*_Complex_I,
+    1.267729903934 +   2.062089282704*_Complex_I,
+   -0.814068862944 +  -0.268491284145*_Complex_I,
+    0.721715833176 +  -0.368317000932*_Complex_I,
+   -0.236677967201 +  -1.031498627296*_Complex_I,
+   -0.027596539058 +  -0.526943246450*_Complex_I,
+    0.607765602666 +   0.747238787304*_Complex_I,
+    0.442222664332 +  -0.844428479402*_Complex_I,
+   -0.584746227099 +   0.008876873310*_Complex_I,
+    0.249251666407 +   1.120945916837*_Complex_I,
+    0.406090989317 +  -0.832304494623*_Complex_I,
+   -1.420792340638 +   0.417700569682*_Complex_I,
+    0.469433523536 +  -0.703036988315*_Complex_I,
+    0.799192929845 +   0.584252218542*_Complex_I,
+   -0.846345284557 +  -0.125059394154*_Complex_I,
+    0.187771816466 +   0.373470766869*_Complex_I,
+   -2.668531322148 +   0.189825888614*_Complex_I,
+    0.684689436275 +   1.519032618934*_Complex_I,
+   -0.452797714368 +   1.843053037274*_Complex_I,
+   -0.614449241246 +  -0.006236652039*_Complex_I,
+    0.929289964606 +   0.965819481866*_Complex_I,
+    0.800433661968 +   0.829558429420*_Complex_I,
+   -1.088281420026 +  -0.801332579437*_Complex_I,
+   -0.080346758942 +   0.412114096369*_Complex_I,
+    1.491530334351 +  -0.037720112002*_Complex_I,
+   -1.419313834957 +   1.309423611663*_Complex_I,
+    0.626194884901 +   0.448698878768*_Complex_I,
+    1.062456184701 +  -1.574268630848*_Complex_I,
+   -0.284112227827 +   0.550018601059*_Complex_I,
+   -0.080443635589 +  -1.362211376503*_Complex_I,
+    0.383016098417 +   1.081967093575*_Complex_I,
+    0.484156075687 +  -0.162493291081*_Complex_I,
+   -0.632696839217 +  -1.499586717062*_Complex_I,
+    1.771132365818 +  -0.691460076219*_Complex_I,
+    1.177429509865 +  -0.486631740300*_Complex_I,
+    0.452912528112 +   1.368403825897*_Complex_I,
+    0.007493867542 +  -0.269397882793*_Complex_I,
+   -1.107355722972 +   0.037650076863*_Complex_I,
+   -0.524329801696 +   1.542074025962*_Complex_I,
+    0.448469395501 +   0.620253813644*_Complex_I,
+    0.937797278085 +   0.094893367525*_Complex_I,
+    0.244627220423 +  -0.865982595984*_Complex_I,
+   -0.620857271928 +   1.618502476045*_Complex_I,
+   -0.727959747987 +  -1.748132622853*_Complex_I,
+   -1.814748553219 +   0.357659723379*_Complex_I,
+   -1.190951476209 +  -0.302683547331*_Complex_I,
+   -0.642926196548 +  -0.441431483518*_Complex_I,
+    0.906018063143 +  -0.456267895782*_Complex_I,
+    1.658412527962 +   0.602929674812*_Complex_I,
+   -1.372573150919 +  -0.165247494352*_Complex_I,
+    0.701050669178 +   0.784117117798*_Complex_I,
+   -1.093947418964 +  -2.169417895648*_Complex_I,
+   -0.619306010978 +   0.346306836794*_Complex_I,
+   -1.153602396000 +   0.310559458662*_Complex_I,
+    1.857961002742 +   1.662749378565*_Complex_I,
+    0.726892056406 +   0.559412940831*_Complex_I,
+    0.618501787181 +   0.753344508665*_Complex_I,
+    0.060088823625 +  -1.132388678000*_Complex_I,
+   -0.790715886898 +   1.256189591885*_Complex_I,
+   -1.346611550340 +  -1.002213804884*_Complex_I,
+   -0.153748736848 +  -1.273877582585*_Complex_I,
+   -1.102237529635 +  -1.113178017398*_Complex_I,
+   -0.526084982723 +   1.255781208343*_Complex_I,
+   -0.738929991536 +   1.454321302002*_Complex_I,
+   -1.015352909554 +   0.057528759460*_Complex_I,
+    0.675998598897 +  -0.143860246243*_Complex_I,
+    0.189558927003 +   0.513691656677*_Complex_I,
+    0.009183574378 +   0.963901374647*_Complex_I,
+   -0.163787067951 +  -1.522554558143*_Complex_I,
+    0.302708128196 +   0.904573259262*_Complex_I,
+    0.975710589669 +  -1.011907000330*_Complex_I,
+   -0.117268931532 +   0.858171829955*_Complex_I,
+   -1.306428434179 +   0.113727230388*_Complex_I,
+   -0.959251067394 +   0.253171642011*_Complex_I,
+    0.898435910380 +   0.293195734212*_Complex_I,
+   -0.621680975624 +  -0.582564866291*_Complex_I,
+    0.179461744683 +   0.209868112155*_Complex_I,
+   -0.203784114877 +  -0.011115684099*_Complex_I,
+    1.795424157294 +  -0.117231500488*_Complex_I,
+    0.015989098360 +  -0.642768987364*_Complex_I,
+   -1.375016631330 +  -2.085463142461*_Complex_I,
+   -0.655990108553 +   0.722884678316*_Complex_I,
+    1.172312994480 +  -0.355468862899*_Complex_I,
+   -0.564442301948 +   0.323979441156*_Complex_I,
+    0.899045729865 +  -1.148993122117*_Complex_I,
+    1.045854100254 +   1.711078921366*_Complex_I,
+    0.592020255938 +   0.219085296351*_Complex_I,
+    0.118211086528 +  -1.123504917557*_Complex_I,
+    0.233426106939 +  -1.883587119594*_Complex_I,
+   -0.301438222142 +  -0.697732158432*_Complex_I,
+    0.312117398590 +  -0.010728436711*_Complex_I,
+   -0.956339212238 +   0.275577083186*_Complex_I,
+    1.086666854829 +  -1.181802410378*_Complex_I,
+    1.074818739604 +  -0.636045956563*_Complex_I,
+    0.644130821770 +  -0.481626402565*_Complex_I,
+    0.055564592606 +  -0.355383569012*_Complex_I,
+   -1.710794754131 +  -0.311499134725*_Complex_I,
+   -1.357530095316 +   1.586038700811*_Complex_I,
+    0.643883320289 +  -1.661496495893*_Complex_I,
+    0.916591063253 +   0.463263213793*_Complex_I,
+    0.795060678890 +   0.253692839397*_Complex_I,
+   -0.353293483561 +  -0.793786778605*_Complex_I,
+    1.523091692648 +   1.009541299320*_Complex_I,
+   -0.351467193764 +  -0.233882680674*_Complex_I,
+   -1.006204919749 +  -1.722099827433*_Complex_I,
+   -0.425646600377 +  -0.067539377037*_Complex_I,
+   -2.152456912795 +   0.355174573898*_Complex_I,
+   -1.397998826498 +   0.043192513073*_Complex_I,
+    0.463250457123 +   0.785418638026*_Complex_I,
+    0.551007796000 +  -0.600950335023*_Complex_I,
+    1.336913064335 +   0.299718316993*_Complex_I,
+    0.576007704981 +  -0.703000454871*_Complex_I,
+    0.501091983740 +   0.079592846760*_Complex_I,
+    0.178571647134 +  -0.055720934489*_Complex_I,
+    0.843544810635 +   0.526236292547*_Complex_I,
+    1.354311088400 +  -0.480046041766*_Complex_I,
+   -1.121411263124 +  -0.220337368903*_Complex_I,
+    0.018969956960 +  -0.526588981826*_Complex_I,
+   -0.874090913826 +   1.731623994129*_Complex_I,
+   -1.108295529258 +   1.163520621835*_Complex_I,
+   -0.656720377715 +   0.245096653391*_Complex_I,
+    0.625251262223 +   1.662769471886*_Complex_I,
+    0.503819391123 +  -1.441549149924*_Complex_I,
+   -0.164837089741 +  -0.715463798642*_Complex_I,
+    0.355362378583 +   2.159694721953*_Complex_I,
+   -0.008848280750 +  -0.018260892822*_Complex_I,
+   -0.136686667816 +   1.182755498355*_Complex_I,
+   -0.136433742988 +   0.851417981048*_Complex_I,
+   -0.395460120756 +   0.812014412771*_Complex_I,
+    0.025308338705 +   0.630907052235*_Complex_I,
+    1.006681294297 +   1.783315896794*_Complex_I,
+   -0.402626477026 +   0.776761257059*_Complex_I,
+   -1.317690399125 +   0.130484673594*_Complex_I,
+   -0.868791706762 +  -0.642771615764*_Complex_I,
+    0.137146795180 +  -0.102157755198*_Complex_I,
+    1.633710393863 +  -0.768782548860*_Complex_I,
+   -0.867759427420 +   1.601477458878*_Complex_I,
+   -1.157703488657 +  -0.206031874994*_Complex_I,
+    0.041016911327 +  -1.180389310249*_Complex_I,
+   -0.189738035969 +  -0.713067619743*_Complex_I,
+    0.933408353482 +   1.194576784859*_Complex_I,
+    0.514861178855 +   0.368016550894*_Complex_I,
+    0.881690626307 +   2.753761417447*_Complex_I,
+   -0.133670635264 +   1.543292970173*_Complex_I,
+    2.091596895159 +  -0.093690078394*_Complex_I,
+   -1.188956830982 +  -0.114523278237*_Complex_I,
+   -0.053197690454 +   0.907571243350*_Complex_I,
+    0.401291232622 +  -0.415340086801*_Complex_I,
+   -0.681924799342 +   0.669543900292*_Complex_I,
+    0.530696047517 +  -0.997585317896*_Complex_I,
+   -0.533192039121 +  -1.431729688530*_Complex_I,
+    0.536364895113 +   1.169805471895*_Complex_I,
+   -1.016375434615 +  -1.450239121127*_Complex_I,
+   -1.561071693564 +  -0.170486342837*_Complex_I,
+   -0.183747193975 +  -0.977837337154*_Complex_I,
+   -0.181940033627 +  -0.248171826564*_Complex_I,
+   -0.328526087010 +  -0.030637600322*_Complex_I,
+   -1.002704137273 +  -0.085997551664*_Complex_I,
+    1.368038996286 +  -0.141325193319*_Complex_I,
+    0.403836121556 +   2.008528522943*_Complex_I,
+   -0.810518568790 +  -0.149400127852*_Complex_I,
+   -0.693120904958 +  -1.117715197785*_Complex_I,
+    1.558993909095 +  -2.012078672111*_Complex_I,
+    0.029046234836 +  -1.049286048426*_Complex_I,
+    0.790327688876 +   0.101616687460*_Complex_I,
+   -0.401919211737 +  -0.430529168165*_Complex_I,
+    0.287473232440 +  -0.564999246068*_Complex_I,
+    1.716421030546 +  -0.404965480273*_Complex_I,
+    0.050376052116 +   0.795529162825*_Complex_I,
+   -1.281143070062 +   0.808984215487*_Complex_I,
+   -2.314127686421 +  -0.020801982220*_Complex_I,
+    1.606737133361 +   0.283817403079*_Complex_I,
+    0.355463854549 +   0.044155238023*_Complex_I,
+   -1.076907023070 +   0.442916691766*_Complex_I,
+    0.087222702651 +   1.349524189157*_Complex_I,
+   -1.625272715780 +   0.294000847904*_Complex_I,
+    2.355923230363 +   0.786258163154*_Complex_I,
+    0.590797134194 +  -1.524824299804*_Complex_I,
+   -0.247232063439 +   0.027525184425*_Complex_I,
+    0.422150431137 +   0.191460073698*_Complex_I,
+    0.333205469444 +   0.566695459783*_Complex_I,
+    2.040179995474 +  -0.033523279638*_Complex_I,
+    1.708801210517 +  -0.514673828477*_Complex_I,
+   -1.148367840353 +   0.175705040636*_Complex_I,
+   -0.208810451052 +   1.955963982995*_Complex_I,
+    0.277462389542 +   0.341691402206*_Complex_I,
+    1.115415958264 +   0.268951601110*_Complex_I,
+    0.039043618322 +   1.093842223424*_Complex_I,
+    0.154656090432 +  -2.528281845522*_Complex_I,
+   -0.169580739559 +   1.232275509700*_Complex_I,
+   -0.117411131677 +  -1.002389093350*_Complex_I,
+    0.035928732692 +   2.439878625945*_Complex_I,
+   -0.332442060919 +   0.086411580288*_Complex_I,
+    1.801599291824 +  -0.728371893507*_Complex_I,
+    2.510167669033 +   0.293277743476*_Complex_I,
+    1.336012143828 +   0.553373968985*_Complex_I,
+    0.179581375233 +   0.219695624924*_Complex_I,
+    0.593263517685 +   0.407445722133*_Complex_I,
+    0.684624183099 +   0.589697664907*_Complex_I,
+   -2.496382874468 +   0.024438752127*_Complex_I,
+   -2.222694698779 +   1.701453939912*_Complex_I,
+    0.154420002119 +  -1.338536134506*_Complex_I,
+    0.256365846642 +  -0.981555435302*_Complex_I,
+    0.646498405245 +   1.015323083702*_Complex_I,
+    1.816099125502 +  -0.873689502536*_Complex_I,
+    0.973113786206 +   0.638143551822*_Complex_I,
+    1.082802452802 +   0.054273301693*_Complex_I,
+   -1.477958702052 +  -1.876253882821*_Complex_I,
+   -1.509174430498 +  -1.366946689175*_Complex_I,
+    0.770200379893 +  -0.037358369644*_Complex_I,
+    0.020528472364 +   0.071112924686*_Complex_I,
+    1.163573145482 +   0.270667445496*_Complex_I,
+   -0.685276141620 +   1.042179563446*_Complex_I,
+   -1.450457525407 +  -0.619572993396*_Complex_I,
+   -1.087848848412 +   0.747504403403*_Complex_I,
+    0.182987452127 +   0.130972663746*_Complex_I,
+   -0.146555893049 +   0.691910395951*_Complex_I,
+   -1.735762675547 +  -0.761410376033*_Complex_I,
+    0.130810608875 +  -1.132633564866*_Complex_I,
+    0.393865724887 +  -0.821588251430*_Complex_I,
+    1.287503514649 +  -0.355212207325*_Complex_I,
+    0.319345692829 +  -1.117466320715*_Complex_I,
+    0.418623558075 +  -1.759140288875*_Complex_I,
+    1.041852719916 +   0.176006788980*_Complex_I,
+   -0.297227854618 +   0.227296263697*_Complex_I,
+   -0.164089543129 +  -1.043612906074*_Complex_I,
+    0.008690483342 +   1.110377157604*_Complex_I,
+   -0.219664588199 +   0.427614664824*_Complex_I,
+    1.343587074840 +   0.375898274813*_Complex_I,
+    1.490739162431 +   0.021350208856*_Complex_I,
+    0.945367199263 +   0.180821025970*_Complex_I,
+    0.522231273825 +  -0.497986782057*_Complex_I,
+    0.523336519862 +  -0.933508311700*_Complex_I,
+    0.496982629732 +   1.473461947118*_Complex_I,
+   -0.003062144935 +  -0.162840951249*_Complex_I,
+    0.984875642474 +  -0.848717378538*_Complex_I,
+   -0.461516263319 +  -0.685745084807*_Complex_I,
+   -1.175877458107 +  -0.177846365813*_Complex_I,
+   -0.932200203193 +   1.107705439242*_Complex_I,
+    1.406397814811 +   0.054681766136*_Complex_I,
+    1.109360316168 +  -1.679067648707*_Complex_I,
+    0.924696326741 +   0.067692426194*_Complex_I,
+   -0.000830800316 +  -0.473092403298*_Complex_I,
+   -0.731674917791 +  -0.551769981624*_Complex_I,
+   -0.569303781517 +  -0.006801228336*_Complex_I,
+    0.110391386502 +  -1.899793883492*_Complex_I,
+    0.814109796724 +  -0.160466492041*_Complex_I,
+    1.138698851351 +  -0.625131274654*_Complex_I,
+   -1.963957708898 +   0.775152493271*_Complex_I,
+   -1.659897790238 +   1.056053755857*_Complex_I,
+   -0.649474775762 +   1.460655545851*_Complex_I,
+   -0.741388571452 +  -1.820826399375*_Complex_I,
+   -1.572911393570 +   1.569354704216*_Complex_I,
+    0.744026763162 +   0.835499539241*_Complex_I,
+    0.095754291390 +   0.796930944242*_Complex_I,
+    0.507814608179 +   0.818154740321*_Complex_I,
+   -0.383708274783 +  -0.068423423510*_Complex_I,
+   -0.888346025964 +   0.571095503271*_Complex_I,
+    0.925179546159 +  -1.052147910287*_Complex_I,
+   -0.438552116998 +  -0.225556401800*_Complex_I,
+    1.164226743249 +   0.148695281475*_Complex_I,
+    0.964606990498 +   0.259930491349*_Complex_I,
+   -0.343680793432 +   0.578310893020*_Complex_I,
+    0.093080205932 +   1.723388863831*_Complex_I,
+   -0.756655985085 +   0.681598873690*_Complex_I,
+    0.939758986711 +   0.011205851149*_Complex_I,
+   -0.141899272761 +   0.521507574016*_Complex_I,
+   -0.716208197806 +   1.303295936061*_Complex_I,
+   -0.813031307398 +   1.372568923775*_Complex_I,
+    1.023111911832 +  -0.701776162450*_Complex_I,
+   -0.059513464951 +  -0.562779118569*_Complex_I,
+   -1.783877678017 +   1.123340885741*_Complex_I,
+   -0.129189002044 +   0.152272891142*_Complex_I,
+    0.594929872664 +   0.920537744435*_Complex_I,
+    0.958591761922 +   1.041651647113*_Complex_I,
+    0.496121593665 +  -0.038971974072*_Complex_I,
+    0.184620294659 +  -2.865914521972*_Complex_I,
+   -0.173289102032 +   1.628057214738*_Complex_I,
+   -1.450876580978 +  -1.542121010661*_Complex_I,
+   -0.585624617416 +  -0.000140441111*_Complex_I,
+   -0.165171139738 +  -0.792078084035*_Complex_I,
+    1.008588335837 +   2.483679153708*_Complex_I,
+   -1.101668002181 +  -0.867864575222*_Complex_I,
+    1.778741775885 +   1.238012027547*_Complex_I,
+    0.418624110589 +  -0.516903858622*_Complex_I,
+   -0.623664358679 +   0.685852774278*_Complex_I,
+    1.473049190104 +  -0.824653187025*_Complex_I,
+   -1.125651402746 +  -0.051861075402*_Complex_I,
+    0.942173492837 +   0.284253723912*_Complex_I,
+    1.468609011081 +   0.872878783090*_Complex_I,
+   -0.523118553730 +   0.065885163104*_Complex_I,
+   -1.209516269354 +   1.712795511905*_Complex_I,
+   -0.164914664862 +  -1.060198690347*_Complex_I,
+    0.081083509872 +   0.514122164431*_Complex_I,
+    0.014993899314 +  -0.421407997613*_Complex_I,
+    0.921737261188 +  -0.891135385409*_Complex_I,
+   -0.186548224004 +   0.410695725270*_Complex_I,
+   -0.138866088362 +  -0.530179759052*_Complex_I};
+
+float complex fft_test_y509[] = {
+   15.695701816574 +  18.430646076972*_Complex_I,
+   17.496535538521 +   6.323908273548*_Complex_I,
+  -15.330535795607 + -16.826373817993*_Complex_I,
+  -22.995137994536 +   5.848541065489*_Complex_I,
+  -29.715938835044 +  12.754412174777*_Complex_I,
+   10.901257286656 +  -8.716929489684*_Complex_I,
+  -19.628646037487 +  19.226286624799*_Complex_I,
+  -20.480875354016 + -31.897908691975*_Complex_I,
+   35.124732513036 +  16.324491517218*_Complex_I,
+   46.193539420959 +  25.851763792302*_Complex_I,
+    5.792757151559 + -12.926180930790*_Complex_I,
+  -22.013847261245 +  -5.613610031493*_Complex_I,
+    9.404166069447 +  -0.019908771755*_Complex_I,
+    8.201675624257 +  -8.967673070400*_Complex_I,
+   15.181089874097 +  -6.297364574465*_Complex_I,
+   14.570696506052 +  -1.051310941558*_Complex_I,
+   -1.882106797398 +  -5.240669196133*_Complex_I,
+  -14.868309562238 +   6.106637681928*_Complex_I,
+  -15.129890494726 +  -2.984311531498*_Complex_I,
+    4.030179316698 + -51.075734845707*_Complex_I,
+  -34.365285768090 +   9.975973436808*_Complex_I,
+  -10.705381975285 +  -9.109285126921*_Complex_I,
+   -9.128619721470 + -32.793223942182*_Complex_I,
+   -3.884615181749 +   5.589810405219*_Complex_I,
+  -53.818876953386 +   9.826695061389*_Complex_I,
+    7.391928508119 +  -6.174427305668*_Complex_I,
+   -9.942167986468 + -39.833241210704*_Complex_I,
+   -1.462704472474 +  -2.725068915079*_Complex_I,
+   -8.330269618062 +  -2.806674135719*_Complex_I,
+   25.194669213199 +  30.149530622780*_Complex_I,
+   24.265507703667 +  35.899758345115*_Complex_I,
+    8.921289067584 +   4.788182047802*_Complex_I,
+  -43.846571049868 + -11.381289894906*_Complex_I,
+   30.751599932238 +  20.081621692604*_Complex_I,
+   12.282504139594 + -23.757854873259*_Complex_I,
+   29.365318997990 + -23.868021174550*_Complex_I,
+  -18.191992659645 +  23.039066554514*_Complex_I,
+  -26.138981350472 + -27.807762467872*_Complex_I,
+    4.906806518050 + -64.830853635320*_Complex_I,
+   17.645400306096 +   6.438919813853*_Complex_I,
+   -8.877478921993 +  22.121925579022*_Complex_I,
+   -8.857083278794 +   2.248345191669*_Complex_I,
+  -10.165898578719 +  14.029663983838*_Complex_I,
+   -4.760350989354 +  14.471217793420*_Complex_I,
+   10.088121839257 +  10.378681492497*_Complex_I,
+    8.818549764286 +  28.002638865768*_Complex_I,
+   15.815201542566 +  -8.171918556057*_Complex_I,
+   11.043628161039 +  -4.557146048106*_Complex_I,
+  -15.424873625059 +  -1.742974782342*_Complex_I,
+    5.374423440509 +  15.897637103576*_Complex_I,
+   10.405830658480 +   5.110319843375*_Complex_I,
+  -26.501807327618 + -12.862253830222*_Complex_I,
+    8.361320271594 + -12.127500776378*_Complex_I,
+   -7.065209772913 + -13.109659416735*_Complex_I,
+    6.150458328525 +  21.558378724678*_Complex_I,
+   19.192390552195 +  57.078335145777*_Complex_I,
+   16.247539210043 +  -5.178795721988*_Complex_I,
+   -2.182213653083 + -31.382522380551*_Complex_I,
+   -3.317969463714 +  -1.385753951871*_Complex_I,
+   24.974480529165 +  22.521516957567*_Complex_I,
+   29.614215910955 +  22.614533535316*_Complex_I,
+    6.616213388437 +  -6.945021315685*_Complex_I,
+  -21.081077266042 +  27.911030510024*_Complex_I,
+  -16.921827301441 +  18.479208860530*_Complex_I,
+    8.043988870051 + -36.113906522591*_Complex_I,
+   41.917272128407 +  26.843086076462*_Complex_I,
+  -13.727079990761 +   5.205402325839*_Complex_I,
+   -4.984705459021 +  -6.291381865515*_Complex_I,
+   -2.300982364859 + -21.708353496884*_Complex_I,
+   32.721343545850 + -16.032669392585*_Complex_I,
+    0.519352193540 +  45.753678340865*_Complex_I,
+   31.562265581157 +  -4.268866755468*_Complex_I,
+  -40.236062083037 +  -2.384241501530*_Complex_I,
+  -30.936637211857 +   3.338370340206*_Complex_I,
+  -21.959385231675 +   0.895928669913*_Complex_I,
+    7.483781797051 + -52.076927900468*_Complex_I,
+  -64.519015760392 +  10.979148490947*_Complex_I,
+  -28.029903074937 +   7.946066875195*_Complex_I,
+   -0.807954143368 +  10.641591910611*_Complex_I,
+   34.165604774109 + -19.071113266579*_Complex_I,
+   10.309282793998 + -14.648195920539*_Complex_I,
+   26.307206562160 +   3.878504634615*_Complex_I,
+  -21.207352431217 +  12.063691363252*_Complex_I,
+  -54.243540120475 +  -0.287217034895*_Complex_I,
+  -32.080496755198 +  30.540317997631*_Complex_I,
+    9.760803073410 +  16.055868478901*_Complex_I,
+  -16.078558738770 +  -5.356833086497*_Complex_I,
+   36.213555936943 + -24.363868386505*_Complex_I,
+  -10.697416407844 +  24.319983619540*_Complex_I,
+   17.684302893605 + -12.378065186883*_Complex_I,
+   41.844539573965 + -46.705571091504*_Complex_I,
+  -24.945971628906 + -10.079221453585*_Complex_I,
+  -10.513894508491 +  -4.599940107164*_Complex_I,
+   26.189867010892 + -17.124071629849*_Complex_I,
+  -34.363585674086 +  -4.077385676473*_Complex_I,
+  -32.102375469900 +  18.736917526908*_Complex_I,
+  -20.887619654518 + -54.461316084166*_Complex_I,
+    0.064775230615 +  24.195856216286*_Complex_I,
+   32.270028792805 +  48.132373628158*_Complex_I,
+   -1.916807810798 + -12.903225028783*_Complex_I,
+   -1.669256173251 +  16.652308679485*_Complex_I,
+   -3.071210123118 +  18.164002571879*_Complex_I,
+   17.931710499892 + -13.922708426108*_Complex_I,
+   19.373112705930 +   8.858054642852*_Complex_I,
+    1.712477146172 + -34.349666851373*_Complex_I,
+    3.720328613972 + -37.016904392694*_Complex_I,
+    5.546925594994 +  -0.892192373145*_Complex_I,
+  -22.124438618468 + -41.843458261756*_Complex_I,
+  -19.235688566387 +  -2.162824631740*_Complex_I,
+   21.783941115220 +  -6.695594391248*_Complex_I,
+   33.493805068723 +  62.763842873419*_Complex_I,
+  -10.896810929881 +  -8.404131804641*_Complex_I,
+   18.216817415924 + -33.590802005326*_Complex_I,
+  -25.763824584844 +  -6.302405967415*_Complex_I,
+  -15.929448259002 +  49.656053432255*_Complex_I,
+   -2.645182567345 +   3.823015231654*_Complex_I,
+   33.565352588744 + -14.801476451303*_Complex_I,
+   34.780028538088 +  14.782512818128*_Complex_I,
+   22.270348337592 +  17.396233784708*_Complex_I,
+  -23.998646351712 +  -2.572970386926*_Complex_I,
+   -5.212660897164 + -38.032125746868*_Complex_I,
+   -5.937127593805 + -21.823373120555*_Complex_I,
+   12.376774565444 +  33.920254294747*_Complex_I,
+   19.629474738185 +  -7.562284518099*_Complex_I,
+  -25.524838793958 + -25.917842984307*_Complex_I,
+    7.908690793819 +  53.160190581823*_Complex_I,
+  -22.547459647000 + -31.810110557445*_Complex_I,
+   14.825411801114 +  -2.623765490622*_Complex_I,
+   21.016615330301 +  22.000151402955*_Complex_I,
+   -3.127363744257 + -13.350072969695*_Complex_I,
+  -57.702154359177 + -15.154831462388*_Complex_I,
+  -13.446876000722 + -13.069388640893*_Complex_I,
+   -2.176317185084 +  17.496721580831*_Complex_I,
+  -54.132263906341 +  16.018239354033*_Complex_I,
+   -3.699736432956 +  27.741980074527*_Complex_I,
+   -7.311559889274 + -20.665154049268*_Complex_I,
+    6.161991265193 +  -5.267960280914*_Complex_I,
+   -0.083765300728 + -15.394352140009*_Complex_I,
+  -10.209228018076 +  -6.471795099909*_Complex_I,
+   -0.812704485184 +  -3.943529308850*_Complex_I,
+   -3.174962269778 + -37.702639197964*_Complex_I,
+   14.138710546364 +  11.048967022727*_Complex_I,
+    6.870463853253 +  10.314002260772*_Complex_I,
+    3.221828051661 +  51.234801230509*_Complex_I,
+   -2.186983936437 +  38.473329840101*_Complex_I,
+   13.533239595473 +  16.037592135971*_Complex_I,
+   39.103943522729 +   7.078614835194*_Complex_I,
+   30.565445217772 +  12.659846713522*_Complex_I,
+   30.303359360199 + -34.312315640466*_Complex_I,
+  -20.057347170511 +  50.200448109342*_Complex_I,
+   15.117190448180 +   7.835893578929*_Complex_I,
+  -15.209082571565 +  10.364865373220*_Complex_I,
+    9.144422828705 + -66.027702727533*_Complex_I,
+  -18.356336362499 + -27.596404803404*_Complex_I,
+  -21.642296590182 + -23.328234101983*_Complex_I,
+  -38.679552975441 + -27.294969526274*_Complex_I,
+  -17.537683264549 +  11.145767113244*_Complex_I,
+  -28.415217286677 +   2.450538508370*_Complex_I,
+   -7.839159290091 +   9.590153493395*_Complex_I,
+   -4.077913680565 +   3.514849319651*_Complex_I,
+   36.547874214296 + -13.621221107484*_Complex_I,
+   14.644978554755 + -21.980389507971*_Complex_I,
+   -3.073299724899 +  -8.758687284937*_Complex_I,
+   35.118241555864 + -42.903789231571*_Complex_I,
+  -22.783245832308 +  -9.082081737025*_Complex_I,
+  -25.929291660507 +  18.758508598154*_Complex_I,
+   33.025142390877 +   5.904152955421*_Complex_I,
+   11.948227318252 +   2.161590454494*_Complex_I,
+   -1.006102550557 + -28.161894386541*_Complex_I,
+   45.136250419544 +   3.976787326589*_Complex_I,
+  -33.291978336755 +   0.798986604330*_Complex_I,
+   11.265970488150 +  -1.327315667000*_Complex_I,
+    3.519436365736 +  16.927952719295*_Complex_I,
+  -33.602300462457 +  32.163188907263*_Complex_I,
+   -8.361310619248 +  -8.809090782568*_Complex_I,
+   11.294901581957 +  -4.245164072484*_Complex_I,
+   14.566011481616 +  26.519668998172*_Complex_I,
+   14.769898592012 +  56.527707423485*_Complex_I,
+   33.116957851259 +  -1.090132926951*_Complex_I,
+   20.848136410578 +  10.345884350214*_Complex_I,
+   18.958342797158 +   6.566708563840*_Complex_I,
+   15.396069027712 + -31.284159225464*_Complex_I,
+    2.886181696729 +  18.205515439993*_Complex_I,
+    2.258601161648 + -12.548779081296*_Complex_I,
+   18.639723094734 + -13.486140556024*_Complex_I,
+  -11.071519074610 + -19.339094005264*_Complex_I,
+   16.895170033002 +  -9.987185658559*_Complex_I,
+   -3.870068718996 +  22.233540496971*_Complex_I,
+    8.964139966565 + -27.313941129646*_Complex_I,
+  -17.059785268381 +  -4.400625859198*_Complex_I,
+   -6.175147302779 +  30.096445776903*_Complex_I,
+   18.669084089316 +   6.968259188573*_Complex_I,
+    7.657184873046 + -41.697203143381*_Complex_I,
+  -33.549050077833 +  24.392895555181*_Complex_I,
+   13.852529446346 + -24.121363551436*_Complex_I,
+   -8.184532144280 + -21.955892305419*_Complex_I,
+  -19.335525950576 +  31.604135153122*_Complex_I,
+   30.219851790692 +   9.312089638267*_Complex_I,
+   -9.099736227858 +  10.891340718152*_Complex_I,
+    8.000001281397 + -11.689007297193*_Complex_I,
+   15.301942508385 + -30.427794390813*_Complex_I,
+   -7.727208416300 +  13.998979881879*_Complex_I,
+   -6.394185171364 +   3.146108002402*_Complex_I,
+    9.252474513141 + -16.461589767495*_Complex_I,
+   26.863242542498 +  24.608331497751*_Complex_I,
+   46.975091007265 + -22.976780803246*_Complex_I,
+  -46.543917156055 +  15.869672486948*_Complex_I,
+   -4.728161553855 +  29.037018944500*_Complex_I,
+    4.070927662548 + -36.042387888924*_Complex_I,
+   14.921971168495 +  30.690814385233*_Complex_I,
+   35.791446149577 +   4.619796889845*_Complex_I,
+    2.303273709336 +  11.205822209351*_Complex_I,
+    8.168201691579 + -10.908743400107*_Complex_I,
+   -0.043567851626 +  24.059334278527*_Complex_I,
+    7.466916906592 +  18.610504314019*_Complex_I,
+    7.031977571019 +  -9.358602921852*_Complex_I,
+    5.391773391774 +  -0.966245883858*_Complex_I,
+    3.392189608224 + -10.986430122434*_Complex_I,
+   34.562609814015 +  40.520419348321*_Complex_I,
+   -9.427806154592 +  14.449317738714*_Complex_I,
+  -28.980935248249 +  35.723542184020*_Complex_I,
+  -25.087680212574 + -12.353756547678*_Complex_I,
+  -10.456225394292 +  26.299032407868*_Complex_I,
+   -3.551781425808 +  36.487758403775*_Complex_I,
+  -13.555856836256 +  11.377498259126*_Complex_I,
+   -5.711875186735 +  63.014002174535*_Complex_I,
+    8.802482546452 +  -9.877617002550*_Complex_I,
+   12.398023754693 + -17.252746153510*_Complex_I,
+   48.590307765142 + -39.869959281353*_Complex_I,
+   -4.228834590895 + -16.849706340702*_Complex_I,
+    1.157565934241 +  10.439891849608*_Complex_I,
+    6.162905961542 + -42.121455332219*_Complex_I,
+   18.623319771899 +   6.782479204326*_Complex_I,
+   40.359667537403 +  -5.227038429096*_Complex_I,
+    3.960846512784 +  -2.184979700635*_Complex_I,
+  -11.294281203268 +   1.462688924503*_Complex_I,
+    4.235940899795 +  -5.069039031037*_Complex_I,
+  -19.757480291431 + -19.995127344309*_Complex_I,
+    9.293994179429 +  30.574726633130*_Complex_I,
+  -11.912765926945 +   1.982525086253*_Complex_I,
+   32.506223545239 + -17.228481622244*_Complex_I,
+   23.261121675341 + -41.619757453530*_Complex_I,
+  -16.699711374017 +  17.184396509178*_Complex_I,
+  -30.981944222728 +  18.381540125799*_Complex_I,
+  -28.134836328395 +  -9.882313867372*_Complex_I,
+  -40.216518853122 +   7.954899537374*_Complex_I,
+  -17.952578865387 +  27.478809719211*_Complex_I,
+  -28.849862783745 +   0.512912610070*_Complex_I,
+   21.721522411732 +  -6.115881482201*_Complex_I,
+  -82.815498795687 +  -8.942733727142*_Complex_I,
+    3.740277816696 + -14.522905567941*_Complex_I,
+  -35.229732095265 +  26.709488731346*_Complex_I,
+   11.290125122898 +  24.224877496545*_Complex_I,
+  -16.635941007754 +   8.630637395391*_Complex_I,
+   22.134876007936 +   2.574582351782*_Complex_I,
+   33.251774617807 +   5.457413908840*_Complex_I,
+   -3.124095302802 + -30.945315750230*_Complex_I,
+   25.380261145256 + -12.895408190578*_Complex_I,
+   29.590560891351 + -35.336175817647*_Complex_I,
+   23.190389336153 + -51.584432366600*_Complex_I,
+   23.182126536797 + -30.991661680296*_Complex_I,
+  -37.142295822241 +   7.721940476748*_Complex_I,
+   26.833304952389 +  15.281809854672*_Complex_I,
+    3.795011817607 +  47.705908075813*_Complex_I,
+   44.822699796166 +   4.630736159645*_Complex_I,
+   20.899042041270 +   2.487657439412*_Complex_I,
+    8.742060078106 +   9.864203556590*_Complex_I,
+   16.980059178338 +  35.222546217902*_Complex_I,
+  -19.255543627730 +   9.410996006466*_Complex_I,
+   21.517616907420 +  26.338377945165*_Complex_I,
+   48.642251867035 +   6.924884525443*_Complex_I,
+   23.548326097336 +  10.784367397399*_Complex_I,
+  -11.550545599595 + -22.108959248798*_Complex_I,
+   16.733078720638 +  -6.587000041071*_Complex_I,
+  -24.552765736255 +  20.310652954928*_Complex_I,
+   17.292953612417 +  29.643678070378*_Complex_I,
+    9.373109698654 +  24.904713123755*_Complex_I,
+   25.238503675115 + -15.183892795659*_Complex_I,
+   -1.508310287534 + -32.049583340956*_Complex_I,
+   -1.442670825788 + -39.824563428950*_Complex_I,
+    3.230145000210 + -25.420530195927*_Complex_I,
+   13.229685977004 + -14.075607613923*_Complex_I,
+  -19.526361502130 + -29.370000953029*_Complex_I,
+  -21.782631521002 + -37.617721671791*_Complex_I,
+   27.816003478663 +  24.303079202481*_Complex_I,
+  -43.381077867759 +  24.837480951743*_Complex_I,
+   -9.560217395595 +   2.382563375055*_Complex_I,
+   -1.168465306817 +   1.589115227489*_Complex_I,
+   52.542490127417 +   1.424383301004*_Complex_I,
+  -10.794625364629 + -19.749473263855*_Complex_I,
+   26.168847841768 + -26.658825280073*_Complex_I,
+    4.233092270819 + -48.303662607018*_Complex_I,
+   -5.014341149907 + -33.130134324155*_Complex_I,
+    9.190470374905 + -26.953795736734*_Complex_I,
+   -2.846241405053 +  -0.651859597413*_Complex_I,
+  -20.225979212962 + -24.560140693339*_Complex_I,
+  -10.802336686843 +  51.446188240204*_Complex_I,
+   -9.492345818087 + -27.286465734423*_Complex_I,
+  -24.394227434292 +  36.495286678206*_Complex_I,
+   -4.610288224660 +  13.785902137795*_Complex_I,
+   25.266817655154 +   8.853318181494*_Complex_I,
+   -1.108114899344 +  -8.902780819762*_Complex_I,
+  -34.949213732955 +  16.304692482063*_Complex_I,
+   12.401176411050 + -49.617470306114*_Complex_I,
+   13.450715987122 +  23.780568951393*_Complex_I,
+    0.327891662517 +  20.945521301723*_Complex_I,
+  -24.347872941432 + -20.261920769569*_Complex_I,
+   -3.742508301325 +  15.297035114593*_Complex_I,
+   20.157765205649 +  -2.771550168366*_Complex_I,
+  -26.861338262008 +  10.330263597466*_Complex_I,
+  -19.103391377917 +  29.070399414699*_Complex_I,
+   33.355739256457 +   0.786653290415*_Complex_I,
+  -15.811410357704 +  12.334342632002*_Complex_I,
+  -31.067213179934 +   4.812703947794*_Complex_I,
+  -38.464666136613 +  -9.734460624273*_Complex_I,
+  -27.018641211199 + -10.676307399360*_Complex_I,
+   10.601332462223 +  12.602266185181*_Complex_I,
+  -14.863945203402 +  18.405001560411*_Complex_I,
+    6.151006234331 +  -1.325265142280*_Complex_I,
+   10.654450023041 +  24.877489064003*_Complex_I,
+    7.753711320427 + -24.877496668386*_Complex_I,
+   14.215523169674 +  15.750750989795*_Complex_I,
+   10.063422021746 +  49.306566727230*_Complex_I,
+  -83.835199786581 + -35.146747496108*_Complex_I,
+   -0.218459362048 +  -0.770721115796*_Complex_I,
+   22.431242892732 + -36.430877828612*_Complex_I,
+   17.435164403992 + -17.764746880777*_Complex_I,
+  -18.336005961075 + -40.785661916947*_Complex_I,
+   -9.708667273758 +  -6.212236081880*_Complex_I,
+   15.661415503945 + -23.058769144173*_Complex_I,
+    6.754601945166 + -19.307935713609*_Complex_I,
+   21.850846235150 +   8.741417315883*_Complex_I,
+   25.652896421543 +   2.796534004126*_Complex_I,
+   -1.843079544502 +   1.784869908972*_Complex_I,
+   -2.568058272174 + -31.336348759351*_Complex_I,
+  -23.530597236048 +  17.793870333201*_Complex_I,
+   41.700575857144 +   9.089193485713*_Complex_I,
+   44.662282998158 +  -3.699022752392*_Complex_I,
+   29.042445473764 + -11.349519417295*_Complex_I,
+   -7.073983554684 +  29.402856163434*_Complex_I,
+   19.939374137561 + -20.731019858441*_Complex_I,
+  -27.044678373347 + -39.264065270489*_Complex_I,
+   20.211317017215 +  41.386816030545*_Complex_I,
+    1.043164784676 +  -2.519947587522*_Complex_I,
+   -6.375700266136 + -29.785712929923*_Complex_I,
+  -27.700591743931 +  16.461560631605*_Complex_I,
+   10.095278660268 +   2.666042949098*_Complex_I,
+    6.614197587453 + -36.267409940122*_Complex_I,
+    3.024977063712 +  22.518622158190*_Complex_I,
+   22.730131517026 +  14.937191189086*_Complex_I,
+    0.061498915482 + -33.204005635365*_Complex_I,
+    3.246333795519 +  12.298349815877*_Complex_I,
+    5.921582958906 +  20.790686987339*_Complex_I,
+   22.777331137604 +   6.555360530677*_Complex_I,
+   21.199065974566 +  30.692801789200*_Complex_I,
+   -1.064699299018 + -30.735783458333*_Complex_I,
+    1.711968922160 +   2.764830482799*_Complex_I,
+   57.479865205031 +  14.741051010187*_Complex_I,
+   -5.222691142638 + -12.945556728288*_Complex_I,
+   24.618941208703 +  -8.335156868108*_Complex_I,
+    5.362345191955 + -29.659123854849*_Complex_I,
+  -13.759594910763 +  -2.839225605024*_Complex_I,
+   -0.744944069183 +   9.536259763486*_Complex_I,
+   -7.266281313994 +  -9.788530410618*_Complex_I,
+  -16.072026028887 +  16.172115574565*_Complex_I,
+   -6.901619139003 +  -7.747255335591*_Complex_I,
+  -16.302588968059 +  -9.014933207001*_Complex_I,
+   -9.878306363842 + -19.984075902846*_Complex_I,
+    2.088664771255 +  -8.999802528190*_Complex_I,
+  -21.345209871678 + -33.541210734395*_Complex_I,
+   12.617534995502 + -12.378687252061*_Complex_I,
+   10.047409736042 +  -6.607933917427*_Complex_I,
+    9.334831941537 +  10.948924308348*_Complex_I,
+   25.695968260034 +   7.063988558158*_Complex_I,
+   -9.625298547784 +  16.677076422518*_Complex_I,
+   -3.108963319630 +   3.376941849928*_Complex_I,
+  -47.044223301845 + -22.447723404064*_Complex_I,
+    6.172396063478 +   3.130567372603*_Complex_I,
+  -12.391233504725 + -18.965209575430*_Complex_I,
+    7.816421386094 + -18.829210484343*_Complex_I,
+    2.299165958987 +  21.664313726413*_Complex_I,
+   23.073574880632 + -40.647794133271*_Complex_I,
+   11.788478207223 +  10.388520650659*_Complex_I,
+    2.495494346482 + -40.903748493834*_Complex_I,
+    9.204196215081 +   3.731305183829*_Complex_I,
+  -11.705334181886 +  -5.698373301119*_Complex_I,
+  -10.576847226819 + -24.400174262686*_Complex_I,
+    1.125349170571 + -42.931316430041*_Complex_I,
+  -34.927593970119 +  15.363588886694*_Complex_I,
+   -5.051369474478 +  57.553472286872*_Complex_I,
+  -17.737882109182 +  20.993190450070*_Complex_I,
+   37.177637689060 +  16.991173325998*_Complex_I,
+   44.283601354742 +  17.412222591528*_Complex_I,
+   36.495623940354 + -20.061033291263*_Complex_I,
+   -0.821113211121 +  -3.092318774340*_Complex_I,
+  -22.377701901316 + -16.198210370561*_Complex_I,
+   12.319993479497 + -21.923811281565*_Complex_I,
+   15.511676349072 +  -9.483307137734*_Complex_I,
+   20.189875802738 + -17.301968082172*_Complex_I,
+   -5.805824615680 + -36.079316378374*_Complex_I,
+   30.255637146078 + -44.362762846218*_Complex_I,
+  -20.832965183425 +  -7.304495538217*_Complex_I,
+  -13.022907320332 + -57.530437725034*_Complex_I,
+  -10.564105798288 + -34.560059804429*_Complex_I,
+   21.361954258419 + -36.855969871716*_Complex_I,
+    4.506530938557 +  32.048954769246*_Complex_I,
+  -33.027950438924 +  28.211482991964*_Complex_I,
+  -29.352439177975 +  26.783042078482*_Complex_I,
+  -28.424459734912 +  -8.558362358430*_Complex_I,
+  -19.967959416569 +  10.959195107292*_Complex_I,
+  -23.791686165010 +  32.501306546259*_Complex_I,
+  -33.216256571387 +   2.738726266389*_Complex_I,
+    8.569384712402 +   1.130870024297*_Complex_I,
+   28.525451062286 +  17.073062203997*_Complex_I,
+   -1.502286920189 + -10.160446478808*_Complex_I,
+   -6.861394365693 +  36.910381121986*_Complex_I,
+   -6.268440765630 + -17.493038905156*_Complex_I,
+   11.322039030102 + -23.206689618074*_Complex_I,
+   -8.701154518730 +  34.222038681525*_Complex_I,
+  -24.669027160846 + -36.326555770368*_Complex_I,
+   20.943711343327 + -24.033336369879*_Complex_I,
+   29.209555657259 + -20.413413259064*_Complex_I,
+   -2.572238542979 + -20.816303598934*_Complex_I,
+   -1.994249544729 +  23.007487924841*_Complex_I,
+  -44.905945955860 +  29.216643846282*_Complex_I,
+  -24.062162530228 +  12.109221679204*_Complex_I,
+  -12.180111153692 +  17.875695962678*_Complex_I,
+   20.435116656247 +   5.797199859832*_Complex_I,
+    3.493004564937 +  -5.812772133417*_Complex_I,
+  -27.034394973848 +  24.948810080561*_Complex_I,
+   21.936211599800 +  -5.553436322258*_Complex_I,
+   -4.478679537619 +  -2.641131963451*_Complex_I,
+    5.120700840097 +   6.186528528634*_Complex_I,
+  -47.624247374659 + -34.895963725954*_Complex_I,
+  -43.479965560285 +  10.001886840601*_Complex_I,
+  -20.718614873021 +  24.908643623897*_Complex_I,
+    9.472646766463 +  -4.586461637219*_Complex_I,
+    9.294883958873 +   5.219642918744*_Complex_I,
+   24.547287109336 +  15.988189397184*_Complex_I,
+   12.811611969328 + -60.146862637486*_Complex_I,
+   33.520279815394 +  29.907221430910*_Complex_I,
+  -14.152533165001 + -16.446995449346*_Complex_I,
+   23.465425390528 +  -4.669981744595*_Complex_I,
+  -29.290184394031 +  -6.824899055323*_Complex_I,
+   36.044925366731 +  39.699979702815*_Complex_I,
+   45.609805078654 +  -0.363597033482*_Complex_I,
+   12.247988815457 +  -6.993931713410*_Complex_I,
+   14.744368729437 +   4.566968366531*_Complex_I,
+   -3.612287261480 + -31.882623904258*_Complex_I,
+   -4.625837065570 + -49.204985934025*_Complex_I,
+   10.369874666738 +  23.286668162235*_Complex_I,
+  -29.995409795889 +  22.748837275811*_Complex_I,
+    7.693482749378 +  12.894738140873*_Complex_I,
+  -74.632562519909 + -20.685236968252*_Complex_I,
+   24.465299052921 + -16.879295578319*_Complex_I,
+  -17.287451187811 +  -5.253265233981*_Complex_I,
+  -12.792544818057 + -22.347701435426*_Complex_I,
+  -30.500042038025 +  -6.625643844530*_Complex_I,
+  -25.032206738905 + -11.617648580783*_Complex_I,
+  -33.767830120583 + -19.967382042671*_Complex_I,
+    8.352625196160 +  -0.210944382477*_Complex_I,
+   -2.824794695557 +   7.815259159406*_Complex_I,
+  -30.449907818874 + -38.156235692042*_Complex_I,
+   34.223672868260 +   5.423974272822*_Complex_I,
+   -3.473532089262 + -45.188663541294*_Complex_I,
+    1.929635364775 + -17.899772348612*_Complex_I,
+  -17.366629103993 +  63.817371804672*_Complex_I,
+  -23.515133356267 +  -0.250964260288*_Complex_I,
+  -26.558406280873 +   5.464454223022*_Complex_I,
+    7.459074663716 +  -5.360173061781*_Complex_I,
+   -2.740551125358 + -28.954513936397*_Complex_I,
+   26.831634155936 +   7.298762429258*_Complex_I,
+   26.189721830795 +  13.528176613886*_Complex_I,
+    0.552527132215 +  12.528332436204*_Complex_I,
+  -18.980402436415 +  14.388413407600*_Complex_I,
+   -2.948074258737 + -26.092284933664*_Complex_I,
+   11.237345817375 +  40.862341094348*_Complex_I,
+  -31.224869298160 +  17.176727608508*_Complex_I,
+    2.721793055983 +  -9.744545888653*_Complex_I,
+   -0.776323448517 + -40.995023347911*_Complex_I,
+  -49.192082010750 +  11.872769267713*_Complex_I,
+  -43.807462185673 +  -6.259081354893*_Complex_I,
+   -2.175065114292 +   6.493046453358*_Complex_I,
+   23.334296943174 +  19.135612282249*_Complex_I,
+   -6.817794711121 +   3.642740073112*_Complex_I,
+   11.873498813840 +  37.496197060973*_Complex_I,
+   14.779256218032 + -13.159994239230*_Complex_I,
+  -19.232512636676 +   5.916208467040*_Complex_I,
+   -0.581631693027 +  22.531394609402*_Complex_I,
+    1.544210909483 +  14.296122579802*_Complex_I,
+    7.044576628257 + -10.190237629704*_Complex_I,
+   -4.813365711741 +   7.129670574765*_Complex_I,
+   41.167970529260 + -12.916981484001*_Complex_I,
+   26.669078124418 + -15.501207232456*_Complex_I,
+   12.492422072442 +  -9.037015700661*_Complex_I,
+   17.418311261915 +  11.203344744823*_Complex_I,
+   18.500273199469 +  19.568644933327*_Complex_I,
+  -23.216101473541 +  -2.306087143311*_Complex_I,
+   -1.651207984446 +   1.920560461483*_Complex_I,
+   22.032664625569 + -67.269204021659*_Complex_I,
+    2.687054355440 +  -6.150483980195*_Complex_I,
+  -42.445780624117 + -52.847480030760*_Complex_I,
+  -18.575368994624 + -11.378786258777*_Complex_I,
+   12.874123481209 +  11.199236114650*_Complex_I,
+    6.553360933113 +  -7.014357063713*_Complex_I,
+   24.351668075370 +  32.239707779122*_Complex_I,
+   -3.293543380713 +  15.964518188392*_Complex_I,
+  -15.175792848341 +  -2.924538577813*_Complex_I,
+   17.561582795484 +  -3.560828531875*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_6.c b/src/fft/tests/data/fft_data_6.c
new file mode 100644
index 0000000..f6b976e
--- /dev/null
+++ b/src/fft/tests/data/fft_data_6.c
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 6-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x6[] = {
+   -0.946868805918 +   0.048419613876*_Complex_I,
+   -1.426556442325 +   1.356194807524*_Complex_I,
+    0.262357323076 +   1.594616904796*_Complex_I,
+   -1.032912520662 +   0.046391595464*_Complex_I,
+   -0.271359734201 +  -2.390517158747*_Complex_I,
+   -0.288151144041 +   0.071324517238*_Complex_I};
+
+float complex fft_test_y6[] = {
+   -3.703491324072 +   0.726430280150*_Complex_I,
+    3.797148775593 +   1.637413185851*_Complex_I,
+   -3.456423352393 +   1.227102112087*_Complex_I,
+    1.791748889984 +  -2.221391560299*_Complex_I,
+    1.220570696725 +  -1.669098764217*_Complex_I,
+   -5.330766521347 +   0.590062429687*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_63.c b/src/fft/tests/data/fft_data_63.c
new file mode 100644
index 0000000..b0a66c9
--- /dev/null
+++ b/src/fft/tests/data/fft_data_63.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 63-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x63[] = {
+   -0.165065089334 +   1.102101871091*_Complex_I,
+   -0.981973148546 +   0.541468656777*_Complex_I,
+   -0.253554650102 +  -0.121332795252*_Complex_I,
+    0.944969891304 +  -0.207365910694*_Complex_I,
+    0.435801732783 +   1.116850636842*_Complex_I,
+   -2.058668461583 +   1.768420128659*_Complex_I,
+    0.404062083774 +  -1.055764673267*_Complex_I,
+   -1.173590903686 +   0.058528368777*_Complex_I,
+   -0.598414155862 +  -0.497560622574*_Complex_I,
+   -1.861198860149 +   0.229658791022*_Complex_I,
+   -2.562456370500 +   0.495624931472*_Complex_I,
+    0.673656989316 +   2.905410000205*_Complex_I,
+    0.867056802014 +   1.798292948964*_Complex_I,
+   -2.078980954663 +   0.098338397564*_Complex_I,
+   -0.330038784071 +   0.481340360434*_Complex_I,
+    1.276384585521 +   1.297002232435*_Complex_I,
+    0.261810533047 +  -1.797403878425*_Complex_I,
+   -2.153713373163 +  -0.426579749412*_Complex_I,
+    0.065709110586 +  -0.887295333513*_Complex_I,
+   -0.504714672950 +  -0.491403278968*_Complex_I,
+    0.314910052074 +  -0.711703174875*_Complex_I,
+    0.817545815846 +   2.684246900528*_Complex_I,
+    0.899097356674 +   2.316804935463*_Complex_I,
+    1.234132120370 +  -0.619880076717*_Complex_I,
+    0.791329122849 +  -1.491623691612*_Complex_I,
+   -1.543373050418 +  -0.676542277175*_Complex_I,
+    0.671442701409 +   0.216191301849*_Complex_I,
+    0.131373689049 +   0.579119405459*_Complex_I,
+   -0.372079387235 +  -0.810986260520*_Complex_I,
+    0.509295824480 +   2.533724020332*_Complex_I,
+    1.724705779066 +   0.166621824873*_Complex_I,
+    1.578021811166 +   0.495673960042*_Complex_I,
+   -0.388517506381 +   0.654051926694*_Complex_I,
+    0.780148148992 +  -1.186745985806*_Complex_I,
+   -0.998699640841 +  -2.290498316764*_Complex_I,
+    1.225655499248 +   0.539258663300*_Complex_I,
+    0.647174747272 +  -1.701050133294*_Complex_I,
+   -1.961810462975 +  -0.885111538141*_Complex_I,
+    0.708722105215 +   2.298722273781*_Complex_I,
+    0.003407269436 +  -0.160897403251*_Complex_I,
+   -1.461677661847 +   0.035003203295*_Complex_I,
+   -0.003031847788 +   0.263418857310*_Complex_I,
+   -0.921189208563 +  -0.548149166993*_Complex_I,
+    1.607243811931 +   1.460044138769*_Complex_I,
+    1.474955000858 +  -0.319625335378*_Complex_I,
+   -1.479420038984 +   0.442261731228*_Complex_I,
+   -0.501705519202 +  -0.107677110586*_Complex_I,
+   -0.555052849202 +  -2.288466181238*_Complex_I,
+    0.115695886330 +   0.958546419969*_Complex_I,
+   -0.272372848704 +  -0.160652497732*_Complex_I,
+    0.329785731274 +  -0.424936229335*_Complex_I,
+    0.480745655276 +   1.205406168541*_Complex_I,
+    0.070825951395 +   2.104542991271*_Complex_I,
+   -0.006185321246 +   0.587388482318*_Complex_I,
+   -0.694069041213 +   0.770735193308*_Complex_I,
+    0.908386043892 +  -1.602236263851*_Complex_I,
+   -0.699555084329 +   0.976937982289*_Complex_I,
+   -0.954674992562 +  -0.016401236894*_Complex_I,
+   -0.309302754137 +   0.958214618988*_Complex_I,
+   -0.127271579222 +   1.856678631720*_Complex_I,
+   -0.871347344939 +  -2.036988579664*_Complex_I,
+    0.135839855058 +  -0.627341937358*_Complex_I,
+   -0.335848209970 +  -2.547865701465*_Complex_I};
+
+float complex fft_test_y63[] = {
+   -7.089662066859 +   9.296545614813*_Complex_I,
+   -8.501752827269 +   5.888942352564*_Complex_I,
+    3.278359396134 +   3.509899786067*_Complex_I,
+    5.034502581381 + -10.325422246173*_Complex_I,
+    0.779395837363 + -11.641131257781*_Complex_I,
+    6.583373182868 +  -0.765170650399*_Complex_I,
+    7.673022911242 +   5.376349934527*_Complex_I,
+    6.901124231347 + -11.572359190453*_Complex_I,
+  -12.551588348101 +  -5.650836104556*_Complex_I,
+   13.135291423808 +  -9.005088743334*_Complex_I,
+    6.256581548275 +   7.656394164588*_Complex_I,
+   -9.007597317001 +   6.540328187752*_Complex_I,
+    5.970311877450 +  11.718061229294*_Complex_I,
+    3.567004157825 +   0.672928597121*_Complex_I,
+  -14.427943038803 +   8.900678790697*_Complex_I,
+    1.056847529506 +   3.528462625077*_Complex_I,
+   13.620920754920 +  14.305922963856*_Complex_I,
+    8.760078200432 +  11.222488908434*_Complex_I,
+    5.722531558556 +   7.234622735459*_Complex_I,
+   -5.247983376866 + -11.345081644681*_Complex_I,
+    1.314257740811 +   1.904662960921*_Complex_I,
+    0.730895747988 +   5.589496067590*_Complex_I,
+    6.527229987789 + -10.176960210420*_Complex_I,
+   -4.341510788445 +  -8.731233871509*_Complex_I,
+    7.581071089827 +  -9.540336644811*_Complex_I,
+    4.648868334483 +  -7.155035929172*_Complex_I,
+   -2.205072254220 +  17.534621681153*_Complex_I,
+    6.858512702532 +   3.813829755562*_Complex_I,
+    0.193418696750 +  11.693363675036*_Complex_I,
+    5.619907773283 +   4.653892039135*_Complex_I,
+   -1.855582230893 +  10.861851324746*_Complex_I,
+   14.627670681377 +  -2.489204973764*_Complex_I,
+    0.605824410285 +  -7.016190132363*_Complex_I,
+    2.624656390216 +   3.561441210498*_Complex_I,
+    0.709206425704 +  12.728091932012*_Complex_I,
+   -3.982794699993 +   6.878915353589*_Complex_I,
+  -13.376734767433 +   1.691306437014*_Complex_I,
+   -2.349794152830 +  -0.330132617385*_Complex_I,
+   -3.726616620789 +  15.817788395019*_Complex_I,
+  -16.729175354871 +  -8.409111133317*_Complex_I,
+   17.882515293846 +  -3.208404044366*_Complex_I,
+   -7.470918853421 +  18.877686110403*_Complex_I,
+   12.668798353590 +  -9.060907565117*_Complex_I,
+   -6.220564671186 +  -5.153664668923*_Complex_I,
+   -1.712331740792 + -15.816196915449*_Complex_I,
+  -11.816550492910 +  12.590312098504*_Complex_I,
+    5.948638061621 +  10.638444837386*_Complex_I,
+   13.008103746204 + -15.557670122101*_Complex_I,
+  -13.266957624044 +   3.898836567164*_Complex_I,
+   -1.862920665209 +  -4.724933699937*_Complex_I,
+   -4.695828593392 +   9.661188336137*_Complex_I,
+   -7.542434926702 +  17.373541879534*_Complex_I,
+    1.008284652559 +   2.011360297386*_Complex_I,
+   -6.854631802630 +  -4.014486905376*_Complex_I,
+   -6.668218293484 +   4.636855650199*_Complex_I,
+    3.172812378583 + -20.418734887614*_Complex_I,
+  -23.330375903759 + -12.181015318516*_Complex_I,
+    6.267118807644 +  17.748053544942*_Complex_I,
+   -8.369069881119 +   5.704956182541*_Complex_I,
+    9.266781179990 +  -5.431082220535*_Complex_I,
+   -2.621123339590 +  -4.576508645033*_Complex_I,
+    3.116382580437 + -13.991211520556*_Complex_I,
+  -15.293666222030 +   1.998407515632*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_64.c b/src/fft/tests/data/fft_data_64.c
new file mode 100644
index 0000000..cc300bc
--- /dev/null
+++ b/src/fft/tests/data/fft_data_64.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 64-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x64[] = {
+   -0.021754290353 +  -0.612017802190*_Complex_I,
+    0.484276408753 +   0.753944723650*_Complex_I,
+   -1.051031916820 +   0.166448506301*_Complex_I,
+    0.185316597591 +  -0.053758419799*_Complex_I,
+   -0.775208247301 +  -1.344741618220*_Complex_I,
+    1.008977328193 +   0.309312932614*_Complex_I,
+    0.225467083836 +  -1.771870588253*_Complex_I,
+    0.469982216778 +  -1.560265270836*_Complex_I,
+    0.015796870295 +   0.514270976406*_Complex_I,
+    0.021523250463 +  -1.531865633331*_Complex_I,
+    0.374810770914 +  -1.819295376215*_Complex_I,
+   -0.271362317574 +   1.223581370818*_Complex_I,
+    1.095264083148 +   0.119410767188*_Complex_I,
+    0.176833870554 +  -0.357067009123*_Complex_I,
+   -0.844150660609 +  -0.402483548788*_Complex_I,
+    0.121027264669 +  -1.289015413302*_Complex_I,
+    0.263581726117 +  -1.020960568111*_Complex_I,
+   -1.850367234488 +   0.252491065243*_Complex_I,
+    0.180744535235 +   0.799594483178*_Complex_I,
+   -0.844109705065 +  -0.510108698929*_Complex_I,
+   -0.036218762055 +   1.294869055398*_Complex_I,
+    0.258512790360 +   0.568441072457*_Complex_I,
+    1.626275624183 +   0.634319322667*_Complex_I,
+    0.322617919951 +   0.212628458295*_Complex_I,
+    0.160934986434 +  -0.163540330171*_Complex_I,
+    1.047849823612 +  -0.095800483192*_Complex_I,
+    0.481794672802 +  -0.457172404256*_Complex_I,
+    0.129133928192 +   0.091760813424*_Complex_I,
+   -0.060049809575 +   0.184851257642*_Complex_I,
+   -1.806557059379 +  -0.712577972074*_Complex_I,
+   -1.514896468556 +  -0.698585251055*_Complex_I,
+    1.839754692918 +   0.489376426678*_Complex_I,
+   -1.243735376399 +   1.952793219096*_Complex_I,
+    0.254245070501 +   1.389680075711*_Complex_I,
+    0.692307414060 +   1.566188401796*_Complex_I,
+   -0.194903488026 +  -0.439000848041*_Complex_I,
+    1.668407720772 +  -0.910104238535*_Complex_I,
+   -0.575848786837 +   0.375593796515*_Complex_I,
+    0.429092097669 +  -0.640962925803*_Complex_I,
+   -1.534318288920 +  -1.204243660541*_Complex_I,
+    1.340275852733 +  -1.091482736862*_Complex_I,
+   -0.683541799234 +   1.328028176092*_Complex_I,
+    0.346674268228 +  -1.556024203629*_Complex_I,
+    0.808325600843 +   0.879310154017*_Complex_I,
+   -1.083115089529 +   0.217436971911*_Complex_I,
+    0.208798197538 +   1.065533831174*_Complex_I,
+   -0.075819341427 +  -1.489702533787*_Complex_I,
+   -0.768117108951 +  -0.395669850882*_Complex_I,
+   -0.171257456484 +  -1.684427171232*_Complex_I,
+    0.008638877159 +   0.828227749071*_Complex_I,
+   -0.907835739258 +   1.661585546412*_Complex_I,
+   -0.842311802407 +   0.128045074908*_Complex_I,
+   -0.480781325156 +  -0.060205164274*_Complex_I,
+   -0.332968153644 +  -0.725579827560*_Complex_I,
+    0.385831273126 +  -0.734953099969*_Complex_I,
+    0.078244895870 +   0.187320802435*_Complex_I,
+    0.822725278030 +   1.761602936253*_Complex_I,
+    0.854139534803 +  -0.447804374730*_Complex_I,
+    1.139585202373 +   0.594363516587*_Complex_I,
+   -0.327644569515 +  -1.601512234292*_Complex_I,
+    0.066470112247 +   0.272352995689*_Complex_I,
+   -0.233514955808 +   0.151832081236*_Complex_I,
+    0.804170356884 +   0.191021429001*_Complex_I,
+   -0.141790367715 +   0.873289762818*_Complex_I};
+
+float complex fft_test_y64[] = {
+    1.725198076750 +  -4.343291505303*_Complex_I,
+   -1.306135600819 +  -7.829723137959*_Complex_I,
+   -5.438260077023 +   4.634490918041*_Complex_I,
+   -1.955793027433 +   4.362156087676*_Complex_I,
+   -8.012216700915 +   8.400026452278*_Complex_I,
+   -4.952000692368 +   3.096434501516*_Complex_I,
+    5.298208914176 +   4.534862681807*_Complex_I,
+   -0.880561110671 +  -1.467056178833*_Complex_I,
+   11.861836819015 +  -0.544519325409*_Complex_I,
+   -1.503603146027 +   8.809918042520*_Complex_I,
+   -5.363184446773 +   9.548803374715*_Complex_I,
+   -6.505800504816 +  -7.798968990377*_Complex_I,
+   14.636787307656 +  -0.228256068710*_Complex_I,
+    0.052560363405 + -12.014345616554*_Complex_I,
+   -1.959253467557 +   8.402198459668*_Complex_I,
+   -6.980453925187 +  -6.436081969205*_Complex_I,
+    5.388968837267 +   3.576485581891*_Complex_I,
+    1.433883925658 +  -1.990690693711*_Complex_I,
+    0.745585452676 +  -3.797939482423*_Complex_I,
+    2.695240683264 +  -0.969955745711*_Complex_I,
+   -6.338874171853 +  -7.732858525855*_Complex_I,
+   11.298952810793 + -13.037152643099*_Complex_I,
+  -15.351176639589 +   9.947482416391*_Complex_I,
+   -5.345010122884 +   0.498205314186*_Complex_I,
+   -4.369620686268 +   0.751093800562*_Complex_I,
+    7.632293961493 +   0.946244674824*_Complex_I,
+   -2.209404645076 +  -8.887676077977*_Complex_I,
+    9.546753476599 + -11.691028922291*_Complex_I,
+  -10.473480711255 +   0.645167184977*_Complex_I,
+    9.912509001236 + -22.116979184633*_Complex_I,
+    7.805895621936 +   2.010265856692*_Complex_I,
+   -7.638461665175 +   0.687493859037*_Complex_I,
+    5.983512814381 +  -4.711548846349*_Complex_I,
+    1.432584757253 +   2.618196841573*_Complex_I,
+  -16.409812005381 +   2.126260364995*_Complex_I,
+   -4.891966320361 +   4.829626903245*_Complex_I,
+   -3.218785198666 +  -4.443535570591*_Complex_I,
+    2.772033282272 +  -1.143468987354*_Complex_I,
+   -8.913495498818 +   3.327029248182*_Complex_I,
+    8.015679354394 +  -3.131930356460*_Complex_I,
+    1.419572328954 +  -0.132902166668*_Complex_I,
+    3.850100186885 +   9.527061186961*_Complex_I,
+    0.681611518886 +   4.259777024769*_Complex_I,
+   12.246604521898 +  -0.790289131992*_Complex_I,
+    3.241695686006 + -15.727090004602*_Complex_I,
+   -9.932323330295 +  -0.102559333310*_Complex_I,
+   -2.411503210251 +  -0.408856287097*_Complex_I,
+    1.884210002432 +   7.477017352531*_Complex_I,
+   -6.852334636700 +   3.198788969708*_Complex_I,
+    1.141049818230 +   1.542427926488*_Complex_I,
+    0.732333269451 +  -1.802063276954*_Complex_I,
+   -6.450414970103 +  -2.758985609350*_Complex_I,
+   -3.738653299253 +  -7.196005387959*_Complex_I,
+    7.300969515165 +  -7.844893935009*_Complex_I,
+    8.053617788503 +   5.389482632745*_Complex_I,
+    9.227999871537 +  -2.302018386805*_Complex_I,
+   -5.824592830409 +  -0.544198322926*_Complex_I,
+   -3.152065877231 +  -6.410831104251*_Complex_I,
+   -1.003575490144 +  23.308352736314*_Complex_I,
+    1.725485190234 + -10.960063101324*_Complex_I,
+  -14.199659988605 +   7.198846575963*_Complex_I,
+    4.186354051025 +  -2.044041800204*_Complex_I,
+   14.017389932813 +   2.146139910120*_Complex_I,
+    4.242720273094 +  -3.627670543295*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_7.c b/src/fft/tests/data/fft_data_7.c
new file mode 100644
index 0000000..b38fcfe
--- /dev/null
+++ b/src/fft/tests/data/fft_data_7.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 7-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x7[] = {
+    0.325737557343 +   0.347762560645*_Complex_I,
+   -0.464568614672 +   1.344201995758*_Complex_I,
+   -1.458140194879 +   0.983317270098*_Complex_I,
+    1.679041515327 +   1.025013762005*_Complex_I,
+   -0.178483024495 +  -0.711524629930*_Complex_I,
+    0.986194459374 +  -1.709315563086*_Complex_I,
+    0.387998802736 +  -1.150726066104*_Complex_I};
+
+float complex fft_test_y7[] = {
+    1.277780500734 +   0.128729329387*_Complex_I,
+    4.360250363806 +   2.591163135631*_Complex_I,
+    1.609972293897 +   2.377175130550*_Complex_I,
+    0.436888889637 +  -3.701058823864*_Complex_I,
+   -0.903757801309 +   3.003131513942*_Complex_I,
+    1.797162255231 +  -0.068636624441*_Complex_I,
+   -6.298133600593 +  -1.896165736688*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_79.c b/src/fft/tests/data/fft_data_79.c
new file mode 100644
index 0000000..c39c8af
--- /dev/null
+++ b/src/fft/tests/data/fft_data_79.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 79-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x79[] = {
+   -0.831644933479 +  -1.369566252119*_Complex_I,
+    1.382261515245 +   0.344632239578*_Complex_I,
+    1.522173067917 +  -0.879128649493*_Complex_I,
+    0.412263305825 +  -2.214373495146*_Complex_I,
+    0.167471424272 +   2.442885876862*_Complex_I,
+   -0.995700286447 +  -0.269798312873*_Complex_I,
+   -0.495500240228 +  -1.483255164915*_Complex_I,
+   -1.294110579031 +   0.198434791977*_Complex_I,
+   -0.725558739350 +   0.113152102484*_Complex_I,
+    0.245130536761 +  -1.776484046331*_Complex_I,
+   -0.902695579029 +  -0.321701515101*_Complex_I,
+    0.612573133829 +   0.706900606440*_Complex_I,
+   -1.446805571584 +   0.549130825767*_Complex_I,
+    0.779487733787 +   1.006717179605*_Complex_I,
+    0.482652400616 +  -0.602577004101*_Complex_I,
+   -0.075416135474 +   0.994234640494*_Complex_I,
+    1.169794509155 +   0.142737162623*_Complex_I,
+   -0.552532601786 +  -0.192607542361*_Complex_I,
+    0.410837027020 +  -0.565889656334*_Complex_I,
+    0.141957082162 +  -0.801461876260*_Complex_I,
+   -0.593199921160 +   0.901175943982*_Complex_I,
+   -1.360958657407 +  -0.483045804780*_Complex_I,
+   -1.919982203277 +   0.057049812902*_Complex_I,
+    0.597068489025 +   1.216028879537*_Complex_I,
+    0.553702635589 +   1.236264938570*_Complex_I,
+   -0.600595429992 +   0.147163421599*_Complex_I,
+    0.311936436694 +  -0.216250614525*_Complex_I,
+   -1.091501993588 +   0.833497783365*_Complex_I,
+    1.744199597330 +   0.588786957660*_Complex_I,
+    2.530575868723 +   1.039372684186*_Complex_I,
+    1.028391846884 +  -0.023635041523*_Complex_I,
+    1.289280692909 +  -1.032658504022*_Complex_I,
+   -0.717633308140 +  -0.744729509226*_Complex_I,
+    0.305920738055 +   0.657498377749*_Complex_I,
+   -1.129964121827 +  -0.435663177627*_Complex_I,
+    1.648005569065 +   1.195099617541*_Complex_I,
+   -2.115562921407 +  -1.910525019001*_Complex_I,
+   -0.596385890353 +   0.887009643025*_Complex_I,
+   -0.230825994339 +  -0.188182502664*_Complex_I,
+    0.263886383603 +  -0.439257543266*_Complex_I,
+   -0.523070205646 +   0.777409974948*_Complex_I,
+   -0.219529035132 +   1.383569408478*_Complex_I,
+   -0.718591132998 +   0.789138773979*_Complex_I,
+   -0.584598456206 +   0.201764886974*_Complex_I,
+   -0.569756495022 +  -0.592160140902*_Complex_I,
+    0.187040275696 +   1.071817729923*_Complex_I,
+   -0.157385303066 +  -0.207843225364*_Complex_I,
+    1.277595054999 +  -0.235819967829*_Complex_I,
+   -0.096458535032 +   0.092010581965*_Complex_I,
+    0.359513513761 +   0.945023449614*_Complex_I,
+   -0.907097860111 +   0.645605162871*_Complex_I,
+   -0.651303141773 +   2.145291744091*_Complex_I,
+    0.114198590617 +   1.903139164821*_Complex_I,
+   -0.079111313379 +   0.022610329502*_Complex_I,
+   -1.170099271217 +  -0.560852156056*_Complex_I,
+   -0.857261592064 +   0.764585655287*_Complex_I,
+   -0.308274947188 +   0.793759490894*_Complex_I,
+    1.075701520371 +   0.665603485423*_Complex_I,
+    0.121769228873 +   1.080351294531*_Complex_I,
+    1.775909597196 +  -0.152691005919*_Complex_I,
+    1.592857837122 +   1.642268527260*_Complex_I,
+    0.206041280093 +   0.680507676427*_Complex_I,
+   -0.447718650910 +  -1.049968522775*_Complex_I,
+   -1.571476809016 +   0.131194680122*_Complex_I,
+    1.668956017050 +  -0.788549453555*_Complex_I,
+   -1.464619492806 +  -0.514115875797*_Complex_I,
+    2.085001980617 +   0.514847563050*_Complex_I,
+    0.928930120111 +  -0.581073787447*_Complex_I,
+    0.446239202479 +  -0.653366428964*_Complex_I,
+    1.064861444804 +  -0.041135862801*_Complex_I,
+    0.072500637635 +   1.082811081123*_Complex_I,
+   -1.400912797632 +  -0.318643643314*_Complex_I,
+   -0.475876438927 +   0.416837808588*_Complex_I,
+   -0.066362697792 +   0.020225742927*_Complex_I,
+   -0.153794040015 +  -0.251785474065*_Complex_I,
+   -1.237542116335 +   0.809600386690*_Complex_I,
+    0.871326964554 +   0.924351961256*_Complex_I,
+   -2.389508044422 +  -0.601457844861*_Complex_I,
+    0.080578131708 +   0.388813172800*_Complex_I};
+
+float complex fft_test_y79[] = {
+   -2.198332092440 +  12.650658598176*_Complex_I,
+   -6.456670989242 +  -7.479301674890*_Complex_I,
+   -2.895336385229 +   1.221846928494*_Complex_I,
+   -4.636342294176 +  -0.801909581371*_Complex_I,
+   -9.731802903280 +  -5.232001275010*_Complex_I,
+    6.761030360809 +  -1.721885218978*_Complex_I,
+    1.867893673405 + -10.436764472711*_Complex_I,
+    7.129857916226 + -15.261303660272*_Complex_I,
+   16.581711031970 +   5.179082607705*_Complex_I,
+  -16.126836936739 +  -5.300370279404*_Complex_I,
+   -4.576577504775 + -12.699766318030*_Complex_I,
+   -7.579093623452 + -12.405022376124*_Complex_I,
+   -1.692330593182 +  -8.516631597504*_Complex_I,
+   -6.195245930268 +   0.070273567822*_Complex_I,
+    1.551185200774 +  -5.151973196311*_Complex_I,
+   -7.892299216869 +  -6.606437030189*_Complex_I,
+   11.038003489600 +   4.657211823028*_Complex_I,
+    7.075974564323 +   0.545694748078*_Complex_I,
+   -6.869510368707 +  -0.939533375646*_Complex_I,
+  -12.801782277417 +   4.703619318878*_Complex_I,
+    4.730103334639 +   3.588402018073*_Complex_I,
+    4.302722751897 +   5.818086352910*_Complex_I,
+    2.141745733069 +  -1.961941960610*_Complex_I,
+    5.318048536295 +  -0.010643144676*_Complex_I,
+   10.574791653730 +  -2.110839910741*_Complex_I,
+   18.403982142892 +  -4.968829844315*_Complex_I,
+   -4.861819678415 +   0.167673326078*_Complex_I,
+   -2.630720570170 +   1.594403564449*_Complex_I,
+    0.363441779769 + -10.436412333140*_Complex_I,
+   -8.279198460723 +  -3.380426807459*_Complex_I,
+   -0.740797623754 +  -2.814734090095*_Complex_I,
+   -9.827753678018 + -12.010129525106*_Complex_I,
+    0.873038175248 +  -1.031218712521*_Complex_I,
+   -8.699339400800 +  12.933077680277*_Complex_I,
+   -5.053719376427 +   3.839008815587*_Complex_I,
+  -17.469957324379 +  -0.249103089343*_Complex_I,
+    3.184401964192 +   3.862169536519*_Complex_I,
+    0.438203015374 +  -2.050441604463*_Complex_I,
+  -14.617902230891 +  13.404027008609*_Complex_I,
+   -3.326663756629 +  -9.464457202630*_Complex_I,
+  -16.780077393079 +   4.427043334783*_Complex_I,
+    2.414049361911 + -12.349549334690*_Complex_I,
+   -2.071827347449 +   0.131158553257*_Complex_I,
+   12.619462722043 +   3.785353059626*_Complex_I,
+    0.681152626465 +  -5.767839231705*_Complex_I,
+   -2.536944134501 +  -4.990736350292*_Complex_I,
+   -9.887559098728 + -30.445442814418*_Complex_I,
+   -8.158210630371 +  -2.609637272072*_Complex_I,
+   14.923059038703 +   2.059503206139*_Complex_I,
+    4.682549931928 +  -0.864654149312*_Complex_I,
+    3.308241623127 +  -8.932248210446*_Complex_I,
+   -7.447372005399 + -15.848177608870*_Complex_I,
+   11.470445857602 + -17.244634800179*_Complex_I,
+   -0.352198189530 +  -5.465835457604*_Complex_I,
+    0.595131131258 +   1.784167835328*_Complex_I,
+   -4.958773866897 +   1.052005175981*_Complex_I,
+   -8.549314756256 +  10.245571680581*_Complex_I,
+  -13.074676867355 +  -9.163782974489*_Complex_I,
+   -2.244945835090 +  13.220346166155*_Complex_I,
+   -4.198374817495 +  10.065229182925*_Complex_I,
+   -0.700355709960 +   6.827087899627*_Complex_I,
+    2.412030908809 +  -6.042014368503*_Complex_I,
+   -1.467215162979 +  16.669129234939*_Complex_I,
+    1.755798753328 +  -6.540065639162*_Complex_I,
+   -6.456991908144 +  -7.304947059496*_Complex_I,
+   -2.375547326101 +  18.123374474356*_Complex_I,
+    6.311057482521 +  -9.358027340793*_Complex_I,
+    2.466570091641 +   5.305925828815*_Complex_I,
+    0.001957161866 +  18.960020221647*_Complex_I,
+   -3.859854792754 +  -4.745420470903*_Complex_I,
+    2.984060727978 +  -4.759922601530*_Complex_I,
+    4.314065539668 +  -4.099188676984*_Complex_I,
+   -4.122177040779 +   6.664910801689*_Complex_I,
+    4.892466468902 +  19.707013122151*_Complex_I,
+   21.618605295806 + -12.301445853986*_Complex_I,
+   -0.720795540227 +   3.447422417270*_Complex_I,
+    6.265639354108 +  13.611595323686*_Complex_I,
+  -11.081101178789 + -14.511845349920*_Complex_I,
+    4.451917671114 + -12.130333484146*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_8.c b/src/fft/tests/data/fft_data_8.c
new file mode 100644
index 0000000..a86eebf
--- /dev/null
+++ b/src/fft/tests/data/fft_data_8.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 8-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x8[] = {
+    1.143832659273 +   0.058730029889*_Complex_I,
+   -0.094390429919 +   0.229144161540*_Complex_I,
+   -0.231936945111 +   0.250418514706*_Complex_I,
+    0.180568135767 +  -0.869698396678*_Complex_I,
+   -0.345282052584 +   1.176003338020*_Complex_I,
+    0.544428216952 +  -0.610473584454*_Complex_I,
+    0.928035714223 +   0.647778401795*_Complex_I,
+    0.441211141066 +  -1.176622015089*_Complex_I};
+
+float complex fft_test_y8[] = {
+    2.566466439667 +  -0.294719550271*_Complex_I,
+    1.635071437815 +   1.055386414782*_Complex_I,
+    1.767442826430 +   0.508277941207*_Complex_I,
+    2.964612333261 +  -2.017902163711*_Complex_I,
+    0.422832311935 +   4.560580119089*_Complex_I,
+    0.548438211721 +  -0.969987712376*_Complex_I,
+   -1.562539151277 +   0.164794961607*_Complex_I,
+    0.808336864628 +  -2.536589771219*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_9.c b/src/fft/tests/data/fft_data_9.c
new file mode 100644
index 0000000..08639b2
--- /dev/null
+++ b/src/fft/tests/data/fft_data_9.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 9-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x9[] = {
+    0.318149471742 +  -0.872622265472*_Complex_I,
+    0.380460329361 +   0.204662364547*_Complex_I,
+   -0.569767779072 +  -0.271995206036*_Complex_I,
+    1.334787120105 +  -0.238015105170*_Complex_I,
+   -0.644864052383 +   0.948536285238*_Complex_I,
+   -0.489784794370 +   0.158143326416*_Complex_I,
+    1.783096398872 +  -2.166235062454*_Complex_I,
+   -0.138901921376 +  -0.646377338691*_Complex_I,
+   -0.257444231274 +   0.857372365765*_Complex_I};
+
+float complex fft_test_y9[] = {
+    1.715730541604 +  -2.026530635857*_Complex_I,
+    1.685963762512 +   0.399226582084*_Complex_I,
+   -4.115380429157 +   1.255898079784*_Complex_I,
+    4.091196716626 +  -4.693323087763*_Complex_I,
+   -1.668677608930 +  -1.439432143007*_Complex_I,
+   -2.523905986916 +  -0.920217192051*_Complex_I,
+    4.501171713926 +  -3.110763575667*_Complex_I,
+    1.269999384456 +   3.193455688437*_Complex_I,
+   -2.092752848444 +  -0.511914105207*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_92.c b/src/fft/tests/data/fft_data_92.c
new file mode 100644
index 0000000..df7c3f9
--- /dev/null
+++ b/src/fft/tests/data/fft_data_92.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 92-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x92[] = {
+   -1.533248238739 +  -0.465630703218*_Complex_I,
+   -0.249958865116 +   0.582751340535*_Complex_I,
+   -0.052014206281 +   0.945510877668*_Complex_I,
+    0.566817251755 +   0.873302572342*_Complex_I,
+   -1.076106091336 +  -0.264177557543*_Complex_I,
+    0.170852437277 +  -0.619571758149*_Complex_I,
+    0.440747636244 +  -0.170212602153*_Complex_I,
+    0.354775561873 +  -0.272786796202*_Complex_I,
+    1.853177639448 +  -1.188389922347*_Complex_I,
+    0.410907829911 +  -0.451660724815*_Complex_I,
+   -0.650435006722 +   1.672142963707*_Complex_I,
+   -0.488214214181 +  -0.805655420589*_Complex_I,
+   -0.731929006826 +   1.054082992594*_Complex_I,
+   -0.288141810907 +   0.209057566980*_Complex_I,
+   -0.611240793053 +   1.293902538452*_Complex_I,
+   -1.776441085660 +   0.958157296522*_Complex_I,
+   -0.692307740144 +   0.075952098739*_Complex_I,
+    0.179804764868 +   1.730521926975*_Complex_I,
+   -0.314045230459 +   1.905941797877*_Complex_I,
+    1.110105359337 +   1.465687036226*_Complex_I,
+   -1.650241785564 +  -2.376992111936*_Complex_I,
+    1.355816874526 +  -2.252579556526*_Complex_I,
+    1.314750282984 +   0.595272183468*_Complex_I,
+    0.387080055146 +   0.205073570108*_Complex_I,
+   -1.088814882881 +   1.793386020599*_Complex_I,
+   -0.720166712307 +   0.890262821808*_Complex_I,
+   -0.062602073879 +  -0.031786415520*_Complex_I,
+   -0.888507446134 +  -0.536607709525*_Complex_I,
+   -0.510561833378 +  -0.675309226502*_Complex_I,
+    0.453615728261 +  -0.387399412056*_Complex_I,
+    2.033274332370 +   1.120734915608*_Complex_I,
+    0.361177016452 +   0.840380849208*_Complex_I,
+    0.907153428882 +   0.117975673876*_Complex_I,
+    0.959754000635 +  -0.182686914078*_Complex_I,
+   -1.880157998612 +   1.024007596710*_Complex_I,
+   -0.355239481254 +  -1.157943305297*_Complex_I,
+    0.732180611349 +   0.476767076883*_Complex_I,
+    0.217871850693 +   0.378282281897*_Complex_I,
+    0.708348214996 +   1.015785074730*_Complex_I,
+    1.376611167796 +   0.025670694927*_Complex_I,
+    0.795489734327 +   2.447726960093*_Complex_I,
+   -0.401589815356 +  -1.887124361666*_Complex_I,
+   -0.332674038856 +   0.654292754224*_Complex_I,
+    0.050773204061 +   1.851727945919*_Complex_I,
+    1.040820725747 +   0.163264568160*_Complex_I,
+   -0.473870168659 +  -0.135085060425*_Complex_I,
+   -0.405218690036 +  -0.210036331236*_Complex_I,
+   -1.048228113315 +   1.224144846525*_Complex_I,
+   -1.039363245830 +   0.571242521536*_Complex_I,
+    0.434387333441 +  -0.667088930502*_Complex_I,
+    1.323498894566 +   0.321341399415*_Complex_I,
+   -0.111430629981 +  -0.911660421532*_Complex_I,
+   -2.119793066060 +  -0.604468380769*_Complex_I,
+   -0.759768309222 +  -1.196195111817*_Complex_I,
+   -0.117965675009 +  -0.853563152009*_Complex_I,
+    0.776854555180 +  -1.044436698117*_Complex_I,
+   -0.208543038837 +  -0.719688047768*_Complex_I,
+    0.329085055714 +  -0.731568530556*_Complex_I,
+    0.948200526516 +   0.778615477506*_Complex_I,
+   -1.004787748593 +  -1.447117819386*_Complex_I,
+   -0.370036778975 +  -1.041283005778*_Complex_I,
+    0.278647623179 +   0.132465770957*_Complex_I,
+   -0.241472714303 +  -0.804153723576*_Complex_I,
+   -2.065839345003 +  -0.285350361634*_Complex_I,
+    0.709097179382 +   1.197671609730*_Complex_I,
+   -0.276234692523 +  -0.085308137997*_Complex_I,
+   -0.892034005938 +   1.470177172557*_Complex_I,
+    2.111093179577 +   0.214091335543*_Complex_I,
+    0.348125157569 +   0.660499475216*_Complex_I,
+   -0.440004517499 +  -0.381421105771*_Complex_I,
+   -0.121445816759 +  -0.900682972998*_Complex_I,
+    0.219762835492 +   0.024815977673*_Complex_I,
+   -0.179837662261 +   0.545204347104*_Complex_I,
+    0.575266794792 +  -0.857820948237*_Complex_I,
+   -0.665258207110 +  -0.142008087173*_Complex_I,
+    0.926210675302 +   0.676996743235*_Complex_I,
+   -0.358453435398 +   0.218721578288*_Complex_I,
+    0.465198395168 +   0.321437159886*_Complex_I,
+   -0.700888464307 +   0.026867992734*_Complex_I,
+    0.077912840097 +  -0.700256177377*_Complex_I,
+    0.814218687572 +  -0.237318115696*_Complex_I,
+    0.735605726247 +  -0.843989440195*_Complex_I,
+    0.326456107919 +   1.621890601713*_Complex_I,
+    0.279920713907 +   1.255568802945*_Complex_I,
+   -0.587220611906 +   0.854502071021*_Complex_I,
+    0.287904397154 +   1.370012449616*_Complex_I,
+    0.262419514043 +   0.783285944590*_Complex_I,
+   -0.960094532334 +  -1.295021304935*_Complex_I,
+    1.282525406387 +  -0.543459424061*_Complex_I,
+   -0.533518221919 +  -0.066344323134*_Complex_I,
+   -1.343771938200 +  -0.320382155842*_Complex_I,
+   -0.432681592095 +   0.159701208438*_Complex_I};
+
+float complex fft_test_y92[] = {
+   -2.518102271573 +  10.044654216417*_Complex_I,
+    6.643542265073 +   4.256887583025*_Complex_I,
+  -11.918532524663 +   7.755442009935*_Complex_I,
+    7.568118092597 + -13.006486128008*_Complex_I,
+  -18.363301435074 +  -1.152166283835*_Complex_I,
+   -5.786681211618 + -13.408541093492*_Complex_I,
+   -8.010496951983 +   2.213039809355*_Complex_I,
+   -1.411436744972 +   0.130962118237*_Complex_I,
+    7.222128824656 +   8.763744480410*_Complex_I,
+   13.775439027085 +   2.076241093317*_Complex_I,
+    6.773877664536 +  -6.756550626162*_Complex_I,
+   -7.137502543426 +  22.613697101003*_Complex_I,
+    9.970236986411 +   8.723751585267*_Complex_I,
+    3.388712243523 + -19.707507460853*_Complex_I,
+  -22.698345626199 +  -8.699233708477*_Complex_I,
+    2.966129501632 +  -5.083169407361*_Complex_I,
+    0.748694115570 +   1.246761748677*_Complex_I,
+    7.286176947585 + -17.177394603147*_Complex_I,
+   -4.640184708188 +  -4.742141169474*_Complex_I,
+  -15.653012036283 +   3.723708191064*_Complex_I,
+   -0.304540600637 +   8.131182330682*_Complex_I,
+    6.514836424576 +  -3.631934956688*_Complex_I,
+   14.631671446075 + -12.918654725994*_Complex_I,
+   -9.079675359871 + -12.980498823161*_Complex_I,
+   -2.396312070783 +  -0.555311616694*_Complex_I,
+   -5.516481227937 +   5.537031351170*_Complex_I,
+   16.946789253652 + -11.802977753669*_Complex_I,
+   -9.863602251151 +  17.961504389879*_Complex_I,
+   -1.089641984420 +   8.797684899764*_Complex_I,
+   -3.199834226297 +  -5.263692533431*_Complex_I,
+    3.613039090453 +  21.668260647267*_Complex_I,
+    4.356502756142 +   3.769573390222*_Complex_I,
+  -11.010076007411 +  -0.606073476312*_Complex_I,
+    1.249394309031 +  10.604580497016*_Complex_I,
+    0.432971670642 +  -8.028445673142*_Complex_I,
+    1.772492721651 +  -3.463373418618*_Complex_I,
+  -12.314467825833 + -13.413674383092*_Complex_I,
+   -4.796108671665 +  -1.279659252444*_Complex_I,
+   -4.135554898661 +   6.501150787072*_Complex_I,
+    4.235361817536 +   7.284416823675*_Complex_I,
+    4.645716809901 + -20.010236546155*_Complex_I,
+  -11.820881373135 +  -3.121702955852*_Complex_I,
+   -1.176033491231 + -12.716343647780*_Complex_I,
+    0.309217041323 +   1.171408626241*_Complex_I,
+    7.362816400606 +   0.244428623814*_Complex_I,
+   -5.010068913978 +  -5.765683583933*_Complex_I,
+   -6.876294123140 +  17.669794480924*_Complex_I,
+   -3.948263111315 +  -6.648206550629*_Complex_I,
+    2.175533159130 +  -1.121668038664*_Complex_I,
+   10.506942679678 +   0.132441506635*_Complex_I,
+   -6.051550803688 +  -3.522998291999*_Complex_I,
+  -12.716395512066 +  -1.993147768949*_Complex_I,
+    0.716905419911 +  -2.118308817826*_Complex_I,
+   -6.214220320215 +   9.890190258116*_Complex_I,
+   -3.496757347797 +   0.760118583470*_Complex_I,
+   -0.165689780061 +   9.326952571297*_Complex_I,
+    3.608065605999 +   1.223288778006*_Complex_I,
+  -15.368180054772 +   4.696419623807*_Complex_I,
+    6.532262844351 + -15.337625047848*_Complex_I,
+   11.463488167992 + -13.033909534322*_Complex_I,
+   -3.089385178638 +   2.386958780173*_Complex_I,
+   -8.764516788755 + -15.458094712422*_Complex_I,
+    5.161844954690 +   3.690438400691*_Complex_I,
+    7.483979541054 +   5.496267849787*_Complex_I,
+   -3.191656352209 +  -2.378455932485*_Complex_I,
+    3.346071918545 +  -5.947101296914*_Complex_I,
+   -7.911752024929 +  15.534843484634*_Complex_I,
+    4.990245834162 +  -8.000769949862*_Complex_I,
+    3.430754206047 +  -6.541036958204*_Complex_I,
+    3.819396364706 +  -6.492827881304*_Complex_I,
+   -5.791806578535 +  -3.103857093968*_Complex_I,
+   -0.378045544744 +   7.208602763143*_Complex_I,
+   -6.569094552634 + -15.273087240577*_Complex_I,
+    2.045385676585 +   5.705039211157*_Complex_I,
+   -3.551170334917 +  -9.324062375092*_Complex_I,
+   -3.762304723821 +   6.431485809437*_Complex_I,
+  -13.592727599777 +  -3.762490773764*_Complex_I,
+   14.293467882786 +  15.519778083180*_Complex_I,
+    7.838444610094 +  -5.394721016744*_Complex_I,
+   -6.164859647536 +   2.323160141970*_Complex_I,
+   -6.775035839038 +   3.176555591565*_Complex_I,
+    3.450970697637 +   5.952471575141*_Complex_I,
+   -9.694440566030 +  17.192480020342*_Complex_I,
+   -4.389843029510 + -14.424868721698*_Complex_I,
+  -13.182552822942 + -10.208834890358*_Complex_I,
+  -15.936588561479 +  10.544520991017*_Complex_I,
+  -14.982276290696 +   9.596052705212*_Complex_I,
+    2.219118227020 +  -0.954468671057*_Complex_I,
+    8.714041016800 +   4.805998810501*_Complex_I,
+   -6.362077161917 +  -2.563077169544*_Complex_I,
+    5.636818008460 + -11.718358270750*_Complex_I,
+  -12.128080581717 +   5.291433812970*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_data_96.c b/src/fft/tests/data/fft_data_96.c
new file mode 100644
index 0000000..a7fecd9
--- /dev/null
+++ b/src/fft/tests/data/fft_data_96.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data for 96-point transform
+//
+
+#include <complex.h>
+
+float complex fft_test_x96[] = {
+    0.231855789328 +   0.392790591230*_Complex_I,
+    0.454049797380 +   0.679061446432*_Complex_I,
+    0.528744029877 +  -0.002005026486*_Complex_I,
+    0.585153010445 +  -0.548050212091*_Complex_I,
+   -0.688324957589 +  -1.837254802682*_Complex_I,
+   -1.210661843723 +  -0.571711786295*_Complex_I,
+    1.276297465652 +  -1.918840937281*_Complex_I,
+    0.903361922100 +  -1.011460232753*_Complex_I,
+    0.477567600087 +   1.205903424233*_Complex_I,
+   -0.786388960666 +   1.649160493258*_Complex_I,
+   -0.690114487232 +   0.189150053367*_Complex_I,
+   -0.123298428085 +   1.440273117047*_Complex_I,
+   -1.824871622703 +  -0.028412622663*_Complex_I,
+   -0.005824551846 +  -1.985955234017*_Complex_I,
+    0.441673373932 +   2.184282527711*_Complex_I,
+   -0.302888535198 +   0.442593100570*_Complex_I,
+   -1.138671997435 +   0.800927463670*_Complex_I,
+   -0.113113592732 +   0.764107269897*_Complex_I,
+   -1.301467098151 +   1.879202303512*_Complex_I,
+    1.216552799598 +   1.187277605073*_Complex_I,
+   -0.414624947172 +  -0.192700931033*_Complex_I,
+    0.157426358097 +  -0.883438491265*_Complex_I,
+    1.314432283757 +  -2.209536410386*_Complex_I,
+    0.115962402453 +   0.100570503666*_Complex_I,
+   -0.184110258490 +  -0.274585396498*_Complex_I,
+    1.182232744060 +  -0.525435229715*_Complex_I,
+    1.369228680727 +  -1.296270108729*_Complex_I,
+    0.679139800457 +   0.521729684119*_Complex_I,
+    1.466742696053 +  -1.109181731398*_Complex_I,
+    0.423928449957 +   0.893374724805*_Complex_I,
+    0.097387993047 +  -1.166367457697*_Complex_I,
+   -0.319951780834 +   0.333472678246*_Complex_I,
+   -0.851035928014 +   1.174430249645*_Complex_I,
+   -0.741367318352 +   0.683540921651*_Complex_I,
+    0.063169486746 +   0.571172676088*_Complex_I,
+   -1.973683326170 +  -1.139982408570*_Complex_I,
+   -0.176533972367 +   2.303718667820*_Complex_I,
+    1.427168284563 +   0.147925345978*_Complex_I,
+    0.168590812490 +   1.242460845878*_Complex_I,
+   -0.463999677511 +  -0.037151173794*_Complex_I,
+   -2.536137467463 +  -1.681395390537*_Complex_I,
+   -0.968155862697 +   1.560777014683*_Complex_I,
+    0.679881050576 +   1.356938443072*_Complex_I,
+    0.882676978778 +  -0.850244916120*_Complex_I,
+    0.356671274755 +  -0.673629272452*_Complex_I,
+    0.770786696014 +   1.394669751828*_Complex_I,
+    1.879418388790 +  -1.423104148622*_Complex_I,
+   -1.625009605282 +   1.071684804070*_Complex_I,
+    0.523881317301 +  -1.877721023134*_Complex_I,
+   -0.810889778170 +   0.977110976206*_Complex_I,
+    1.184940081921 +   1.172520365453*_Complex_I,
+    0.614349326074 +   0.102072818821*_Complex_I,
+   -0.022137672632 +  -1.083247965012*_Complex_I,
+   -0.608327216411 +  -0.797618802167*_Complex_I,
+    1.718428073949 +  -1.056519198093*_Complex_I,
+    0.060567885123 +   1.676707157575*_Complex_I,
+   -0.495113579030 +   0.653357680357*_Complex_I,
+   -0.854188871032 +   0.411288746049*_Complex_I,
+   -0.410417801407 +   0.607919306211*_Complex_I,
+   -1.442853726865 +  -1.517927421271*_Complex_I,
+    0.274363566701 +   0.446776625735*_Complex_I,
+   -0.625143151100 +   1.305210751509*_Complex_I,
+    1.212571305417 +   0.002561202196*_Complex_I,
+    0.964380743057 +  -0.130831945021*_Complex_I,
+   -1.467000571347 +  -0.173752863470*_Complex_I,
+   -0.361064943808 +  -0.453821406270*_Complex_I,
+   -0.435558558438 +  -0.349465935625*_Complex_I,
+    1.160653554933 +   0.097609577607*_Complex_I,
+    0.824326827067 +  -0.141377219902*_Complex_I,
+    0.151294380041 +   0.722300985052*_Complex_I,
+   -0.160268192079 +   2.322410769696*_Complex_I,
+    1.756726727738 +  -0.285586369209*_Complex_I,
+   -1.614230674187 +  -0.241407720255*_Complex_I,
+   -0.035193854160 +  -0.456767111405*_Complex_I,
+    1.997549686826 +   0.610571776676*_Complex_I,
+   -1.013290679060 +   1.072224366678*_Complex_I,
+    0.966131190541 +   1.156926252511*_Complex_I,
+   -0.085548688965 +  -1.173983462045*_Complex_I,
+   -1.258925646165 +   0.570319789600*_Complex_I,
+   -0.009163839577 +  -0.458346841578*_Complex_I,
+    0.328942919560 +  -0.410428744477*_Complex_I,
+    1.359093898482 +  -0.077096836434*_Complex_I,
+    0.269427793307 +   1.756132969724*_Complex_I,
+   -1.612257645892 +   1.531514720616*_Complex_I,
+    1.344264226102 +   0.027502937744*_Complex_I,
+    1.369576658531 +   0.382536868127*_Complex_I,
+    0.172747665849 +   0.388258199964*_Complex_I,
+   -0.226008514710 +  -0.315160242926*_Complex_I,
+    0.150075935489 +   0.260574914740*_Complex_I,
+   -0.170611752947 +  -1.382717411879*_Complex_I,
+   -1.216483547464 +  -0.767792980333*_Complex_I,
+   -1.976466005823 +   0.458969099499*_Complex_I,
+   -0.645507778235 +   2.051797937683*_Complex_I,
+    0.981465939529 +   2.517091270417*_Complex_I,
+    1.174746235178 +  -1.612669537430*_Complex_I,
+    0.896264508571 +   0.843449048976*_Complex_I};
+
+float complex fft_test_y96[] = {
+    4.609981707790 +  14.165957863955*_Complex_I,
+   -2.663372690989 +   3.381865489792*_Complex_I,
+   -6.250889898983 +   0.856018997500*_Complex_I,
+    4.388447703102 +   0.709127580060*_Complex_I,
+   10.524498612708 + -15.765075233017*_Complex_I,
+  -21.883023288862 +  -5.754013136199*_Complex_I,
+   -3.032497734395 +   3.435059152768*_Complex_I,
+   10.141510735231 +   6.569590619684*_Complex_I,
+   -3.943788049939 +   4.985622407622*_Complex_I,
+   -8.471948591549 +   3.436253039913*_Complex_I,
+   -8.880296739352 +  18.329925169953*_Complex_I,
+    8.081550419966 +   4.959140273664*_Complex_I,
+    1.902048356207 +  12.753862442288*_Complex_I,
+   23.710137236631 +  -5.734748416060*_Complex_I,
+   18.967547060099 +   2.261420011861*_Complex_I,
+  -13.117649377758 +   5.255774731680*_Complex_I,
+    2.565218803705 + -25.049831530536*_Complex_I,
+    0.395850231743 +  -4.532129398987*_Complex_I,
+    6.995600852169 +  -7.386596256854*_Complex_I,
+   -2.132719003770 +   6.698594558144*_Complex_I,
+   -9.929306991332 +   6.869396317874*_Complex_I,
+   11.346428057260 +  15.752032502527*_Complex_I,
+  -10.078874711603 +  -6.207899239799*_Complex_I,
+   -4.061971394211 +   1.001130636914*_Complex_I,
+  -13.995272885614 +  -4.455343352338*_Complex_I,
+    4.451519347892 +   7.926049461659*_Complex_I,
+   16.857007165977 +   8.029468101958*_Complex_I,
+    6.827258648561 +  14.930217154284*_Complex_I,
+   -0.559549304382 +   8.208567303238*_Complex_I,
+   -3.551940117450 +  -7.211758268580*_Complex_I,
+    1.734999071287 +  -3.632491293309*_Complex_I,
+   -7.304392369767 + -14.527214303395*_Complex_I,
+  -11.262783549629 +  -1.605589170344*_Complex_I,
+  -11.512374272203 +  16.561245028560*_Complex_I,
+    7.482726055364 +  -2.727439658405*_Complex_I,
+   15.246746816163 +  17.569026228008*_Complex_I,
+  -15.136445648942 +  -1.722052787143*_Complex_I,
+   -0.596784032900 +   1.036483442957*_Complex_I,
+   -8.295090222700 + -14.787777542426*_Complex_I,
+  -10.388667668932 +  -3.677948124346*_Complex_I,
+    1.442374069335 +   2.369895137413*_Complex_I,
+  -11.286677919418 +  -0.442216831320*_Complex_I,
+   -4.258359681535 +  -5.877299409944*_Complex_I,
+    8.474879358850 +  11.133599053803*_Complex_I,
+    4.521324398684 +  -5.575361516985*_Complex_I,
+  -10.852733452716 +  -8.187793263992*_Complex_I,
+   10.282241432010 + -14.787653906149*_Complex_I,
+   -0.967326508687 +   1.045774363086*_Complex_I,
+    5.315060279059 +  -6.564076763307*_Complex_I,
+  -14.103481813190 +   7.024414693592*_Complex_I,
+    7.672189491846 +  -8.914876516755*_Complex_I,
+    5.234244256475 +   3.602992887989*_Complex_I,
+   -0.039164279610 + -30.448360387467*_Complex_I,
+  -16.064792699661 +   6.718770045507*_Complex_I,
+    4.235111030143 +   2.478115209729*_Complex_I,
+   -9.198909959126 +  -8.290580416319*_Complex_I,
+   10.578975231033 +   2.559822357827*_Complex_I,
+    9.243136171627 +  -9.377891910936*_Complex_I,
+    5.862895817529 +   0.431692246149*_Complex_I,
+  -11.618990491528 +  17.539850472094*_Complex_I,
+   -1.161363450862 +  -2.941910938932*_Complex_I,
+   -1.268260133904 +   1.005441709892*_Complex_I,
+   31.421272506426 +  -0.240397785028*_Complex_I,
+  -10.921843261114 +  10.855028296636*_Complex_I,
+    6.680208942916 +   7.902869278527*_Complex_I,
+    9.744767683416 + -15.653561656933*_Complex_I,
+   11.963236965081 +   2.757863055376*_Complex_I,
+   -0.657516528727 +  -1.513346377612*_Complex_I,
+   -5.778133404808 +   0.380938385839*_Complex_I,
+    3.001034357249 +  -0.472690292826*_Complex_I,
+    4.361975174738 +  -0.082813022517*_Complex_I,
+    7.393956533023 +   3.595190860536*_Complex_I,
+  -16.383681435953 +  -0.148093500883*_Complex_I,
+   15.448535613211 +  13.699034854832*_Complex_I,
+  -26.046188537886 +   4.672758210947*_Complex_I,
+  -25.431394530510 +  19.149176229623*_Complex_I,
+    0.452366402127 +  -9.284164787222*_Complex_I,
+   -8.853642184491 +   7.831408406510*_Complex_I,
+   -6.688478485009 +  -3.439251128678*_Complex_I,
+   -6.810859551138 +  -8.440912926245*_Complex_I,
+  -19.102617555696 +   6.927486844509*_Complex_I,
+    8.934833377990 + -11.564936252737*_Complex_I,
+   -7.393024014592 +  -0.714887967192*_Complex_I,
+   -0.719859551514 + -13.605720898747*_Complex_I,
+  -17.742142235287 + -12.458797479604*_Complex_I,
+   15.978186136167 +   5.980922906846*_Complex_I,
+   10.780516210769 +  -5.413148370626*_Complex_I,
+   18.156965553233 +  -2.629625388886*_Complex_I,
+   16.391744903218 +   7.895225147305*_Complex_I,
+    6.290790245017 +   6.997124329976*_Complex_I,
+    3.689747766167 +  11.534985962311*_Complex_I,
+    9.391595767018 +  17.298374496500*_Complex_I,
+   25.027955260120 +  -7.023151206389*_Complex_I,
+   -5.241064162059 +  -8.249917715987*_Complex_I,
+   -0.085493630186 +  -3.829269584478*_Complex_I,
+    3.782595963629 +  -0.412071285666*_Complex_I};
+
diff --git a/src/fft/tests/data/fft_r2rdata_27.c b/src/fft/tests/data/fft_r2rdata_27.c
new file mode 100644
index 0000000..f500a77
--- /dev/null
+++ b/src/fft/tests/data/fft_r2rdata_27.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft r2r (real-to-real) data
+//
+
+// 27-point real even/odd dft data
+float fftdata_r2r_x27[] = {
+      1.00000000e+00,   9.67720449e-01,   8.74232948e-01,   7.29134023e-01,
+      5.46968460e-01,   3.45309585e-01,   1.42522618e-01,  -4.44371328e-02,
+     -2.01871306e-01,  -3.20590228e-01,  -3.96443307e-01,  -4.30132151e-01,
+     -4.26421911e-01,  -3.92946303e-01,  -3.38838935e-01,  -2.73413390e-01,
+     -2.05070078e-01,  -1.40540510e-01,  -8.45066383e-02,  -3.95672210e-02,
+     -6.47628168e-03,   1.54448878e-02,   2.78246514e-02,   3.27720679e-02,
+      3.24839540e-02,   2.89606918e-02,   2.38370150e-02 };
+
+
+// REDFT00
+float fftdata_r2r_REDFT00_y27[] = {
+      1.90807498e+00,   6.92315578e+00,   1.18128624e+01,   5.63927364e+00,
+      6.24250948e-01,   6.22979142e-02,  -2.71553919e-02,   1.96029730e-02,
+     -1.47504276e-02,   1.15671754e-02,  -9.38177109e-03,   7.82203674e-03,
+     -6.67411089e-03,   5.80593944e-03,  -5.13643026e-03,   4.61196899e-03,
+     -4.19497490e-03,   3.86145711e-03,  -3.59269325e-03,   3.37751862e-03,
+     -3.20415385e-03,   3.06731090e-03,  -2.96133757e-03,   2.88200378e-03,
+     -2.82669067e-03,   2.79450417e-03,  -2.78437138e-03 };
+
+
+// REDFT10
+float fftdata_r2r_REDFT10_y27[] = {
+      2.93191195e+00,   7.81435204e+00,   1.25262451e+01,   5.87700558e+00,
+      3.13943624e-01,  -2.23003000e-01,  -1.98579907e-01,  -9.58713368e-02,
+     -9.68946069e-02,  -5.19125164e-02,  -5.74545190e-02,  -3.24527398e-02,
+     -3.75072435e-02,  -2.18437612e-02,  -2.58425809e-02,  -1.53105119e-02,
+     -1.83160957e-02,  -1.09222922e-02,  -1.30745629e-02,  -7.76020437e-03,
+     -9.17540491e-03,  -5.33153862e-03,  -6.08742610e-03,  -3.34636122e-03,
+     -3.48186493e-03,  -1.61480904e-03,  -1.13347173e-03 };
+
+
+// REDFT01
+float fftdata_r2r_REDFT01_y27[] = {
+      3.24506021e+00,   1.02413387e+01,   1.02888603e+01,   2.94090056e+00,
+      3.01434755e-01,  -3.94552648e-02,   4.12662849e-02,  -3.65541428e-02,
+      3.23217735e-02,  -2.86298655e-02,   2.54328623e-02,  -2.26499923e-02,
+      2.02035718e-02,  -1.80307329e-02,   1.60805881e-02,  -1.43106822e-02,
+      1.26882717e-02,  -1.11882668e-02,   9.78851132e-03,  -8.47033411e-03,
+      7.21932389e-03,  -6.02424145e-03,   4.87267971e-03,  -3.75625165e-03,
+      2.66467361e-03,  -1.59203156e-03,   5.29353623e-04 };
+
+
+// REDFT11
+float fftdata_r2r_REDFT11_y27[] = {
+      4.32605982e+00,   1.13771296e+01,   1.02061100e+01,   2.13518620e+00,
+     -1.33218557e-01,  -2.76073009e-01,  -8.56223330e-02,  -1.44742057e-01,
+     -2.83730812e-02,  -9.47302654e-02,  -7.39809964e-03,  -6.90608099e-02,
+      2.49255938e-03,  -5.37654683e-02,   7.92805944e-03,  -4.37831581e-02,
+      1.13052810e-02,  -3.68458964e-02,   1.36666447e-02,  -3.17945704e-02,
+      1.55290905e-02,  -2.79758442e-02,   1.71851665e-02,  -2.49931645e-02,
+      1.88217703e-02,  -2.25886870e-02,   2.05794629e-02 };
+
+
+// RODFT00
+float fftdata_r2r_RODFT00_y27[] = {
+     -3.07457280e+00,   4.34810066e+00,   1.09639254e+01,   7.64511538e+00,
+      4.62730789e+00,   3.26789522e+00,   2.68699503e+00,   2.17084169e+00,
+      1.91607285e+00,   1.60684299e+00,   1.45886230e+00,   1.24392998e+00,
+      1.14396501e+00,   9.81968999e-01,   9.06752288e-01,   7.78145194e-01,
+      7.16473997e-01,   6.10574782e-01,   5.56297779e-01,   4.66658950e-01,
+      4.16026711e-01,   3.38447809e-01,   2.88945198e-01,   2.20500469e-01,
+      1.70261383e-01,   1.08778238e-01,   5.62527180e-02 };
+
+
+// RODFT10
+float fftdata_r2r_RODFT10_y27[] = {
+     -3.45587444e+00,   3.69455957e+00,   1.00895662e+01,   7.11557245e+00,
+      4.58258820e+00,   3.34214282e+00,   2.84152031e+00,   2.34757495e+00,
+      2.14987850e+00,   1.85646343e+00,   1.76226008e+00,   1.56126976e+00,
+      1.51575768e+00,   1.36674178e+00,   1.34836006e+00,   1.23211598e+00,
+      1.23075652e+00,   1.13685036e+00,   1.14728224e+00,   1.06952679e+00,
+      1.08892000e+00,   1.02343559e+00,   1.05029333e+00,   9.94573593e-01,
+      1.02823102e+00,   9.80662346e-01,   1.02105260e+00 };
+
+
+// RODFT01
+float fftdata_r2r_RODFT01_y27[] = {
+     -2.61891890e+00,  -4.18362617e-02,   9.58289337e+00,   9.41432190e+00,
+      5.27894020e+00,   3.62661290e+00,   2.76587272e+00,   2.31010056e+00,
+      1.92357457e+00,   1.67965138e+00,   1.44504440e+00,   1.28557563e+00,
+      1.12212110e+00,   1.00580621e+00,   8.81629765e-01,   7.90346980e-01,
+      6.89975739e-01,   6.14315927e-01,   5.29168069e-01,   4.63644713e-01,
+      3.88459831e-01,   3.29564214e-01,   2.60825515e-01,   2.06111193e-01,
+      1.41254663e-01,   8.88549462e-02,   2.58288011e-02 };
+
+
+// RODFT11
+float fftdata_r2r_RODFT11_y27[] = {
+     -2.72205544e+00,  -1.06129467e+00,   8.21426964e+00,   9.14769840e+00,
+      5.52553558e+00,   3.88690162e+00,   3.03581023e+00,   2.59010696e+00,
+      2.22776008e+00,   2.00389194e+00,   1.79835343e+00,   1.66311359e+00,
+      1.53181064e+00,   1.44247937e+00,   1.35311365e+00,   1.29120958e+00,
+      1.22834873e+00,   1.18450797e+00,   1.13985527e+00,   1.10885823e+00,
+      1.07762277e+00,   1.05639505e+00,   1.03571594e+00,   1.02242053e+00,
+      1.01060724e+00,   1.00419796e+00,   1.00035369e+00 };
+
+
+
diff --git a/src/fft/tests/data/fft_r2rdata_32.c b/src/fft/tests/data/fft_r2rdata_32.c
new file mode 100644
index 0000000..9fb3152
--- /dev/null
+++ b/src/fft/tests/data/fft_r2rdata_32.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft r2r (real-to-real) data
+//
+
+// 32-point real even/odd dft data
+float fftdata_r2r_x32[] = {
+      1.00000000e+00,   9.76961553e-01,   9.09556091e-01,   8.02746117e-01,
+      6.64265335e-01,   5.03880322e-01,   3.32481116e-01,   1.61105037e-01,
+     -3.40424648e-08,  -1.42174676e-01,  -2.58936524e-01,  -3.46311510e-01,
+     -4.02897298e-01,  -4.29679066e-01,  -4.29643869e-01,  -4.07258183e-01,
+     -3.67879450e-01,  -3.17172974e-01,  -2.60592192e-01,  -2.02966005e-01,
+     -1.48217648e-01,  -9.92199481e-02,  -5.77765778e-02,  -2.47062612e-02,
+      1.25687316e-09,   1.69803519e-02,   2.72917245e-02,   3.22119817e-02,
+      3.30718160e-02,   3.11258435e-02,   2.74662171e-02,   2.29759347e-02 };
+
+
+// REDFT00
+float fftdata_r2r_REDFT00_y32[] = {
+      2.27039862e+00,   8.13895798e+00,   1.40223389e+01,   6.84894085e+00,
+      7.96620846e-01,   7.64858574e-02,  -3.17813009e-02,   2.30059568e-02,
+     -1.72815435e-02,   1.35012688e-02,  -1.08927619e-02,   9.02237184e-03,
+     -7.63727259e-03,   6.58483105e-03,  -5.76706184e-03,   5.12006786e-03,
+     -4.60112654e-03,   4.17934125e-03,  -3.83267412e-03,   3.54650151e-03,
+     -3.30836512e-03,   3.10952030e-03,  -2.94351298e-03,   2.80511752e-03,
+     -2.69039162e-03,   2.59574503e-03,  -2.51914933e-03,   2.45943666e-03,
+     -2.41351128e-03,   2.37989426e-03,  -2.36272812e-03,   2.35617161e-03 };
+
+
+// REDFT10
+float fftdata_r2r_REDFT10_y32[] = {
+      3.29337406e+00,   9.03484726e+00,   1.47411804e+01,   7.09897327e+00,
+      4.95285779e-01,  -2.08808795e-01,  -2.04274148e-01,  -9.34088752e-02,
+     -1.00554205e-01,  -5.08193560e-02,  -6.01783060e-02,  -3.20817754e-02,
+     -3.97967622e-02,  -2.19255462e-02,  -2.79445965e-02,  -1.57238189e-02,
+     -2.03754716e-02,  -1.16170188e-02,  -1.51962955e-02,  -8.72472394e-03,
+     -1.14511019e-02,  -6.57916535e-03,  -8.60831141e-03,  -4.91373613e-03,
+     -6.35385327e-03,  -3.56272981e-03,  -4.48495522e-03,  -2.41775438e-03,
+     -2.86681950e-03,  -1.40333176e-03,  -1.39808655e-03,  -4.60088253e-04 };
+
+
+// REDFT01
+float fftdata_r2r_REDFT01_y32[] = {
+      3.84605742e+00,   1.21377001e+01,   1.21945066e+01,   3.48508573e+00,
+      3.57804537e-01,  -4.74350490e-02,   4.97081988e-02,  -4.42513935e-02,
+      3.93650271e-02,  -3.51234674e-02,   3.14703509e-02,  -2.83105075e-02,
+      2.55550444e-02,  -2.31280141e-02,   2.09678225e-02,  -1.90287828e-02,
+      1.72705650e-02,  -1.56631656e-02,   1.41822286e-02,  -1.28069818e-02,
+      1.15218461e-02,  -1.03116995e-02,   9.16616712e-03,  -8.07419885e-03,
+      7.02799764e-03,  -6.01985678e-03,   5.04441932e-03,  -4.09346819e-03,
+      3.16351652e-03,  -2.24826857e-03,   1.34513900e-03,  -4.47750092e-04 };
+
+
+// REDFT11
+float fftdata_r2r_REDFT11_y32[] = {
+      4.92722511e+00,   1.32802725e+01,   1.21282454e+01,   2.68794346e+00,
+     -7.75258020e-02,  -2.84586310e-01,  -7.88819566e-02,  -1.52703851e-01,
+     -2.31686458e-02,  -1.01293720e-01,  -3.44139524e-03,  -7.46235624e-02,
+      5.45739289e-03,  -5.86049184e-02,   1.00630848e-02,  -4.80872169e-02,
+      1.27020460e-02,  -4.07497510e-02,   1.43614262e-02,  -3.54013182e-02,
+      1.55183356e-02,  -3.13723050e-02,   1.64255053e-02,  -2.82586850e-02,
+      1.72268860e-02,  -2.58033480e-02,   1.80160627e-02,  -2.38352567e-02,
+      1.88578963e-02,  -2.22345591e-02,   1.98070407e-02,  -2.09140405e-02 };
+
+
+// RODFT00
+float fftdata_r2r_RODFT00_y32[] = {
+     -3.80376291e+00,   4.75984573e+00,   1.26453495e+01,   8.96690750e+00,
+      5.51104832e+00,   3.91941833e+00,   3.23827934e+00,   2.63059449e+00,
+      2.33582568e+00,   1.97329426e+00,   1.80615771e+00,   1.55536008e+00,
+      1.44624805e+00,   1.25815463e+00,   1.17955160e+00,   1.03102541e+00,
+      9.69759405e-01,   8.48199964e-01,   7.97134459e-01,   6.94987476e-01,
+      6.49889886e-01,   5.62294245e-01,   5.20456970e-01,   4.44106996e-01,
+      4.03675139e-01,   3.36212277e-01,   2.95823514e-01,   2.35492110e-01,
+      1.94067836e-01,   1.39503479e-01,   9.61183906e-02,   4.62093353e-02 };
+
+
+// RODFT10
+float fftdata_r2r_RODFT10_y32[] = {
+     -4.18236399e+00,   4.10336685e+00,   1.17561054e+01,   8.40457535e+00,
+      5.42492485e+00,   3.95339370e+00,   3.35005546e+00,   2.76342392e+00,
+      2.52070665e+00,   2.17183208e+00,   2.05204034e+00,   1.81259131e+00,
+      1.75025475e+00,   1.57222724e+00,   1.54146743e+00,   1.40205300e+00,
+      1.39061260e+00,   1.27737224e+00,   1.27877247e+00,   1.18427241e+00,
+      1.19484186e+00,   1.11434531e+00,   1.13192439e+00,   1.06226850e+00,
+      1.08557940e+00,   1.02458429e+00,   1.05290902e+00,   9.99043226e-01,
+      1.03204751e+00,   9.84234095e-01,   1.02187860e+00,   9.79380608e-01 };
+
+
+// RODFT01
+float fftdata_r2r_RODFT01_y32[] = {
+     -3.14800310e+00,  -4.45067883e-01,   1.08461933e+01,   1.10677624e+01,
+      6.34346628e+00,   4.38031149e+00,   3.35651875e+00,   2.81504726e+00,
+      2.36018848e+00,   2.07406235e+00,   1.80141354e+00,   1.61707830e+00,
+      1.43000770e+00,   1.29786718e+00,   1.15834939e+00,   1.05674410e+00,
+      9.46406841e-01,   8.64174008e-01,   7.72926450e-01,   7.03647554e-01,
+      6.25416100e-01,   5.65090656e-01,   4.95949268e-01,   4.41907734e-01,
+      3.79158407e-01,   3.29515547e-01,   2.71191031e-01,   2.24530101e-01,
+      1.69109464e-01,   1.24305725e-01,   7.05337524e-02,   2.66184807e-02 };
+
+
+// RODFT11
+float fftdata_r2r_RODFT11_y32[] = {
+     -3.24692512e+00,  -1.44892704e+00,   9.45565414e+00,   1.07434196e+01,
+      6.54239464e+00,   4.59986401e+00,   3.58518982e+00,   3.05150127e+00,
+      2.61707282e+00,   2.34726048e+00,   2.09873915e+00,   1.93409348e+00,
+      1.77332234e+00,   1.66290724e+00,   1.55142403e+00,   1.47315228e+00,
+      1.39249706e+00,   1.33510482e+00,   1.27525985e+00,   1.23238277e+00,
+      1.18744755e+00,   1.15523088e+00,   1.12153363e+00,   1.09753525e+00,
+      1.07270765e+00,   1.05533838e+00,   1.03783357e+00,   1.02606010e+00,
+      1.01488125e+00,   1.00805402e+00,   1.00260448e+00,   1.00036693e+00 };
+
diff --git a/src/fft/tests/data/fft_r2rdata_8.c b/src/fft/tests/data/fft_r2rdata_8.c
new file mode 100644
index 0000000..352a56c
--- /dev/null
+++ b/src/fft/tests/data/fft_r2rdata_8.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft r2r (real-to-real) data
+//
+
+// 8-point real even/odd dft data
+float fftdata_r2r_x8[] = {
+      0.0000000000,
+      1.0000000000,
+      2.0000000000,
+      3.0000000000,
+      4.0000000000,
+      5.0000000000,
+      6.0000000000,
+      7.0000000000};
+
+// REDFT00
+float fftdata_r2r_REDFT00_y8[] = {
+     49.0000000000,
+    -20.1956693581,
+     -0.0000000000,
+     -2.5724165284,
+     -0.0000000000,
+     -1.2319141135,
+     -0.0000000000,
+     -1.0000000000};
+
+// REDFT10
+float fftdata_r2r_REDFT10_y8[] = {
+     56.0000000000,
+    -25.7692920908,
+      0.0000000000,
+     -2.6938192036,
+      0.0000000000,
+     -0.8036116149,
+      0.0000000000,
+     -0.2028092910};
+
+// REDFT01
+float fftdata_r2r_REDFT01_y8[] = {
+     29.1819286410,
+    -32.3061136840,
+     12.7168729872,
+    -10.9904036256,
+      5.7286734878,
+     -4.9189401648,
+      1.8807638636,
+     -1.2927815051};
+
+// REDFT11
+float fftdata_r2r_REDFT11_y8[] = {
+     24.7243981823,
+    -31.5148535947,
+     13.9257769120,
+    -12.7826885392,
+      9.1714938314,
+     -8.8071984223,
+      7.6789810021,
+     -7.5857732734};
+
+// RODFT00
+float fftdata_r2r_RODFT00_y8[] = {
+     39.6989727373,
+    -24.7272967751,
+     12.1243556530,
+    -10.7257823333,
+      5.8736974182,
+     -5.1961524227,
+      2.5477916399,
+     -1.5869428264};
+
+// RODFT10
+float fftdata_r2r_RODFT10_y8[] = {
+     35.8808162684,
+    -20.9050074380,
+     12.5996671239,
+    -11.3137084990,
+      8.4188284171,
+     -8.6591376023,
+      7.1371381075,
+     -8.0000000000};
+
+// RODFT01
+float fftdata_r2r_RODFT01_y8[] = {
+     41.8902640723,
+     -9.2302062214,
+      0.3792058953,
+     -2.4608789465,
+      0.0160780480,
+     -1.1773622132,
+      0.2426629216,
+     -0.6033416816};
+
+// RODFT11
+float fftdata_r2r_RODFT11_y8[] = {
+     46.6916824794,
+     -7.4005942194,
+      0.9237106918,
+     -1.7485238108,
+     -0.1159888643,
+     -0.8699819349,
+     -0.3640003929,
+     -0.5519032668};
+
diff --git a/src/fft/tests/fft_composite_autotest.c b/src/fft/tests/fft_composite_autotest.c
new file mode 100644
index 0000000..d2cd75c
--- /dev/null
+++ b/src/fft/tests/fft_composite_autotest.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_composite_autotest.c : test FFTs of 'composite' length (not
+//   prime, not of form 2^m)
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/fft/tests/fft_runtest.h"
+
+// 
+// AUTOTESTS: n-point ffts
+//
+void autotest_fft_10()  { fft_test( fft_test_x10,   fft_test_y10,   10); }
+void autotest_fft_21()  { fft_test( fft_test_x21,   fft_test_y21,   21); }
+void autotest_fft_22()  { fft_test( fft_test_x22,   fft_test_y22,   22); }
+void autotest_fft_24()  { fft_test( fft_test_x24,   fft_test_y24,   24); }
+void autotest_fft_26()  { fft_test( fft_test_x26,   fft_test_y26,   26); }
+void autotest_fft_30()  { fft_test( fft_test_x30,   fft_test_y30,   30); }
+void autotest_fft_35()  { fft_test( fft_test_x35,   fft_test_y35,   35); }
+void autotest_fft_36()  { fft_test( fft_test_x36,   fft_test_y36,   36); }
+void autotest_fft_48()  { fft_test( fft_test_x48,   fft_test_y48,   48); }
+void autotest_fft_63()  { fft_test( fft_test_x63,   fft_test_y63,   63); }
+void autotest_fft_92()  { fft_test( fft_test_x92,   fft_test_y92,   92); }
+void autotest_fft_96()  { fft_test( fft_test_x96,   fft_test_y96,   96); }
+
+void autotest_fft_120() { fft_test( fft_test_x120,  fft_test_y120, 120); }
+void autotest_fft_130() { fft_test( fft_test_x130,  fft_test_y130, 130); }
+void autotest_fft_192() { fft_test( fft_test_x192,  fft_test_y192, 192); }
+
diff --git a/src/fft/tests/fft_prime_autotest.c b/src/fft/tests/fft_prime_autotest.c
new file mode 100644
index 0000000..186249e
--- /dev/null
+++ b/src/fft/tests/fft_prime_autotest.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_prime_autotest.c : test FFTs of prime length
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/fft/tests/fft_runtest.h"
+
+// 
+// AUTOTESTS: n-point ffts
+//
+void autotest_fft_17()  { fft_test( fft_test_x17,   fft_test_y17,   17); }
+void autotest_fft_43()  { fft_test( fft_test_x43,   fft_test_y43,   43); }
+void autotest_fft_79()  { fft_test( fft_test_x79,   fft_test_y79,   79); }
+void autotest_fft_157() { fft_test( fft_test_x157,  fft_test_y157, 157); }
+void autotest_fft_317() { fft_test( fft_test_x317,  fft_test_y317, 317); }
+void autotest_fft_509() { fft_test( fft_test_x509,  fft_test_y509, 509); }
diff --git a/src/fft/tests/fft_r2r_autotest.c b/src/fft/tests/fft_r2r_autotest.c
new file mode 100644
index 0000000..0d4331b
--- /dev/null
+++ b/src/fft/tests/fft_r2r_autotest.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// include data sets
+#include "fft_runtest.h"
+
+// autotest helper function
+void fft_r2r_test(float *      _x,
+                  float *      _test,
+                  unsigned int _n,
+                  unsigned int _kind)
+{
+    int _flags = 0;
+    float tol=1e-4f;
+
+    unsigned int i;
+
+    float y[_n];
+
+    // compute real even/odd FFT
+    fftplan q = fft_create_plan_r2r_1d(_n, _x, y, _kind, _flags);
+    fft_execute(q);
+
+    // print results
+    if (liquid_autotest_verbose) {
+        printf("%12s %12s\n", "expected", "actual");
+        for (i=0; i<_n; i++)
+            printf("%12.8f %12.8f\n", _test[i], y[i]);
+    }
+
+    // validate results
+    for (i=0; i<_n; i++)
+        CONTEND_DELTA( y[i], _test[i], tol);
+
+    // destroy plans
+    fft_destroy_plan(q);
+}
+
+
+// 
+// AUTOTESTS: 8-point real-to-real ffts
+//
+
+void autotest_fft_r2r_REDFT00_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_REDFT00_y8, 8, LIQUID_FFT_REDFT00); }
+void autotest_fft_r2r_REDFT10_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_REDFT10_y8, 8, LIQUID_FFT_REDFT10); }
+void autotest_fft_r2r_REDFT01_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_REDFT01_y8, 8, LIQUID_FFT_REDFT01); }
+void autotest_fft_r2r_REDFT11_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_REDFT11_y8, 8, LIQUID_FFT_REDFT11); }
+
+void autotest_fft_r2r_RODFT00_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_RODFT00_y8, 8, LIQUID_FFT_RODFT00); }
+void autotest_fft_r2r_RODFT10_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_RODFT10_y8, 8, LIQUID_FFT_RODFT10); }
+void autotest_fft_r2r_RODFT01_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_RODFT01_y8, 8, LIQUID_FFT_RODFT01); }
+void autotest_fft_r2r_RODFT11_n8()  { fft_r2r_test(fftdata_r2r_x8, fftdata_r2r_RODFT11_y8, 8, LIQUID_FFT_RODFT11); }
+
+
+// 
+// AUTOTESTS: 32-point real-to-real ffts
+//
+
+void autotest_fft_r2r_REDFT00_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_REDFT00_y32, 32, LIQUID_FFT_REDFT00); }
+void autotest_fft_r2r_REDFT10_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_REDFT10_y32, 32, LIQUID_FFT_REDFT10); }
+void autotest_fft_r2r_REDFT01_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_REDFT01_y32, 32, LIQUID_FFT_REDFT01); }
+void autotest_fft_r2r_REDFT11_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_REDFT11_y32, 32, LIQUID_FFT_REDFT11); }
+
+void autotest_fft_r2r_RODFT00_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_RODFT00_y32, 32, LIQUID_FFT_RODFT00); }
+void autotest_fft_r2r_RODFT10_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_RODFT10_y32, 32, LIQUID_FFT_RODFT10); }
+void autotest_fft_r2r_RODFT01_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_RODFT01_y32, 32, LIQUID_FFT_RODFT01); }
+void autotest_fft_r2r_RODFT11_n32()  { fft_r2r_test(fftdata_r2r_x32, fftdata_r2r_RODFT11_y32, 32, LIQUID_FFT_RODFT11); }
+
+
+// 
+// AUTOTESTS: 27-point real-to-real ffts
+//
+
+void autotest_fft_r2r_REDFT00_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_REDFT00_y27, 27, LIQUID_FFT_REDFT00); }
+void autotest_fft_r2r_REDFT10_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_REDFT10_y27, 27, LIQUID_FFT_REDFT10); }
+void autotest_fft_r2r_REDFT01_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_REDFT01_y27, 27, LIQUID_FFT_REDFT01); }
+void autotest_fft_r2r_REDFT11_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_REDFT11_y27, 27, LIQUID_FFT_REDFT11); }
+
+void autotest_fft_r2r_RODFT00_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_RODFT00_y27, 27, LIQUID_FFT_RODFT00); }
+void autotest_fft_r2r_RODFT10_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_RODFT10_y27, 27, LIQUID_FFT_RODFT10); }
+void autotest_fft_r2r_RODFT01_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_RODFT01_y27, 27, LIQUID_FFT_RODFT01); }
+void autotest_fft_r2r_RODFT11_n27()  { fft_r2r_test(fftdata_r2r_x27, fftdata_r2r_RODFT11_y27, 27, LIQUID_FFT_RODFT11); }
+
diff --git a/src/fft/tests/fft_radix2_autotest.c b/src/fft/tests/fft_radix2_autotest.c
new file mode 100644
index 0000000..c14883d
--- /dev/null
+++ b/src/fft/tests/fft_radix2_autotest.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_radix2_autotest.c : test power-of-two transforms
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/fft/tests/fft_runtest.h"
+
+// 
+// AUTOTESTS: power-of-two transforms
+//
+void autotest_fft_2()       { fft_test( fft_test_x2,   fft_test_y2,      2);     }
+void autotest_fft_4()       { fft_test( fft_test_x4,   fft_test_y4,      4);     }
+void autotest_fft_8()       { fft_test( fft_test_x8,   fft_test_y8,      8);     }
+void autotest_fft_16()      { fft_test( fft_test_x16,  fft_test_y16,     16);    }
+void autotest_fft_32()      { fft_test( fft_test_x32,  fft_test_y32,     32);    }
+void autotest_fft_64()      { fft_test( fft_test_x64,  fft_test_y64,     64);    }
+
diff --git a/src/fft/tests/fft_runtest.c b/src/fft/tests/fft_runtest.c
new file mode 100644
index 0000000..9be4739
--- /dev/null
+++ b/src/fft/tests/fft_runtest.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _x      :   fft input array
+//  _test   :   expected fft output
+//  _n      :   fft size
+void fft_test(float complex * _x,
+              float complex * _test,
+              unsigned int    _n)
+{
+    int _method = 0;
+    float tol=2e-4f;
+
+    unsigned int i;
+
+    float complex y[_n], z[_n];
+
+    // compute FFT
+    fftplan pf = fft_create_plan(_n, _x, y, LIQUID_FFT_FORWARD, _method);
+    fft_execute(pf);
+
+    // compute IFFT
+    fftplan pr = fft_create_plan(_n, y, z, LIQUID_FFT_BACKWARD, _method);
+    fft_execute(pr);
+
+    // normalize inverse
+    for (i=0; i<_n; i++)
+        z[i] /= (float) _n;
+
+    // validate results
+    float fft_error, ifft_error;
+    for (i=0; i<_n; i++) {
+        fft_error = cabsf( y[i] - _test[i] );
+        ifft_error = cabsf( _x[i] - z[i] );
+        CONTEND_DELTA( fft_error, 0, tol);
+        CONTEND_DELTA( ifft_error, 0, tol);
+    }
+
+    // destroy plans
+    fft_destroy_plan(pf);
+    fft_destroy_plan(pr);
+}
+
diff --git a/src/fft/tests/fft_runtest.h b/src/fft/tests/fft_runtest.h
new file mode 100644
index 0000000..92a0e83
--- /dev/null
+++ b/src/fft/tests/fft_runtest.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fft data definitions
+//
+
+#ifndef __LIQUID_FFT_DATA_H__
+#define __LIQUID_FFT_DATA_H__
+
+// autotest helper function
+//  _x      :   fft input array
+//  _test   :   expected fft output
+//  _n      :   fft size
+void fft_test(float complex * _x,
+              float complex * _test,
+              unsigned int    _n);
+
+// 
+// autotest datasets
+//
+
+//
+extern float complex fft_test_x2[];
+extern float complex fft_test_y2[];
+
+extern float complex fft_test_x3[];
+extern float complex fft_test_y3[];
+
+extern float complex fft_test_x4[];
+extern float complex fft_test_y4[];
+
+extern float complex fft_test_x5[];
+extern float complex fft_test_y5[];
+
+extern float complex fft_test_x6[];
+extern float complex fft_test_y6[];
+
+extern float complex fft_test_x7[];
+extern float complex fft_test_y7[];
+
+extern float complex fft_test_x8[];
+extern float complex fft_test_y8[];
+
+extern float complex fft_test_x9[];
+extern float complex fft_test_y9[];
+
+//
+extern float complex fft_test_x10[];
+extern float complex fft_test_y10[];
+
+extern float complex fft_test_x16[];
+extern float complex fft_test_y16[];
+
+extern float complex fft_test_x17[];
+extern float complex fft_test_y17[];
+
+extern float complex fft_test_x20[];
+extern float complex fft_test_y20[];
+
+extern float complex fft_test_x21[];
+extern float complex fft_test_y21[];
+
+extern float complex fft_test_x22[];
+extern float complex fft_test_y22[];
+
+extern float complex fft_test_x24[];
+extern float complex fft_test_y24[];
+
+extern float complex fft_test_x26[];
+extern float complex fft_test_y26[];
+
+extern float complex fft_test_x30[];
+extern float complex fft_test_y30[];
+
+extern float complex fft_test_x32[];
+extern float complex fft_test_y32[];
+
+extern float complex fft_test_x35[];
+extern float complex fft_test_y35[];
+
+extern float complex fft_test_x36[];
+extern float complex fft_test_y36[];
+
+extern float complex fft_test_x43[];
+extern float complex fft_test_y43[];
+
+extern float complex fft_test_x48[];
+extern float complex fft_test_y48[];
+
+extern float complex fft_test_x63[];
+extern float complex fft_test_y63[];
+
+extern float complex fft_test_x64[];
+extern float complex fft_test_y64[];
+
+extern float complex fft_test_x79[];
+extern float complex fft_test_y79[];
+
+extern float complex fft_test_x92[];
+extern float complex fft_test_y92[];
+
+extern float complex fft_test_x96[];
+extern float complex fft_test_y96[];
+
+//
+extern float complex fft_test_x120[];
+extern float complex fft_test_y120[];
+
+extern float complex fft_test_x130[];
+extern float complex fft_test_y130[];
+
+extern float complex fft_test_x157[];
+extern float complex fft_test_y157[];
+
+extern float complex fft_test_x192[];
+extern float complex fft_test_y192[];
+
+extern float complex fft_test_x317[];
+extern float complex fft_test_y317[];
+
+extern float complex fft_test_x509[];
+extern float complex fft_test_y509[];
+
+// 8-point real even/odd dft data
+extern float fftdata_r2r_x8[];
+extern float fftdata_r2r_REDFT00_y8[];
+extern float fftdata_r2r_REDFT10_y8[];
+extern float fftdata_r2r_REDFT01_y8[];
+extern float fftdata_r2r_REDFT11_y8[];
+extern float fftdata_r2r_RODFT00_y8[];
+extern float fftdata_r2r_RODFT10_y8[];
+extern float fftdata_r2r_RODFT01_y8[];
+extern float fftdata_r2r_RODFT11_y8[];
+
+// 27-point real even/odd dft data
+extern float fftdata_r2r_x27[];
+extern float fftdata_r2r_REDFT00_y27[];
+extern float fftdata_r2r_REDFT10_y27[];
+extern float fftdata_r2r_REDFT01_y27[];
+extern float fftdata_r2r_REDFT11_y27[];
+extern float fftdata_r2r_RODFT00_y27[];
+extern float fftdata_r2r_RODFT10_y27[];
+extern float fftdata_r2r_RODFT01_y27[];
+extern float fftdata_r2r_RODFT11_y27[];
+
+// 32-point real even/odd dft data
+extern float fftdata_r2r_x32[];
+extern float fftdata_r2r_REDFT00_y32[];
+extern float fftdata_r2r_REDFT10_y32[];
+extern float fftdata_r2r_REDFT01_y32[];
+extern float fftdata_r2r_REDFT11_y32[];
+extern float fftdata_r2r_RODFT00_y32[];
+extern float fftdata_r2r_RODFT10_y32[];
+extern float fftdata_r2r_RODFT01_y32[];
+extern float fftdata_r2r_RODFT11_y32[];
+
+#endif // __LIQUID_FFT_DATA_H__
diff --git a/src/fft/tests/fft_shift_autotest.c b/src/fft/tests/fft_shift_autotest.c
new file mode 100644
index 0000000..5c5f147
--- /dev/null
+++ b/src/fft/tests/fft_shift_autotest.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+void autotest_fft_shift_4()
+{
+    float complex x[] = {
+        0 + 0*_Complex_I,
+        1 + 1*_Complex_I,
+        2 + 2*_Complex_I,
+        3 + 3*_Complex_I
+    };
+
+    float complex test[] = {
+        2 + 2*_Complex_I,
+        3 + 3*_Complex_I,
+        0 + 0*_Complex_I,
+        1 + 1*_Complex_I
+    };
+
+    fft_shift(x,4);
+
+    CONTEND_SAME_DATA(x,test,4*sizeof(float complex));
+}
+
+void autotest_fft_shift_8()
+{
+    float complex x[] = {
+        0 + 0*_Complex_I,
+        1 + 1*_Complex_I,
+        2 + 2*_Complex_I,
+        3 + 3*_Complex_I,
+        4 + 4*_Complex_I,
+        5 + 5*_Complex_I,
+        6 + 6*_Complex_I,
+        7 + 7*_Complex_I
+    };
+
+    float complex test[] = {
+        4 + 4*_Complex_I,
+        5 + 5*_Complex_I,
+        6 + 6*_Complex_I,
+        7 + 7*_Complex_I,
+        0 + 0*_Complex_I,
+        1 + 1*_Complex_I,
+        2 + 2*_Complex_I,
+        3 + 3*_Complex_I
+    };
+
+    fft_shift(x,8);
+
+    CONTEND_SAME_DATA(x,test,8*sizeof(float complex));
+}
+
diff --git a/src/fft/tests/fft_small_autotest.c b/src/fft/tests/fft_small_autotest.c
new file mode 100644
index 0000000..03d0891
--- /dev/null
+++ b/src/fft/tests/fft_small_autotest.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fft_small_autotest.c : test small transforms
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/fft/tests/fft_runtest.h"
+
+// 
+// AUTOTESTS: small FFTs
+//
+void autotest_fft_3()       { fft_test( fft_test_x4,   fft_test_y4,      4);     }
+void autotest_fft_5()       { fft_test( fft_test_x5,   fft_test_y5,      5);     }
+void autotest_fft_6()       { fft_test( fft_test_x6,   fft_test_y6,      6);     }
+void autotest_fft_7()       { fft_test( fft_test_x7,   fft_test_y7,      7);     }
+void autotest_fft_9()       { fft_test( fft_test_x9,   fft_test_y9,      9);     }
+
diff --git a/src/fft/tests/gen_fft_data.m b/src/fft/tests/gen_fft_data.m
new file mode 100644
index 0000000..4af2db3
--- /dev/null
+++ b/src/fft/tests/gen_fft_data.m
@@ -0,0 +1,68 @@
+% generate fft data for autotests
+
+function gen_fft_data(n);
+n = round(n);
+
+x = randn(1,n) + j*randn(1,n);
+y = fft(x);
+
+% print results
+
+filename = ['fft_data_' num2str(n) '.c'];
+fid = fopen(filename,'w');
+
+fprintf(fid,'/* Copyright (c) 2007 - 2015 Joseph Gaeddert\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * Permission is hereby granted, free of charge, to any person obtaining a copy\n');
+fprintf(fid,' * of this software and associated documentation files (the "Software"), to deal\n');
+fprintf(fid,' * in the Software without restriction, including without limitation the rights\n');
+fprintf(fid,' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n');
+fprintf(fid,' * copies of the Software, and to permit persons to whom the Software is\n');
+fprintf(fid,' * furnished to do so, subject to the following conditions:\n');
+fprintf(fid,' * \n');
+fprintf(fid,' * The above copyright notice and this permission notice shall be included in\n');
+fprintf(fid,' * all copies or substantial portions of the Software.\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n');
+fprintf(fid,' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n');
+fprintf(fid,' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n');
+fprintf(fid,' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n');
+fprintf(fid,' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n');
+fprintf(fid,' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n');
+fprintf(fid,' * THE SOFTWARE.\n');
+fprintf(fid,' */\n');
+fprintf(fid,'\n');
+fprintf(fid,'//\n');
+fprintf(fid,'// autotest fft data for %u-point transform\n', n);
+fprintf(fid,'//\n');
+fprintf(fid,'\n');
+fprintf(fid,'#include <complex.h>\n');
+fprintf(fid,'\n');
+
+
+fprintf(fid,'float complex fft_test_x%d[] = {\n',n);
+for i=1:n,
+    fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(x(i)), imag(x(i)));
+    if i==n,
+        fprintf(fid,'};\n');
+    else,
+        fprintf(fid,',\n');
+    end;
+end;
+
+fprintf(fid,'\n');
+
+fprintf(fid,'float complex fft_test_y%d[] = {\n',n);
+for i=1:n,
+    fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(y(i)), imag(y(i)));
+    if i==n,
+        fprintf(fid,'};\n');
+    else,
+        fprintf(fid,',\n');
+    end;
+end;
+
+fprintf(fid,'\n');
+
+fclose(fid);
+printf('results written to %s\n', filename);
diff --git a/src/fft/tests/gen_fft_files.m b/src/fft/tests/gen_fft_files.m
new file mode 100644
index 0000000..5e8c85d
--- /dev/null
+++ b/src/fft/tests/gen_fft_files.m
@@ -0,0 +1,11 @@
+% generate fft data for autotests
+
+n = [2, 3, 4, 5, 6, 7, 8, 9,...
+     10, 16, 17, 20, 21, 22, 24, 26,...
+     30, 32, 35, 36, 43, 48, 63, 64, 79, 92, 96,...
+     120, 130, 157, 192, 317, 509]
+
+for i=1:length(n),
+    gen_fft_data(n(i));
+end;
+
diff --git a/src/filter/bench/fftfilt_crcf_benchmark.c b/src/filter/bench/fftfilt_crcf_benchmark.c
new file mode 100644
index 0000000..06c6fad
--- /dev/null
+++ b/src/filter/bench/fftfilt_crcf_benchmark.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void fftfilt_crcf_bench(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int        _n)
+{
+    // adjust number of iterations:
+    *_num_iterations *= 100;
+
+    if      (_n <  6) *_num_iterations /= 120;
+    else if (_n < 12) *_num_iterations /= 40;
+    else              *_num_iterations /= 5*_n;
+
+    // generate coefficients
+    unsigned int h_len = _n+1;
+    float h[h_len];
+    unsigned long int i;
+    for (i=0; i<h_len; i++)
+        h[i] = randnf();
+
+    // create filter object
+    fftfilt_crcf q = fftfilt_crcf_create(h,h_len,_n);
+
+    // generate input vector
+    float complex x[_n + 4];
+    for (i=0; i<_n+4; i++)
+        x[i] = randnf() + _Complex_I*randnf();
+
+    // output vector
+    float complex y[_n];
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fftfilt_crcf_execute(q, &x[0], y);
+        fftfilt_crcf_execute(q, &x[1], y);
+        fftfilt_crcf_execute(q, &x[2], y);
+        fftfilt_crcf_execute(q, &x[3], y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // scale number of iterations: loop unrolled 4 times, _n samples/block
+    *_num_iterations *= 4 * _n;
+
+    // destroy filter object
+    fftfilt_crcf_destroy(q);
+}
+
+#define FFTFILT_CRCF_BENCHMARK_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ fftfilt_crcf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_fftfilt_crcf_4    FFTFILT_CRCF_BENCHMARK_API(4)
+void benchmark_fftfilt_crcf_8    FFTFILT_CRCF_BENCHMARK_API(8)
+void benchmark_fftfilt_crcf_16   FFTFILT_CRCF_BENCHMARK_API(16)
+void benchmark_fftfilt_crcf_32   FFTFILT_CRCF_BENCHMARK_API(32)
+void benchmark_fftfilt_crcf_64   FFTFILT_CRCF_BENCHMARK_API(64)
+
diff --git a/src/filter/bench/firdecim_crcf_benchmark.c b/src/filter/bench/firdecim_crcf_benchmark.c
new file mode 100644
index 0000000..7df1927
--- /dev/null
+++ b/src/filter/bench/firdecim_crcf_benchmark.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void firdecim_crcf_bench(struct rusage *     _start,
+                         struct rusage *     _finish,
+                         unsigned long int * _num_iterations,
+                         unsigned int        _M,
+                         unsigned int        _h_len)
+{
+    // normalize number of iterations
+    *_num_iterations /= _h_len;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float h[_h_len];
+    unsigned int i;
+    for (i=0; i<_h_len; i++)
+        h[i] = 1.0f;
+
+    firdecim_crcf q = firdecim_crcf_create(_M,h,_h_len);
+
+    // initialize input
+    float complex x[_M];
+    for (i=0; i<_M; i++)
+        x[i] = (i%2) ? 1.0f : -1.0f;
+
+    float complex y;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        firdecim_crcf_execute(q, x, &y);
+        firdecim_crcf_execute(q, x, &y);
+        firdecim_crcf_execute(q, x, &y);
+        firdecim_crcf_execute(q, x, &y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firdecim_crcf_destroy(q);
+}
+
+#define FIRDECIM_CRCF_BENCHMARK_API(M,H_LEN)    \
+(   struct rusage *_start,                      \
+    struct rusage *_finish,                     \
+    unsigned long int *_num_iterations)         \
+{ firdecim_crcf_bench(_start, _finish, _num_iterations, M, H_LEN); }
+
+void benchmark_firdecim_crcf_m2_h8     FIRDECIM_CRCF_BENCHMARK_API(2, 8)
+void benchmark_firdecim_crcf_m4_h16    FIRDECIM_CRCF_BENCHMARK_API(4, 16)
+void benchmark_firdecim_crcf_m8_h32    FIRDECIM_CRCF_BENCHMARK_API(8, 32)
+void benchmark_firdecim_crcf_m16_h64   FIRDECIM_CRCF_BENCHMARK_API(16,64)
+void benchmark_firdecim_cccf_m32_h128  FIRDECIM_CRCF_BENCHMARK_API(32,128)
+
diff --git a/src/filter/bench/firfilt_crcf_benchmark.c b/src/filter/bench/firfilt_crcf_benchmark.c
new file mode 100644
index 0000000..f4d1a86
--- /dev/null
+++ b/src/filter/bench/firfilt_crcf_benchmark.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void firfilt_crcf_bench(struct rusage *_start,
+                        struct rusage *_finish,
+                        unsigned long int *_num_iterations,
+                        unsigned int _n)
+{
+    // adjust number of iterations:
+    // cycles/trial ~ 107 + 4.3*_n
+    *_num_iterations *= 1000;
+    *_num_iterations /= (unsigned int)(107+4.3*_n);
+
+    // generate coefficients
+    float h[_n];
+    unsigned long int i;
+    for (i=0; i<_n; i++)
+        h[i] = randnf();
+
+    // create filter object
+    firfilt_crcf f = firfilt_crcf_create(h,_n);
+
+    // generate input vector
+    float complex x[4];
+    for (i=0; i<4; i++)
+        x[i] = randnf() + _Complex_I*randnf();
+
+    // output vector
+    float complex y[4];
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        firfilt_crcf_push(f, x[0]); firfilt_crcf_execute(f, &y[0]);
+        firfilt_crcf_push(f, x[1]); firfilt_crcf_execute(f, &y[1]);
+        firfilt_crcf_push(f, x[2]); firfilt_crcf_execute(f, &y[2]);
+        firfilt_crcf_push(f, x[3]); firfilt_crcf_execute(f, &y[3]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firfilt_crcf_destroy(f);
+}
+
+#define FIRFILT_CRCF_BENCHMARK_API(N)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ firfilt_crcf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_firfilt_crcf_4    FIRFILT_CRCF_BENCHMARK_API(4)
+void benchmark_firfilt_crcf_8    FIRFILT_CRCF_BENCHMARK_API(8)
+void benchmark_firfilt_crcf_16   FIRFILT_CRCF_BENCHMARK_API(16)
+void benchmark_firfilt_crcf_32   FIRFILT_CRCF_BENCHMARK_API(32)
+void benchmark_firfilt_crcf_64   FIRFILT_CRCF_BENCHMARK_API(64)
+
diff --git a/src/filter/bench/firhilb_benchmark.c b/src/filter/bench/firhilb_benchmark.c
new file mode 100644
index 0000000..b427a69
--- /dev/null
+++ b/src/filter/bench/firhilb_benchmark.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void firhilbf_decim_bench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations,
+    unsigned int _m)
+{
+    // normalize number of trials
+    *_num_iterations *= 20;
+    *_num_iterations /= liquid_nextpow2(_m+1);
+
+    // create hilber transform object
+    firhilbf q = firhilbf_create(_m,60.0f);
+
+    float x[] = {1.0f, -1.0f};
+    float complex y;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        firhilbf_decim_execute(q,x,&y);
+        firhilbf_decim_execute(q,x,&y);
+        firhilbf_decim_execute(q,x,&y);
+        firhilbf_decim_execute(q,x,&y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firhilbf_destroy(q);
+}
+
+#define FIRHILB_DECIM_BENCHMARK_API(M)  \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ firhilbf_decim_bench(_start, _finish, _num_iterations, M); }
+
+void benchmark_firhilbf_decim_m3    FIRHILB_DECIM_BENCHMARK_API(3)  // m=3
+void benchmark_firhilbf_decim_m5    FIRHILB_DECIM_BENCHMARK_API(5)  // m=5
+void benchmark_firhilbf_decim_m9    FIRHILB_DECIM_BENCHMARK_API(9)  // m=9
+void benchmark_firhilbf_decim_m13   FIRHILB_DECIM_BENCHMARK_API(13) // m=13
+
diff --git a/src/filter/bench/firinterp_crcf_benchmark.c b/src/filter/bench/firinterp_crcf_benchmark.c
new file mode 100644
index 0000000..049ecec
--- /dev/null
+++ b/src/filter/bench/firinterp_crcf_benchmark.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void firinterp_crcf_bench(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations,
+                          unsigned int _M,
+                          unsigned int _h_len)
+{
+    // normalize number of iterations
+    *_num_iterations *= 80;
+    *_num_iterations /= _h_len;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float h[_h_len];
+    unsigned int i;
+    for (i=0; i<_h_len; i++)
+        h[i] = 1.0f;
+
+    firinterp_crcf q = firinterp_crcf_create(_M,h,_h_len);
+
+    float complex y[_M];
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        firinterp_crcf_execute(q,1.0f,y);
+        firinterp_crcf_execute(q,1.0f,y);
+        firinterp_crcf_execute(q,1.0f,y);
+        firinterp_crcf_execute(q,1.0f,y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firinterp_crcf_destroy(q);
+}
+
+#define FIRINTERP_CRCF_BENCHMARK_API(M,H_LEN)  \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ firinterp_crcf_bench(_start, _finish, _num_iterations, M, H_LEN); }
+
+void benchmark_firinterp_crcf_m2_h8    FIRINTERP_CRCF_BENCHMARK_API(2, 8)
+void benchmark_firinterp_crcf_m4_h16   FIRINTERP_CRCF_BENCHMARK_API(4, 16)
+void benchmark_firinterp_crcf_m8_h32   FIRINTERP_CRCF_BENCHMARK_API(8, 32)
+void benchmark_firinterp_crcf_m16_h64  FIRINTERP_CRCF_BENCHMARK_API(16,64)
+void benchmark_firinterp_crcf_m32_h128 FIRINTERP_CRCF_BENCHMARK_API(32,128)
+
diff --git a/src/filter/bench/iirdecim_crcf_benchmark.c b/src/filter/bench/iirdecim_crcf_benchmark.c
new file mode 100644
index 0000000..e8cfbdf
--- /dev/null
+++ b/src/filter/bench/iirdecim_crcf_benchmark.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void iirdecim_crcf_bench(struct rusage *     _start,
+                         struct rusage *     _finish,
+                         unsigned long int * _num_iterations,
+                         unsigned int        _M,
+                         unsigned int        _order)
+{
+    // normalize number of iterations
+    *_num_iterations /= _order;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // create decimator from prototype
+    liquid_iirdes_filtertype ftype  = LIQUID_IIRDES_BUTTER;
+    liquid_iirdes_bandtype   btype  = LIQUID_IIRDES_LOWPASS;
+    liquid_iirdes_format     format = LIQUID_IIRDES_SOS;
+    float fc =  0.5f / (float)_M;
+    float f0 =  0.0f;
+    float Ap =  0.1f;
+    float As = 60.0f;
+    iirdecim_crcf q = iirdecim_crcf_create_prototype(_M,ftype,btype,format,_order,fc,f0,Ap,As);
+
+    // initialize input
+    float complex x[_M];
+    unsigned int i;
+    for (i=0; i<_M; i++)
+        x[i] = (i%2) ? 1.0f : -1.0f;
+
+    float complex y;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        iirdecim_crcf_execute(q, x, &y);
+        iirdecim_crcf_execute(q, x, &y);
+        iirdecim_crcf_execute(q, x, &y);
+        iirdecim_crcf_execute(q, x, &y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    iirdecim_crcf_destroy(q);
+}
+
+#define IIRDECIM_CRCF_BENCHMARK_API(M,ORDER)    \
+(   struct rusage *_start,                      \
+    struct rusage *_finish,                     \
+    unsigned long int *_num_iterations)         \
+{ iirdecim_crcf_bench(_start, _finish, _num_iterations, M, ORDER); }
+
+void benchmark_iirdecim_crcf_M2     IIRDECIM_CRCF_BENCHMARK_API(2, 5)
+void benchmark_iirdecim_crcf_M4     IIRDECIM_CRCF_BENCHMARK_API(4, 5)
+void benchmark_iirdecim_crcf_M8     IIRDECIM_CRCF_BENCHMARK_API(8, 5)
+void benchmark_iirdecim_crcf_M16    IIRDECIM_CRCF_BENCHMARK_API(16,5)
+void benchmark_iirdecim_cccf_M32    IIRDECIM_CRCF_BENCHMARK_API(32,5)
+
diff --git a/src/filter/bench/iirfilt_crcf_benchmark.c b/src/filter/bench/iirfilt_crcf_benchmark.c
new file mode 100644
index 0000000..80b54f3
--- /dev/null
+++ b/src/filter/bench/iirfilt_crcf_benchmark.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void iirfilt_crcf_bench(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int        _order,
+                        unsigned int        _format)
+{
+    unsigned int i;
+
+    // scale number of iterations (trials)
+    if (_format == LIQUID_IIRDES_TF) {
+        // cycles/trial ~ 128 + 15.3*_order;
+        *_num_iterations *= 1000;
+        *_num_iterations /= (unsigned int)(128 + 15.3*_order);
+    } else {
+        // cycles/trial ~ 93 + 53.3*_order
+        *_num_iterations *= 800;
+        *_num_iterations /= (unsigned int)(93 + 53.3*_order);
+    }
+
+    // create filter object from prototype
+    float fc    =  0.2f;    // filter cut-off frequency
+    float f0    =  0.0f;    // filter center frequency (band-pass, band-stop)
+    float Ap    =  0.1f;    // filter pass-band ripple
+    float As    = 60.0f;    // filter stop-band attenuation
+    iirfilt_crcf q = iirfilt_crcf_create_prototype(LIQUID_IIRDES_BUTTER,
+                                                   LIQUID_IIRDES_LOWPASS,
+                                                   _format,
+                                                   _order,
+                                                   fc, f0, Ap, As);
+
+    // initialize input/output
+    float complex x[4];
+    float complex y[4];
+    for (i=0; i<4; i++)
+        x[i] = randnf() + _Complex_I*randnf();
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        iirfilt_crcf_execute(q, x[0], &y[0]);
+        iirfilt_crcf_execute(q, x[1], &y[1]);
+        iirfilt_crcf_execute(q, x[2], &y[2]);
+        iirfilt_crcf_execute(q, x[3], &y[3]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy filter object
+    iirfilt_crcf_destroy(q);
+}
+
+#define IIRFILT_CRCF_BENCHMARK_API(N,T)     \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ iirfilt_crcf_bench(_start, _finish, _num_iterations, N, T); }
+
+// benchmark regular transfer function form
+void benchmark_iirfilt_crcf_4        IIRFILT_CRCF_BENCHMARK_API(4,    LIQUID_IIRDES_TF)
+void benchmark_iirfilt_crcf_8        IIRFILT_CRCF_BENCHMARK_API(8,    LIQUID_IIRDES_TF)
+void benchmark_iirfilt_crcf_16       IIRFILT_CRCF_BENCHMARK_API(16,   LIQUID_IIRDES_TF)
+void benchmark_iirfilt_crcf_32       IIRFILT_CRCF_BENCHMARK_API(32,   LIQUID_IIRDES_TF)
+void benchmark_iirfilt_crcf_64       IIRFILT_CRCF_BENCHMARK_API(64,   LIQUID_IIRDES_TF)
+
+// benchmark second-order sections form
+void benchmark_iirfilt_crcf_sos_4    IIRFILT_CRCF_BENCHMARK_API(4,    LIQUID_IIRDES_SOS)
+void benchmark_iirfilt_crcf_sos_8    IIRFILT_CRCF_BENCHMARK_API(8,    LIQUID_IIRDES_SOS)
+void benchmark_iirfilt_crcf_sos_16   IIRFILT_CRCF_BENCHMARK_API(16,   LIQUID_IIRDES_SOS)
+void benchmark_iirfilt_crcf_sos_32   IIRFILT_CRCF_BENCHMARK_API(32,   LIQUID_IIRDES_SOS)
+void benchmark_iirfilt_crcf_sos_64   IIRFILT_CRCF_BENCHMARK_API(64,   LIQUID_IIRDES_SOS)
+
+// benchmark DC-blocking filter
+void benchmark_irfilt_crcf_dcblock(struct rusage *     _start,
+                                   struct rusage *     _finish,
+                                   unsigned long int * _num_iterations)
+{
+    unsigned long int i;
+
+    // create filter object
+    iirfilt_crcf q = iirfilt_crcf_create_dc_blocker(0.1f);
+
+    // initialize input/output
+    float complex x[4];
+    for (i=0; i<4; i++)
+        x[i] = randnf() + _Complex_I*randnf();
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        iirfilt_crcf_execute(q, x[0], &x[0]);
+        iirfilt_crcf_execute(q, x[1], &x[1]);
+        iirfilt_crcf_execute(q, x[2], &x[2]);
+        iirfilt_crcf_execute(q, x[3], &x[3]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy filter object
+    iirfilt_crcf_destroy(q);
+}
+
diff --git a/src/filter/bench/iirinterp_crcf_benchmark.c b/src/filter/bench/iirinterp_crcf_benchmark.c
new file mode 100644
index 0000000..c9016da
--- /dev/null
+++ b/src/filter/bench/iirinterp_crcf_benchmark.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void iirinterp_crcf_bench(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations,
+                          unsigned int _M,
+                          unsigned int _order)
+{
+    // normalize number of iterations
+    *_num_iterations *= 80;
+    *_num_iterations /= _M*_order;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // create interpolator from prototype
+    liquid_iirdes_filtertype ftype  = LIQUID_IIRDES_BUTTER;
+    liquid_iirdes_bandtype   btype  = LIQUID_IIRDES_LOWPASS;
+    liquid_iirdes_format     format = LIQUID_IIRDES_SOS;
+    float fc =  0.5f / (float)_M;   // filter cut-off frequency
+    float f0 =  0.0f;
+    float Ap =  0.1f;
+    float As = 60.0f;
+    iirinterp_crcf q = iirinterp_crcf_create_prototype(_M,ftype,btype,format,_order,fc,f0,Ap,As);
+
+    float complex y[_M];
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    unsigned int i;
+    for (i=0; i<(*_num_iterations); i++) {
+        iirinterp_crcf_execute(q, 1.0f, y);
+        iirinterp_crcf_execute(q, 1.0f, y);
+        iirinterp_crcf_execute(q, 1.0f, y);
+        iirinterp_crcf_execute(q, 1.0f, y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    iirinterp_crcf_destroy(q);
+}
+
+#define IIRINTERP_CRCF_BENCHMARK_API(M,ORDER)   \
+(   struct rusage *_start,                      \
+    struct rusage *_finish,                     \
+    unsigned long int *_num_iterations)         \
+{ iirinterp_crcf_bench(_start, _finish, _num_iterations, M, ORDER); }
+
+void benchmark_iirinterp_crcf_M2    IIRINTERP_CRCF_BENCHMARK_API(2, 5)
+void benchmark_iirinterp_crcf_M4    IIRINTERP_CRCF_BENCHMARK_API(4, 5)
+void benchmark_iirinterp_crcf_M8    IIRINTERP_CRCF_BENCHMARK_API(8, 5)
+void benchmark_iirinterp_crcf_M16   IIRINTERP_CRCF_BENCHMARK_API(16,5)
+void benchmark_iirinterp_crcf_M32   IIRINTERP_CRCF_BENCHMARK_API(32,5)
+
diff --git a/src/filter/bench/resamp2_crcf_benchmark.c b/src/filter/bench/resamp2_crcf_benchmark.c
new file mode 100644
index 0000000..831e02a
--- /dev/null
+++ b/src/filter/bench/resamp2_crcf_benchmark.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+typedef enum {
+    RESAMP2_DECIM,
+    RESAMP2_INTERP
+} resamp2_type;
+
+// Helper function to keep code base small
+void resamp2_crcf_bench(struct rusage *_start,
+                        struct rusage *_finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int _m,
+                        resamp2_type _type)
+{
+    // scale number of iterations by filter length
+    // NOTE: n = 4*m+1
+    // cycles/trial ~ 70.5 + 7.74*_m
+    *_num_iterations *= 200;
+    *_num_iterations /= 70.5 + 7.74*_m;
+
+    unsigned long int i;
+
+    resamp2_crcf q = resamp2_crcf_create(_m,0.0f,60.0f);
+
+    float complex x[] = {1.0f, -1.0f};
+    float complex y[] = {1.0f, -1.0f};
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    if (_type == RESAMP2_DECIM) {
+
+        // run decimator
+        for (i=0; i<(*_num_iterations); i++) {
+            resamp2_crcf_decim_execute(q,x,y);
+            resamp2_crcf_decim_execute(q,x,y);
+            resamp2_crcf_decim_execute(q,x,y);
+            resamp2_crcf_decim_execute(q,x,y);
+        }
+    } else {
+
+        // run interpolator
+        for (i=0; i<(*_num_iterations); i++) {
+            resamp2_crcf_interp_execute(q,x[0],y);
+            resamp2_crcf_interp_execute(q,x[0],y);
+            resamp2_crcf_interp_execute(q,x[0],y);
+            resamp2_crcf_interp_execute(q,x[0],y);
+        }
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    resamp2_crcf_destroy(q);
+}
+
+#define RESAMP2_CRCF_BENCHMARK_API(M,T) \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ resamp2_crcf_bench(_start, _finish, _num_iterations, M, T); }
+
+//
+// Decimators
+//
+void benchmark_resamp2_crcf_decim_m2    RESAMP2_CRCF_BENCHMARK_API( 2,RESAMP2_DECIM)  // n=9
+void benchmark_resamp2_crcf_decim_m4    RESAMP2_CRCF_BENCHMARK_API( 4,RESAMP2_DECIM)  // n=17
+void benchmark_resamp2_crcf_decim_m8    RESAMP2_CRCF_BENCHMARK_API( 8,RESAMP2_DECIM)  // n=33
+void benchmark_resamp2_crcf_decim_m16   RESAMP2_CRCF_BENCHMARK_API(16,RESAMP2_DECIM)  // n=65
+
+// 
+// Interpolators
+//
+void benchmark_resamp2_crcf_interp_m2   RESAMP2_CRCF_BENCHMARK_API( 2,RESAMP2_INTERP) // n=9
+void benchmark_resamp2_crcf_interp_m4   RESAMP2_CRCF_BENCHMARK_API( 4,RESAMP2_INTERP) // n=17
+void benchmark_resamp2_crcf_interp_m8   RESAMP2_CRCF_BENCHMARK_API( 8,RESAMP2_INTERP) // n=33
+void benchmark_resamp2_crcf_interp_m16  RESAMP2_CRCF_BENCHMARK_API(16,RESAMP2_INTERP) // n=65
+
diff --git a/src/filter/bench/resamp_crcf_benchmark.c b/src/filter/bench/resamp_crcf_benchmark.c
new file mode 100644
index 0000000..5e115ec
--- /dev/null
+++ b/src/filter/bench/resamp_crcf_benchmark.c
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void resamp_crcf_bench(struct rusage *     _start,
+                       struct rusage *     _finish,
+                       unsigned long int * _num_iterations,
+                       unsigned int        _m)
+{
+    unsigned long int i;
+    float r = 1.03f;        // resampling rate
+    float bw = 0.35f;       // filter bandwidth
+    float As = 60.0f;       // stop-band attenuation [dB]
+    unsigned int npfb = 32; // number of polyphase filters
+    unsigned int m = _m;    // filter semi-length
+
+    resamp_crcf q = resamp_crcf_create(r,m,bw,As,npfb);
+
+    float complex y[4];
+
+    unsigned int num_written;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        resamp_crcf_execute(q, 1.0f, y, &num_written);
+        resamp_crcf_execute(q, 1.1f, y, &num_written);
+        resamp_crcf_execute(q, 0.9f, y, &num_written);
+        resamp_crcf_execute(q, 1.0f, y, &num_written);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    resamp_crcf_destroy(q);
+}
+
+#define RESAMP_CRCF_BENCHMARK_API(M)    \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ resamp_crcf_bench(_start, _finish, _num_iterations, M); }
+
+//
+// Resampler benchmark prototypes
+//
+void benchmark_resamp_crcf_m4    RESAMP_CRCF_BENCHMARK_API(4)
+void benchmark_resamp_crcf_m8    RESAMP_CRCF_BENCHMARK_API(8)
+void benchmark_resamp_crcf_m16   RESAMP_CRCF_BENCHMARK_API(16)
+void benchmark_resamp_crcf_m32   RESAMP_CRCF_BENCHMARK_API(32)
+void benchmark_resamp_crcf_m64   RESAMP_CRCF_BENCHMARK_API(64)
+void benchmark_resamp_crcf_m128  RESAMP_CRCF_BENCHMARK_API(128)
+
diff --git a/src/filter/bench/symsync_crcf_benchmark.c b/src/filter/bench/symsync_crcf_benchmark.c
new file mode 100644
index 0000000..37c517f
--- /dev/null
+++ b/src/filter/bench/symsync_crcf_benchmark.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void symsync_crcf_bench(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int        _k,
+                        unsigned int        _m)
+{
+    unsigned long int i;
+    unsigned int npfb = 16;     // number of filters in bank
+    unsigned int k    = _k;     // samples/symbol
+    unsigned int m    = _m;     // filter delay [symbols]
+    float beta        = 0.3f;   // filter excess bandwidth factor
+
+    // create symbol synchronizer
+    symsync_crcf q = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC,
+                                                  k, m, beta, npfb);
+
+    //
+    unsigned int num_samples = 64;
+    *_num_iterations /= num_samples;
+
+    unsigned int num_written;
+    float complex x[num_samples];
+    float complex y[num_samples];
+
+    // generate pseudo-random data
+    msequence ms = msequence_create_default(6);
+    for (i=0; i<num_samples; i++)
+        x[i] = ((float)msequence_generate_symbol(ms, 6) - 31.5) / 24.0f;
+    msequence_destroy(ms);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        symsync_crcf_execute(q, x, num_samples, y, &num_written);
+        symsync_crcf_execute(q, x, num_samples, y, &num_written);
+        symsync_crcf_execute(q, x, num_samples, y, &num_written);
+        symsync_crcf_execute(q, x, num_samples, y, &num_written);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4 * num_samples;
+
+    symsync_crcf_destroy(q);
+}
+
+#define SYMSYNC_CRCF_BENCHMARK_API(K,M)     \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ symsync_crcf_bench(_start, _finish, _num_iterations, K, M); }
+
+// 
+// BENCHMARKS
+//
+void benchmark_symsync_crcf_k2_m2   SYMSYNC_CRCF_BENCHMARK_API(2, 2)
+void benchmark_symsync_crcf_k2_m4   SYMSYNC_CRCF_BENCHMARK_API(2, 4)
+void benchmark_symsync_crcf_k2_m8   SYMSYNC_CRCF_BENCHMARK_API(2, 8)
+void benchmark_symsync_crcf_k2_m16  SYMSYNC_CRCF_BENCHMARK_API(2, 16)
+
diff --git a/src/filter/dds.readme.txt b/src/filter/dds.readme.txt
new file mode 100644
index 0000000..6d82825
--- /dev/null
+++ b/src/filter/dds.readme.txt
@@ -0,0 +1,34 @@
+#
+# Direct digital synthesis (dds)
+#
+
+dds_cccf dds_cccf_create(unsigned int _num_stages,
+                         float _fc,
+                         float _bw,
+                         float _As);
+    Create a DDS object.  The interpolation (decimation) rate is
+    equal to 2^_num_stages.
+
+void dds_cccf_destroy(dds_cccf _q);
+    Destroy a DDS object, cleaning up all internally-allocated
+    memory buffers, arrays, and objects.
+
+void dds_cccf_print(dds_cccf _q);
+    Print information about the DDS object.
+
+void dds_cccf_reset(dds_cccf _q);
+    Reset/clear the internal buffers of the DDS object.
+
+void dds_cccf_decim_execute(dds_cccf _q,
+                            float complex * _x,
+                            float complex * _y);
+    Run decimation on an input array of 2^_num_stages inputs,
+    and store in a single output
+
+void dds_cccf_interp_execute(dds_cccf _q,
+                             float complex _x,
+                             float complex * _y);
+    Run interpolation on a single input and store result into
+    an array of 2^_num_stages outputs.
+
+
diff --git a/src/filter/iirdes.readme.txt b/src/filter/iirdes.readme.txt
new file mode 100644
index 0000000..5c74c24
--- /dev/null
+++ b/src/filter/iirdes.readme.txt
@@ -0,0 +1,45 @@
+#
+# iirdes (infinite impulse response filter design)
+#
+
+liquid implements infinite impulse respone (IIR) filter design for the four major classes of filters (Butterworth, Chebyshev, elliptic, and Bessel) as follows:
+
+1. Use butterf(), cheby1f(), cheby2f(), ellipf(), besself() to design a low-pass analog prototype in terms of complex zeros, poles, and gain.
+
+    butter_azpkf()  :   Butterworth (maximally flat in the pass-band)
+    cheby1_azpkf()  :   Chebyshev Type-I (equiripple in the pass-band)
+    cheby2_azpkf()  :   Chebyshev Type-II (equiripple in the stop-band)
+    ellip_azpkf()   :   elliptic filter (equiripple in the pass- and stop-bands)
+    bessel_azpkf()  :   Bessel (maximally flat group delay)
+
+2. Compute frequency pre-warping factor, m, to set cutoff frequency (and center frequency if designing a band-pass or band-stop filter).
+
+    iirdes_freqprewarp()
+
+3. Convert the low-pass analog prototype to its digital equivalent (also in terms of zeros, poles, and gain) using the bilinear z-transform
+    
+    bilinear_zpkf()
+
+4. Transform the low-pass digital prototype to high-pass, band-pass, or band-stop.  For the band-pass and band-stop cases, the number of poles and zeros will need to be doubled.
+    - [LP]  : low-pass filter   : s = m * (1+z^-1) / (1-z^-1)
+    - [HP]  : high-pass filter  : s = m * (1-z^-1) / (1+z^-1)
+    - [BP]  : band-pass filter  : s = m * (1-c0z^-1+z^-2) / (1-z^-2)
+    - [BS]  : band-stop filter  : s = m * (1-z^-2) / (1-c0z^-1+z^-2)
+
+    iirdes_dzpk_lp2bp()
+
+5. Transform the digital z/p/k form of the filter to one of the two forms:
+    - [tf]  : typical transfer function for digital iir filters of the form B(z)/A(z)
+    - [sos] : second-order sections form : prod( Bk(z)/Ak(z) ), preferred method
+
+    iirdes_dzpk2tff()
+    iirdes_dzpk2sosf()
+
+6. Create the filter object (e.g. iirfilt, iirqmfb) from the resulting structure
+
+    iirfilt_crcf_create()
+    iirfilt_crcf_create_sosf()
+
+
+An extensive example is given in sandbox/iirdes_example.c while examples/iirdes_example.c gives the simplified interface.
+
diff --git a/src/filter/src/autocorr.c b/src/filter/src/autocorr.c
new file mode 100644
index 0000000..26cd25d
--- /dev/null
+++ b/src/filter/src/autocorr.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// auto-correlator (delay cross-correlation)
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+// defined:
+//  AUTOCORR()      name-mangling macro
+//  TI              type (input)
+//  TC              type (coefficients)
+//  TO              type (output)
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+struct AUTOCORR(_s) {
+    unsigned int window_size;
+    unsigned int delay;
+
+    WINDOW() w;         // input buffer
+    WINDOW() wdelay;    // input buffer with delay
+
+    float * we2;        // energy buffer
+    float e2_sum;       // running sum of energy
+    unsigned int ie2;   // read index
+};
+
+// create auto-correlator object                            
+//  _window_size    : size of the correlator window         
+//  _delay          : correlator delay [samples]            
+AUTOCORR() AUTOCORR(_create)(unsigned int _window_size,
+                             unsigned int _delay)
+{
+    // create main object
+    AUTOCORR() q = (AUTOCORR()) malloc(sizeof(struct AUTOCORR(_s)));
+
+    // set user-based parameters
+    q->window_size = _window_size;
+    q->delay       = _delay;
+
+    // create window objects
+    q->w      = WINDOW(_create)(q->window_size);
+    q->wdelay = WINDOW(_create)(q->window_size + q->delay);
+
+    // allocate array for squared energy buffer
+    q->we2 = (float*) malloc( (q->window_size)*sizeof(float) );
+
+    // clear object
+    AUTOCORR(_reset)(q);
+
+    // return main object
+    return q;
+}
+
+// destroy auto-correlator object, freeing internal memory
+void AUTOCORR(_destroy)(AUTOCORR() _q)
+{
+    // destroy internal window objects
+    WINDOW(_destroy)(_q->w);
+    WINDOW(_destroy)(_q->wdelay);
+
+    // free array for squared energy buffer
+    free(_q->we2);
+
+    // free main object memory
+    free(_q);
+}
+
+// reset auto-correlator object's internals
+void AUTOCORR(_reset)(AUTOCORR() _q)
+{
+    // clear/reset internal window buffers
+    WINDOW(_clear)(_q->w);
+    WINDOW(_clear)(_q->wdelay);
+    
+    // reset internal squared energy buffer
+    _q->e2_sum = 0.0;
+    unsigned int i;
+    for (i=0; i<_q->window_size; i++)
+        _q->we2[i] = 0.0;
+    _q->ie2 = 0;    // reset read index to zero
+}
+
+// print auto-correlator parameters to stdout
+void AUTOCORR(_print)(AUTOCORR() _q)
+{
+    printf("autocorr [%u window, %u delay]\n", _q->window_size, _q->delay);
+}
+
+// push sample into auto-correlator object
+void AUTOCORR(_push)(AUTOCORR() _q, TI _x)
+{
+    // push input sample into buffers
+    WINDOW(_push)(_q->w,      _x);          // non-delayed buffer
+    WINDOW(_push)(_q->wdelay, conj(_x));    // delayed buffer
+
+    // push |_x|^2 into buffer at appropriate location
+    float e2 = creal( _x*conj(_x) );
+    _q->e2_sum -= _q->we2[ _q->ie2 ];
+    _q->e2_sum += e2;
+    _q->we2[ _q->ie2 ] = e2;
+    _q->ie2 = (_q->ie2+1) % _q->window_size;
+}
+
+// compute auto-correlation output
+void AUTOCORR(_execute)(AUTOCORR() _q, TO *_rxx)
+{
+    // provide pointers for reading buffer
+    TI * rw;        // input buffer read pointer
+    TC * rwdelay;   // input buffer read pointer (with delay)
+
+    // read buffers; set internal pointers appropriately
+    WINDOW(_read)(_q->w,      &rw     );
+    WINDOW(_read)(_q->wdelay, &rwdelay);
+
+    // execute vector dot product on arrays, saving result to
+    // user-supplied output pointer
+    DOTPROD(_run4)(rw, rwdelay, _q->window_size, _rxx);
+}
+
+// compute auto-correlation on block of samples; the input
+// and output arrays may have the same pointer
+//  _q      :   auto-correlation object
+//  _x      :   input array [size: _n x 1]
+//  _n      :   number of input, output samples
+//  _rxx    :   input array [size: _n x 1]
+void AUTOCORR(_execute_block)(AUTOCORR()   _q,
+                              TI *         _x,
+                              unsigned int _n,
+                              TO *         _rxx)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // push input sample into auto-correlator
+        AUTOCORR(_push)(_q, _x[i]);
+
+        // compute output
+        AUTOCORR(_execute)(_q, &_rxx[i]);
+    }
+}
+
+// return sum of squares of buffered samples
+float AUTOCORR(_get_energy)(AUTOCORR() _q)
+{
+    // value is already computed; simply return value
+    return _q->e2_sum;
+}
+
diff --git a/src/filter/src/bessel.c b/src/filter/src/bessel.c
new file mode 100644
index 0000000..176f4b2
--- /dev/null
+++ b/src/filter/src/bessel.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Bessel filter design
+//
+// References:
+//  [Bianchi:2007] G. Bianchi and R. Sorrentino, "Electronic Filter Simulation
+//      and Design." New York: McGraw-Hill, 2007.
+//  [Orchard:1965] H. J. Orchard, "The Roots of the Maximally Flat-Delay
+//      Polynomials." IEEE Transactions on Circuit Theory, September, 1965.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_BESSEL_PRINT   0
+
+// forward declarations
+
+void fpoly_bessel(unsigned int _n, float * _p);
+
+void fpoly_bessel_roots(unsigned int _n, float complex * _roots);
+
+void fpoly_bessel_roots_orchard(unsigned int _n, float complex * _roots);
+
+void fpoly_bessel_roots_orchard_recursion(unsigned int _n,
+                                          float _x,
+                                          float _y,
+                                          float * _x_hat,
+                                          float * _y_hat);
+
+// ****************************************
+
+// Compute analog zeros, poles, gain of low-pass Bessel
+// filter, grouping complex conjugates together. If
+// the filter order is odd, the single real pole is at
+// the end of the array.  There are no zeros for the
+// analog Bessel filter.  The gain is unity.
+//  _n      :   filter order
+//  _z      :   output analog zeros [length:  0]
+//  _p      :   output analog poles [length: _n]
+//  _k      :   output analog gain
+void bessel_azpkf(unsigned int _n,
+                  float complex * _za,
+                  float complex * _pa,
+                  float complex * _ka)
+{
+    // compute poles (roots to Bessel polynomial)
+    fpoly_bessel_roots(_n+1,_pa);
+
+    // analog Bessel filter prototype has no zeros
+
+    // The analog Bessel filter's 3-dB cut-off frequency is a
+    // non-linear function of its order.  This frequency can
+    // be approximated from [Bianchi:2007] (1.67), pp. 33.
+    // Re-normalize poles by (approximated) 3-dB frequency.
+    float w3dB = sqrtf((2*_n-1)*logf(2.0f));
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        _pa[i] /= w3dB;
+
+    // set gain
+    *_ka = 1.0f;
+    for (i=0; i<_n; i++)
+        *_ka *= _pa[i];
+}
+
+void fpoly_bessel(unsigned int _n, float * _p)
+{
+    unsigned int k;
+    unsigned int N = _n-1;
+    for (k=0; k<_n; k++) {
+#if 0
+        // use internal log(gamma(z))
+        float t0 = liquid_lngammaf((float)(2*N-k)+1);
+        float t1 = liquid_lngammaf((float)(N-k)  +1);
+        float t2 = liquid_lngammaf((float)(k)     +1);
+#else
+        // use standard math log(gamma(z))
+        float t0 = lgammaf((float)(2*N-k)+1);
+        float t1 = lgammaf((float)(N-k)  +1);
+        float t2 = lgammaf((float)(k)    +1);
+#endif
+
+        // M_LN2 = log(2) = 0.693147180559945
+        float t3 = M_LN2 * (float)(N-k);    // log(2^(N-k)) = log(2)*log(N-k)
+
+        _p[k] = roundf(expf(t0 - t1 - t2 - t3));
+
+#if 0
+        printf("  p[%3u,%3u] = %12.4e\n", k, _n, _p[k]);
+        printf("    t0 : %12.4e\n", t0);
+        printf("    t1 : %12.4e\n", t1);
+        printf("    t2 : %12.4e\n", t2);
+        printf("    t3 : %12.4e\n", t3);
+#endif
+    }
+}
+
+void fpoly_bessel_roots(unsigned int _n,
+                        float complex * _roots)
+{
+    fpoly_bessel_roots_orchard(_n, _roots);
+}
+
+// Estimate the roots of the _n^th-order Bessel polynomial using
+// Orchard's recursion.  The initial estimates for the roots of
+// L_{k} are extrapolated from those in L_{k-2} and L_{k-1}.
+// The resulting root is near enough the true root such that
+// Orchard's recursion will find it.
+void fpoly_bessel_roots_orchard(unsigned int _n,
+                                float complex * _roots)
+{
+    // initialize arrays
+    float complex r0[_n];       // roots of L_{k-2}
+    float complex r1[_n];       // roots of L_{k-1}
+    float complex r_hat[_n];    // roots of L_{k}
+
+    unsigned int i, j;
+    unsigned int p, L;
+    for (i=1; i<_n; i++) {
+        p = i % 2;  // order is odd?
+        L = (i+p)/2;
+        //printf("\n***** order %3u, p=%3u, L=%3u\n", i, p, L);
+
+        if (i == 1) {
+            r1[0]    = -1;
+            r_hat[0] = -1;
+        } else if (i == 2) {
+            r1[0]    = -1;
+            r_hat[0] = -1.5f + _Complex_I*0.5f*sqrtf(3.0f);
+        } else {
+
+            // use previous 2 sets of roots to estimate this set
+            if (p) {
+                // odd order : one real root on negative imaginary axis
+                r_hat[0] = 2*crealf(r1[0]) - crealf(r0[0]);
+            } else {
+                // even order
+                r_hat[0] = 2*r1[0] - conjf(r0[0]);
+            }
+
+            // linear extrapolation of roots of L_{k-2} and L_{k-1} for
+            // new root estimate in L_{k}
+            for (j=1; j<L; j++)
+                r_hat[j] = 2*r1[j-p] - r0[j-1];
+
+            for (j=0; j<L; j++) {
+                float x = crealf(r_hat[j]);
+                float y = cimagf(r_hat[j]);
+                float x_hat, y_hat;
+                fpoly_bessel_roots_orchard_recursion(i,x,y,&x_hat,&y_hat);
+                r_hat[j] = x_hat + _Complex_I*y_hat;
+            }
+        }
+
+        // copy roots:  roots(L_{k+1}) -> roots(L_{k+2))
+        //              roots(L_{k})   -> roots(L_{k+1))
+        memmove(r0, r1,    (L-p)*sizeof(float complex));
+        memmove(r1, r_hat,     L*sizeof(float complex));
+    }
+
+    // copy results to output
+    p = _n % 2;
+    L = (_n-p)/2;
+    for (i=0; i<L; i++) {
+        unsigned int p = L-i-1;
+        _roots[2*i+0] =       r_hat[p];
+        _roots[2*i+1] = conjf(r_hat[p]);
+    }
+
+    // if order is odd, copy single real root last
+    if (p)
+        _roots[_n-1] = r_hat[0];
+}
+
+// from [Orchard:1965]
+void fpoly_bessel_roots_orchard_recursion(unsigned int _n,
+                                          float _x,
+                                          float _y,
+                                          float * _x_hat,
+                                          float * _y_hat)
+{
+    if (_n < 2) {
+        fprintf(stderr,"error: fpoly_bessel_roots_orchard_recursion(), n < 2\n");
+        exit(1);
+    }
+
+    // create internal variables (use long double precision to help
+    // algorithm converge, particularly for large _n)
+    long double u0, u1, u2=0, u2p=0;
+    long double v0, v1, v2=0, v2p=0;
+    long double x = _x;
+    long double y = _y;
+    //long double eps = 1e-6f;
+
+    unsigned int k,i;
+    unsigned int num_iterations = 50;
+    for (k=0; k<num_iterations; k++) {
+        //printf("%3u :   %16.8e + j*%16.8e\n", k, x, y);
+        u0 = 1.0;
+        u1 = 1.0 + x;
+
+        v0 = 0.0f;
+        v1 = y;
+
+        // compute u_r, v_r
+        for (i=2; i<=_n; i++) {
+            u2 = (2*i-1)*u1 + (x*x - y*y)*u0 - 2*x*y*v0;
+            v2 = (2*i-1)*v1 + (x*x - y*y)*v0 + 2*x*y*u0;
+
+            // if not on last iteration, update u0, v0, u1, v1
+            if (i < _n) {
+                u0 = u1; v0 = v1;
+                u1 = u2; v1 = v2;
+            }
+        }
+
+        // compute derivatives
+        u2p = u2 - x*u1 + y*v1;
+        v2p = v2 - x*v1 - y*u1;
+
+        // update roots
+        long double g = u2p*u2p + v2p*v2p;
+        if (g == 0.) break;
+
+        // For larger order _n, the step values dx and dy will be the
+        // evaluation of the ratio of two large numbers which can prevent
+        // the algorithm from converging for finite machine precision.
+        long double dx = -(u2p*u2 + v2p*v2)/g;
+        long double dy = -(u2p*v2 - v2p*u2)/g;
+        x += dx;
+        y += dy;
+    }
+
+    *_x_hat = x;
+    *_y_hat = y;
+}
+
diff --git a/src/filter/src/bilinear.c b/src/filter/src/bilinear.c
new file mode 100644
index 0000000..dbdc7db
--- /dev/null
+++ b/src/filter/src/bilinear.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// binlinear z-transforms
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_BILINEAR_PRINT 0
+
+// bilinear z-transform using zeros, poles, gain
+//
+//            (s-z[0])*(s-z[1])*...*(s-z[nz-1])
+// H(s) = k * ---------------------------------
+//            (s-p[0])*(s-p[1])*...*(s-z[np-1])
+//
+// inputs
+//  _z      :   array of polynomial zeros (length _nz)
+//  _nz     :   number of zeros
+//  _p      :   array of polynomial poles (length _np)
+//  _np     :   number of poles
+//  _k      :   scaling factor
+//  _m      :   bilateral warping factor
+//
+// outputs
+//  _b      :   digital numerator (length _nz+1)
+//  _a      :   digital denominator (length _np+1)
+void bilinear_zpk(float complex * _z,
+                  unsigned int _nz,
+                  float complex * _p,
+                  unsigned int _np,
+                  float complex _k,
+                  float _m,
+                  float complex * _b,
+                  float complex * _a)
+{
+    unsigned int i;
+
+    // expand numerator
+    unsigned int nb = _nz+1;
+    float complex pb[nb];
+    cfpoly_expandroots(_z,_nz,pb);
+    if (_nz == 0) pb[0] = 1.;
+
+    // expand denominator
+    unsigned int na = _np+1;
+    float complex pa[na];
+    cfpoly_expandroots(_p,_np,pa);
+    if (_np == 0) pa[0] = 1.;
+
+    // scale numerator by _k
+    for (i=0; i<nb; i++)
+        pb[i] *= _k;
+
+#if LIQUID_DEBUG_BILINEAR_PRINT
+    printf("bilinear(zpk), numerator:\n");
+    for (i=0; i<nb; i++)
+        printf("  b[%3u] = %12.8f + j*%12.8f\n", i, crealf(pb[i]), cimagf(pb[i]));
+
+    printf("bilinear(zpk), denominator:\n");
+    for (i=0; i<na; i++)
+        printf("  a[%3u] = %12.8f + j*%12.8f\n", i, crealf(pa[i]), cimagf(pa[i]));
+#endif
+
+    // compute bilinear z-transform on result
+    bilinear_nd(pb,nb-1,
+                pa,na-1,
+                _m,
+                _b, _a);
+}
+
+// bilinear z-transform using polynomial expansion in numerator and
+// denominator
+//
+//          b[0] + b[1]*s + ... + b[nb]*s^(nb-1)
+// H(s) =   ------------------------------------
+//          a[0] + a[1]*s + ... + a[na]*s^(na-1)
+//
+// computes H(z) = H( s -> _m*(z-1)/(z+1) ) and expands as
+//
+//          bd[0] + bd[1]*z^-1 + ... + bd[nb]*z^-n
+// H(z) =   --------------------------------------
+//          ad[0] + ad[1]*z^-1 + ... + ad[nb]*z^-m
+//
+// inputs
+//  _b          :   numerator array (length _b_order+1)
+//  _b_order    :   polynomial order of _b
+//  _a          :   denominator array (length _a_order+1)
+//  _a_order    :   polynomial order of _a
+//  _m          :   bilateral warping factor
+//
+// outputs
+//  _bd         :   digital filter numerator (length _b_order+1)
+//  _ad         :   digital filter numerator (length _a_order+1)
+void bilinear_nd(float complex * _b,
+                 unsigned int _b_order,
+                 float complex * _a,
+                 unsigned int _a_order,
+                 float _m,
+                 float complex * _bd,
+                 float complex * _ad)
+{
+    if (_b_order > _a_order) {
+        fprintf(stderr,"error: bilinear_nd(), numerator order cannot be higher than denominator\n");
+        exit(1);
+    }
+
+#if LIQUID_DEBUG_BILINEAR_PRINT
+    printf("***********************************\n");
+    printf("bilinear(nd), numerator order   : %u\n", _b_order);
+    printf("bilinear(nd), denominator order : %u\n", _a_order);
+#endif
+
+    // ...
+    unsigned int nb = _b_order+1;   // input numerator polynomial array length
+    unsigned int na = _a_order+1;   // input denominator polynomial array length
+
+    unsigned int i, j;
+
+    // clear output arrays (both are length na = _a_order+1)
+    for (i=0; i<na; i++) _bd[i] = 0.;
+    for (i=0; i<na; i++) _ad[i] = 0.;
+
+    // temporary polynomial: (1 + 1/z)^(k) * (1 - 1/z)^(n-k)
+    int poly_1pz[na];
+
+    float mk=1.0f;
+
+    // multiply denominator by ((1-1/z)/(1+1/z))^na and expand
+    for (i=0; i<na; i++) {
+        // expand the polynomial (1+x)^i * (1-x)^(_a_order-i)
+        poly_binomial_expand_pm(_a_order,
+                                _a_order-i,
+                                poly_1pz);
+
+#if LIQUID_DEBUG_BILINEAR_PRINT
+        printf("  %-4u : a=%12.4e + j*%12.4e, mk=%12.8f\n", i, crealf(_a[i]), cimagf(_a[i]), mk);
+        for (j=0; j<na; j++)
+            printf("    poly_1pz[%3u] = %6d : %12.4f + j*%12.4f\n", j, poly_1pz[j], crealf(_a[i]*mk*poly_1pz[j]),
+                                                                                    cimagf(_a[i]*mk*poly_1pz[j]));
+#endif
+
+        // accumulate polynomial coefficients
+        for (j=0; j<na; j++)
+            _ad[j] += _a[i]*mk*poly_1pz[j];
+
+        // update multiplier
+        mk *= _m;
+    }
+
+    // multiply numerator by ((1-1/z)/(1+1/z))^na and expand
+    mk = 1.0f;
+    for (i=0; i<nb; i++) {
+        // expand the polynomial (1+x)^i * (1-x)^(_a_order-i)
+        poly_binomial_expand_pm(_a_order,
+                                _a_order-i,
+                                poly_1pz);
+
+        // accumulate polynomial coefficients
+        for (j=0; j<na; j++)
+            _bd[j] += _b[i]*mk*poly_1pz[j];
+
+        // update multiplier
+        mk *= _m;
+    }
+
+    // normalize by a[0]
+    float complex a0_inv = 1.0f / _ad[0];
+    for (i=0; i<na; i++) {
+        _bd[i] *= a0_inv;
+        _ad[i] *= a0_inv;
+    }
+}
+
diff --git a/src/filter/src/butter.c b/src/filter/src/butter.c
new file mode 100644
index 0000000..90b1f35
--- /dev/null
+++ b/src/filter/src/butter.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Butterworth filter design
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_BUTTER_PRINT   0
+
+// Compute analog zeros, poles, gain of low-pass Butterworth
+// filter, grouping complex conjugates together. If filter
+// order is odd, the single real pole (-1) is at the end of
+// the array.  There are no zeros for the analog Butterworth
+// filter.  The gain is unity.
+//  _n      :   filter order
+//  _za     :   output analog zeros [length:  0]
+//  _pa     :   output analog poles [length: _n]
+//  _ka     :   output analog gain
+void butter_azpkf(unsigned int _n,
+                  liquid_float_complex * _za,
+                  liquid_float_complex * _pa,
+                  liquid_float_complex * _ka)
+{
+    unsigned int r = _n%2;
+    unsigned int L = (_n - r)/2;
+    
+    unsigned int i;
+    unsigned int k=0;
+    for (i=0; i<L; i++) {
+        float theta = (float)(2*(i+1) + _n - 1)*M_PI/(float)(2*_n);
+        _pa[k++] = cexpf( _Complex_I*theta);
+        _pa[k++] = cexpf(-_Complex_I*theta);
+    }
+
+    if (r) _pa[k++] = -1.0f;
+
+    assert(k==_n);
+
+    *_ka = 1.0;
+}
+
diff --git a/src/filter/src/cheby1.c b/src/filter/src/cheby1.c
new file mode 100644
index 0000000..6174d8e
--- /dev/null
+++ b/src/filter/src/cheby1.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Chebeshev type-I filter design
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_CHEBY1_PRINT   0
+
+// Compute analog zeros, poles, gain of low-pass Chebyshev
+// Type I filter, grouping complex conjugates together. If
+// the filter order is odd, the single real pole is at the
+// end of the array.  There are no zeros for the analog
+// Chebyshev Type I filter.
+//  _n      :   filter order
+//  _ep     :   epsilon, related to pass-band ripple
+//  _za     :   output analog zeros [length:  0]
+//  _pa     :   output analog poles [length: _n]
+//  _ka     :   output analog gain
+void cheby1_azpkf(unsigned int _n,
+                  float _ep,
+                  liquid_float_complex * _za,
+                  liquid_float_complex * _pa,
+                  liquid_float_complex * _ka)
+{
+    // temporary values
+    float t0 = sqrt(1.0 + 1.0/(_ep*_ep));
+    float tp = powf( t0 + 1.0/_ep, 1.0/(float)(_n) );
+    float tm = powf( t0 - 1.0/_ep, 1.0/(float)(_n) );
+
+    float b = 0.5*(tp + tm);    // ellipse major axis
+    float a = 0.5*(tp - tm);    // ellipse minor axis
+
+#if LIQUID_DEBUG_CHEBY1_PRINT
+    printf("  ep : %12.8f\n", _ep);
+    printf("  b  : %12.8f\n", b);
+    printf("  a  : %12.8f\n", a);
+#endif
+
+    // filter order variables
+    unsigned int r = _n%2;          // odd order?
+    unsigned int L = (_n - r)/2;    // half order
+    
+    // compute poles
+    unsigned int i;
+    unsigned int k=0;
+    for (i=0; i<L; i++) {
+        float theta = (float)(2*(i+1) + _n - 1)*M_PI/(float)(2*_n);
+        _pa[k++] = a*cosf(theta) - _Complex_I*b*sinf(theta);
+        _pa[k++] = a*cosf(theta) + _Complex_I*b*sinf(theta);
+    }
+
+    // if filter order is odd, there is an additional pole on the
+    // real axis
+    if (r) _pa[k++] = -a;
+
+    // ensure we have written exactly _n poles
+    assert(k==_n);
+
+    // compute analog gain (ignored in digital conversion)
+    *_ka = r ? 1.0f : 1.0f / sqrtf(1.0f + _ep*_ep);
+    for (i=0; i<_n; i++)
+        *_ka *= _pa[i];
+}
+
diff --git a/src/filter/src/cheby2.c b/src/filter/src/cheby2.c
new file mode 100644
index 0000000..14b1f07
--- /dev/null
+++ b/src/filter/src/cheby2.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Chebeshev type-II filter design
+//
+
+#include <stdio.h>
+#include <complex.h>
+#include <math.h>
+#include <assert.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_CHEBY2_PRINT   0
+
+// Compute analog zeros, poles, gain of low-pass Chebyshev
+// Type II filter, grouping complex conjugates together. If
+// the filter order is odd, the single real pole is at the
+// end of the array.
+//  _n      :   filter order
+//  _ep     :   epsilon, related to stop-band ripple
+//  _za     :   output analog zeros [length: floor(_n/2)]
+//  _pa     :   output analog poles [length: _n]
+//  _ka     :   output analog gain
+void cheby2_azpkf(unsigned int _n,
+                  float _es,
+                  liquid_float_complex * _za,
+                  liquid_float_complex * _pa,
+                  liquid_float_complex * _ka)
+{
+    // temporary values
+    float t0 = sqrt(1.0 + 1.0/(_es*_es));
+    float tp = powf( t0 + 1.0/_es, 1.0/(float)(_n) );
+    float tm = powf( t0 - 1.0/_es, 1.0/(float)(_n) );
+
+    float b = 0.5*(tp + tm);    // ellipse major axis
+    float a = 0.5*(tp - tm);    // ellipse minor axis
+
+#if LIQUID_DEBUG_CHEBY2_PRINT
+    printf("ep : %12.8f\n", _es);
+    printf("b  : %12.8f\n", b);
+    printf("a  : %12.8f\n", a);
+#endif
+
+    // filter order variables
+    unsigned int r = _n % 2;        // odd order?
+    unsigned int L = (_n - r)/2;    // half order
+    
+    // compute poles
+    unsigned int i;
+    unsigned int k=0;
+    for (i=0; i<L; i++) {
+        float theta = (float)(2*(i+1) + _n - 1)*M_PI/(float)(2*_n);
+        _pa[k++] = 1.0f / (a*cosf(theta) - _Complex_I*b*sinf(theta));
+        _pa[k++] = 1.0f / (a*cosf(theta) + _Complex_I*b*sinf(theta));
+    }
+
+    // if filter order is odd, there is an additional pole on the
+    // real axis
+    if (r) _pa[k++] = -1.0f / a;
+
+    // ensure we have written exactly _n poles
+    assert(k==_n);
+
+    // compute zeros
+    k=0;
+    for (i=0; i<L; i++) {
+        float theta = (float)(0.5f*M_PI*(2*(i+1)-1)/(float)(_n));
+        _za[k++] = -1.0f / (_Complex_I*cosf(theta));
+        _za[k++] =  1.0f / (_Complex_I*cosf(theta));
+    }
+
+    // ensure we have written exactly 2*L poles
+    assert(k==2*L);
+
+    // compute analog gain (ignored in digital conversion)
+    *_ka = 1.0f;
+    for (i=0; i<_n; i++)
+        *_ka *= _pa[i];
+    for (i=0; i<2*L; i++)
+        *_ka /= _za[i];
+}
+
diff --git a/src/filter/src/ellip.c b/src/filter/src/ellip.c
new file mode 100644
index 0000000..28a81fa
--- /dev/null
+++ b/src/filter/src/ellip.c
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Elliptic filter design
+//
+//  [Orfanidis:2006] Sophocles J. Orfanidis, "Lecture Notes on
+//      Elliptic Filter Design." 2006
+//  [Orfanidis:2005] Sophocles J. Orfanidis, source code available
+//      on www.ece.rutgers.edu/~orfanidi/hpeq
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <assert.h>
+#include "liquid.internal.h"
+
+#define LIQUID_DEBUG_ELLIP_PRINT   0
+
+// landenf()
+//
+// Compute the Landen transformation of _k over _n iterations,
+//  k[n] = (k[n-1] / (1+k'[n-1]))^2
+// where
+//  k'[n-1] = sqrt(1-k[n-1]^2)
+//
+//  _k      :   elliptic modulus
+//  _n      :   number of iterations
+//  _v      :   sequence of decreasing moduli [size: _n]
+void landenf(float _k,
+             unsigned int _n,
+             float * _v)
+{
+    unsigned int i;
+
+    float k = _k;       // k[n]
+    float kp;           // k'[n]
+    for (i=0; i<_n; i++) {
+        kp = sqrtf(1 - k*k);
+        k  = (1 - kp)/(1 + kp);
+        _v[i] = k;
+    }
+}
+
+// ellipkf()
+//
+// compute elliptic integral K(k) for _n recursions
+//
+//  _k      :   elliptic modulus
+//  _n      :   number of iterations
+//  _K      :   complete elliptic integral (modulus k)
+//  _Kp     :   complete elliptic integral (modulus k')
+void ellipkf(float _k,
+             unsigned int _n,
+             float * _K,
+             float * _Kp)
+{
+    // define range for k due to machine precision
+    float kmin = 4e-4f;
+    float kmax = sqrtf(1-kmin*kmin);
+    
+    float K;
+    float Kp;
+
+    float kp = sqrtf(1-_k*_k);
+
+    //printf("ellipkf: k = %16.8e, kp = %16.8e\n", _k, kp);
+
+    // Floating point resolution limits the range of the
+    // computation of K on input _k [Orfanidis:2005]
+    if (_k > kmax) {
+        // input exceeds maximum extreme for this precision; use
+        // approximation
+        float L = -logf(0.25f*kp);
+        K = L + 0.25f*(L-1)*kp*kp;
+    } else {
+        float v[_n];
+        landenf(_k,_n,v);
+        K = M_PI * 0.5f;
+        unsigned int i;
+        for (i=0; i<_n; i++)
+            K *= (1 + v[i]);
+    }
+
+    if (_k < kmin) {
+        // input exceeds minimum extreme for this precision; use
+        // approximation
+        float L = -logf(_k*0.25f);
+        Kp = L + 0.25f*(L-1)*_k*_k;
+    } else {
+        float vp[_n];
+        landenf(kp,_n,vp);
+        Kp = M_PI * 0.5f;
+        unsigned int i;
+        for (i=0; i<_n; i++)
+            Kp *= (1 + vp[i]);
+    }
+
+    // set return values
+    *_K  = K;
+    *_Kp = Kp;
+}
+
+// ellipdegf()
+//
+// Compute elliptic degree using _n iterations
+//
+//  _N      :   analog filter order
+//  _k1     :   elliptic modulus for stop-band, ep/ep1
+//  _n      :   number of Landen iterations
+float ellipdegf(float _N,
+                float _k1,
+                unsigned int _n)
+{
+    // compute K1, K1p from _k1
+    float K1, K1p;
+    ellipkf(_k1,_n,&K1,&K1p);
+
+    // compute q1 from K1, K1p
+    float q1 = expf(-M_PI*K1p/K1);
+
+    // compute q from q1
+    float q = powf(q1,1.0f/_N);
+
+    // expand numerator, denominator
+    unsigned int m;
+    float b = 0.0f;
+    for (m=0; m<_n; m++)
+        b += powf(q,(float)(m*(m+1)));
+    float a = 0.0f;
+    for (m=1; m<_n; m++)
+        a += powf(q,(float)(m*m));
+
+    float g = b / (1.0f + 2.0f*a);
+    float k = 4.0f*sqrtf(q)*g*g;
+
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("ellipdegf(N=%f, k1=%f, n=%u):\n", _N, _k1, _n);
+    printf("  K1        :   %12.4e;\n", K1);
+    printf("  K1p       :   %12.4e;\n", K1p);
+    printf("  q1        :   %12.4e;\n", q1);
+    printf("  q         :   %12.4e;\n", q);
+#endif
+
+    return k;
+}
+
+// ellip_cdf()
+//
+// complex elliptic cd() function (Jacobian elliptic cosine)
+//
+//  _u      :   vector in the complex u-plane
+//  _k      :   elliptic modulus (0 <= _k < 1)
+//  _n      :   number of Landen iterations (typically 5-6)
+float complex ellip_cdf(float complex _u,
+                        float _k,
+                        unsigned int _n)
+{
+    float complex wn = ccosf(_u*M_PI*0.5f);
+    float v[_n];
+    landenf(_k,_n,v);
+    unsigned int i;
+    for (i=_n; i>0; i--) {
+        wn = (1 + v[i-1])*wn / (1 + v[i-1]*wn*wn);
+    }
+    return wn;
+}
+
+// ellip_snf()
+//
+// complex elliptic sn() function (Jacobian elliptic sine)
+//
+//  _u      :   vector in the complex u-plane
+//  _k      :   elliptic modulus (0 <= _k < 1)
+//  _n      :   number of Landen iterations (typically 5-6)
+float complex ellip_snf(float complex _u,
+                        float _k,
+                        unsigned int _n)
+{
+    float complex wn = csinf(_u*M_PI*0.5f);
+    float v[_n];
+    landenf(_k,_n,v);
+    unsigned int i;
+    for (i=_n; i>0; i--) {
+        wn = (1 + v[i-1])*wn / (1 + v[i-1]*wn*wn);
+    }
+    return wn;
+}
+
+// ellip_acdf()
+//
+// complex elliptic inverse cd() function (Jacobian elliptic
+// arc cosine)
+//
+//  _w      :   vector in the complex u-plane
+//  _k      :   elliptic modulus (0 <= _k < 1)
+//  _n      :   number of Landen iterations (typically 5-6)
+float complex ellip_acdf(float complex _w,
+                         float _k,
+                         unsigned int _n)
+{
+    float v[_n];
+    landenf(_k,_n,v);
+    float v1;
+
+    float complex w = _w;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        v1 = (i==0) ? _k : v[i-1];
+        w = w / (1 + liquid_csqrtf(1 - w*w*v1*v1)) * 2.0 / (1+v[i]);
+        //printf("  w[%3u] = %12.8f + j*%12.8f\n", i, crealf(w), cimagf(w));
+    }
+
+    float complex u = liquid_cacosf(w) * 2.0 / M_PI;
+    //printf("  u = %12.8f + j*%12.8f\n", crealf(u), cimagf(u));
+
+#if 0
+    float K, Kp;
+    ellipkf(_k, _n, &K, &Kp);
+    float R = Kp / K;
+#endif
+    return u;
+}
+
+// ellip_asnf()
+//
+// complex elliptic inverse sn() function (Jacobian elliptic
+// arc sine)
+//
+//  _w      :   vector in the complex u-plane
+//  _k      :   elliptic modulus (0 <= _k < 1)
+//  _n      :   number of Landen iterations (typically 5-6)
+float complex ellip_asnf(float complex _w,
+                         float _k,
+                         unsigned int _n)
+{
+    return 1.0 - ellip_acdf(_w,_k,_n);
+}
+
+// ellip_azpkf()
+//
+// Compute analog zeros, poles, gain of low-pass elliptic
+// filter, grouping complex conjugates together. If
+// the filter order is odd, the single real pole is at the
+// end of the array.
+//  _n      :   filter order
+//  _ep     :   epsilon_p, related to pass-band ripple
+//  _es     :   epsilon_s, related to stop-band ripple
+//  _za     :   output analog zeros [length: floor(_n/2)]
+//  _pa     :   output analog poles [length: _n]
+//  _ka     :   output analog gain
+void ellip_azpkf(unsigned int _n,
+                 float _ep,
+                 float _es,
+                 float complex * _za,
+                 float complex * _pa,
+                 float complex * _ka)
+{
+    // filter specifications
+    float fp = 1.0f / (2.0f * M_PI);    // pass-band cutoff
+    float fs = 1.1*fp;                  // stop-band cutoff
+    //float Gp = 1/sqrtf(1 + _ep*_ep);    // pass-band gain
+    //float Gs = 1/sqrtf(1 + _es*_es);    // stop-band gain
+
+    // number of iterations for elliptic integral
+    // approximations
+    unsigned int n=7;
+
+    float Wp = 2*M_PI*fp;
+    float Ws = 2*M_PI*fs;
+
+    // ripples passband, stopband
+    float ep = _ep;
+    float es = _es;
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("ep, es      : %12.8f, %12.8f\n", _ep, _es);
+#endif
+
+    float k  = Wp/Ws;           // 0.8889f;
+    float k1 = ep/es;           // 0.0165f;
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("k           : %12.4e\n", k);
+    printf("k1          : %12.4e\n", k1);
+#endif
+
+    float K,  Kp;
+    float K1, K1p;
+    ellipkf(k, n, &K,  &Kp);    // K  = 2.23533416, Kp  = 1.66463780
+    ellipkf(k1,n, &K1, &K1p);   // K1 = 1.57090271, K1p = 5.49361753
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("K,  Kp      : %12.8f, %12.8f\n", K,  Kp);
+    printf("K1, K1p     : %12.8f, %12.8f\n", K1, K1p);
+#endif
+
+    float N      = (float)_n;       // ceilf(Nexact) = 5
+#if LIQUID_DEBUG_ELLIP_PRINT
+    float Nexact = (K1p/K1)/(Kp/K); // 4.69604063
+    printf("N (exact)   : %12.8f\n", Nexact);
+    printf("N           : %12.8f\n", N);
+#endif
+
+    k = ellipdegf(N,k1,n);      // 0.91427171
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("k           : %12.4e\n", k);
+#endif
+
+#if LIQUID_DEBUG_ELLIP_PRINT
+    float fs_new = fp/k;        // 4.37506723
+    printf("fs_new      : %12.8f\n", fs_new);
+#endif
+
+    unsigned int L = (unsigned int)(floorf(N/2.0f)); // 2
+    unsigned int r = ((unsigned int)N) % 2;
+    float u[L];
+    unsigned int i;
+    for (i=0; i<L; i++) {
+        float t = (float)i + 1.0f;
+        u[i] = (2.0f*t - 1.0f)/N;
+#if LIQUID_DEBUG_ELLIP_PRINT
+        printf("u[%3u]      : %12.8f\n", i, u[i]);
+#endif
+    }
+    float complex zeta[L];
+    for (i=0; i<L; i++) {
+        zeta[i] = ellip_cdf(u[i],k,n);
+#if LIQUID_DEBUG_ELLIP_PRINT
+        printf("zeta[%3u]   : %12.8f + j*%12.8f\n", i, crealf(zeta[i]), cimagf(zeta[i]));
+#endif
+    }
+
+    // compute filter zeros
+    float complex za[L];
+    for (i=0; i<L; i++) {
+        za[i] = _Complex_I * Wp / (k*zeta[i]);
+#if LIQUID_DEBUG_ELLIP_PRINT
+        printf("za[%3u]     : %12.8f + j*%12.8f\n", i, crealf(za[i]), cimagf(za[i]));
+#endif
+    }
+
+    float complex v0 = -_Complex_I*ellip_asnf(_Complex_I/ep, k1, n)/N;
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("v0          : %12.8f + j*%12.8f\n", crealf(v0), cimagf(v0));
+#endif
+
+    float complex pa[L];
+    for (i=0; i<L; i++) {
+        pa[i] = Wp*_Complex_I*ellip_cdf(u[i]-_Complex_I*v0, k, n);
+#if LIQUID_DEBUG_ELLIP_PRINT
+        printf("pa[%3u]     : %12.8f + j*%12.8f\n", i, crealf(pa[i]), cimagf(pa[i]));
+#endif
+    }
+    float complex pa0 = Wp * _Complex_I*ellip_snf(_Complex_I*v0, k, n);
+#if LIQUID_DEBUG_ELLIP_PRINT
+    printf("pa0         : %12.8f + j*%12.8f\n", crealf(pa0), cimagf(pa0));
+#endif
+
+    // compute poles
+    unsigned int t=0;
+    for (i=0; i<L; i++) {
+        _pa[t++] =       pa[i];
+        _pa[t++] = conjf(pa[i]);
+    }
+    if (r) _pa[t++] = pa0;
+    assert(t==_n);
+
+    t=0;
+    for (i=0; i<L; i++) {
+        _za[t++] =       za[i];
+        _za[t++] = conjf(za[i]);
+    }
+    assert(t==2*L);
+
+    // compute gain
+    *_ka = r ? 1.0f : 1.0f / sqrtf(1.0f + _ep*_ep);
+    for (i=0; i<_n; i++)
+        *_ka *= _pa[i];
+    for (i=0; i<2*L; i++)
+        *_ka /= _za[i];
+}
+
+
diff --git a/src/filter/src/fftfilt.c b/src/filter/src/fftfilt.c
new file mode 100644
index 0000000..9fca23f
--- /dev/null
+++ b/src/filter/src/fftfilt.c
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt : finite impulse response (FIR) filter using fast Fourier
+//           transforms (FFTs)
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// defined:
+//  FFTFILT()       name-mangling macro
+//  T               coefficients type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+// fftfilt object structure
+struct FFTFILT(_s) {
+    TC * h;             // filter coefficients array [size; h_len x 1]
+    unsigned int h_len; // filter length
+    unsigned int n;     // input/output block size
+
+    // internal memory arrays
+    // TODO: make TI/TO type, but ensuring complex
+    // TODO: use special format for fftfilt_rrrf type
+    float complex * time_buf;   // time buffer [size: 2*n x 1]
+    float complex * freq_buf;   // freq buffer [size: 2*n x 1]
+    float complex * H;          // FFT of filter coefficients [size: 2*n x 1]
+    float complex * w;          // overlap array [size: n x 1]
+
+    // FFT objects
+#ifdef LIQUID_FFTOVERRIDE
+    fftplan fft;        // FFT object (forward)
+    fftplan ifft;       // FFT object (inverse)
+#else
+    FFT_PLAN fft;       // FFT object (forward)
+    FFT_PLAN ifft;      // FFT object (inverse)
+#endif
+
+    TC scale;           // output scaling factor
+};
+
+// create FFT-based FIR filter using external coefficients
+//  _h      : filter coefficients [size: _h_len x 1]
+//  _h_len  : filter length, _h_len > 0
+//  _n      : block size = nfft/2, at least _h_len-1
+FFTFILT() FFTFILT(_create)(TC *         _h,
+                           unsigned int _h_len,
+                           unsigned int _n)
+{
+    // validate input
+    if (_h_len == 0) {
+        fprintf(stderr,"error: fftfilt_%s_create(), filter length must be greater than zero\n",
+                EXTENSION_FULL);
+        exit(1);
+    } else if (_n < _h_len-1) {
+        fprintf(stderr,"error: fftfilt_%s_create(), block length must be greater than _h_len-1 (%u)\n",
+                EXTENSION_FULL,
+                _h_len-1);
+        exit(1);
+    }
+
+    // create filter object and initialize
+    FFTFILT() q = (FFTFILT()) malloc(sizeof(struct FFTFILT(_s)));
+    q->h_len    = _h_len;
+    q->n        = _n;
+
+    // copy filter coefficients
+    q->h = (TC *) malloc((q->h_len)*sizeof(TC));
+    memmove(q->h, _h, _h_len*sizeof(TC));
+
+    // allocate internal memory arrays
+    q->time_buf = (float complex *) malloc((2*q->n)* sizeof(float complex)); // time buffer
+    q->freq_buf = (float complex *) malloc((2*q->n)* sizeof(float complex)); // frequency buffer
+    q->H        = (float complex *) malloc((2*q->n)* sizeof(float complex)); // FFT{ h }
+    q->w        = (float complex *) malloc((  q->n)* sizeof(float complex)); // delay buffer
+
+    // create internal FFT objects
+#ifdef LIQUID_FFTOVERRIDE
+    q->fft  = fft_create_plan(2*q->n, q->time_buf, q->freq_buf, LIQUID_FFT_FORWARD,  0);
+    q->ifft = fft_create_plan(2*q->n, q->freq_buf, q->time_buf, LIQUID_FFT_BACKWARD, 0);
+#else
+    q->fft  = FFT_CREATE_PLAN(2*q->n, q->time_buf, q->freq_buf, FFT_DIR_FORWARD,  FFT_METHOD);
+    q->ifft = FFT_CREATE_PLAN(2*q->n, q->freq_buf, q->time_buf, FFT_DIR_BACKWARD, FFT_METHOD);
+#endif
+
+    // compute FFT of filter coefficients and copy to internal H array
+    unsigned int i;
+    for (i=0; i<2*q->n; i++)
+        q->time_buf[i] = (i < q->h_len) ? q->h[i] : 0;
+    // time_buf > {FFT} > freq_buf
+#ifdef LIQUID_FFTOVERRIDE
+    fft_execute(q->fft);
+#else
+    FFT_EXECUTE(q->fft);
+#endif
+    memmove(q->H, q->freq_buf, 2*q->n*sizeof(float complex));
+
+    // set default scaling
+    FFTFILT(_set_scale)(q, 1);
+
+    // reset filter state (clear buffer)
+    FFTFILT(_reset)(q);
+
+    // return object
+    return q;
+}
+
+// destroy object, freeing all internally-allocated memory
+void FFTFILT(_destroy)(FFTFILT() _q)
+{
+    // free internal arrays
+    free(_q->h);                // filter coefficients
+    free(_q->time_buf);         // buffer (time domain)
+    free(_q->freq_buf);         // buffer (frequency domain)
+    free(_q->H);                // frequency response of filter coefficients
+    free(_q->w);                // output window buffer
+
+    // destroy FFT objects
+#ifdef LIQUID_FFTOVERRIDE
+    fft_destroy_plan(_q->fft);       // forward transform
+    fft_destroy_plan(_q->ifft);      // reverse transform
+#else
+    FFT_DESTROY_PLAN(_q->fft);  // forward transform
+    FFT_DESTROY_PLAN(_q->ifft); // reverse transform
+#endif
+
+    // free main object
+    free(_q);
+}
+
+// reset internal state of filter object
+void FFTFILT(_reset)(FFTFILT() _q)
+{
+    // reset overlap window
+    unsigned int i;
+    for (i=0; i<_q->n; i++)
+        _q->w[i] = 0;
+}
+
+// print filter object internals (taps, buffer)
+void FFTFILT(_print)(FFTFILT() _q)
+{
+    printf("fftfilt_%s: [h_len=%u, n=%u]\n", EXTENSION_FULL, _q->h_len, _q->n);
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++) {
+        printf("  h(%3u) = ", i+1);
+        PRINTVAL_TC(_q->h[n-i-1],%12.8f);
+        printf("\n");
+    }
+
+    // print scaling
+    printf("  scale = ");
+    PRINTVAL_TC(_q->scale,%12.8f);
+    printf("\n");
+}
+
+// set output scaling for filter
+void FFTFILT(_set_scale)(FFTFILT() _q,
+                         TC        _scale)
+{
+    // set scale, normalized by fft size
+    _q->scale = _scale / (float)(2*_q->n);
+}
+
+// execute the filter on internal buffer and coefficients
+//  _q      : filter object
+//  _x      : pointer to input data array  [size: _n x 1]
+//  _y      : pointer to output data array [size: _n x 1]
+void FFTFILT(_execute)(FFTFILT() _q,
+                       TI *      _x,
+                       TO *      _y)
+{
+    unsigned int i;
+
+    // copy input
+#if 0 //TI_COMPLEX
+    memmove(_q->time_buf, _x, _q->n*sizeof(TI));
+#else
+    // manual copy for type conversion
+    // TODO: use DCT or equivalent
+    for (i=0; i<_q->n; i++)
+        _q->time_buf[i] = _x[i];
+#endif
+
+    // pad end of time-domain buffer with zeros
+    // TODO: not necessary to do this every time
+#if 0
+    memset(&_q->time_buf[_q->n], 0, _q->n*sizeof(TI));
+#else
+    for (i=0; i<_q->n; i++)
+        _q->time_buf[_q->n + i] = 0;
+#endif
+
+    // run forward transform
+#ifdef LIQUID_FFTOVERRIDE
+    fft_execute(_q->fft);
+#else
+    FFT_EXECUTE(_q->fft);
+#endif
+
+    // compute inner product between FFT{ _x } and FFT{ H }
+#if 1
+    for (i=0; i<2*_q->n; i++)
+        _q->freq_buf[i] *= _q->H[i];
+#else
+    // use SIMD vector extensions
+# if TI_COMPLEX
+    // complex floating-point inner product
+    liquid_vectorcf_mul(_q->freq_buf, _q->H, 2*_q->n, _q->freq_buf);
+# else
+    // real floating-point inner product
+    liquid_vectorf_mul(_q->freq_buf, _q->H, 2*_q->n, _q->freq_buf);
+# endif
+#endif
+
+    // compute inverse transform
+#ifdef LIQUID_FFTOVERRIDE
+    fft_execute(_q->ifft);
+#else
+    FFT_EXECUTE(_q->ifft);
+#endif
+
+    // copy output summed with buffer and scaled
+#if TI_COMPLEX
+    for (i=0; i<_q->n; i++)
+        _y[i] = (_q->time_buf[i] + _q->w[i]) * _q->scale;
+#else
+    // manual copy for type conversion
+    // TODO: use DCT or equivalent
+    for (i=0; i<_q->n; i++)
+        _y[i] = (T) crealf(_q->time_buf[i] + _q->w[i]) * _q->scale;
+#endif
+
+    // copy buffer
+    memmove(_q->w, &_q->time_buf[_q->n], _q->n*sizeof(float complex));
+}
+
+// return length of filter object's internal coefficients
+unsigned int FFTFILT(_get_length)(FFTFILT() _q)
+{
+    return _q->h_len;
+}
+
diff --git a/src/filter/src/filter_cccf.c b/src/filter/src/filter_cccf.c
new file mode 100644
index 0000000..e36083d
--- /dev/null
+++ b/src/filter/src/filter_cccf.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Filter API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "cccf"
+
+// 
+#define AUTOCORR(name)      LIQUID_CONCAT(autocorr_cccf,name)
+#define FFTFILT(name)       LIQUID_CONCAT(fftfilt_cccf,name)
+#define FIRDECIM(name)      LIQUID_CONCAT(firdecim_cccf,name)
+#define FIRFILT(name)       LIQUID_CONCAT(firfilt_cccf,name)
+#define FIRINTERP(name)     LIQUID_CONCAT(firinterp_cccf,name)
+#define FIRPFB(name)        LIQUID_CONCAT(firpfb_cccf,name)
+#define IIRDECIM(name)      LIQUID_CONCAT(iirdecim_cccf,name)
+#define IIRFILT(name)       LIQUID_CONCAT(iirfilt_cccf,name)
+#define IIRFILTSOS(name)    LIQUID_CONCAT(iirfiltsos_cccf,name)
+#define IIRINTERP(name)     LIQUID_CONCAT(iirinterp_cccf,name)
+#define NCO(name)           LIQUID_CONCAT(nco_crcf,name)
+#define MSRESAMP(name)      LIQUID_CONCAT(msresamp_cccf,name)
+#define MSRESAMP2(name)     LIQUID_CONCAT(msresamp2_cccf,name)
+#define RESAMP(name)        LIQUID_CONCAT(resamp_cccf,name)
+#define RESAMP2(name)       LIQUID_CONCAT(resamp2_cccf,name)
+//#define SYMSYNC(name)       LIQUID_CONCAT(symsync_cccf,name)
+
+#define T                   float complex   // general
+#define TO                  float complex   // output
+#define TC                  float complex   // coefficients
+#define TI                  float complex   // input
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_cccf,name)
+#define POLY(name)          LIQUID_CONCAT(polyf,name)
+
+#define TO_COMPLEX          1
+#define TC_COMPLEX          1
+#define TI_COMPLEX          1
+
+#define PRINTVAL_TO(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// source files
+#include "autocorr.c"
+#include "fftfilt.c"
+#include "firdecim.c"
+#include "firfilt.c"
+#include "firinterp.c"
+#include "firpfb.c"
+#include "iirdecim.c"
+#include "iirfilt.c"
+#include "iirfiltsos.c"
+#include "iirinterp.c"
+//#include "qmfb.c"
+#include "msresamp.c"
+#include "msresamp2.c"
+#include "resamp.c"
+#include "resamp2.c"
+//#include "symsync.c"
diff --git a/src/filter/src/filter_crcf.c b/src/filter/src/filter_crcf.c
new file mode 100644
index 0000000..181c485
--- /dev/null
+++ b/src/filter/src/filter_crcf.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Filter API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "crcf"
+
+// 
+#define AUTOCORR(name)      LIQUID_CONCAT(autocorr_crcf,name)
+#define FFTFILT(name)       LIQUID_CONCAT(fftfilt_crcf,name)
+#define FIRDECIM(name)      LIQUID_CONCAT(firdecim_crcf,name)
+#define FIRFARROW(name)     LIQUID_CONCAT(firfarrow_crcf,name)
+#define FIRFILT(name)       LIQUID_CONCAT(firfilt_crcf,name)
+#define FIRINTERP(name)     LIQUID_CONCAT(firinterp_crcf,name)
+#define FIRPFB(name)        LIQUID_CONCAT(firpfb_crcf,name)
+#define IIRDECIM(name)      LIQUID_CONCAT(iirdecim_crcf,name)
+#define IIRFILT(name)       LIQUID_CONCAT(iirfilt_crcf,name)
+#define IIRFILTSOS(name)    LIQUID_CONCAT(iirfiltsos_crcf,name)
+#define IIRINTERP(name)     LIQUID_CONCAT(iirinterp_crcf,name)
+#define MSRESAMP(name)      LIQUID_CONCAT(msresamp_crcf,name)
+#define MSRESAMP2(name)     LIQUID_CONCAT(msresamp2_crcf,name)
+#define RESAMP(name)        LIQUID_CONCAT(resamp_crcf,name)
+#define RESAMP2(name)       LIQUID_CONCAT(resamp2_crcf,name)
+#define SYMSYNC(name)       LIQUID_CONCAT(symsync_crcf,name)
+
+#define T                   float complex   // general
+#define TO                  float complex   // output
+#define TC                  float           // coefficients
+#define TI                  float complex   // input
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_crcf,name)
+#define POLY(name)          LIQUID_CONCAT(polyf,name)
+
+#define TO_COMPLEX          1
+#define TC_COMPLEX          0
+#define TI_COMPLEX          1
+
+#define PRINTVAL_TO(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// source files
+//#include "autocorr.c"
+#include "fftfilt.c"
+#include "firdecim.c"
+#include "firfarrow.c"
+#include "firfilt.c"
+#include "firinterp.c"
+#include "firpfb.c"
+#include "iirdecim.c"
+#include "iirfilt.c"
+#include "iirfiltsos.c"
+#include "iirinterp.c"
+#include "msresamp.c"
+#include "msresamp2.c"
+#include "resamp.c"         // floating-point phase version
+//#include "resamp.fixed.c" // fixed-point phase version
+#include "resamp2.c"
+#include "symsync.c"
diff --git a/src/filter/src/filter_rrrf.c b/src/filter/src/filter_rrrf.c
new file mode 100644
index 0000000..30ee098
--- /dev/null
+++ b/src/filter/src/filter_rrrf.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Filter API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "rrrf"
+
+// 
+#define AUTOCORR(name)      LIQUID_CONCAT(autocorr_rrrf,name)
+#define FFTFILT(name)       LIQUID_CONCAT(fftfilt_rrrf,name)
+#define FIRDECIM(name)      LIQUID_CONCAT(firdecim_rrrf,name)
+#define FIRFARROW(name)     LIQUID_CONCAT(firfarrow_rrrf,name)
+#define FIRFILT(name)       LIQUID_CONCAT(firfilt_rrrf,name)
+#define FIRINTERP(name)     LIQUID_CONCAT(firinterp_rrrf,name)
+#define FIRHILB(name)       LIQUID_CONCAT(firhilbf,name)
+#define FIRPFB(name)        LIQUID_CONCAT(firpfb_rrrf,name)
+#define IIRDECIM(name)      LIQUID_CONCAT(iirdecim_rrrf,name)
+#define IIRFILT(name)       LIQUID_CONCAT(iirfilt_rrrf,name)
+#define IIRFILTSOS(name)    LIQUID_CONCAT(iirfiltsos_rrrf,name)
+#define IIRINTERP(name)     LIQUID_CONCAT(iirinterp_rrrf,name)
+#define MSRESAMP(name)      LIQUID_CONCAT(msresamp_rrrf,name)
+#define MSRESAMP2(name)     LIQUID_CONCAT(msresamp2_rrrf,name)
+#define RESAMP(name)        LIQUID_CONCAT(resamp_rrrf,name)
+#define RESAMP2(name)       LIQUID_CONCAT(resamp2_rrrf,name)
+#define SYMSYNC(name)       LIQUID_CONCAT(symsync_rrrf,name)
+
+#define T                   float   // general
+#define TO                  float   // output
+#define TC                  float   // coefficients
+#define TI                  float   // input
+#define WINDOW(name)        LIQUID_CONCAT(windowf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_rrrf,name)
+#define POLY(name)          LIQUID_CONCAT(polyf,name)
+
+#define TO_COMPLEX          0
+#define TC_COMPLEX          0
+#define TI_COMPLEX          0
+
+#define PRINTVAL_TO(X,F)    PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_FLOAT(X,F)
+
+// source files
+#include "autocorr.c"
+#include "fftfilt.c"
+#include "firdecim.c"
+#include "firfarrow.c"
+#include "firfilt.c"
+#include "firinterp.c"
+#include "firhilb.c"
+#include "firpfb.c"
+#include "iirdecim.c"
+#include "iirfilt.c"
+#include "iirfiltsos.c"
+#include "iirinterp.c"
+#include "msresamp.c"
+#include "msresamp2.c"
+#include "resamp.c"
+#include "resamp2.c"
+#include "symsync.c"
diff --git a/src/filter/src/firdecim.c b/src/filter/src/firdecim.c
new file mode 100644
index 0000000..092c07d
--- /dev/null
+++ b/src/filter/src/firdecim.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim.c
+//
+// finite impulse response decimator object definitions
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// decimator structure
+struct FIRDECIM(_s) {
+    TC * h;             // coefficients array
+    unsigned int h_len; // number of coefficients
+    unsigned int M;     // decimation factor
+
+    WINDOW() w;         // buffer
+    DOTPROD() dp;       // vector dot product
+};
+
+// create decimator object
+//  _M      :   decimation factor
+//  _h      :   filter coefficients [size: _h_len x 1]
+//  _h_len  :   filter coefficients length
+FIRDECIM() FIRDECIM(_create)(unsigned int _M,
+                             TC *         _h,
+                             unsigned int _h_len)
+{
+    // validate input
+    if (_h_len == 0) {
+        fprintf(stderr,"error: decim_%s_create(), filter length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_M == 0) {
+        fprintf(stderr,"error: decim_%s_create(), decimation factor must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    FIRDECIM() q = (FIRDECIM()) malloc(sizeof(struct FIRDECIM(_s)));
+    q->h_len = _h_len;
+    q->M     = _M;
+
+    // allocate memory for coefficients
+    q->h = (TC*) malloc((q->h_len)*sizeof(TC));
+
+    // load filter in reverse order
+    unsigned int i;
+    for (i=0; i<q->h_len; i++)
+        q->h[i] = _h[_h_len-i-1];
+
+    // create window (internal buffer)
+    q->w = WINDOW(_create)(q->h_len);
+
+    // create dot product object
+    q->dp = DOTPROD(_create)(q->h, q->h_len);
+
+    // reset filter state (clear buffer)
+    FIRDECIM(_clear)(q);
+
+    return q;
+}
+
+// create decimator from Kaiser prototype
+//  _M      :   decimolation factor
+//  _m      :   symbol delay
+//  _As     :   stop-band attenuation [dB]
+FIRDECIM() FIRDECIM(_create_kaiser)(unsigned int _M,
+                                    unsigned int _m,
+                                    float        _As)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: decim_%s_create_kaiser(), decim factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: decim_%s_create_kaiser(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_As < 0.0f) {
+        fprintf(stderr,"error: decim_%s_create_kaiser(), stop-band attenuation must be positive\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute filter coefficients (floating point precision)
+    unsigned int h_len = 2*_M*_m + 1;
+    float hf[h_len];
+    float fc = 0.5f / (float) (_M);
+    liquid_firdes_kaiser(h_len, fc, _As, 0.0f, hf);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    TC hc[h_len];
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        hc[i] = hf[i];
+    
+    // return decimator object
+    return FIRDECIM(_create)(_M, hc, 2*_M*_m);
+}
+
+// create square-root Nyquist decimator
+//  _type   :   filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _M      :   samples/symbol _M > 1
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   excess bandwidth factor, 0 < _beta < 1
+//  _dt     :   fractional sample delay, 0 <= _dt < 1
+FIRDECIM() FIRDECIM(_create_prototype)(int          _type,
+                                       unsigned int _M,
+                                       unsigned int _m,
+                                       float        _beta,
+                                       float        _dt)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: decim_%s_create_prototype(), decimation factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: decim_%s_create_prototype(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: decim_%s_create_prototype(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: decim_%s_create_prototype(), filter fractional sample delay must be in [-1,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int h_len = 2*_M*_m + 1;
+    float h[h_len];
+    liquid_firdes_prototype(_type,_M,_m,_beta,_dt,h);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    TC hc[h_len];
+    for (i=0; i<h_len; i++)
+        hc[i] = h[i];
+
+    // return decimator object
+    return FIRDECIM(_create)(_M, hc, h_len);
+}
+
+// destroy decimator object
+void FIRDECIM(_destroy)(FIRDECIM() _q)
+{
+    WINDOW(_destroy)(_q->w);
+    DOTPROD(_destroy)(_q->dp);
+    free(_q->h);
+    free(_q);
+}
+
+// print decimator object internals
+void FIRDECIM(_print)(FIRDECIM() _q)
+{
+    printf("FIRDECIM() [%u] :\n", _q->M);
+    printf("  window:\n");
+    WINDOW(_print)(_q->w);
+}
+
+// clear decimator object
+void FIRDECIM(_clear)(FIRDECIM() _q)
+{
+    WINDOW(_clear)(_q->w);
+}
+
+// execute decimator
+//  _q      :   decimator object
+//  _x      :   input sample array [size: _M x 1]
+//  _y      :   output sample pointer
+void FIRDECIM(_execute)(FIRDECIM() _q,
+                        TI *       _x,
+                        TO *       _y)
+{
+    TI * r; // read pointer
+    unsigned int i;
+    for (i=0; i<_q->M; i++) {
+        WINDOW(_push)(_q->w, _x[i]);
+
+        if (i==0) {
+            // read buffer (retrieve pointer to aligned memory array)
+            WINDOW(_read)(_q->w, &r);
+
+            // execute dot product
+            DOTPROD(_execute)(_q->dp, r, _y);
+        }
+    }
+}
+
+// execute decimator on block of _n*_M input samples
+//  _q      : decimator object
+//  _x      : input array [size: _n*_M x 1]
+//  _n      : number of _output_ samples
+//  _y      : output array [_size: _n x 1]
+void FIRDECIM(_execute_block)(FIRDECIM()   _q,
+                              TI *         _x,
+                              unsigned int _n,
+                              TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // execute _M input samples computing just one output each time
+        FIRDECIM(_execute)(_q, &_x[i*_q->M], &_y[i]);
+    }
+}
+
diff --git a/src/filter/src/firdes.c b/src/filter/src/firdes.c
new file mode 100644
index 0000000..2b2b638
--- /dev/null
+++ b/src/filter/src/firdes.c
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Finite impulse response filter design
+//
+// References:
+//  [Herrmann:1973] O. Herrmann, L. R. Rabiner, and D. S. K. Chan,
+//      "Practical design rules for optimum finite impulse response
+//      lowpass digital filters," Bell Syst. Tech. Journal, vol. 52,
+//      pp. 769--99, July-Aug. 1973
+//  [Vaidyanathan:1993] Vaidyanathan, P. P., "Multirate Systems and
+//      Filter Banks," 1993, Prentice Hall, Section 3.2.1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// method to estimate required filter length
+#define ESTIMATE_REQ_FILTER_LEN_METHOD_KAISER   (0)
+#define ESTIMATE_REQ_FILTER_LEN_METHOD_HERRMANN (1)
+
+// select filter estimate method
+#define ESTIMATE_REQ_FILTER_LEN_METHOD          (0)
+
+// esimate required filter length given transition bandwidth and
+// stop-band attenuation
+//  _df     :   transition bandwidth (0 < _df < 0.5)
+//  _As     :   stopband suppression level [dB] (_As > 0)
+unsigned int estimate_req_filter_len(float _df,
+                                     float _As)
+{
+    if (_df > 0.5f || _df <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len(), invalid bandwidth : %f\n", _df);
+        exit(1);
+    } else if (_As <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len(), invalid stopband level : %f\n", _As);
+        exit(1);
+    }
+
+    // compute filter length estimate
+#if ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_KAISER
+    // use Kaiser's estimate
+    unsigned int h_len = (unsigned int) estimate_req_filter_len_Kaiser(_df,_As);
+#elif ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_HERRMANN
+    // use Herrmann's estimate
+    unsigned int h_len = (unsigned int) estimate_req_filter_len_Herrmann(_df,_As);
+#else
+#   error "invalid required filter length estimation method"
+#endif
+    
+    return h_len;
+}
+
+// estimate filter stop-band attenuation given
+//  _df     :   transition bandwidth (0 < _b < 0.5)
+//  _N      :   filter length
+float estimate_req_filter_As(float        _df,
+                             unsigned int _N)
+{
+    // run search for stop-band attenuation which gives these results
+    float As0   = 0.01f;    // lower bound
+    float As1   = 200.0f;   // upper bound
+
+    float As_hat = 0.0f;    // stop-band attenuation estimate
+    float N_hat = 0.0f;     // filter length estimate
+
+    // perform simple bisection search
+    unsigned int num_iterations = 20;
+    unsigned int i;
+    for (i=0; i<num_iterations; i++) {
+        // bisect limits
+        As_hat = 0.5f*(As1 + As0);
+#if ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_KAISER
+        N_hat = estimate_req_filter_len_Kaiser(_df, As_hat);
+#elif ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_HERRMANN
+        N_hat = estimate_req_filter_len_Herrmann(_df, As_hat);
+#else
+#       error "invalid required filter length estimation method"
+#endif
+
+        //printf("range[%8.2f, %8.2f] As-hat=%8.2fdB, N=%8.2f (target: %3u taps)\n",
+        //        As0, As1, As_hat, N_hat, _N);
+
+        // update limits
+        if (N_hat < (float)_N) {
+            As0 = As_hat;
+        } else {
+            As1 = As_hat;
+        }
+    }
+    return As_hat;
+}
+
+// estimate filter transition bandwidth given
+//  _As     :   stop-band attenuation [dB], _As > 0
+//  _N      :   filter length
+float estimate_req_filter_df(float        _As,
+                             unsigned int _N)
+{
+    // run search for stop-band attenuation which gives these results
+    float df0   = 1e-3f;    // lower bound
+    float df1   = 0.499f;   // upper bound
+
+    float df_hat = 0.0f;    // stop-band attenuation estimate
+    float N_hat = 0.0f;     // filter length estimate
+
+    // perform simple bisection search
+    unsigned int num_iterations = 20;
+    unsigned int i;
+    for (i=0; i<num_iterations; i++) {
+        // bisect limits
+        df_hat = 0.5f*(df1 + df0);
+#if ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_KAISER
+        N_hat = estimate_req_filter_len_Kaiser(df_hat, _As);
+#elif ESTIMATE_REQ_FILTER_LEN_METHOD == ESTIMATE_REQ_FILTER_LEN_METHOD_HERRMANN
+        N_hat = estimate_req_filter_len_Herrmann(df_hat, _As);
+#else
+#       error "invalid required filter length estimation method"
+#endif
+
+        //printf("range[%8.5f, %8.5f] df-hat=%8.5fdB, N=%8.2f (target: %3u taps)\n",
+        //        df0, df1, df_hat, N_hat, _N);
+
+        // update limits
+        if (N_hat < (float)_N) {
+            df1 = df_hat;
+        } else {
+            df0 = df_hat;
+        }
+    }
+    return df_hat;
+
+}
+
+
+// esimate required filter length given transition bandwidth and
+// stop-band attenuation (algorithm from [Vaidyanathan:1993])
+//  _df     :   transition bandwidth (0 < _df < 0.5)
+//  _As     :   stop-band attenuation [dB] (As > 0)
+float estimate_req_filter_len_Kaiser(float _df,
+                                     float _As)
+{
+    if (_df > 0.5f || _df <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len_Kaiser(), invalid bandwidth : %f\n", _df);
+        exit(1);
+    } else if (_As <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len(), invalid stopband level : %f\n", _As);
+        exit(1);
+    }
+
+    // compute filter length estimate
+    return (_As - 7.95f)/(14.26f*_df);
+}
+
+
+// esimate required filter length given transition bandwidth and
+// stop-band attenuation (algorithm from [Herrmann:1973])
+//  _df     :   transition bandwidth (0 < _df < 0.5)
+//  _As     :   stop-band attenuation [dB] (As > 0)
+float estimate_req_filter_len_Herrmann(float _df,
+                                       float _As)
+{
+    if (_df > 0.5f || _df <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len_Herrmann(), invalid bandwidth : %f\n", _df);
+        exit(1);
+    } else if (_As <= 0.0f) {
+        fprintf(stderr,"error: estimate_req_filter_len(), invalid stopband level : %f\n", _As);
+        exit(1);
+    }
+
+    // Gaeddert's revisions:
+    if (_As > 105.0f)
+        return estimate_req_filter_len_Kaiser(_df,_As);
+
+    _As += 7.4f;
+
+    // compute delta_1, delta_2
+    float d1, d2;
+    d1 = d2 = powf(10.0, -_As/20.0);
+
+    // compute log of delta_1, delta_2
+    float t1 = log10f(d1);
+    float t2 = log10f(d2);
+
+    // compute D_infinity(delta_1, delta_2)
+    float Dinf = (0.005309f*t1*t1 + 0.07114f*t1 - 0.4761f)*t2 -
+                 (0.002660f*t1*t1 + 0.59410f*t1 + 0.4278f);
+
+    // compute f(delta_1, delta_2)
+    float f = 11.012f + 0.51244f*(t1-t2);
+
+    // compute filter length estimate
+    float h_len = (Dinf - f*_df*_df) / _df + 1.0f;
+    
+    return h_len;
+}
+
+// returns the Kaiser window beta factor give the filter's target
+// stop-band attenuation (As) [Vaidyanathan:1993]
+//  _As     :   target filter's stop-band attenuation [dB], _As > 0
+float kaiser_beta_As(float _As)
+{
+    _As = fabsf(_As);
+    float beta;
+    if (_As > 50.0f)
+        beta = 0.1102f*(_As - 8.7f);
+    else if (_As > 21.0f)
+        beta = 0.5842*powf(_As - 21, 0.4f) + 0.07886f*(_As - 21);
+    else
+        beta = 0.0f;
+
+    return beta;
+}
+
+// Design FIR using kaiser window
+//  _n      : filter length, _n > 0
+//  _fc     : cutoff frequency, 0 < _fc < 0.5
+//  _As     : stop-band attenuation [dB], _As > 0
+//  _mu     : fractional sample offset, -0.5 < _mu < 0.5
+//  _h      : output coefficient buffer, [size: _n x 1]
+void liquid_firdes_kaiser(unsigned int _n,
+                          float _fc,
+                          float _As,
+                          float _mu,
+                          float *_h)
+{
+    // validate inputs
+    if (_mu < -0.5f || _mu > 0.5f) {
+        fprintf(stderr,"error: liquid_firdes_kaiser(), _mu (%12.4e) out of range [-0.5,0.5]\n", _mu);
+        exit(1);
+    } else if (_fc < 0.0f || _fc > 0.5f) {
+        fprintf(stderr,"error: liquid_firdes_kaiser(), cutoff frequency (%12.4e) out of range (0, 0.5)\n", _fc);
+        exit(1);
+    } else if (_n == 0) {
+        fprintf(stderr,"error: liquid_firdes_kaiser(), filter length must be greater than zero\n");
+        exit(1);
+    }
+
+    // choose kaiser beta parameter (approximate)
+    float beta = kaiser_beta_As(_As);
+
+    float t, h1, h2; 
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        t = (float)i - (float)(_n-1)/2 + _mu;
+     
+        // sinc prototype
+        h1 = sincf(2.0f*_fc*t);
+
+        // kaiser window
+        h2 = kaiser(i,_n,beta,_mu);
+
+        //printf("t = %f, h1 = %f, h2 = %f\n", t, h1, h2);
+
+        // composite
+        _h[i] = h1*h2;
+    }
+}
+
+// Design (root-)Nyquist filter from prototype
+//  _type   : filter type (e.g. LIQUID_FIRFILT_RRRC)
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : excess bandwidth factor, _beta in [0,1]
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_prototype(liquid_firfilt_type _type,
+                             unsigned int        _k,
+                             unsigned int        _m,
+                             float               _beta,
+                             float               _dt,
+                             float *             _h)
+{
+    // compute filter parameters
+    unsigned int h_len = 2*_k*_m + 1;   // length
+    float fc = 0.5f / (float)_k;        // cut-off frequency
+    float df = _beta / (float)_k;       // transition bandwidth
+    float As = estimate_req_filter_As(df,h_len);   // stop-band attenuation
+
+    // Parks-McClellan algorithm parameters
+    float bands[6] = {  0.0f,       fc-0.5f*df,
+                        fc,         fc,
+                        fc+0.5f*df, 0.5f};
+    float des[3] = { (float)_k, 0.5f*_k, 0.0f };
+    float weights[3] = {1.0f, 1.0f, 1.0f};
+    liquid_firdespm_wtype wtype[3] = {  LIQUID_FIRDESPM_FLATWEIGHT,
+                                        LIQUID_FIRDESPM_FLATWEIGHT,
+                                        LIQUID_FIRDESPM_FLATWEIGHT};
+
+    switch (_type) {
+    
+    // Nyquist filter prototypes
+
+    case LIQUID_FIRFILT_KAISER:
+        liquid_firdes_kaiser(h_len, fc, As, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_PM:
+        // WARNING: input timing offset is ignored here
+        firdespm_run(h_len, 3, bands, des, weights, wtype, LIQUID_FIRDESPM_BANDPASS, _h);
+        break;
+    case LIQUID_FIRFILT_RCOS:
+        liquid_firdes_rcos(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_FEXP:
+        liquid_firdes_fexp(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_FSECH:
+        liquid_firdes_fsech(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_FARCSECH:
+        liquid_firdes_farcsech(_k, _m, _beta, _dt, _h);
+        break;
+
+    // root-Nyquist filter prototypes
+
+    case LIQUID_FIRFILT_ARKAISER:
+        liquid_firdes_arkaiser(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_RKAISER:
+        liquid_firdes_rkaiser(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_RRC:
+        liquid_firdes_rrcos(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_hM3:
+        liquid_firdes_hM3(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_GMSKTX:
+        liquid_firdes_gmsktx(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_GMSKRX:
+        liquid_firdes_gmskrx(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_RFEXP:
+        liquid_firdes_rfexp(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_RFSECH:
+        liquid_firdes_rfsech(_k, _m, _beta, _dt, _h);
+        break;
+    case LIQUID_FIRFILT_RFARCSECH:
+        liquid_firdes_rfarcsech(_k, _m, _beta, _dt, _h);
+        break;
+    default:
+        fprintf(stderr,"error: liquid_firdes_prototype(), invalid root-Nyquist filter type '%d'\n", _type);
+        exit(1);
+    }
+}
+
+
+// Design FIR doppler filter
+//  _n      : filter length
+//  _fd     : normalized doppler frequency (0 < _fd < 0.5)
+//  _K      : Rice fading factor (K >= 0)
+//  _theta  : LoS component angle of arrival
+//  _h      : output coefficient buffer
+void liquid_firdes_doppler(unsigned int _n,
+                           float        _fd,
+                           float        _K,
+                           float        _theta,
+                           float *      _h)
+{
+    float t, J, r, w;
+    float beta = 4; // kaiser window parameter
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // time sample
+        t = (float)i - (float)(_n-1)/2;
+
+        // Bessel
+        J = 1.5*liquid_besselj0f(fabsf((float)(2*M_PI*_fd*t)));
+
+        // Rice-K component
+        r = 1.5*_K/(_K+1)*cosf(2*M_PI*_fd*t*cosf(_theta));
+
+        // Window
+        w = kaiser(i, _n, beta, 0);
+
+        // composite
+        _h[i] = (J+r)*w;
+
+        //printf("t=%f, J=%f, r=%f, w=%f\n", t, J, r, w);
+    }
+}
+
+
+// 
+// filter analysis
+//
+
+// liquid_filter_autocorr()
+//
+// Compute auto-correlation of filter at a specific lag.
+//
+//  _h      :   filter coefficients [size: _h_len x 1]
+//  _h_len  :   filter length
+//  _lag    :   auto-correlation lag (samples)
+float liquid_filter_autocorr(float *      _h,
+                             unsigned int _h_len,
+                             int          _lag)
+{
+    // auto-correlation is even symmetric
+    _lag = abs(_lag);
+
+    // lag outside of filter length is zero
+    if (_lag >= _h_len) return 0.0f;
+
+    // compute auto-correlation
+    float rxx=0.0f; // initialize auto-correlation to zero
+    unsigned int i;
+    for (i=_lag; i<_h_len; i++)
+        rxx += _h[i] * _h[i-_lag];
+
+    return rxx;
+}
+
+// liquid_filter_crosscorr()
+//
+// Compute cross-correlation of two filters at a specific lag.
+//
+//  _h      :   filter coefficients [size: _h_len]
+//  _h_len  :   filter length
+//  _g      :   filter coefficients [size: _g_len]
+//  _g_len  :   filter length
+//  _lag    :   cross-correlation lag (samples)
+float liquid_filter_crosscorr(float *      _h,
+                              unsigned int _h_len,
+                              float *      _g,
+                              unsigned int _g_len,
+                              int          _lag)
+{
+    // cross-correlation is odd symmetric
+    if (_h_len < _g_len) {
+        return liquid_filter_crosscorr(_g, _g_len,
+                                       _h, _h_len,
+                                       -_lag);
+    }
+
+    // at this point _h_len > _g_len
+    // assert(_h_len > _g_len);
+
+    if (_lag <= -(int)_g_len) return 0.0f;
+    if (_lag >=  (int)_h_len) return 0.0f;
+
+    int ig = _lag < 0 ? -_lag : 0;  // starting index for _g
+    int ih = _lag > 0 ?  _lag : 0;  // starting index for _h
+
+    // compute length of overlap
+    //     condition 1:             condition 2:          condition 3:
+    //    [------ h ------]     [------ h ------]     [------ h ------]
+    //  [-- g --]                    [-- g --]                  [-- g --]
+    //   >|  n  |<                  >|   n   |<                >|  n  |<
+    //
+    int n;
+    if (_lag < 0)
+        n = (int)_g_len + _lag;
+    else if (_lag < (_h_len-_g_len))
+        n = _g_len;
+    else
+        n = _h_len - _lag;
+
+    // compute cross-correlation
+    float rxy=0.0f; // initialize auto-correlation to zero
+    int i;
+    for (i=0; i< n; i++)
+        rxy += _h[ih+i] * _g[ig+i];
+
+    return rxy;
+}
+
+// liquid_filter_isi()
+//
+// Compute inter-symbol interference (ISI)--both RMS and
+// maximum--for the filter _h.
+//
+//  _h      :   filter coefficients [size: 2*_k*_m+1 x 1]
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _rms    :   output root mean-squared ISI
+//  _max    :   maximum ISI
+void liquid_filter_isi(float *      _h,
+                       unsigned int _k,
+                       unsigned int _m,
+                       float *      _rms,
+                       float *      _max)
+{
+    unsigned int h_len = 2*_k*_m+1;
+
+    // compute zero-lag auto-correlation
+    float rxx0 = liquid_filter_autocorr(_h,h_len,0);
+    //printf("rxx0 = %12.8f\n", rxx0);
+    //exit(1);
+
+    unsigned int i;
+    float isi_rms = 0.0f;
+    float isi_max = 0.0f;
+    float e;
+    for (i=1; i<=2*_m; i++) {
+        e = liquid_filter_autocorr(_h,h_len,i*_k) / rxx0;
+        e = fabsf(e);
+
+        isi_rms += e*e;
+        
+        if (i==1 || e > isi_max)
+            isi_max = e;
+    }
+
+    *_rms = sqrtf( isi_rms / (float)(2*_m) );
+    *_max = isi_max;
+}
+
+// Compute relative out-of-band energy
+//
+//  _h      :   filter coefficients [size: _h_len x 1]
+//  _h_len  :   filter length
+//  _fc     :   analysis cut-off frequency
+//  _nfft   :   fft size
+float liquid_filter_energy(float *      _h,
+                           unsigned int _h_len,
+                           float        _fc,
+                           unsigned int _nfft)
+{
+    // validate input
+    if (_fc < 0.0f || _fc > 0.5f) {
+        fprintf(stderr,"error: liquid_filter_energy(), cut-off frequency must be in [0,0.5]\n");
+        exit(1);
+    } else if (_h_len == 0) {
+        fprintf(stderr,"error: liquid_filter_energy(), filter length must be greater than zero\n");
+        exit(1);
+    } else if (_nfft == 0) {
+        fprintf(stderr,"error: liquid_filter_energy(), fft size must be greater than zero\n");
+        exit(1);
+    }
+
+    // allocate memory for complex phasor
+    float complex expjwt[_h_len];
+
+    // initialize accumulators
+    float e_total = 0.0f;       // total energy
+    float e_stopband = 0.0f;    // stop-band energy
+
+    // create dotprod object
+    dotprod_crcf dp = dotprod_crcf_create(_h,_h_len);
+
+    unsigned int i;
+    unsigned int k;
+    for (i=0; i<_nfft; i++) {
+        float f = 0.5f * (float)i / (float)(_nfft);
+        
+        // initialize complex phasor
+        for (k=0; k<_h_len; k++)
+            expjwt[k] = cexpf(_Complex_I*2*M_PI*f*k);
+
+        // compute vector dot product
+        float complex v;
+        dotprod_crcf_execute(dp, expjwt, &v);
+
+        // accumulate output
+        float e2 = crealf( v*conjf(v) );
+        e_total += e2;
+        e_stopband += (f >= _fc) ? e2 : 0.0f;
+    }
+
+    // destroy dotprod object
+    dotprod_crcf_destroy(dp);
+
+    // return energy ratio
+    return e_stopband / e_total;
+}
+
+// returns filter type based on input string
+int liquid_getopt_str2firfilt(const char * _str)
+{
+    // Generic filter designs
+    if      (strcmp(_str,"kaiser")   ==0) return LIQUID_FIRFILT_KAISER;
+    else if (strcmp(_str,"pm")       ==0) return LIQUID_FIRFILT_PM;
+    
+    // Nyquist filter designs
+    else if (strcmp(_str,"rcos")     ==0) return LIQUID_FIRFILT_RCOS;
+    else if (strcmp(_str,"fexp")     ==0) return LIQUID_FIRFILT_FEXP;
+    else if (strcmp(_str,"fsech")    ==0) return LIQUID_FIRFILT_FSECH;
+    else if (strcmp(_str,"farcsech") ==0) return LIQUID_FIRFILT_FARCSECH;
+
+    // root-Nyquist filter designs
+    else if (strcmp(_str,"arkaiser") ==0) return LIQUID_FIRFILT_ARKAISER;
+    else if (strcmp(_str,"rkaiser")  ==0) return LIQUID_FIRFILT_RKAISER;
+    else if (strcmp(_str,"rrcos")    ==0) return LIQUID_FIRFILT_RRC;
+    else if (strcmp(_str,"hM3")      ==0) return LIQUID_FIRFILT_hM3;
+    else if (strcmp(_str,"gmsktx")   ==0) return LIQUID_FIRFILT_GMSKTX;
+    else if (strcmp(_str,"gmskrx")   ==0) return LIQUID_FIRFILT_GMSKRX;
+    else if (strcmp(_str,"rfexp")    ==0) return LIQUID_FIRFILT_RFEXP;
+    else if (strcmp(_str,"rfsech")   ==0) return LIQUID_FIRFILT_RFSECH;
+    else if (strcmp(_str,"rfarcsech")==0) return LIQUID_FIRFILT_RFARCSECH;
+
+    // filter type unknown
+    return LIQUID_FIRFILT_UNKNOWN;
+}
+
+
+
diff --git a/src/filter/src/firdespm.c b/src/filter/src/firdespm.c
new file mode 100644
index 0000000..69deb31
--- /dev/null
+++ b/src/filter/src/firdespm.c
@@ -0,0 +1,798 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fir (finite impulse response) filter design using Parks-McClellan
+// algorithm
+//
+// Much of this program has been borrowed heavily from [McClellan:1973]
+// and [Janovetz:1998] with the exception of the Lagrange polynomial
+// interpolation formulas and the structured 'firdespm' object. Several
+// improvements have been made in the search algorithm to help maintain
+// stability and convergence.
+//
+// References:
+//  [Parks:1972] T. W. Parks and J. H. McClellan, "Chebyshev
+//      Approximation for Nonrecursive Digital Filters with Linear
+//      Phase," IEEE Transactions on Circuit Theory, vol. CT-19,
+//      no. 2, March 1972.
+//  [McClellan:1973] J. H. McClellan, T. W. Parks, L. R. Rabiner, "A
+//      Computer Program for Designing Optimum FIR Linear Phase
+//      Digital Filters," IEEE Transactions on Audio and
+//      Electroacoustics, vol. AU-21, No. 6, December 1973.
+//  [Rabiner:1975] L. R. Rabiner, J. H. McClellan, T. W. Parks, "FIR
+//      Digital filter Design Techniques Using Weighted Chebyshev
+//      Approximations," Proceedings of the IEEE, March 1975.
+//  [Parks:1987] T. W. Parks and C. S. Burrus, "Digital Filter
+//      Design," Upper Saddle River, NJ, John Wiley & Sons, Inc., 1987
+//      (Section 3.3.3)
+//  [Janovetz:1998] J. Janovetz, online: http://www.janovetz.com/jake/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_FIRDESPM_DEBUG       0
+#define LIQUID_FIRDESPM_DEBUG_PRINT 0
+
+#define LIQUID_FIRDESPM_DEBUG_FILENAME "firdespm_internal_debug.m"
+#if LIQUID_FIRDESPM_DEBUG
+void firdespm_output_debug_file(firdespm _q);
+#endif
+
+// structured data type
+struct firdespm_s {
+    // constants
+    unsigned int h_len;         // filter length
+    unsigned int s;             // odd/even filter length
+    unsigned int n;             // filter semi-length
+    unsigned int r;             // number of approximating functions
+    unsigned int num_bands;     // number of discrete bands
+    unsigned int grid_size;     // number of points on the grid
+    unsigned int grid_density;  // density of the grid
+
+    // band type (e.g. LIQUID_FIRDESPM_BANDPASS)
+    liquid_firdespm_btype btype;
+
+    // filter description parameters
+    double * bands;             // bands array [size: 2*num_bands]
+    double * des;               // desired response [size: num_bands]
+    double * weights;           // weights [size: num_bands]
+    liquid_firdespm_wtype * wtype;
+    
+    // dense grid elements
+    double * F;                 // frequencies, [0, 0.5]
+    double * D;                 // desired response
+    double * W;                 // weight
+    double * E;                 // error
+
+    double * x;                 // Chebyshev points : cos(2*pi*f)
+    double * alpha;             // Lagrange interpolating polynomial
+    double * c;                 // interpolants
+    double rho;                 // extremal weighted error
+
+    unsigned int * iext;        // indices of extrema
+    unsigned int num_exchanges; // number of changes in extrema
+
+#if LIQUID_FIRDESPM_DEBUG
+    FILE * fid;
+#endif
+
+};
+
+// run filter design (full life cycle of object)
+//  _h_len      :   length of filter (number of taps)
+//  _num_bands  :   number of frequency bands
+//  _bands      :   band edges, f in [0,0.5], [size: _num_bands x 2]
+//  _des        :   desired response [size: _num_bands x 1]
+//  _weights    :   response weighting [size: _num_bands x 1]
+//  _wtype      :   weight types (e.g. LIQUID_FIRDESPM_FLATWEIGHT) [size: _num_bands x 1]
+//  _btype      :   band type (e.g. LIQUID_FIRDESPM_BANDPASS)
+//  _h          :   output coefficients array [size: _h_len x 1]
+void firdespm_run(unsigned int _h_len,
+                  unsigned int _num_bands,
+                  float * _bands,
+                  float * _des,
+                  float * _weights,
+                  liquid_firdespm_wtype * _wtype,
+                  liquid_firdespm_btype _btype,
+                  float * _h)
+{
+    // create object
+    firdespm q = firdespm_create(_h_len,_num_bands,_bands,_des,_weights,_wtype,_btype);
+
+    // execute
+    firdespm_execute(q,_h);
+
+    // destroy
+    firdespm_destroy(q);
+}
+
+// create firdespm object
+//  _h_len      :   length of filter (number of taps)
+//  _num_bands  :   number of frequency bands
+//  _bands      :   band edges, f in [0,0.5], [size: _num_bands x 2]
+//  _des        :   desired response [size: _num_bands x 1]
+//  _weights    :   response weighting [size: _num_bands x 1]
+//  _wtype      :   weight types (e.g. LIQUID_FIRDESPM_FLATWEIGHT) [size: _num_bands x 1]
+//  _btype      :   band type (e.g. LIQUID_FIRDESPM_BANDPASS)
+firdespm firdespm_create(unsigned int _h_len,
+                         unsigned int _num_bands,
+                         float * _bands,
+                         float * _des,
+                         float * _weights,
+                         liquid_firdespm_wtype * _wtype,
+                         liquid_firdespm_btype _btype)
+{
+    unsigned int i;
+
+    // validate input
+    int bands_valid = 1;
+    int weights_valid = 1;
+    // ensure bands are withing [0,0.5]
+    for (i=0; i<2*_num_bands; i++)
+        bands_valid &= _bands[i] >= 0.0 && _bands[i] <= 0.5;
+    // ensure bands are non-decreasing
+    for (i=1; i<2*_num_bands; i++)
+        bands_valid &= _bands[i] >= _bands[i-1];
+    // ensure weights are greater than 0
+    for (i=0; i<_num_bands; i++)
+        weights_valid &= _weights[i] > 0;
+
+    if (!bands_valid) {
+        fprintf(stderr,"error: firdespm_create(), invalid bands\n");
+        exit(1);
+    } else if (!weights_valid) {
+        fprintf(stderr,"error: firdespm_create(), invalid weights (must be positive)\n");
+        exit(1);
+    } else if (_num_bands == 0) {
+        fprintf(stderr,"error: firdespm_create(), number of bands must be > 0\n");
+        exit(1);
+    }
+
+    // create object
+    firdespm q = (firdespm) malloc(sizeof(struct firdespm_s));
+
+    // compute number of extremal frequencies
+    q->h_len = _h_len;              // filter length
+    q->s     = q->h_len % 2;        // odd/even length
+    q->n     = (q->h_len - q->s)/2; // filter semi-length
+    q->r     = q->n + q->s;         // number of approximating functions
+    q->btype = _btype;              // set band type
+
+    // allocate memory for extremal frequency set, interpolating polynomial
+    q->iext  = (unsigned int*) malloc((q->r+1)*sizeof(unsigned int));
+    q->x     = (double*) malloc((q->r+1)*sizeof(double));
+    q->alpha = (double*) malloc((q->r+1)*sizeof(double));
+    q->c     = (double*) malloc((q->r+1)*sizeof(double));
+
+    // allocate memory for arrays
+    q->num_bands = _num_bands;
+    q->bands    = (double*) malloc(2*q->num_bands*sizeof(double));
+    q->des      = (double*) malloc(  q->num_bands*sizeof(double));
+    q->weights  = (double*) malloc(  q->num_bands*sizeof(double));
+
+    // allocate memory for weighting types
+    q->wtype = (liquid_firdespm_wtype*) malloc(q->num_bands*sizeof(liquid_firdespm_wtype));
+    if (_wtype == NULL) {
+        // set to default (LIQUID_FIRDESPM_FLATWEIGHT)
+        for (i=0; i<q->num_bands; i++)
+            q->wtype[i] = LIQUID_FIRDESPM_FLATWEIGHT;
+    } else {
+        // copy from input
+        for (i=0; i<q->num_bands; i++)
+            q->wtype[i] = _wtype[i];
+    }
+
+    // copy input arrays
+    for (i=0; i<q->num_bands; i++) {
+        q->bands[2*i+0] = _bands[2*i+0];
+        q->bands[2*i+1] = _bands[2*i+1];
+
+        q->des[i]       = _des[i];
+
+        if (_weights == NULL)
+            q->weights[i] = 1.0f;
+        else
+            q->weights[i]   = _weights[i];
+    }
+
+    // estimate grid size
+    // TODO : adjust grid density based on expected value for rho
+    q->grid_density = 20;
+    q->grid_size = 0;
+    double df = 0.5/(q->grid_density*q->r); // frequency step
+    for (i=0; i<q->num_bands; i++) {
+        double f0 = q->bands[2*i+0];         // lower band edge
+        double f1 = q->bands[2*i+1];         // upper band edge
+        q->grid_size += (unsigned int)( (f1-f0)/df + 1.0 );
+    }
+
+    // create the grid
+    q->F = (double*) malloc(q->grid_size*sizeof(double));
+    q->D = (double*) malloc(q->grid_size*sizeof(double));
+    q->W = (double*) malloc(q->grid_size*sizeof(double));
+    q->E = (double*) malloc(q->grid_size*sizeof(double));
+    firdespm_init_grid(q);
+    // TODO : fix grid, weights according to filter type
+
+    // return object
+    return q;
+}
+
+// destroy firdespm object
+void firdespm_destroy(firdespm _q)
+{
+#if LIQUID_FIRDESPM_DEBUG
+    firdespm_output_debug_file(_q);
+#endif
+
+    // free memory for extremal frequency set, interpolating polynomial
+    free(_q->iext);
+    free(_q->x);
+    free(_q->alpha);
+    free(_q->c);
+
+    // free dense grid elements
+    free(_q->F);
+    free(_q->D);
+    free(_q->W);
+    free(_q->E);
+
+    // free band description elements
+    free(_q->bands);
+    free(_q->des);
+    free(_q->weights);
+    free(_q->wtype);
+
+    // free object
+    free(_q);
+}
+
+// print firdespm object internals
+void firdespm_print(firdespm _q)
+{
+    unsigned int i;
+
+    printf("firdespm:               ");
+    for (i=0; i<_q->num_bands; i++) printf("      band %-5u", i);
+    printf("\n");
+
+    printf("  lower band edge       ");
+    for (i=0; i<_q->num_bands; i++) printf("%16.8f", _q->bands[2*i+0]);
+    printf("\n");
+
+    printf("  upper band edge       ");
+    for (i=0; i<_q->num_bands; i++) printf("%16.8f", _q->bands[2*i+1]);
+    printf("\n");
+
+    printf("  desired value         ");
+    for (i=0; i<_q->num_bands; i++) printf("%16.8f", _q->des[i]);
+    printf("\n");
+
+    printf("  weighting             ");
+    for (i=0; i<_q->num_bands; i++) printf("%16.8f", _q->weights[i]);
+    printf("\n");
+}
+
+// execute filter design, storing result in _h
+void firdespm_execute(firdespm _q, float * _h)
+{
+    unsigned int i;
+
+    // initial guess of extremal frequencies evenly spaced on F
+    // TODO : guarantee at least one extremal frequency lies in each band
+    for (i=0; i<_q->r+1; i++) {
+        _q->iext[i] = (i * (_q->grid_size-1)) / _q->r;
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+        printf("iext_guess[%3u] = %u\n", i, _q->iext[i]);
+#endif
+    }
+
+    // iterate over the Remez exchange algorithm
+    unsigned int p;
+    unsigned int max_iterations = 40;
+    for (p=0; p<max_iterations; p++) {
+        // compute interpolator
+        firdespm_compute_interp(_q);
+
+        // compute error
+        firdespm_compute_error(_q);
+
+        // search for new extremal frequencies
+        firdespm_iext_search(_q);
+
+        // check exit criteria
+        if (firdespm_is_search_complete(_q))
+            break;
+    }
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("search complete in %u iterations\n", p);
+#endif
+
+    // compute filter taps
+    firdespm_compute_taps(_q, _h);
+}
+
+
+// 
+// internal methods
+//
+
+// initialize the frequency grid on the disjoint bounded set
+void firdespm_init_grid(firdespm _q)
+{
+    unsigned int i,j;
+
+    // frequency step size
+    double df = 0.5/(_q->grid_density*_q->r);
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("df : %12.8f\n", df);
+#endif
+
+#if 0
+    firdespm_print(_q);
+
+    // compute total bandwidth
+    double b=0.0;
+    for (i=0; i<_q->num_bands; i++)
+        b += _q->bands[2*i+1] - _q->bands[2*i+0];
+
+    printf("b : %12.8f\n", b);
+
+    // adjust frequency step size if it is too large
+    double g = b / (df * _q->r); // approximate oversampling rate (points/extrema)
+    double gmin = 4.0;
+    if (g < gmin)
+        df *= g / gmin;
+    printf("df : %12.8f\n", df);
+    //exit(1);
+#endif
+
+    // number of grid points counter
+    unsigned int n = 0;
+
+    double f0, f1;
+    double fw = 1.0f;   // weighting function
+    for (i=0; i<_q->num_bands; i++) {
+        // extract band edges
+        f0 = _q->bands[2*i+0];
+        f1 = _q->bands[2*i+1];
+
+        // ensure first point is not zero for differentiator
+        // and Hilbert transforms due to transformation (below)
+        if (i==0 && _q->btype != LIQUID_FIRDESPM_BANDPASS)
+            f0 = f0 < df ? df : f0;
+
+        // compute the number of gridpoints in this band
+        unsigned int num_points = (unsigned int)( (f1-f0)/df + 0.5 );
+
+        // ensure at least one point per band
+        if (num_points < 1) num_points = 1;
+
+        //printf("band : [%12.8f %12.8f] %3u points\n",f0,f1,num_points);
+
+        // add points to grid
+        for (j=0; j<num_points; j++) {
+            // add frequency points
+            _q->F[n] = f0 + j*df;
+
+            // compute desired response
+            // TODO : use function pointer
+            _q->D[n] = _q->des[i];
+
+            // compute weight, applying weighting function
+            // TODO : use function pointer?
+            switch (_q->wtype[i]) {
+            case LIQUID_FIRDESPM_FLATWEIGHT: fw = 1.0f;             break;
+            case LIQUID_FIRDESPM_EXPWEIGHT:  fw = expf(2.0f*j*df);  break;
+            case LIQUID_FIRDESPM_LINWEIGHT:  fw = 1.0f + 2.7f*j*df; break;
+            default:
+                fprintf(stderr,"error: firdespm_init_grid(), invalid weighting specifyer: %d\n", _q->wtype[i]);
+                exit(1);
+            }
+            _q->W[n] = _q->weights[i] * fw;
+
+            n++;
+        }
+        // force endpoint to be upper edge of frequency band
+        _q->F[n-1] = f1;   // according to Janovetz
+    }
+    _q->grid_size = n;
+
+    // take care of special symmetry conditions here
+    if (_q->btype == LIQUID_FIRDESPM_BANDPASS) {
+        if (_q->s == 0) {
+            // even length filter
+            for (i=0; i<_q->grid_size; i++) {
+                _q->D[i] /= cos(M_PI*_q->F[i]);
+                _q->W[i] *= cos(M_PI*_q->F[i]);
+            }
+            // force weight at endpoint to be (nearly) zero
+            //_q->W[_q->grid_size-1] = 6.12303177e-17f;
+        }
+    } else {
+        // differentiator, Hilbert transform
+        if (_q->s == 0) {
+            // even length filter
+            for (i=0; i<_q->grid_size; i++) {
+                _q->D[i] /= sin(M_PI*_q->F[i]);
+                _q->W[i] *= sin(M_PI*_q->F[i]);
+            }
+        } else {
+            // odd length filter
+            for (i=0; i<_q->grid_size; i++) {
+                _q->D[i] /= sin(2*M_PI*_q->F[i]);
+                _q->W[i] *= sin(2*M_PI*_q->F[i]);
+            }
+        }
+    }
+}
+
+// compute interpolating polynomial
+void firdespm_compute_interp(firdespm _q)
+{
+    unsigned int i;
+
+    // compute Chebyshev points on F[iext[]] : cos(2*pi*f)
+    for (i=0; i<_q->r+1; i++) {
+        _q->x[i] = cos(2*M_PI*_q->F[_q->iext[i]]);
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+        printf("x[%3u] = %12.8f\n", i, _q->x[i]);
+#endif
+    }
+    //printf("\n");
+
+    // compute Lagrange interpolating polynomial
+    poly_fit_lagrange_barycentric(_q->x,_q->r+1,_q->alpha);
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    for (i=0; i<_q->r+1; i++)
+        printf("a[%3u] = %12.8f\n", i, _q->alpha[i]);
+#endif
+
+    // compute rho
+    double t0 = 0.0;    // numerator
+    double t1 = 0.0;    // denominator
+    for (i=0; i<_q->r+1; i++) {
+        //printf("D[%3u] = %16.8e, W[%3u] = %16.8e\n", i, _q->D[_q->iext[i]], i, _q->W[_q->iext[i]]);
+        t0 += _q->alpha[i] * _q->D[_q->iext[i]];
+        t1 += _q->alpha[i] / _q->W[_q->iext[i]] * (i % 2 ? -1.0 : 1.0);
+    }
+    _q->rho = t0/t1;
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("  rho   :   %12.4e\n", _q->rho);
+    printf("\n");
+#endif
+
+    // compute polynomial values (interpolants)
+    for (i=0; i<_q->r+1; i++) {
+        _q->c[i] = _q->D[_q->iext[i]] - (i % 2 ? -1 : 1) * _q->rho / _q->W[_q->iext[i]];
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+        printf("c[%3u] = %16.8e\n", i, _q->c[i]);
+#endif
+    }
+
+}
+
+void firdespm_compute_error(firdespm _q)
+{
+    unsigned int i;
+
+    double xf;
+    double H;
+    for (i=0; i<_q->grid_size; i++) {
+        // compute actual response
+        xf = cos(2*M_PI*_q->F[i]);
+        H = poly_val_lagrange_barycentric(_q->x,_q->c,_q->alpha,xf,_q->r+1);
+
+        // compute error
+        _q->E[i] = _q->W[i] * (_q->D[i] - H);
+    }
+}
+
+// search error curve for r+1 extremal indices
+// TODO : return number of values which have changed (exit criteria)
+void firdespm_iext_search(firdespm _q)
+{
+    unsigned int i;
+
+    // found extremal frequency indices
+    unsigned int nmax = 2*_q->r + 2*_q->num_bands; // max number of extremals
+    unsigned int found_iext[nmax];
+    unsigned int num_found=0;
+
+#if 0
+    // check for extremum at f=0
+    if ( fabs(_q->E[0]) > fabs(_q->E[1]) )
+        found_iext[num_found++] = 0;
+#else
+    // force f=0 into candidate set
+    found_iext[num_found++] = 0;
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("num_found : %4u [%4u / %4u]\n", num_found, 0, _q->grid_size);
+#endif
+#endif
+
+    // search inside grid
+    for (i=1; i<_q->grid_size-1; i++) {
+        if ( ((_q->E[i]>=0.0) && (_q->E[i-1]<=_q->E[i]) && (_q->E[i+1]<=_q->E[i]) ) ||
+             ((_q->E[i]< 0.0) && (_q->E[i-1]>=_q->E[i]) && (_q->E[i+1]>=_q->E[i]) ) )
+        {
+            assert(num_found < nmax);
+            found_iext[num_found++] = i;
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+            printf("num_found : %4u [%4u / %4u]\n", num_found, i, _q->grid_size);
+#endif
+        }
+    }
+
+#if 0
+    // check for extremum at f=0.5
+    if ( fabs(_q->E[_q->grid_size-1]) > fabs(_q->E[_q->grid_size-2]) )
+        found_iext[num_found++] = _q->grid_size-1;
+#else
+    // force f=0.5 into candidate set
+    assert(num_found < nmax);
+    found_iext[num_found++] = _q->grid_size-1;
+    //printf("num_found : %4u [%4u / %4u]\n", num_found, _q->grid_size-1, _q->grid_size);
+#endif
+    //printf("r+1 = %4u, num_found = %4u\n", _q->r+1, num_found);
+    if (num_found < _q->r+1) {
+        // too few extremal frequencies found.  Theoretically, this
+        // should never happen as the Chebyshev alternation theorem
+        // guarantees at least r+1 extrema, however due to finite
+        // machine precision, interpolation can be imprecise
+
+        fprintf(stderr,"warning: firdespm_iext_search(), too few extrema found (expected %u, found %u); exiting prematurely\n",
+                _q->r+1, num_found);
+        _q->num_exchanges = 0;
+        return;
+    }
+
+    assert(num_found <= nmax);
+
+    // search extrema and eliminate smallest
+    unsigned int imin=0;    // index of found_iext where _E is a minimum extreme
+    unsigned int sign=0;    // sign of error
+    unsigned int num_extra = num_found - _q->r - 1; // number of extra extremal frequencies
+    unsigned int alternating_sign;
+
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    for (i=0; i<_q->r+1; i++)
+        printf("iext[%4u] = %4u : %16.8e\n", i, found_iext[i], _q->E[found_iext[i]]);
+#endif
+
+    while (num_extra) {
+        // evaluate sign of first extrema
+        sign = _q->E[found_iext[0]] > 0.0;
+
+        //
+        imin = 0;
+        alternating_sign = 1;
+        for (i=1; i<num_found; i++) {
+            // update new minimum error extreme
+            if ( fabs(_q->E[found_iext[i]]) < fabs(_q->E[found_iext[imin]]) )
+                imin = i;
+    
+            if ( sign && _q->E[found_iext[i]] < 0.0 ) {
+                sign = 0;
+            } else if ( !sign && _q->E[found_iext[i]] >= 0.0 ) {
+                sign = 1;
+            } else {
+                // found two extrema with non-alternating sign; delete
+                // the smaller of the two
+                if ( fabs(_q->E[found_iext[i]]) < fabs(_q->E[found_iext[i-1]]) )
+                    imin = i;
+                else
+                    imin = i-1;
+                alternating_sign = 0;
+                break;
+            }
+        }
+        //printf("  imin : %3u : %12.4e;\n", imin, _q->E[found_iext[imin]]);
+
+        // 
+        if ( alternating_sign && num_extra==1) {
+            //imin = (fabs(_q->E[found_iext[0]]) > fabs(_q->E[found_iext[num_extra-1]])) ? 0 : num_extra-1;
+            if (fabs(_q->E[found_iext[0]]) < fabs(_q->E[found_iext[num_found-1]]))
+                imin = 0;
+            else
+                imin = num_found-1;
+        }
+
+        // Delete value in 'found_iext' at 'index imin'.  This
+        // is equivalent to shifing all values left one position
+        // starting at index imin+1
+        //printf("deleting found_iext[%3u] = %3u\n", imin, found_iext[imin]);
+#if 0
+        memmove( &found_iext[imin],
+                 &found_iext[imin+1],
+                 (num_found-imin)*sizeof(unsigned int));
+#else
+        // equivalent code:
+        for (i=imin; i<num_found; i++)
+            found_iext[i] = found_iext[i+1];
+#endif
+
+        num_extra--;
+        num_found--;
+
+        //printf("num extra: %3u, num found: %3u\n", num_extra, num_found);
+    }
+
+    // count number of changes
+    _q->num_exchanges=0;
+    for (i=0; i<_q->r+1; i++)
+        _q->num_exchanges += _q->iext[i] == found_iext[i] ? 0 : 1;
+
+    // copy new values
+    memmove(_q->iext, found_iext, (_q->r+1)*sizeof(unsigned int));
+
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    for (i=0; i<_q->r+1; i++)
+        printf("iext_new[%4u] = %4u : %16.8e\n", i, found_iext[i], _q->E[found_iext[i]]);
+#endif
+
+}
+
+// evaluates result to determine if Remez exchange algorithm
+// has converged
+int firdespm_is_search_complete(firdespm _q)
+{
+    // if no extremal frequencies have been exchanged, Remez
+    // algorithm has converged
+    if (_q->num_exchanges == 0)
+        return 1;
+
+    unsigned int i;
+    double tol = 1e-3f;
+
+    double e=0.0;
+    double emin=0.0;
+    double emax=0.0;
+    for (i=0; i<_q->r+1; i++) {
+        e = fabs(_q->E[_q->iext[i]]);
+        if (i==0 || e < emin) emin = e;
+        if (i==0 || e > emax) emax = e;
+    }
+
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("emin : %16.8e, emax : %16.8e, metric : %16.8e\n", emin, emax, (emax-emin)/emax);
+#endif
+    return (emax-emin) / emax < tol ? 1 : 0;
+}
+
+// compute filter taps (coefficients) from result
+void firdespm_compute_taps(firdespm _q, float * _h)
+{
+    unsigned int i;
+
+    // re-generate interpolator and compute coefficients
+    // for best cosine approximation
+    firdespm_compute_interp(_q);
+
+    // evaluate Lagrange polynomial on evenly spaced points
+    unsigned int p = _q->r - _q->s + 1;
+    double G[p];
+    for (i=0; i<p; i++) {
+        double f = (double)(i) / (double)(_q->h_len);
+        double xf = cos(2*M_PI*f);
+        double cf = poly_val_lagrange_barycentric(_q->x,_q->c,_q->alpha,xf,_q->r+1);
+        double g=1.0;
+
+        if (_q->btype == LIQUID_FIRDESPM_BANDPASS && _q->s==1) {
+            // odd filter length, even symmetry
+            g = 1.0;
+        } else if (_q->btype == LIQUID_FIRDESPM_BANDPASS && _q->s==0) {
+            // even filter length, even symmetry
+            g = cos(M_PI * i / _q->h_len);
+        } else if (_q->btype != LIQUID_FIRDESPM_BANDPASS && _q->s==1) {
+            // odd filter length, odd symmetry
+        } else if (_q->btype != LIQUID_FIRDESPM_BANDPASS && _q->s==0) {
+            // even filter length, odd symmetry
+        }
+
+        G[i] = cf * g;
+        //printf("G(%3u) = %12.4e (cf = %12.8f, f=%12.8f, c = %12.8f);\n", i+1, G[i], cf, f, g);
+    }
+
+    // compute inverse DFT (slow method), performing
+    // transformation here for different filter types
+    // TODO : flesh out computation for other filter types
+    unsigned int j;
+    if (_q->btype == LIQUID_FIRDESPM_BANDPASS) {
+        // odd filter length, even symmetry
+        for (i=0; i<_q->h_len; i++) {
+            double v = G[0];
+            double f = ((double)i - (double)(p-1) + 0.5*(1-_q->s)) / (double)(_q->h_len);
+            for (j=1; j<_q->r; j++)
+                v += 2.0 * G[j] * cos(2*M_PI*f*j);
+            _h[i] = v / (double)(_q->h_len);
+        }
+    } else if (_q->btype != LIQUID_FIRDESPM_BANDPASS && _q->s==1) {
+        // odd filter length, odd symmetry
+        fprintf(stderr,"warning: firdespm_compute_taps(), filter configuration not yet supported\n");
+    } else if (_q->btype != LIQUID_FIRDESPM_BANDPASS && _q->s==0) {
+        // even filter length, odd symmetry
+        fprintf(stderr,"warning: firdespm_compute_taps(), filter configuration not yet supported\n");
+    }
+#if LIQUID_FIRDESPM_DEBUG_PRINT
+    printf("\n");
+    for (i=0; i<_q->h_len; i++)
+        printf("h(%3u) = %12.8f;\n", i+1, _h[i]);
+#endif
+}
+
+#if LIQUID_FIRDESPM_DEBUG
+void firdespm_output_debug_file(firdespm _q)
+{
+    FILE * fid = fopen(LIQUID_FIRDESPM_DEBUG_FILENAME,"w");
+    fprintf(fid,"%% %s : auto-generated file\n", LIQUID_FIRDESPM_DEBUG_FILENAME);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    unsigned int i;
+    for (i=0; i<_q->grid_size; i++) {
+        fprintf(fid,"F(%4u) = %16.8e;\n", i+1, _q->F[i]);
+        fprintf(fid,"D(%4u) = %16.8e;\n", i+1, _q->D[i]);
+        fprintf(fid,"W(%4u) = %16.8e;\n", i+1, _q->W[i]);
+        fprintf(fid,"E(%4u) = %16.8e;\n", i+1, _q->E[i]);
+    }
+
+    for (i=0; i<_q->r+1; i++) {
+        fprintf(fid,"iext(%4u) = %u;\n", i+1, _q->iext[i]+1);
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(F,E,'-', F(iext),E(iext),'x');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('frequency');\n");
+    fprintf(fid,"ylabel('error');\n");
+
+    // evaluate poly
+    unsigned int n=1024;
+    for (i=0; i<n; i++) {
+        double f = (double) i / (double)(2*(n-1));
+        double x = cos(2*M_PI*f);
+        double c = poly_val_lagrange_barycentric(_q->x,_q->c,_q->alpha,x,_q->r+1);
+
+        fprintf(fid,"f(%4u) = %20.12e; H(%4u) = %20.12e;\n", i+1, f, i+1, c);
+    }
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,H,'-', F(iext),D(iext)-E(iext),'x');\n");
+    //fprintf(fid,"plot(f,20*log10(abs(H)),'-', F(iext),20*log10(abs(D(iext)-E(iext))),'x');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('frequency');\n");
+    fprintf(fid,"ylabel('filter response');\n");
+
+    fprintf(fid,"rho = %20.12e;\n", _q->rho);
+
+    fclose(fid);
+    printf("internal debugging results written to %s.\n", LIQUID_FIRDESPM_DEBUG_FILENAME);
+}
+#endif
+
diff --git a/src/filter/src/firfarrow.c b/src/filter/src/firfarrow.c
new file mode 100644
index 0000000..a4513e0
--- /dev/null
+++ b/src/filter/src/firfarrow.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfarrow.c
+//
+// Finite impulse response Farrow filter
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#define FIRFARROW_USE_DOTPROD 1
+
+#define FIRFARROW_DEBUG 0
+
+// defined:
+//  FIRFARROW()     name-mangling macro
+//  T               coefficients type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+struct FIRFARROW(_s) {
+    TC * h;             // filter coefficients
+    unsigned int h_len; // filter length
+    float fc;           // filter cutoff
+    float As;           // stop-band attenuation [dB]
+    unsigned int Q;     // polynomial order
+
+    float mu;           // fractional sample delay
+    float * P;          // polynomail coefficients matrix [ h_len x Q+1 ]
+    float gamma;        // inverse of DC response (normalization factor)
+
+#if FIRFARROW_USE_DOTPROD
+    WINDOW() w;
+#else
+    TI * v;
+    unsigned int v_index;
+#endif
+};
+
+// create firfarrow object
+//  _h_len      : filter length
+//  _p          : polynomial order
+//  _fc         : filter cutoff frequency
+//  _As         : stopband attenuation [dB]
+FIRFARROW() FIRFARROW(_create)(unsigned int _h_len,
+                               unsigned int _p,
+                               float        _fc,
+                               float        _As)
+{
+    // validate input
+    if (_h_len < 2) {
+        fprintf(stderr,"error: firfarrow_%s_create(), filter length must be > 2\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_p < 1) {
+        fprintf(stderr,"error: firfarrow_%s_create(), polynomial order must be at least 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fc < 0.0f || _fc > 0.5f) {
+        fprintf(stderr,"error: firfarrow_%s_create(), filter cutoff must be in [0,0.5]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create main object
+    FIRFARROW() q = (FIRFARROW()) malloc(sizeof(struct FIRFARROW(_s)));
+
+    // set internal properties
+    q->h_len = _h_len;  // filter length
+    q->Q     = _p;      // polynomial order
+    q->As    = _As;     // filter stop-band attenuation
+    q->fc    = _fc;     // filter cutoff frequency
+
+    // allocate memory for filter coefficients
+    q->h = (TC *) malloc((q->h_len)*sizeof(TC));
+
+#if FIRFARROW_USE_DOTPROD
+    q->w = WINDOW(_create)(q->h_len);
+#else
+    q->v = malloc((q->h_len)*sizeof(TI));
+#endif
+
+    // allocate memory for polynomial matrix [ h_len x Q+1 ]
+    q->P = (float*) malloc((q->h_len)*(q->Q+1)*sizeof(float));
+
+    // reset the filter object
+    FIRFARROW(_reset)(q);
+
+    // generate polynomials
+    FIRFARROW(_genpoly)(q);
+
+    // set nominal delay of 0
+    FIRFARROW(_set_delay)(q,0.0f);
+
+    // return main object
+    return q;
+}
+
+// destroy firfarrow object, freeing all internal memory
+void FIRFARROW(_destroy)(FIRFARROW() _q)
+{
+#if FIRFARROW_USE_DOTPROD
+    WINDOW(_destroy)(_q->w);
+#else
+    free(_q->v);
+#endif
+    free(_q->h);    // free the filter coefficients array
+    free(_q->P);    // free the polynomial matrix
+
+    // free main object
+    free(_q);
+}
+
+// print firfarrow object's internal properties
+void FIRFARROW(_print)(FIRFARROW() _q)
+{
+    printf("firfarrow [len : %u, poly-order : %u]\n", _q->h_len, _q->Q);
+    printf("polynomial coefficients:\n");
+
+    // print coefficients
+    unsigned int i, j, n=0;
+    for (i=0; i<_q->h_len; i++) {
+        printf("  %3u : ", i);
+        for (j=0; j<_q->Q+1; j++)
+            printf("%12.4e ", _q->P[n++]);
+        printf("\n");
+    }
+
+    printf("filter coefficients (mu=%8.4f):\n", _q->mu);
+    n = _q->h_len;
+    for (i=0; i<n; i++) {
+        printf("  h(%3u) = ", i+1);
+        PRINTVAL_TC(_q->h[n-i-1],%12.8f);
+        printf(";\n");
+    }
+}
+
+// reset firfarrow object's internal state
+void FIRFARROW(_reset)(FIRFARROW() _q)
+{
+#if FIRFARROW_USE_DOTPROD
+    WINDOW(_clear)(_q->w);
+#else
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        _q->v[i] = 0;
+    _q->v_index = 0;
+#endif
+}
+
+// push sample into firfarrow object
+//  _q      :   firfarrow object
+//  _x      :   input sample
+void FIRFARROW(_push)(FIRFARROW() _q,
+                      TI _x)
+{
+#if FIRFARROW_USE_DOTPROD
+    WINDOW(_push)(_q->w, _x);
+#else
+    _q->v[ _q->v_index ] = _x;
+    (_q->v_index)++;
+    _q->v_index = (_q->v_index) % (_q->h_len);
+#endif
+}
+
+// set fractional delay of firfarrow object
+//  _q      : firfarrow object
+//  _mu     : fractional sample delay
+void FIRFARROW(_set_delay)(FIRFARROW() _q,
+                           float _mu)
+{
+    // validate input
+    if (_mu < -1.0f || _mu > 1.0f) {
+        fprintf(stderr,"warning: firfarrow_%s_set_delay(), delay out of range\n", EXTENSION_FULL);
+    }
+
+    unsigned int i, n=0;
+    for (i=0; i<_q->h_len; i++) {
+        // compute filter tap from polynomial using negative
+        // value for _mu
+        _q->h[i] = POLY(_val)(_q->P+n, _q->Q, -_mu);
+
+        // normalize filter by inverse of DC response
+        _q->h[i] *= _q->gamma;
+
+        n += _q->Q+1;
+
+        //printf("  h[%3u] = %12.8f\n", i, _q->h[i]);
+    }
+}
+
+// execute firfarrow internal dot product
+//  _q      : firfarrow object
+//  _y      : output sample pointer
+void FIRFARROW(_execute)(FIRFARROW() _q,
+                         TO *        _y)
+{
+#if FIRFARROW_USE_DOTPROD
+    TI *r;
+    WINDOW(_read)(_q->w, &r);
+    DOTPROD(_run4)(_q->h, r, _q->h_len, _y);
+#else
+    TO y = 0;
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        y += _q->v[ (i+_q->v_index)%(_q->h_len) ] * _q->h[i];
+    *_y = y;
+#endif
+}
+
+// compute firfarrow filter on block of samples; the input
+// and output arrays may have the same pointer
+//  _q      : firfarrow object
+//  _x      : input array [size: _n x 1]
+//  _n      : input, output array size
+//  _y      : output array [size: _n x 1]
+void FIRFARROW(_execute_block)(FIRFARROW()  _q,
+                               TI *         _x,
+                               unsigned int _n,
+                               TO *         _y)
+{
+    unsigned int i;
+
+    for (i=0; i<_n; i++) {
+        // push input sample
+        FIRFARROW(_push)(_q, _x[i]);
+
+        // compute output
+        FIRFARROW(_execute)(_q, &_y[i]);
+    }
+}
+
+// get length of firfarrow object (number of filter taps)
+unsigned int FIRFARROW(_get_length)(FIRFARROW() _q)
+{
+    return _q->h_len;
+}
+
+// get coefficients of firfarrow object
+//  _q      : firfarrow object
+//  _h      : output coefficients pointer
+void FIRFARROW(_get_coefficients)(FIRFARROW() _q,
+                                  TC *        _h)
+{
+    memmove(_h, _q->h, (_q->h_len)*sizeof(TC));
+}
+
+// compute complex frequency response
+//  _q      : filter object
+//  _fc     : frequency
+//  _H      : output frequency response
+void FIRFARROW(_freqresponse)(FIRFARROW() _q,
+                              float _fc,
+                              float complex * _H)
+{
+    unsigned int i;
+    float complex H = 0.0f;
+
+    for (i=0; i<_q->h_len; i++)
+        H += _q->h[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+
+    // set return value
+    *_H = H;
+}
+
+// compute group delay [samples]
+//  _q      :   filter object
+//  _fc     :   frequency
+float FIRFARROW(_groupdelay)(FIRFARROW() _q,
+                             float _fc)
+{
+    // copy coefficients to be in correct order
+    float h[_q->h_len];
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++)
+        h[i] = crealf(_q->h[i]);
+
+    return fir_group_delay(h, n, _fc);
+}
+
+
+
+// 
+// internal
+//
+
+// generate polynomials to represent filter coefficients
+void FIRFARROW(_genpoly)(FIRFARROW() _q)
+{
+    // TODO : shy away from 'float' and use 'TC' types
+    unsigned int i, j, n=0;
+    float x, mu, h0, h1;
+    float mu_vect[_q->Q+1];
+    float hp_vect[_q->Q+1];
+    float p[_q->Q];
+    float beta = kaiser_beta_As(_q->As);
+    for (i=0; i<_q->h_len; i++) {
+#if FIRFARROW_DEBUG
+        printf("i : %3u / %3u\n", i, _q->h_len);
+#endif
+        x = (float)(i) - (float)(_q->h_len-1)/2.0f;
+        for (j=0; j<=_q->Q; j++) {
+            mu = ((float)j - (float)_q->Q)/((float)_q->Q) + 0.5f;
+
+            h0 = sincf(2.0f*(_q->fc)*(x + mu));
+            h1 = kaiser(i,_q->h_len,beta,mu);
+#if FIRFARROW_DEBUG
+            printf("  %3u : x=%12.8f, mu=%12.8f, h0=%12.8f, h1=%12.8f, hp=%12.8f\n",
+                    j, x, mu, h0, h1, h0*h1);
+#endif
+
+            mu_vect[j] = mu;
+            hp_vect[j] = h0*h1;
+        }
+        POLY(_fit)(mu_vect,hp_vect,_q->Q+1,p,_q->Q+1);
+#if FIRFARROW_DEBUG
+        printf("  polynomial : ");
+        for (j=0; j<=_q->Q; j++)
+            printf("%8.4f,", p[j]);
+        printf("\n");
+#endif
+
+        // copy coefficients to internal matrix
+        memmove(_q->P+n, p, (_q->Q+1)*sizeof(float));
+        n += _q->Q+1;
+    }
+
+#if FIRFARROW_DEBUG
+    // print coefficients
+    n=0;
+    for (i=0; i<_q->h_len; i++) {
+        printf("%3u : ", i);
+        for (j=0; j<_q->Q+1; j++)
+            printf("%12.4e ", _q->P[n++]);
+        printf("\n");
+    }
+#endif
+
+    // normalize DC gain
+    _q->gamma = 1.0f;                // initialize gamma to 1
+    FIRFARROW(_set_delay)(_q,0.0f); // compute filter taps with zero delay
+    _q->gamma = 0.0f;                // clear gamma
+    for (i=0; i<_q->h_len; i++)      // compute DC response
+        _q->gamma += _q->h[i];
+    _q->gamma = 1.0f / (_q->gamma);   // invert result
+
+}
+
diff --git a/src/filter/src/firfilt.c b/src/filter/src/firfilt.c
new file mode 100644
index 0000000..67b32fe
--- /dev/null
+++ b/src/filter/src/firfilt.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt : finite impulse response (FIR) filter
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// defined:
+//  FIRFILT()       name-mangling macro
+//  T               coefficients type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+#define LIQUID_FIRFILT_USE_WINDOW   (0)
+
+// firfilt object structure
+struct FIRFILT(_s) {
+    TC * h;             // filter coefficients array [size; h_len x 1]
+    unsigned int h_len; // filter length
+
+#if LIQUID_FIRFILT_USE_WINDOW
+    // use window object for internal buffer
+    WINDOW() w;
+#else
+    // use array as internal buffer (faster)
+    TI * w;                 // internal buffer object
+    unsigned int w_len;     // window length
+    unsigned int w_mask;    // window index mask
+    unsigned int w_index;   // window read index
+#endif
+    DOTPROD() dp;           // dot product object
+    TC scale;               // output scaling factor
+};
+
+// create firfilt object
+//  _h      :   coefficients (filter taps) [size: _n x 1]
+//  _n      :   filter length
+FIRFILT() FIRFILT(_create)(TC * _h,
+                           unsigned int _n)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: firfilt_%s_create(), filter length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create filter object and initialize
+    FIRFILT() q = (FIRFILT()) malloc(sizeof(struct FIRFILT(_s)));
+    q->h_len = _n;
+    q->h = (TC *) malloc((q->h_len)*sizeof(TC));
+
+#if LIQUID_FIRFILT_USE_WINDOW
+    // create window (internal buffer)
+    q->w = WINDOW(_create)(q->h_len);
+#else
+    // initialize array for buffering
+    q->w_len   = 1<<liquid_msb_index(q->h_len); // effectively 2^{floor(log2(len))+1}
+    q->w_mask  = q->w_len - 1;
+    q->w       = (TI *) malloc((q->w_len + q->h_len + 1)*sizeof(TI));
+    q->w_index = 0;
+#endif
+
+    // load filter in reverse order
+    unsigned int i;
+    for (i=_n; i>0; i--)
+        q->h[i-1] = _h[_n-i];
+
+    // create dot product object
+    q->dp = DOTPROD(_create)(q->h, q->h_len);
+
+    // set default scaling
+    q->scale = 1;
+
+    // reset filter state (clear buffer)
+    FIRFILT(_reset)(q);
+
+    return q;
+}
+
+// create filter using Kaiser-Bessel windowed sinc method
+//  _n      : filter length, _n > 0
+//  _fc     : cutoff frequency, 0 < _fc < 0.5
+//  _As     : stop-band attenuation [dB], _As > 0
+//  _mu     : fractional sample offset, -0.5 < _mu < 0.5
+FIRFILT() FIRFILT(_create_kaiser)(unsigned int _n,
+                                  float        _fc,
+                                  float        _As,
+                                  float        _mu)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: firfilt_%s_create_kaiser(), filter length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute temporary array for holding coefficients
+    float hf[_n];
+    liquid_firdes_kaiser(_n, _fc, _As, _mu, hf);
+
+    // copy coefficients to type-specific array
+    TC h[_n];
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        h[i] = (TC) hf[i];
+
+    // 
+    return FIRFILT(_create)(h, _n);
+}
+
+// create from square-root Nyquist prototype
+//  _type   : filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _k      : nominal samples/symbol, _k > 1
+//  _m      : filter delay [symbols], _m > 0
+//  _beta   : rolloff factor, 0 < beta <= 1
+//  _mu     : fractional sample offset,-0.5 < _mu < 0.5
+FIRFILT() FIRFILT(_create_rnyquist)(int          _type,
+                                    unsigned int _k,
+                                    unsigned int _m,
+                                    float        _beta,
+                                    float        _mu)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: firfilt_%s_create_rnyquist(), filter samples/symbol must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firfilt_%s_create_rnyquist(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: firfilt_%s_create_rnyquist(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int h_len = 2*_k*_m + 1;
+    float hf[h_len];
+    liquid_firdes_prototype(_type,_k,_m,_beta,_mu,hf);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    TC hc[h_len];
+    for (i=0; i<h_len; i++)
+        hc[i] = hf[i];
+
+    // return filter object and return
+    return FIRFILT(_create)(hc, h_len);
+}
+
+// create rectangular filter prototype
+FIRFILT() FIRFILT(_create_rect)(unsigned int _n)
+{
+    // validate input
+    if (_n == 0 || _n > 1024) {
+        fprintf(stderr,"error: firfilt_%s_create_rect(), filter length must be in [1,1024]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create float array coefficients
+    float hf[_n];
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        hf[i] = 1.0f;
+
+    // copy coefficients to type-specific array
+    TC h[_n];
+    for (i=0; i<_n; i++)
+        h[i] = (TC) hf[i];
+
+    // return filter object and return
+    return FIRFILT(_create)(h, _n);
+}
+
+// re-create firfilt object
+//  _q      :   original firfilt object
+//  _h      :   new coefficients [size: _n x 1]
+//  _n      :   new filter length
+FIRFILT() FIRFILT(_recreate)(FIRFILT() _q,
+                             TC * _h,
+                             unsigned int _n)
+{
+    unsigned int i;
+
+    // reallocate memory array if filter length has changed
+    if (_n != _q->h_len) {
+        // reallocate memory
+        _q->h_len = _n;
+        _q->h = (TC*) realloc(_q->h, (_q->h_len)*sizeof(TC));
+
+#if LIQUID_FIRFILT_USE_WINDOW
+        // recreate window object, preserving internal state
+        _q->w = WINDOW(_recreate)(_q->w, _q->h_len);
+#else
+        // free old array
+        free(_q->w);
+
+        // initialize array for buffering
+        _q->w_len   = 1<<liquid_msb_index(_q->h_len);   // effectively 2^{floor(log2(len))+1}
+        _q->w_mask  = _q->w_len - 1;
+        _q->w       = (TI *) malloc((_q->w_len + _q->h_len + 1)*sizeof(TI));
+        _q->w_index = 0;
+#endif
+    }
+
+    // load filter in reverse order
+    for (i=_n; i>0; i--)
+        _q->h[i-1] = _h[_n-i];
+
+    // re-create dot product object
+    DOTPROD(_destroy)(_q->dp);
+    _q->dp = DOTPROD(_create)(_q->h, _q->h_len);
+
+    return _q;
+}
+
+// destroy firfilt object
+void FIRFILT(_destroy)(FIRFILT() _q)
+{
+#if LIQUID_FIRFILT_USE_WINDOW
+    WINDOW(_destroy)(_q->w);
+#else
+    free(_q->w);
+#endif
+    DOTPROD(_destroy)(_q->dp);
+    free(_q->h);
+    free(_q);
+}
+
+// reset internal state of filter object
+void FIRFILT(_reset)(FIRFILT() _q)
+{
+#if LIQUID_FIRFILT_USE_WINDOW
+    WINDOW(_clear)(_q->w);
+#else
+    unsigned int i;
+    for (i=0; i<_q->w_len; i++)
+        _q->w[i] = 0.0;
+    _q->w_index = 0;
+#endif
+}
+
+// print filter object internals (taps, buffer)
+void FIRFILT(_print)(FIRFILT() _q)
+{
+    printf("firfilt_%s:\n", EXTENSION_FULL);
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++) {
+        printf("  h(%3u) = ", i+1);
+        PRINTVAL_TC(_q->h[n-i-1],%12.8f);
+        printf("\n");
+    }
+
+    // print scaling
+    printf("  scale = ");
+    PRINTVAL_TC(_q->scale,%12.8f);
+    printf("\n");
+
+#if LIQUID_FIRFILT_USE_WINDOW
+    WINDOW(_print)(_q->w);
+#endif
+}
+
+// set output scaling for filter
+void FIRFILT(_set_scale)(FIRFILT() _q,
+                         TC        _scale)
+{
+    _q->scale = _scale;
+}
+
+// push sample into filter object's internal buffer
+//  _q      :   filter object
+//  _x      :   input sample
+void FIRFILT(_push)(FIRFILT() _q,
+                    TI        _x)
+{
+#if LIQUID_FIRFILT_USE_WINDOW
+    WINDOW(_push)(_q->w, _x);
+#else
+    // increment index
+    _q->w_index++;
+
+    // wrap around pointer
+    _q->w_index &= _q->w_mask;
+
+    // if pointer wraps around, copy excess memory
+    if (_q->w_index == 0)
+        memmove(_q->w, _q->w + _q->w_len, (_q->h_len)*sizeof(TI));
+
+    // append value to end of buffer
+    _q->w[_q->w_index + _q->h_len - 1] = _x;
+#endif
+}
+
+// compute output sample (dot product between internal
+// filter coefficients and internal buffer)
+//  _q      :   filter object
+//  _y      :   output sample pointer
+void FIRFILT(_execute)(FIRFILT() _q,
+                       TO *      _y)
+{
+    // read buffer (retrieve pointer to aligned memory array)
+#if LIQUID_FIRFILT_USE_WINDOW
+    TI *r;
+    WINDOW(_read)(_q->w, &r);
+#else
+    TI *r = _q->w + _q->w_index;
+#endif
+
+    // execute dot product
+    DOTPROD(_execute)(_q->dp, r, _y);
+
+    // apply scaling factor
+    *_y *= _q->scale;
+}
+
+// execute the filter on a block of input samples; the
+// input and output buffers may be the same
+//  _q      : filter object
+//  _x      : pointer to input array [size: _n x 1]
+//  _n      : number of input, output samples
+//  _y      : pointer to output array [size: _n x 1]
+void FIRFILT(_execute_block)(FIRFILT()    _q,
+                             TI *         _x,
+                             unsigned int _n,
+                             TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // push sample into filter
+        FIRFILT(_push)(_q, _x[i]);
+
+        // compute output sample
+        FIRFILT(_execute)(_q, &_y[i]);
+    }
+}
+
+// get filter length
+unsigned int FIRFILT(_get_length)(FIRFILT() _q)
+{
+    return _q->h_len;
+}
+
+// compute complex frequency response
+//  _q      :   filter object
+//  _fc     :   frequency
+//  _H      :   output frequency response
+void FIRFILT(_freqresponse)(FIRFILT()       _q,
+                            float           _fc,
+                            float complex * _H)
+{
+    unsigned int i;
+    float complex H = 0.0f;
+
+    // compute dot product between coefficients and exp{ 2 pi fc {0..n-1} }
+    for (i=0; i<_q->h_len; i++)
+        H += _q->h[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+
+    // apply scaling
+    H *= _q->scale;
+
+    // set return value
+    *_H = H;
+}
+
+
+// compute group delay in samples
+//  _q      :   filter object
+//  _fc     :   frequency
+float FIRFILT(_groupdelay)(FIRFILT() _q,
+                           float     _fc)
+{
+    // copy coefficients to be in correct order
+    float h[_q->h_len];
+    unsigned int i;
+    unsigned int n = _q->h_len;
+    for (i=0; i<n; i++)
+        h[i] = crealf(_q->h[n-i-1]);
+
+    return fir_group_delay(h, n, _fc);
+}
+
diff --git a/src/filter/src/firhilb.c b/src/filter/src/firhilb.c
new file mode 100644
index 0000000..d8cb19e
--- /dev/null
+++ b/src/filter/src/firhilb.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firhilb.c
+//
+// finite impulse response (FIR) Hilbert transform
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+// defined:
+//  FIRHILB()       name-mangling macro
+//  T               coefficients type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+struct FIRHILB(_s) {
+    T * h;                  // filter coefficients
+    T complex * hc;         // filter coefficients (complex)
+    unsigned int h_len;     // length of filter
+    float As;               // filter stop-band attenuation [dB]
+
+    unsigned int m;         // filter semi-length, h_len = 4*m+1
+
+    // quadrature filter component
+    T * hq;                 // quadrature filter coefficients
+    unsigned int hq_len;    // quadrature filter length (2*m)
+
+    // input buffers
+    WINDOW() w0;            // input buffer (even samples)
+    WINDOW() w1;            // input buffer (odd samples)
+
+    // vector dot product
+    DOTPROD() dpq;
+
+    // regular real-to-complex/complex-to-real operation
+    unsigned int toggle;
+};
+
+// create firhilb object
+//  _m      :   filter semi-length (delay: 2*m+1)
+//  _As     :   stop-band attenuation [dB]
+FIRHILB() FIRHILB(_create)(unsigned int _m,
+                           float        _As)
+{
+    // validate firhilb inputs
+    if (_m < 2) {
+        fprintf(stderr,"error: firhilb_create(), filter semi-length (m) must be at least 2\n");
+        exit(1);
+    }
+
+    // allocate memory for main object
+    FIRHILB() q = (FIRHILB()) malloc(sizeof(struct FIRHILB(_s)));
+    q->m  = _m;         // filter semi-length
+    q->As = fabsf(_As); // stop-band attenuation
+
+    // set filter length and allocate memory for coefficients
+    q->h_len = 4*(q->m) + 1;
+    q->h     = (T *)         malloc((q->h_len)*sizeof(T));
+    q->hc    = (T complex *) malloc((q->h_len)*sizeof(T complex));
+
+    // allocate memory for quadrature filter component
+    q->hq_len = 2*(q->m);
+    q->hq     = (T *) malloc((q->hq_len)*sizeof(T));
+
+    // compute filter coefficients for half-band filter
+    liquid_firdes_kaiser(q->h_len, 0.25f, q->As, 0.0f, q->h);
+
+    // alternate sign of non-zero elements
+    unsigned int i;
+    for (i=0; i<q->h_len; i++) {
+        float t = (float)i - (float)(q->h_len-1)/2.0f;
+        q->hc[i] = q->h[i] * cexpf(_Complex_I*0.5f*M_PI*t);
+        q->h[i]  = cimagf(q->hc[i]);
+    }
+
+    // resample, reverse direction
+    unsigned int j=0;
+    for (i=1; i<q->h_len; i+=2)
+        q->hq[j++] = q->h[q->h_len - i - 1];
+
+    // create windows for upper and lower polyphase filter branches
+    q->w1 = WINDOW(_create)(2*(q->m));
+    q->w0 = WINDOW(_create)(2*(q->m));
+    WINDOW(_clear)(q->w0);
+    WINDOW(_clear)(q->w1);
+
+    // create internal dot product object
+    q->dpq = DOTPROD(_create)(q->hq, q->hq_len);
+
+    // reset internal state and return object
+    FIRHILB(_reset)(q);
+    return q;
+}
+
+// destroy firhilb object
+void FIRHILB(_destroy)(FIRHILB() _q)
+{
+    // destroy window buffers
+    WINDOW(_destroy)(_q->w0);
+    WINDOW(_destroy)(_q->w1);
+    
+    // destroy internal dot product object
+    DOTPROD(_destroy)(_q->dpq);
+
+    // free coefficients arrays
+    free(_q->h);
+    free(_q->hc);
+    free(_q->hq);
+
+    // free main object memory
+    free(_q);
+}
+
+// print firhilb object internals
+void FIRHILB(_print)(FIRHILB() _q)
+{
+    printf("fir hilbert transform: [%u]\n", _q->h_len);
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++) {
+        printf("  hc(%4u) = %8.4f + j*%8.4f;\n", i+1, crealf(_q->hc[i]), cimagf(_q->hc[i]));
+    }
+    printf("---\n");
+    for (i=0; i<_q->h_len; i++) {
+        printf("  h(%4u) = %8.4f;\n", i+1, _q->h[i]);
+    }
+    printf("---\n");
+    for (i=0; i<_q->hq_len; i++) {
+        printf("  hq(%4u) = %8.4f;\n", i+1, _q->hq[i]);
+    }
+}
+
+// reset firhilb object internal state
+void FIRHILB(_reset)(FIRHILB() _q)
+{
+    // clear window buffers
+    WINDOW(_clear)(_q->w0);
+    WINDOW(_clear)(_q->w1);
+
+    // reset toggle flag
+    _q->toggle = 0;
+}
+
+// execute Hilbert transform (real to complex)
+//  _q      :   firhilb object
+//  _x      :   real-valued input sample
+//  _y      :   complex-valued output sample
+void FIRHILB(_r2c_execute)(FIRHILB()   _q,
+                           T           _x,
+                           T complex * _y)
+{
+    T * r;  // buffer read pointer
+    T yi;   // in-phase component
+    T yq;   // quadrature component
+
+    if ( _q->toggle == 0 ) {
+        // push sample into upper branch
+        WINDOW(_push)(_q->w0, _x);
+
+        // upper branch (delay)
+        WINDOW(_index)(_q->w0, _q->m-1, &yi);
+
+        // lower branch (filter)
+        WINDOW(_read)(_q->w1, &r);
+        
+        // execute dotprod
+        DOTPROD(_execute)(_q->dpq, r, &yq);
+    } else {
+        // push sample into lower branch
+        WINDOW(_push)(_q->w1, _x);
+
+        // upper branch (delay)
+        WINDOW(_index)(_q->w1, _q->m-1, &yi);
+
+        // lower branch (filter)
+        WINDOW(_read)(_q->w0, &r);
+
+        // execute dotprod
+        DOTPROD(_execute)(_q->dpq, r, &yq);
+    }
+
+    // toggle flag
+    _q->toggle = 1 - _q->toggle;
+
+    // set return value
+    *_y = yi + _Complex_I * yq;
+}
+
+// execute Hilbert transform (complex to real)
+//  _q      :   firhilb object
+//  _y      :   complex-valued input sample
+//  _x      :   real-valued output sample
+void FIRHILB(_c2r_execute)(FIRHILB() _q,
+                           T complex _x,
+                           T *       _y)
+{
+    *_y = crealf(_x);
+}
+
+// execute Hilbert transform decimator (real to complex)
+//  _q      :   firhilb object
+//  _x      :   real-valued input array [size: 2 x 1]
+//  _y      :   complex-valued output sample
+void FIRHILB(_decim_execute)(FIRHILB()   _q,
+                             T *         _x,
+                             T complex * _y)
+{
+    T * r;  // buffer read pointer
+    T yi;   // in-phase component
+    T yq;   // quadrature component
+
+    // compute quadrature component (filter branch)
+    WINDOW(_push)(_q->w1, _x[0]);
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dpq, r, &yq);
+
+    WINDOW(_push)(_q->w0, _x[1]);
+    WINDOW(_index)(_q->w0, _q->m-1, &yi);
+
+    // set return value
+    *_y = yi + _Complex_I * yq;
+}
+
+// execute Hilbert transform decimator (real to complex) on
+// a block of samples
+//  _q      :   Hilbert transform object
+//  _x      :   real-valued input array [size: 2*_n x 1]
+//  _n      :   number of *output* samples
+//  _y      :   complex-valued output array [size: _n x 1]
+void FIRHILB(_decim_execute_block)(FIRHILB()    _q,
+                                   T *          _x,
+                                   unsigned int _n,
+                                   T complex *  _y)
+{
+    unsigned int i;
+
+    for (i=0; i<_n; i++)
+        FIRHILB(_decim_execute)(_q, &_x[2*i], &_y[i]);
+}
+
+// execute Hilbert transform interpolator (complex to real)
+//  _q      :   firhilb object
+//  _y      :   complex-valued input sample
+//  _x      :   real-valued output array [size: 2 x 1]
+void FIRHILB(_interp_execute)(FIRHILB() _q,
+                              T complex _x,
+                              T *       _y)
+{
+    T * r;  // buffer read pointer
+
+    // TODO macro for crealf, cimagf?
+    
+    WINDOW(_push)(_q->w0, cimagf(_x));
+    WINDOW(_index)(_q->w0, _q->m-1, &_y[0]);
+
+    // compute second branch (filter)
+    WINDOW(_push)(_q->w1, crealf(_x));
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dpq, r, &_y[1]);
+}
+
+// execute Hilbert transform interpolator (complex to real)
+// on a block of samples
+//  _q      :   Hilbert transform object
+//  _x      :   complex-valued input array [size: _n x 1]
+//  _n      :   number of *input* samples
+//  _y      :   real-valued output array [size: 2*_n x 1]
+void FIRHILB(_interp_execute_block)(FIRHILB()    _q,
+                                    T complex *  _x,
+                                    unsigned int _n,
+                                    T *          _y)
+{
+    unsigned int i;
+
+    for (i=0; i<_n; i++)
+        FIRHILB(_interp_execute)(_q, _x[i], &_y[2*i]);
+}
diff --git a/src/filter/src/firinterp.c b/src/filter/src/firinterp.c
new file mode 100644
index 0000000..513684b
--- /dev/null
+++ b/src/filter/src/firinterp.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Interpolator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct FIRINTERP(_s) {
+    TC * h;                 // prototype filter coefficients
+    unsigned int h_len;     // prototype filter length
+    unsigned int h_sub_len; // sub-filter length
+    unsigned int M;         // interpolation factor
+    FIRPFB() filterbank;    // polyphase filterbank object
+};
+
+// create interpolator
+//  _M      :   interpolation factor
+//  _h      :   filter coefficients array [size: _h_len x 1]
+//  _h_len  :   filter length
+FIRINTERP() FIRINTERP(_create)(unsigned int _M,
+                               TC *         _h,
+                               unsigned int _h_len)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: firinterp_%s_create(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_h_len < _M) {
+        fprintf(stderr,"error: firinterp_%s_create(), filter length cannot be less than interp factor\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and set internal parameters
+    FIRINTERP() q = (FIRINTERP()) malloc(sizeof(struct FIRINTERP(_s)));
+    q->M = _M;
+    q->h_len = _h_len;
+
+    // compute sub-filter length
+    q->h_sub_len=0;
+    while (q->M * q->h_sub_len < _h_len)
+        q->h_sub_len++;
+
+    // compute effective filter length (pad end of prototype with zeros)
+    q->h_len = q->M * q->h_sub_len;
+    q->h = (TC*) malloc((q->h_len)*sizeof(TC));
+
+    // load filter coefficients in regular order, padding end with zeros
+    unsigned int i;
+    for (i=0; i<q->h_len; i++)
+        q->h[i] = i < _h_len ? _h[i] : 0.0f;
+
+    // create polyphase filterbank
+    q->filterbank = FIRPFB(_create)(q->M, q->h, q->h_len);
+
+    // return interpolator object
+    return q;
+}
+
+// create interpolator from Kaiser prototype
+//  _M      :   interpolation factor
+//  _m      :   symbol delay
+//  _As     :   stop-band attenuation [dB]
+FIRINTERP() FIRINTERP(_create_kaiser)(unsigned int _M,
+                                      unsigned int _m,
+                                      float        _As)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: firinterp_%s_create_kaiser(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firinterp_%s_create_kaiser(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_As < 0.0f) {
+        fprintf(stderr,"error: firinterp_%s_create_kaiser(), stop-band attenuation must be positive\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute filter coefficients (floating point precision)
+    unsigned int h_len = 2*_M*_m + 1;
+    float hf[h_len];
+    float fc = 0.5f / (float) (_M);
+    liquid_firdes_kaiser(h_len, fc, _As, 0.0f, hf);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    TC hc[h_len];
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        hc[i] = hf[i];
+    
+    // return interpolator object
+    return FIRINTERP(_create)(_M, hc, 2*_M*_m);
+}
+
+// create prototype (root-)Nyquist interpolator
+//  _type   :   filter type (e.g. LIQUID_NYQUIST_RCOS)
+//  _k      :   samples/symbol,          _k > 1
+//  _m      :   filter delay (symbols),  _m > 0
+//  _beta   :   excess bandwidth factor, _beta < 1
+//  _dt     :   fractional sample delay, _dt in (-1, 1)
+FIRINTERP() FIRINTERP(_create_prototype)(int          _type,
+                                         unsigned int _k,
+                                         unsigned int _m,
+                                         float        _beta,
+                                         float        _dt)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: firinterp_%s_create_prototype(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firinterp_%s_create_prototype(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: firinterp_%s_create_prototype(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: firinterp_%s_create_prototype(), filter fractional sample delay must be in [-1,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate Nyquist filter
+    unsigned int h_len = 2*_k*_m + 1;
+    float h[h_len];
+    liquid_firdes_prototype(_type,_k,_m,_beta,_dt,h);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    TC hc[h_len];
+    for (i=0; i<h_len; i++)
+        hc[i] = h[i];
+
+    // return interpolator object
+    return FIRINTERP(_create)(_k, hc, h_len);
+}
+
+// destroy interpolator object
+void FIRINTERP(_destroy)(FIRINTERP() _q)
+{
+    FIRPFB(_destroy)(_q->filterbank);
+    free(_q->h);
+    free(_q);
+}
+
+// print interpolator state
+void FIRINTERP(_print)(FIRINTERP() _q)
+{
+    printf("interp():\n");
+    printf("    M       :   %u\n", _q->M);
+    printf("    h_len   :   %u\n", _q->h_len);
+    FIRPFB(_print)(_q->filterbank);
+}
+
+// clear internal state
+void FIRINTERP(_reset)(FIRINTERP() _q)
+{
+    FIRPFB(_reset)(_q->filterbank);
+}
+
+// execute interpolator
+//  _q      : interpolator object
+//  _x      : input sample
+//  _y      : output array [size: 1 x _M]
+void FIRINTERP(_execute)(FIRINTERP() _q,
+                         TI          _x,
+                         TO *        _y)
+{
+    // push sample into filterbank
+    FIRPFB(_push)(_q->filterbank,  _x);
+
+    // compute output for each filter in the bank
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        FIRPFB(_execute)(_q->filterbank, i, &_y[i]);
+}
+
+// execute interpolation on block of input samples
+//  _q      : firinterp object
+//  _x      : input array [size: _n x 1]
+//  _n      : size of input array
+//  _y      : output sample array [size: _M*_n x 1]
+void FIRINTERP(_execute_block)(FIRINTERP()  _q,
+                               TI *         _x,
+                               unsigned int _n,
+                               TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // execute one input at a time with an output stride _M
+        FIRINTERP(_execute)(_q, _x[i], &_y[i*_q->M]);
+    }
+}
+
diff --git a/src/filter/src/firpfb.c b/src/filter/src/firpfb.c
new file mode 100644
index 0000000..9855fd4
--- /dev/null
+++ b/src/filter/src/firpfb.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// FIR Polyphase filter bank
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+struct FIRPFB(_s) {
+    TC * h;                     // filter coefficients array
+    unsigned int h_len;         // total number of filter coefficients
+    unsigned int h_sub_len;     // sub-sampled filter length
+    unsigned int num_filters;   // number of filters
+
+    WINDOW() w;                 // window buffer
+    DOTPROD() * dp;             // array of vector dot product objects
+    TC scale;                   // output scaling factor
+};
+
+// create firpfb from external coefficients
+//  _M      : number of filters in the bank
+//  _h      : coefficients [size: _M*_h_len x 1]
+//  _h_len  : filter delay (symbols)
+FIRPFB() FIRPFB(_create)(unsigned int _M,
+                         TC *         _h,
+                         unsigned int _h_len)
+{
+    // validate input
+    if (_M == 0) {
+        fprintf(stderr,"error: firpfb_%s_create(), number of filters must be greater than zero\n",
+                EXTENSION_FULL);
+        exit(1);
+    } else if (_h_len == 0) {
+        fprintf(stderr,"error: firpfb_%s_create(), filter length must be greater than zero\n",
+                EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create main filter object
+    FIRPFB() q = (FIRPFB()) malloc(sizeof(struct FIRPFB(_s)));
+
+    // set user-defined parameters
+    q->num_filters = _M;
+    q->h_len       = _h_len;
+
+    // each filter is realized as a dotprod object
+    q->dp = (DOTPROD()*) malloc((q->num_filters)*sizeof(DOTPROD()));
+
+    // generate bank of sub-samped filters
+    // length of each sub-sampled filter
+    unsigned int h_sub_len = _h_len / q->num_filters;
+    TC h_sub[h_sub_len];
+    unsigned int i, n;
+    for (i=0; i<q->num_filters; i++) {
+        for (n=0; n<h_sub_len; n++) {
+            // load filter in reverse order
+            h_sub[h_sub_len-n-1] = _h[i + n*(q->num_filters)];
+        }
+
+        // create dot product object
+        q->dp[i] = DOTPROD(_create)(h_sub,h_sub_len);
+    }
+
+    // save sub-sampled filter length
+    q->h_sub_len = h_sub_len;
+
+    // create window buffer
+    q->w = WINDOW(_create)(q->h_sub_len);
+
+    // set default scaling
+    q->scale = 1;
+
+    // reset object and return
+    FIRPFB(_reset)(q);
+    return q;
+}
+
+// create firpfb from external coefficients
+//  _M      : number of filters in the bank
+//  _m      : filter semi-length [samples]
+//  _fc     : filter cut-off frequency 0 < _fc < 0.5
+//  _As     : filter stop-band suppression [dB]
+FIRPFB() FIRPFB(_create_kaiser)(unsigned int _M,
+                                unsigned int _m,
+                                float        _fc,
+                                float        _As)
+{
+    // validate input
+    if (_M == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_kaiser(), number of filters must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_kaiser(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fc < 0.0f || _fc > 0.5f) {
+        fprintf(stderr,"error: firpfb_%s_create_kaiser(), filter cut-off frequence must be in (0,0.5)\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_As < 0.0f) {
+        fprintf(stderr,"error: firpfb_%s_create_kaiser(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int H_len = 2*_M*_m + 1;
+    float Hf[H_len];
+    liquid_firdes_kaiser(H_len, _fc/(float)_M, _As, 0.0f, Hf);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    TC Hc[H_len];
+    for (i=0; i<H_len; i++)
+        Hc[i] = Hf[i];
+
+    // return filterbank object
+    return FIRPFB(_create)(_M, Hc, H_len);
+}
+
+// create square-root Nyquist filterbank
+//  _type   :   filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _M      :   number of filters in the bank
+//  _k      :   samples/symbol _k > 1
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   excess bandwidth factor, 0 < _beta < 1
+FIRPFB() FIRPFB(_create_rnyquist)(int          _type,
+                                  unsigned int _M,
+                                  unsigned int _k,
+                                  unsigned int _m,
+                                  float        _beta)
+{
+    // validate input
+    if (_M == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_rnyquist(), number of filters must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_k < 2) {
+        fprintf(stderr,"error: firpfb_%s_create_rnyquist(), filter samples/symbol must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_rnyquist(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: firpfb_%s_create_rnyquist(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int H_len = 2*_M*_k*_m + 1;
+    float Hf[H_len];
+    liquid_firdes_prototype(_type,_M*_k,_m,_beta,0,Hf);
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    unsigned int i;
+    TC Hc[H_len];
+    for (i=0; i<H_len; i++)
+        Hc[i] = Hf[i];
+
+    // return filterbank object
+    return FIRPFB(_create)(_M, Hc, H_len);
+}
+
+// create firpfb derivative square-root Nyquist filterbank
+//  _type   :   filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _M      :   number of filters in the bank
+//  _k      :   samples/symbol _k > 1
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   excess bandwidth factor, 0 < _beta < 1
+FIRPFB() FIRPFB(_create_drnyquist)(int          _type,
+                                   unsigned int _M,
+                                   unsigned int _k,
+                                   unsigned int _m,
+                                   float        _beta)
+{
+    // validate input
+    if (_M == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_drnyquist(), number of filters must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_k < 2) {
+        fprintf(stderr,"error: firpfb_%s_create_drnyquist(), filter samples/symbol must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firpfb_%s_create_drnyquist(), filter delay must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: firpfb_%s_create_drnyquist(), filter excess bandwidth factor must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // generate square-root Nyquist filter
+    unsigned int H_len = 2*_M*_k*_m + 1;
+    float Hf[H_len];
+    liquid_firdes_prototype(_type,_M*_k,_m,_beta,0,Hf);
+    
+    // compute derivative filter
+    float dHf[H_len];
+    float HdH_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<H_len; i++) {
+        if (i==0) {
+            dHf[i] = Hf[i+1] - Hf[H_len-1];
+        } else if (i==H_len-1) {
+            dHf[i] = Hf[0]   - Hf[i-1];
+        } else {
+            dHf[i] = Hf[i+1] - Hf[i-1];
+        }
+
+        // find maximum of h*dh
+        if ( fabsf(Hf[i]*dHf[i]) > HdH_max )
+            HdH_max = fabsf(Hf[i]*dHf[i]);
+    }
+
+    // copy coefficients to type-specific array (e.g. float complex)
+    // and apply scaling factor for normalized response
+    TC Hc[H_len];
+    for (i=0; i<H_len; i++)
+        Hc[i] = dHf[i] * 0.06f / HdH_max;
+
+    // return filterbank object
+    return FIRPFB(_create)(_M, Hc, H_len);
+}
+
+
+// re-create filterbank object
+//  _q      : original firpfb object
+//  _M      : number of filters in the bank
+//  _h      : coefficients [size: _M x _h_len]
+//  _h_len  : length of each filter
+FIRPFB() FIRPFB(_recreate)(FIRPFB()     _q,
+                           unsigned int _M,
+                           TC *         _h,
+                           unsigned int _h_len)
+{
+    // check to see if filter length has changed
+    if (_h_len != _q->h_len || _M != _q->num_filters) {
+        // filter length has changed: recreate entire filter
+        FIRPFB(_destroy)(_q);
+        _q = FIRPFB(_create)(_M,_h,_h_len);
+        return _q;
+    }
+
+    // re-create each dotprod object
+    TC h_sub[_q->h_sub_len];
+    unsigned int i, n;
+    for (i=0; i<_q->num_filters; i++) {
+        for (n=0; n<_q->h_sub_len; n++) {
+            // load filter in reverse order
+            h_sub[_q->h_sub_len-n-1] = _h[i + n*(_q->num_filters)];
+        }
+
+        _q->dp[i] = DOTPROD(_recreate)(_q->dp[i],h_sub,_q->h_sub_len);
+    }
+    return _q;
+}
+
+// destroy firpfb object, freeing all internal memory
+void FIRPFB(_destroy)(FIRPFB() _q)
+{
+    unsigned int i;
+    for (i=0; i<_q->num_filters; i++)
+        DOTPROD(_destroy)(_q->dp[i]);
+    free(_q->dp);
+    WINDOW(_destroy)(_q->w);
+    free(_q);
+}
+
+// print firpfb object's parameters
+void FIRPFB(_print)(FIRPFB() _q)
+{
+    printf("fir polyphase filterbank [%u] :\n", _q->num_filters);
+    unsigned int i,n;
+
+    for (i=0; i<_q->num_filters; i++) {
+        printf("  bank %3u: ",i);
+        for (n=0; n<_q->h_len; n++) {
+            //printf("%6.4f+j%6.4f ", crealf(_q->dp[i]->h[n]), cimagf(_q->dp[i]->h[n]));
+        }
+        printf("\n");
+    }
+}
+
+// clear/reset firpfb object internal state
+void FIRPFB(_reset)(FIRPFB() _q)
+{
+    WINDOW(_clear)(_q->w);
+}
+
+// set output scaling for filter
+void FIRPFB(_set_scale)(FIRPFB() _q,
+                         TC      _scale)
+{
+    _q->scale = _scale;
+}
+
+// push sample into firpfb internal buffer
+void FIRPFB(_push)(FIRPFB() _q, TI _x)
+{
+    // push value into window buffer
+    WINDOW(_push)(_q->w, _x);
+}
+
+// execute the filter on internal buffer and coefficients
+//  _q      : firpfb object
+//  _i      : index of filter to use
+//  _y      : pointer to output sample
+void FIRPFB(_execute)(FIRPFB()     _q,
+                      unsigned int _i,
+                      TO *         _y)
+{
+    // validate input
+    if (_i >= _q->num_filters) {
+        fprintf(stderr,"error: firpfb_execute(), filterbank index (%u) exceeds maximum (%u)\n",
+                _i, _q->num_filters);
+        exit(1);
+    }
+
+    // read buffer
+    TI *r;
+    WINDOW(_read)(_q->w, &r);
+
+    // execute dot product
+    DOTPROD(_execute)(_q->dp[_i], r, _y);
+
+    // apply scaling factor
+    *_y *= _q->scale;
+}
+
diff --git a/src/filter/src/fnyquist.c b/src/filter/src/fnyquist.c
new file mode 100644
index 0000000..d257257
--- /dev/null
+++ b/src/filter/src/fnyquist.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Flipped Nyquist/root-Nyquist filter designs
+//
+// References:
+//   [Beaulieu:2001]
+//   [Assalini:2004]
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// Design flipped Nyquist/root-Nyquist filter
+//  _type   : filter type (e.g. LIQUID_FIRFILT_FEXP)
+//  _root   : square-root Nyquist filter?
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_fnyquist(liquid_firfilt_type _type,
+                            int                 _root,
+                            unsigned int        _k,
+                            unsigned int        _m,
+                            float               _beta,
+                            float               _dt,
+                            float *             _h)
+{
+    // validate input
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_fnyquist(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_fnyquist(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_fnyquist(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    unsigned int i;
+
+    // derived values
+    unsigned int h_len = 2*_k*_m+1;   // filter length
+
+    float H_prime[h_len];   // frequency response of Nyquist filter (real)
+    float complex H[h_len]; // frequency response of Nyquist filter
+    float complex h[h_len]; // impulse response of Nyquist filter
+
+    // compute Nyquist filter frequency response
+    switch (_type) {
+    case LIQUID_FIRFILT_FEXP:
+        liquid_firdes_fexp_freqresponse(_k, _m, _beta, H_prime);
+        break;
+    case LIQUID_FIRFILT_FSECH:
+        liquid_firdes_fsech_freqresponse(_k, _m, _beta, H_prime);
+        break;
+    case LIQUID_FIRFILT_FARCSECH:
+        liquid_firdes_farcsech_freqresponse(_k, _m, _beta, H_prime);
+        break;
+    default:
+        fprintf(stderr,"error: liquid_firdes_fnyquist(), unknown/unsupported filter type\n");
+        exit(1);
+    }
+
+    // copy result to fft input buffer, computing square root
+    // if required
+    for (i=0; i<h_len; i++)
+        H[i] = _root ? sqrtf(H_prime[i]) : H_prime[i];
+
+    // compute ifft
+    fft_run(h_len, H, h, LIQUID_FFT_BACKWARD, 0);
+    
+    // copy shifted, scaled response
+    for (i=0; i<h_len; i++)
+        _h[i] = crealf( h[(i+_k*_m+1)%h_len] ) * (float)_k / (float)(h_len);
+}
+
+// Design fexp Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_fexp(unsigned int _k,
+                        unsigned int _m,
+                        float _beta,
+                        float _dt,
+                        float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FEXP, 0, _k, _m, _beta, _dt, _h);
+}
+
+// Design fexp square-root Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rfexp(unsigned int _k,
+                         unsigned int _m,
+                         float _beta,
+                         float _dt,
+                         float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FEXP, 1, _k, _m, _beta, _dt, _h);
+}
+
+// flipped exponential frequency response
+void liquid_firdes_fexp_freqresponse(unsigned int _k,
+                                     unsigned int _m,
+                                     float        _beta,
+                                     float *      _H)
+{
+    // TODO : validate input
+
+    unsigned int i;
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    float f0 = 0.5f*(1.0f - _beta) / (float)_k;
+    float f1 = 0.5f*(1.0f        ) / (float)_k;
+    float f2 = 0.5f*(1.0f + _beta) / (float)_k;
+
+    float B     = 0.5f/(float)_k;
+    float gamma = logf(2.0f)/(_beta*B);
+
+    // compute frequency response of Nyquist filter
+    for (i=0; i<h_len; i++) {
+        float f = (float)i / (float)h_len;
+        if (f > 0.5f) f = f - 1.0f;
+
+        // enforce even symmetry
+        f = fabsf(f);
+
+        if ( f < f0 ) {
+            // pass band
+            _H[i] = 1.0f;
+        } else if (f > f0 && f < f2) {
+            // transition band
+            if ( f < f1) {
+                _H[i] = expf(gamma*(B*(1-_beta) - f));
+            } else {
+                _H[i] = 1.0f - expf(gamma*(f - (1+_beta)*B));
+            }
+        } else {
+            // stop band
+            _H[i] = 0.0f;
+        }
+    }
+}
+
+// Design fsech Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_fsech(unsigned int _k,
+                         unsigned int _m,
+                         float _beta,
+                         float _dt,
+                         float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FSECH, 0, _k, _m, _beta, _dt, _h);
+}
+
+// Design fsech square-root Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rfsech(unsigned int _k,
+                          unsigned int _m,
+                          float _beta,
+                          float _dt,
+                          float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FSECH, 1, _k, _m, _beta, _dt, _h);
+}
+
+// flipped exponential frequency response
+void liquid_firdes_fsech_freqresponse(unsigned int _k,
+                                     unsigned int _m,
+                                     float        _beta,
+                                     float *      _H)
+{
+    // TODO : validate input
+
+    unsigned int i;
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    float f0 = 0.5f*(1.0f - _beta) / (float)_k;
+    float f1 = 0.5f*(1.0f        ) / (float)_k;
+    float f2 = 0.5f*(1.0f + _beta) / (float)_k;
+
+    float B     = 0.5f/(float)_k;
+    float gamma = logf(sqrtf(3.0f) + 2.0f) / (_beta*B);
+
+    // compute frequency response of Nyquist filter
+    for (i=0; i<h_len; i++) {
+        float f = (float)i / (float)h_len;
+        if (f > 0.5f) f = f - 1.0f;
+
+        // enforce even symmetry
+        f = fabsf(f);
+
+        if ( f < f0 ) {
+            // pass band
+            _H[i] = 1.0f;
+        } else if (f > f0 && f < f2) {
+            // transition band
+            if ( f < f1) {
+                _H[i] = 1.0f / coshf(gamma*(f - B*(1-_beta)));
+            } else {
+                _H[i] = 1.0f - 1.0f / coshf(gamma*(B*(1+_beta) - f));
+            }
+        } else {
+            // stop band
+            _H[i] = 0.0f;
+        }
+    }
+}
+
+// Design farcsech Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_farcsech(unsigned int _k,
+                            unsigned int _m,
+                            float _beta,
+                            float _dt,
+                            float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FARCSECH, 0, _k, _m, _beta, _dt, _h);
+}
+
+// Design farcsech square-root Nyquist filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rfarcsech(unsigned int _k,
+                             unsigned int _m,
+                             float _beta,
+                             float _dt,
+                             float * _h)
+{
+    // compute resonse using generic function
+    liquid_firdes_fnyquist(LIQUID_FIRFILT_FARCSECH, 1, _k, _m, _beta, _dt, _h);
+}
+
+// hyperbolic arc-secant
+float liquid_asechf(float _z)
+{
+    if (_z <= 0.0f || _z > 1.0f) {
+        fprintf(stderr,"warning: liquid_asechf(), input out of range\n");
+        return 0.0f;
+    }
+
+    float z_inv = 1.0f / _z;
+
+    return logf( sqrtf(z_inv - 1.0f)*sqrtf(z_inv + 1.0f) + z_inv );
+}
+
+// flipped exponential frequency response
+void liquid_firdes_farcsech_freqresponse(unsigned int _k,
+                                         unsigned int _m,
+                                         float        _beta,
+                                         float *      _H)
+{
+    // TODO : validate input
+
+    unsigned int i;
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    float f0 = 0.5f*(1.0f - _beta) / (float)_k;
+    float f1 = 0.5f*(1.0f        ) / (float)_k;
+    float f2 = 0.5f*(1.0f + _beta) / (float)_k;
+
+    float B     = 0.5f/(float)_k;
+    float gamma = logf(sqrtf(3.0f) + 2.0f) / (_beta*B);
+    float zeta  = 1.0f / (2.0f * _beta * B);
+
+    // compute frequency response of Nyquist filter
+    for (i=0; i<h_len; i++) {
+        float f = (float)i / (float)h_len;
+        if (f > 0.5f) f = f - 1.0f;
+
+        // enforce even symmetry
+        f = fabsf(f);
+
+        if ( f < f0 ) {
+            // pass band
+            _H[i] = 1.0f;
+        } else if (f > f0 && f < f2) {
+            // transition band
+            if ( f < f1) {
+                _H[i] = 1.0f - (zeta/gamma)*liquid_asechf(zeta*(B*(1+_beta) - f));
+            } else {
+                _H[i] = (zeta/gamma)*liquid_asechf(zeta*(f - B*(1-_beta)));
+            }
+        } else {
+            // stop band
+            _H[i] = 0.0f;
+        }
+    }
+}
+
diff --git a/src/filter/src/gmsk.c b/src/filter/src/gmsk.c
new file mode 100644
index 0000000..697563f
--- /dev/null
+++ b/src/filter/src/gmsk.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Finite impulse response GMSK transmit/receive filter design
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// Design GMSK transmit filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_gmsktx(unsigned int _k,
+                          unsigned int _m,
+                          float        _beta,
+                          float        _dt,
+                          float *      _h)
+{
+    // validate input
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_gmsktx(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_gmsktx(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_gmsktx(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    // derived values
+    unsigned int h_len = 2*_k*_m+1;
+
+    // compute filter coefficients
+    unsigned int i;
+    float t;
+    float c0 = 1.0f / sqrtf(logf(2.0f));
+    for (i=0; i<h_len; i++) {
+        t = (float)i/(float)(_k)-(float)(_m) + _dt;
+
+        _h[i] = liquid_Qf(2*M_PI*_beta*(t-0.5f)*c0) -
+                liquid_Qf(2*M_PI*_beta*(t+0.5f)*c0);
+    }
+
+    // normalize filter coefficients such that the filter's
+    // integral is pi/2
+    float e = 0.0f;
+    for (i=0; i<h_len; i++)
+        e += _h[i];
+    for (i=0; i<h_len; i++)
+        _h[i] *= M_PI / (2.0f * e);
+    for (i=0; i<h_len; i++)
+        _h[i] *= (float)_k;
+}
+
+// Design GMSK receive filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_gmskrx(unsigned int _k,
+                          unsigned int _m,
+                          float        _beta,
+                          float        _dt,
+                          float *      _h)
+{
+    // validate input
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_gmskrx(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_gmskrx(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_gmskrx(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    unsigned int k = _k;
+    unsigned int m = _m;
+    float BT = _beta;
+
+    // internal options
+    float beta = BT;                // prototype filter cut-off
+    float delta = 1e-3f;            // filter design correction factor
+    liquid_firfilt_type prototype = LIQUID_FIRFILT_KAISER;    // Nyquist prototype
+
+    unsigned int i;
+
+    // derived values
+    unsigned int h_len = 2*k*m+1;   // filter length
+
+    // arrays
+    float ht[h_len];         // transmit filter coefficients
+    float hr[h_len];         // recieve filter coefficients
+
+    // design transmit filter
+    liquid_firdes_gmsktx(k,m,BT,0.0f,ht);
+
+    //
+    // start of filter design procedure
+    //
+
+    // 'internal' arrays
+    float h_primef[h_len];          // temporary buffer for real 'prototype' coefficients
+    float g_primef[h_len];          // temporary buffer for real 'gain' coefficient
+
+    float complex h_tx[h_len];      // impulse response of transmit filter
+    float complex h_prime[h_len];   // impulse response of 'prototype' filter
+    float complex g_prime[h_len];   // impulse response of 'gain' filter
+    float complex h_hat[h_len];     // impulse response of receive filter
+    
+    float complex H_tx[h_len];      // frequency response of transmit filter
+    float complex H_prime[h_len];   // frequency response of 'prototype' filter
+    float complex G_prime[h_len];   // frequency response of 'gain' filter
+    float complex H_hat[h_len];     // frequency response of receive filter
+
+    // create 'prototype' matched filter
+    liquid_firdes_prototype(prototype,k,m,beta,0.0f,h_primef);
+
+    // create 'gain' filter to improve stop-band rejection
+    float fc = (0.7f + 0.1*beta) / (float)k;
+    float As = 60.0f;
+    liquid_firdes_kaiser(h_len, fc, As, 0.0f, g_primef);
+
+    // copy to fft input buffer, shifting appropriately
+    for (i=0; i<h_len; i++) {
+        h_prime[i] = h_primef[ (i+k*m)%h_len ];
+        g_prime[i] = g_primef[ (i+k*m)%h_len ];
+        h_tx[i]    = ht[       (i+k*m)%h_len ];
+    }
+
+    // run ffts
+    fft_run(h_len, h_prime, H_prime, LIQUID_FFT_FORWARD, 0);
+    fft_run(h_len, g_prime, G_prime, LIQUID_FFT_FORWARD, 0);
+    fft_run(h_len, h_tx,    H_tx,    LIQUID_FFT_FORWARD, 0);
+
+    // find minimum of reponses
+    float H_tx_min = 0.0f;
+    float H_prime_min = 0.0f;
+    float G_prime_min = 0.0f;
+    for (i=0; i<h_len; i++) {
+        if (i==0 || crealf(H_tx[i])    < H_tx_min)    H_tx_min    = crealf(H_tx[i]);
+        if (i==0 || crealf(H_prime[i]) < H_prime_min) H_prime_min = crealf(H_prime[i]);
+        if (i==0 || crealf(G_prime[i]) < G_prime_min) G_prime_min = crealf(G_prime[i]);
+    }
+
+    // compute 'prototype' response, removing minima, and add correction factor
+    for (i=0; i<h_len; i++) {
+        // compute response necessary to yeild prototype response (not exact, but close)
+        H_hat[i] = crealf(H_prime[i] - H_prime_min + delta) / crealf(H_tx[i] - H_tx_min + delta);
+
+        // include additional term to add stop-band suppression
+        H_hat[i] *= crealf(G_prime[i] - G_prime_min) / crealf(G_prime[0]);
+    }
+
+    // compute ifft and copy response
+    fft_run(h_len, H_hat, h_hat, LIQUID_FFT_BACKWARD, 0);
+    for (i=0; i<h_len; i++)
+        hr[i] = crealf( h_hat[(i+k*m+1)%h_len] ) / (float)(k*h_len);
+
+    // copy result, scaling by (samples/symbol)^2
+    for (i=0; i<h_len; i++)
+        _h[i] = hr[i]*_k*_k;
+}
+
diff --git a/src/filter/src/group_delay.c b/src/filter/src/group_delay.c
new file mode 100644
index 0000000..f5a485b
--- /dev/null
+++ b/src/filter/src/group_delay.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+
+// Compute group delay for a FIR filter
+//  _h      : filter coefficients array [size: _n x 1]
+//  _n      : filter length
+//  _fc     : frequency at which delay is evaluated (-0.5 < _fc < 0.5)
+float fir_group_delay(float * _h,
+                      unsigned int _n,
+                      float _fc)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: fir_group_delay(), length must be greater than zero\n");
+        exit(1);
+    } else if (_fc < -0.5 || _fc > 0.5) {
+        fprintf(stderr,"error: fir_group_delay(), _fc must be in [-0.5,0.5]\n");
+        exit(1);
+    }
+
+    unsigned int i;
+    float complex t0=0.0f;
+    float complex t1=0.0f;
+    for (i=0; i<_n; i++) {
+        t0 += _h[i] * cexpf(_Complex_I*2*M_PI*_fc*i) * i;
+        t1 += _h[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+    }
+
+    return crealf(t0/t1);
+}
+
+// Compute group delay for an IIR filter
+//  _b      : filter coefficients array (numerator), [size: _nb x 1]
+//  _nb     : filter length (numerator)
+//  _a      : filter coefficients array (denominator), [size: _na x 1]
+//  _na     : filter length (denominator)
+//  _fc     : frequency at which delay is evaluated (-0.5 < _fc < 0.5)
+float iir_group_delay(float * _b,
+                      unsigned int _nb,
+                      float * _a,
+                      unsigned int _na,
+                      float _fc)
+{
+    // validate input
+    if (_nb == 0) {
+        fprintf(stderr,"error: iir_group_delay(), numerator length must be greater than zero\n");
+        exit(1);
+    } else if (_na == 0) {
+        fprintf(stderr,"error: iir_group_delay(), denominator length must be greater than zero\n");
+        exit(1);
+    } else if (_fc < -0.5 || _fc > 0.5) {
+        fprintf(stderr,"error: iir_group_delay(), _fc must be in [-0.5,0.5]\n");
+        exit(1);
+    }
+
+    // compute c = conv(b,fliplr(a))
+    //         c(z) = b(z)*a(1/z)*z^(-_na)
+    unsigned int nc = _na + _nb - 1;
+    float c[nc];
+    unsigned int i,j;
+    for (i=0; i<nc; i++)
+        c[i] = 0.0;
+
+    for (i=0; i<_na; i++) {
+        for (j=0; j<_nb; j++) {
+            float sum = conjf(_a[_na-i-1])*_b[j];
+            c[i+j] += sum;
+        }
+    }
+
+    // compute 
+    //      sum(c[i] * exp(j 2 pi fc i) * i)
+    //      --------------------------------
+    //      sum(c[i] * exp(j 2 pi fc i))
+    float complex t0=0.0f;
+    float complex t1=0.0f;
+    float complex c0;
+    for (i=0; i<nc; i++) {
+        c0  = c[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+        t0 += c0*i;
+        t1 += c0;
+    }
+
+    // prevent divide-by-zero (check magnitude for tolerance range)
+    float tol = 1e-5f;
+    if (cabsf(t1)<tol)
+        return 0.0f;
+
+    // return result, scaled by length of denominator
+    return crealf(t0/t1) - (_na - 1);
+}
+
diff --git a/src/filter/src/hM3.c b/src/filter/src/hM3.c
new file mode 100644
index 0000000..9bccbb5
--- /dev/null
+++ b/src/filter/src/hM3.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Design root-Nyquist Kaiser filter
+//
+// References
+//  [harris:2005] f. harris, C. Dick, S. Seshagiri, K. Moerder, "An
+//      Improved Square-Root Nyquist Shaping Filter," Proceedings of
+//      the Software-Defined Radio Forum, 2005
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_hM3   0
+
+// Design root-Nyquist harris-Moerder filter using Parks-McClellan
+// algorithm
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+void liquid_firdes_hM3(unsigned int _k,
+                       unsigned int _m,
+                       float _beta,
+                       float _dt,
+                       float * _h)
+{
+    if ( _k < 2 ) {
+        fprintf(stderr,"error: liquid_firdes_hM3(): k must be greater than 1\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_hM3(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_hM3(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    unsigned int n=2*_k*_m+1;       // filter length
+
+    //
+    float fc = 1.0 / (float)(2*_k); // filter cutoff
+    float fp = fc*(1.0 - _beta);    // pass-band
+    float fs = fc*(1.0 + _beta);    // stop-band
+
+    // root nyquist
+    unsigned int num_bands = 3;
+    float bands[6]   = {0.0f, fp, fc, fc, fs, 0.5f};
+    float des[3]     = {1.0f, 1.0f/sqrtf(2.0f), 0.0f};
+    float weights[3] = {1.0f, 1.0f, 1.0f};
+
+    liquid_firdespm_btype btype = LIQUID_FIRDESPM_BANDPASS;
+    liquid_firdespm_wtype wtype[3] = {LIQUID_FIRDESPM_FLATWEIGHT,
+                                      LIQUID_FIRDESPM_FLATWEIGHT,
+                                      LIQUID_FIRDESPM_EXPWEIGHT};
+
+    //unsigned int i;
+    float h[n];
+    firdespm_run(n,num_bands,bands,des,weights,wtype,btype,h);
+    // copy results
+    memmove(_h, h, n*sizeof(float));
+
+    float isi_max;
+    float isi_rms;
+    liquid_filter_isi(h,_k,_m,&isi_rms,&isi_max);
+
+    // iterate...
+    float isi_rms_min = isi_rms;
+    unsigned int p, pmax=100;
+    for (p=0; p<pmax; p++) {
+        // increase pass-band edge
+        fp = fc*(1.0 - _beta * p / (float)(pmax) );
+        bands[1] = fp;
+
+        // execute filter design
+        firdespm_run(n,num_bands,bands,des,weights,wtype,btype,h);
+
+        // compute inter-symbol interference (MSE, max)
+        liquid_filter_isi(h,_k,_m,&isi_rms,&isi_max);
+
+#if DEBUG_hM3
+        printf("  isi mse : %20.8e (min: %20.8e)\n", isi_rms, isi_rms_min);
+#endif
+        if (isi_rms > isi_rms_min) {
+            // search complete
+            break;
+        } else {
+            isi_rms_min = isi_rms;
+            // copy results
+            memmove(_h, h, n*sizeof(float));
+        }
+    };
+
+    // normalize
+    float e2 = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) e2 += _h[i]*_h[i];
+    for (i=0; i<n; i++) _h[i] *= sqrtf(_k/e2);
+}
+
diff --git a/src/filter/src/iirdecim.c b/src/filter/src/iirdecim.c
new file mode 100644
index 0000000..44b61e0
--- /dev/null
+++ b/src/filter/src/iirdecim.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim.c
+//
+// finite impulse response decimator object definitions
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// decimator structure
+struct IIRDECIM(_s) {
+    unsigned int M;     // decimation factor
+
+    // TODO: use IIR polyphase filterbank
+    IIRFILT() iirfilt;  // filter object
+};
+
+// create interpolator from external coefficients
+//  _M      : interpolation factor
+//  _b      : feed-back coefficients [size: _nb x 1]
+//  _nb     : feed-back coefficients length
+//  _a      : feed-forward coefficients [size: _na x 1]
+//  _na     : feed-forward coefficients length
+IIRDECIM() IIRDECIM(_create)(unsigned int _M,
+                             TC *         _b,
+                             unsigned int _nb,
+                             TC *         _a,
+                             unsigned int _na)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: iirinterp_%s_create(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and set internal parameters
+    IIRDECIM() q = (IIRDECIM()) malloc(sizeof(struct IIRDECIM(_s)));
+    q->M = _M;
+
+    // create filter
+    q->iirfilt = IIRFILT(_create)(_b, _nb, _a, _na);
+
+    // return interpolator object
+    return q;
+}
+
+// create decimator with default Butterworth prototype
+//  _M      : decimation factor
+//  _order  : filter order
+IIRDECIM() IIRDECIM(_create_default)(unsigned int _M,
+                                     unsigned int _order)
+{
+    return IIRDECIM(_create_prototype)(_M,
+                                       LIQUID_IIRDES_BUTTER,
+                                       LIQUID_IIRDES_LOWPASS,
+                                       LIQUID_IIRDES_SOS,
+                                       _order,
+                                       0.5f / (float)_M,    // fc
+                                       0.0f,                // f0
+                                       0.1f,                // pass-band ripple,
+                                       60.0f);              // stop-band attenuation
+}
+
+// create interpolator from prototype
+//  _M      :   interpolation factor
+IIRDECIM() IIRDECIM(_create_prototype)(unsigned int             _M,
+                                       liquid_iirdes_filtertype _ftype,
+                                       liquid_iirdes_bandtype   _btype,
+                                       liquid_iirdes_format     _format,
+                                       unsigned int             _order,
+                                       float                    _fc,
+                                       float                    _f0,
+                                       float                    _Ap,
+                                       float                    _As)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: iirinterp_%s_create_prototype(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and set internal parameters
+    IIRDECIM() q = (IIRDECIM()) malloc(sizeof(struct IIRDECIM(_s)));
+    q->M = _M;
+
+    // create filter
+    q->iirfilt = IIRFILT(_create_prototype)(_ftype, _btype, _format, _order, _fc, _f0, _Ap, _As);
+
+    // return interpolator object
+    return q;
+}
+
+// destroy interpolator object
+void IIRDECIM(_destroy)(IIRDECIM() _q)
+{
+    IIRFILT(_destroy)(_q->iirfilt);
+    free(_q);
+}
+
+// print interpolator state
+void IIRDECIM(_print)(IIRDECIM() _q)
+{
+    printf("interp():\n");
+    printf("    M       :   %u\n", _q->M);
+    IIRFILT(_print)(_q->iirfilt);
+}
+
+// clear internal state
+void IIRDECIM(_reset)(IIRDECIM() _q)
+{
+    IIRFILT(_reset)(_q->iirfilt);
+}
+
+// execute decimator
+//  _q      :   decimator object
+//  _x      :   input sample array [size: _M x 1]
+//  _y      :   output sample pointer
+//  _index  :   decimator output index [0,_M-1]
+void IIRDECIM(_execute)(IIRDECIM()   _q,
+                        TI *         _x,
+                        TO *         _y)
+{
+    TO v; // output value
+    unsigned int i;
+    for (i=0; i<_q->M; i++) {
+        // run filter
+        IIRFILT(_execute)(_q->iirfilt, _x[i], &v);
+
+        // save output at appropriate index
+        if (i==0)
+            *_y = v;
+    }
+}
+
+// execute decimator on block of _n*_M input samples
+//  _q      : decimator object
+//  _x      : input array [size: _n*_M x 1]
+//  _n      : number of _output_ samples
+//  _y      : output array [_sze: _n x 1]
+void IIRDECIM(_execute_block)(IIRDECIM()   _q,
+                              TI *         _x,
+                              unsigned int _n,
+                              TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // execute _M input samples computing just one output each time
+        IIRDECIM(_execute)(_q, &_x[i*_q->M], &_y[i]);
+    }
+}
+
+// get system group delay at frequency _fc
+//  _q      :   interpolator object
+//  _f      :   frequency
+float IIRDECIM(_groupdelay)(IIRDECIM() _q,
+                            float      _fc)
+{
+    return IIRFILT(_groupdelay)(_q->iirfilt, _fc);
+}
+
diff --git a/src/filter/src/iirdes.c b/src/filter/src/iirdes.c
new file mode 100644
index 0000000..8f1f6c3
--- /dev/null
+++ b/src/filter/src/iirdes.c
@@ -0,0 +1,705 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iir (infinite impulse response) filter design
+//
+// References
+//  [Constantinides:1967] A. G. Constantinides, "Frequency
+//      Transformations for Digital Filters." IEEE Electronic
+//      Letters, vol. 3, no. 11, pp 487-489, 1967.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_IIRDES_DEBUG_PRINT 0
+
+// Sorts array _z of complex numbers into complex conjugate pairs to
+// within a tolerance. Conjugate pairs are ordered by increasing real
+// component with the negative imaginary element first. All pure-real
+// elements are placed at the end of the array.
+//
+// Example:
+//      v:              liquid_cplxpair(v):
+//      10 + j*3        -3 - j*4
+//       5 + j*0         3 + j*4
+//      -3 + j*4        10 - j*3
+//      10 - j*3        10 + j*3
+//       3 + j*0         3 + j*0
+//      -3 + j*4         5 + j*0
+//
+//  _z      :   complex array (size _n)
+//  _n      :   number of elements in _z
+//  _tol    :   tolerance for finding complex pairs
+//  _p      :   resulting pairs, pure real values of _z at end
+void liquid_cplxpair(float complex * _z,
+                     unsigned int _n,
+                     float _tol,
+                     float complex * _p)
+{
+    // validate input
+    if (_tol < 0) {
+        fprintf(stderr,"error: liquid_cplxpair(), tolerance must be positive\n");
+        exit(1);
+    }
+
+    // keep track of which elements have been paired
+    unsigned char paired[_n];
+    memset(paired,0,sizeof(paired));
+    unsigned int num_pairs=0;
+
+    unsigned int i,j,k=0;
+    for (i=0; i<_n; i++) {
+        // ignore value if already paired, or if imaginary
+        // component is less than tolerance
+        if (paired[i] || fabsf(cimagf(_z[i])) < _tol)
+            continue;
+
+        for (j=0; j<_n; j++) {
+            // ignore value if already paired, or if imaginary
+            // component is less than tolerance
+            if (j==i || paired[j] || fabsf(cimagf(_z[j])) < _tol)
+                continue;
+
+            if ( fabsf(cimagf(_z[i])+cimagf(_z[j])) < _tol &&
+                 fabsf(crealf(_z[i])-crealf(_z[j])) < _tol )
+            {
+                // found complex conjugate pair
+                _p[k++] = _z[i];
+                _p[k++] = _z[j];
+                paired[i] = 1;
+                paired[j] = 1;
+                num_pairs++;
+                break;
+            }
+        }
+    }
+    assert(k <= _n);
+
+    // sort through remaining unpaired values and ensure
+    // they are purely real
+    for (i=0; i<_n; i++) {
+        if (paired[i])
+            continue;
+
+        if (cimagf(_z[i]) > _tol) {
+            fprintf(stderr,"warning, liquid_cplxpair(), complex numbers cannot be paired\n");
+        } else {
+            _p[k++] = _z[i];
+            paired[i] = 1;
+        }
+    }
+
+    // clean up result
+    //  * force pairs to be perfect conjugates with
+    //    negative imaginary component first
+    //  * complex conjugate pairs are ordered by
+    //    increasing real component
+    //  * pure-real elements are ordered by increasing
+    //    value
+    liquid_cplxpair_cleanup(_p, _n, num_pairs);
+}
+
+// post-process cleanup used with liquid_cplxpair
+//
+// once pairs have been identified and ordered, this method
+// will clean up the result by ensuring the following:
+//  * pairs are perfect conjugates
+//  * pairs have negative imaginary component first
+//  * pairs are ordered by increasing real component
+//  * pure-real elements are ordered by increasing value
+//
+//  _p          :   pre-processed complex array [size: _n x 1]
+//  _n          :   array length
+//  _num_pairs  :   number of complex conjugate pairs
+void liquid_cplxpair_cleanup(float complex * _p,
+                             unsigned int _n,
+                             unsigned int _num_pairs)
+{
+    unsigned int i;
+    unsigned int j;
+    float complex tmp;
+
+    // ensure perfect conjugates, with negative imaginary
+    // element coming first
+    for (i=0; i<_num_pairs; i++) {
+        // ensure perfect conjugates
+        _p[2*i+0] = cimagf(_p[2*i]) < 0 ? _p[2*i] : conjf(_p[2*i]);
+        _p[2*i+1] = conjf(_p[2*i+0]);
+    }
+
+    // sort conjugate pairs
+    for (i=0; i<_num_pairs; i++) {
+        for (j=_num_pairs-1; j>i; j--) {
+            if ( crealf(_p[2*(j-1)]) > crealf(_p[2*j]) ) {
+                // swap pairs
+                tmp = _p[2*(j-1)+0];
+                _p[2*(j-1)+0] = _p[2*j+0];
+                _p[2*j    +0] = tmp;
+
+                tmp = _p[2*(j-1)+1];
+                _p[2*(j-1)+1] = _p[2*j+1];
+                _p[2*j    +1] = tmp;
+            }
+        }
+    }
+
+    // sort pure-real values
+    for (i=2*_num_pairs; i<_n; i++) {
+        for (j=_n-1; j>i; j--) {
+            if ( crealf(_p[j-1]) > crealf(_p[j]) ) {
+                // swap elements
+                tmp = _p[j-1];
+                _p[j-1] = _p[j];
+                _p[j  ] = tmp;
+            }
+        }
+    }
+
+}
+
+
+// 
+// new IIR design
+//
+
+// Compute frequency pre-warping factor.  See [Constantinides:1967]
+//  _btype  :   band type (e.g. LIQUID_IIRDES_HIGHPASS)
+//  _fc     :   low-pass cutoff frequency
+//  _f0     :   center frequency (band-pass|stop cases only)
+float iirdes_freqprewarp(liquid_iirdes_bandtype _btype,
+                         float _fc,
+                         float _f0)
+{
+    float m = 0.0f;
+    if (_btype == LIQUID_IIRDES_LOWPASS) {
+        // low pass
+        m = tanf(M_PI * _fc);
+    } else if (_btype == LIQUID_IIRDES_HIGHPASS) {
+        // high pass
+        m = -cosf(M_PI * _fc) / sinf(M_PI * _fc);
+    } else if (_btype == LIQUID_IIRDES_BANDPASS) {
+        // band pass
+        m = (cosf(2*M_PI*_fc) - cosf(2*M_PI*_f0) )/ sinf(2*M_PI*_fc);
+    } else if (_btype == LIQUID_IIRDES_BANDSTOP) {
+        // band stop
+        m = sinf(2*M_PI*_fc)/(cosf(2*M_PI*_fc) - cosf(2*M_PI*_f0));
+    }
+    m = fabsf(m);
+
+    return m;
+}
+
+// convert analog zeros, poles, gain to digital zeros, poles gain
+//  _za     :   analog zeros (length: _nza)
+//  _nza    :   number of analog zeros
+//  _pa     :   analog poles (length: _npa)
+//  _npa    :   number of analog poles
+//  _ka     :   nominal gain (NOTE: this does not necessarily carry over from analog gain)
+//  _m      :   frequency pre-warping factor
+//  _zd     :   digital zeros (length: _npa)
+//  _pd     :   digital poles (length: _npa)
+//  _kd     :   digital gain
+//
+// The filter order is characterized by the number of analog
+// poles.  The analog filter may have up to _npa zeros.
+// The number of digital zeros and poles is equal to _npa.
+void bilinear_zpkf(float complex * _za,
+                   unsigned int _nza,
+                   float complex * _pa,
+                   unsigned int _npa,
+                   float complex _ka,
+                   float _m,
+                   float complex * _zd,
+                   float complex * _pd,
+                   float complex * _kd)
+{
+    unsigned int i;
+
+    // filter order is equal to number of analog poles
+    unsigned int n = _npa;
+    float complex G = _ka;  // nominal gain
+    for (i=0; i<n; i++) {
+        // compute digital zeros (pad with -1s)
+        if (i < _nza) {
+            float complex zm = _za[i] * _m;
+            _zd[i] = (1.0 + zm)/(1.0 - zm);
+        } else {
+            _zd[i] = -1.0;
+        }
+
+        // compute digital poles
+        float complex pm = _pa[i] * _m;
+        _pd[i] = (1.0 + pm)/(1.0 - pm);
+
+        // compute digital gain
+        G *= (1.0 - _pd[i])/(1.0 - _zd[i]);
+    }
+    *_kd = G;
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+    // print poles and zeros
+    printf("zpk_a2df() poles (discrete):\n");
+    for (i=0; i<n; i++)
+        printf("  pd[%3u] = %12.8f + j*%12.8f\n", i, crealf(_pd[i]), cimagf(_pd[i]));
+    printf("zpk_a2df() zeros (discrete):\n");
+    for (i=0; i<n; i++)
+        printf("  zd[%3u] = %12.8f + j*%12.8f\n", i, crealf(_zd[i]), cimagf(_zd[i]));
+    printf("zpk_a2df() gain (discrete):\n");
+    printf("  kd      = %12.8f + j*%12.8f\n", crealf(G), cimagf(G));
+#endif
+}
+
+// convert discrete z/p/k form to transfer function form
+//  _zd     :   digital zeros (length: _n)
+//  _pd     :   digital poles (length: _n)
+//  _n      :   filter order
+//  _k      :   digital gain
+//  _b      :   output numerator (length: _n+1)
+//  _a      :   output denominator (length: _n+1)
+void iirdes_dzpk2tff(float complex * _zd,
+                     float complex * _pd,
+                     unsigned int _n,
+                     float complex _k,
+                     float * _b,
+                     float * _a)
+{
+    unsigned int i;
+    float complex q[_n+1];
+
+    // expand poles
+    polycf_expandroots(_pd,_n,q);
+    for (i=0; i<=_n; i++)
+        _a[i] = crealf(q[_n-i]);
+
+    // expand zeros
+    polycf_expandroots(_zd, _n, q);
+    for (i=0; i<=_n; i++)
+        _b[i] = crealf(q[_n-i]*_k);
+}
+
+// converts discrete-time zero/pole/gain (zpk) recursive (iir)
+// filter representation to second-order sections (sos) form
+//
+//  _zd     :   discrete zeros array (size _n)
+//  _pd     :   discrete poles array (size _n)
+//  _n      :   number of poles, zeros
+//  _kd     :   gain
+//
+//  _B      :   output numerator matrix (size (L+r) x 3)
+//  _A      :   output denominator matrix (size (L+r) x 3)
+//
+//  L is the number of sections in the cascade:
+//      r = _n % 2
+//      L = (_n - r) / 2;
+void iirdes_dzpk2sosf(float complex * _zd,
+                      float complex * _pd,
+                      unsigned int _n,
+                      float complex _kd,
+                      float * _B,
+                      float * _A)
+{
+    int i;
+    float tol=1e-6f; // tolerance for conjuate pair computation
+
+    // find/group complex conjugate pairs (poles)
+    float complex zp[_n];
+    liquid_cplxpair(_zd,_n,tol,zp);
+
+    // find/group complex conjugate pairs (zeros)
+    float complex pp[_n];
+    liquid_cplxpair(_pd,_n,tol,pp);
+
+    // TODO : group pole pairs with zero pairs
+
+    // _n = 2*L + r
+    unsigned int r = _n % 2;        // odd/even order
+    unsigned int L = (_n - r)/2;    // filter semi-length
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+    printf("  n=%u, r=%u, L=%u\n", _n, r, L);
+    printf("poles :\n");
+    for (i=0; i<_n; i++)
+        printf("  p[%3u] = %12.8f + j*%12.8f\n", i, crealf(_pd[i]), cimagf(_pd[i]));
+    printf("zeros :\n");
+    for (i=0; i<_n; i++)
+        printf("  z[%3u] = %12.8f + j*%12.8f\n", i, crealf(_zd[i]), cimagf(_zd[i]));
+
+    printf("poles (conjugate pairs):\n");
+    for (i=0; i<_n; i++)
+        printf("  p[%3u] = %12.8f + j*%12.8f\n", i, crealf(pp[i]), cimagf(pp[i]));
+    printf("zeros (conjugate pairs):\n");
+    for (i=0; i<_n; i++)
+        printf("  z[%3u] = %12.8f + j*%12.8f\n", i, crealf(zp[i]), cimagf(zp[i]));
+#endif
+
+    float complex z0, z1;
+    float complex p0, p1;
+    for (i=0; i<L; i++) {
+        p0 = -pp[2*i+0];
+        p1 = -pp[2*i+1];
+
+        z0 = -zp[2*i+0];
+        z1 = -zp[2*i+1];
+
+        // expand complex pole pairs
+        _A[3*i+0] = 1.0;
+        _A[3*i+1] = crealf(p0+p1);
+        _A[3*i+2] = crealf(p0*p1);
+
+        // expand complex zero pairs
+        _B[3*i+0] = 1.0;
+        _B[3*i+1] = crealf(z0+z1);
+        _B[3*i+2] = crealf(z0*z1);
+    }
+
+    // add remaining zero/pole pair if order is odd
+    if (r) {
+        // keep these two lines for when poles and zeros get grouped
+        p0 = -pp[_n-1];
+        z0 = -zp[_n-1];
+        
+        _A[3*i+0] = 1.0;
+        _A[3*i+1] = p0;
+        _A[3*i+2] = 0.0;
+
+        _B[3*i+0] = 1.0;
+        _B[3*i+1] = z0;
+        _B[3*i+2] = 0.0;
+    }
+
+    // distribute gain equally amongst all feed-forward
+    // coefficients
+    float k = powf( crealf(_kd), 1.0f/(float)(L+r) );
+
+    // adjust gain of first element
+    for (i=0; i<L+r; i++) {
+        _B[3*i+0] *= k;
+        _B[3*i+1] *= k;
+        _B[3*i+2] *= k;
+    }
+}
+
+// digital z/p/k low-pass to high-pass transformation
+//  _zd     :   digital zeros (low-pass prototype)
+//  _pd     :   digital poles (low-pass prototype)
+//  _n      :   low-pass filter order
+//  _zdt    :   digital zeros transformed [length: _n]
+//  _pdt    :   digital poles transformed [length: _n]
+void iirdes_dzpk_lp2hp(liquid_float_complex * _zd,
+                       liquid_float_complex * _pd,
+                       unsigned int _n,
+                       liquid_float_complex * _zdt,
+                       liquid_float_complex * _pdt)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        _zdt[i] = -_zd[i];
+        _pdt[i] = -_pd[i];
+    }
+}
+
+
+// digital z/p/k low-pass to band-pass transformation
+//  _zd     :   digital zeros (low-pass prototype)
+//  _pd     :   digital poles (low-pass prototype)
+//  _n      :   low-pass filter order
+//  _f0     :   center frequency
+//  _zdt    :   digital zeros transformed [length: 2*_n]
+//  _pdt    :   digital poles transformed [length: 2*_n]
+void iirdes_dzpk_lp2bp(liquid_float_complex * _zd,
+                       liquid_float_complex * _pd,
+                       unsigned int _n,
+                       float _f0,
+                       liquid_float_complex * _zdt,
+                       liquid_float_complex * _pdt)
+{
+    // 
+    float c0 = cosf(2*M_PI*_f0);
+
+    // transform zeros, poles using quadratic formula
+    unsigned int i;
+    float complex t0;
+    for (i=0; i<_n; i++) {
+        t0 = 1 + _zd[i];
+        _zdt[2*i+0] = 0.5f*(c0*t0 + csqrtf(c0*c0*t0*t0 - 4*_zd[i]));
+        _zdt[2*i+1] = 0.5f*(c0*t0 - csqrtf(c0*c0*t0*t0 - 4*_zd[i]));
+
+        t0 = 1 + _pd[i];
+        _pdt[2*i+0] = 0.5f*(c0*t0 + csqrtf(c0*c0*t0*t0 - 4*_pd[i]));
+        _pdt[2*i+1] = 0.5f*(c0*t0 - csqrtf(c0*c0*t0*t0 - 4*_pd[i]));
+    }
+}
+
+// IIR filter design template
+//  _ftype      :   filter type (e.g. LIQUID_IIRDES_BUTTER)
+//  _btype      :   band type (e.g. LIQUID_IIRDES_BANDPASS)
+//  _format     :   coefficients format (e.g. LIQUID_IIRDES_SOS)
+//  _n          :   filter order
+//  _fc         :   low-pass prototype cut-off frequency
+//  _f0         :   center frequency (band-pass, band-stop)
+//  _Ap         :   pass-band ripple in dB
+//  _As         :   stop-band ripple in dB
+//  _B          :   numerator
+//  _A          :   denominator
+void liquid_iirdes(liquid_iirdes_filtertype _ftype,
+                   liquid_iirdes_bandtype   _btype,
+                   liquid_iirdes_format     _format,
+                   unsigned int _n,
+                   float _fc,
+                   float _f0,
+                   float _Ap,
+                   float _As,
+                   float * _B,
+                   float * _A)
+{
+    // validate input
+    if (_fc <= 0 || _fc >= 0.5) {
+        fprintf(stderr,"error: liquid_iirdes(), cutoff frequency out of range\n");
+        exit(1);
+    } else if (_f0 < 0 || _f0 > 0.5) {
+        fprintf(stderr,"error: liquid_iirdes(), center frequency out of range\n");
+        exit(1);
+    } else if (_Ap <= 0) {
+        fprintf(stderr,"error: liquid_iirdes(), pass-band ripple out of range\n");
+        exit(1);
+    } else if (_As <= 0) {
+        fprintf(stderr,"error: liquid_iirdes(), stop-band ripple out of range\n");
+        exit(1);
+    } else if (_n == 0) {
+        fprintf(stderr,"error: liquid_iirdes(), filter order must be > 0\n");
+        exit(1);
+    }
+
+    // number of analaog poles/zeros
+    unsigned int npa = _n;
+    unsigned int nza;
+
+    // analog poles/zeros/gain
+    float complex pa[_n];
+    float complex za[_n];
+    float complex ka;
+    float complex k0 = 1.0f; // nominal digital gain
+
+    // derived values
+    unsigned int r = _n%2;      // odd/even filter order
+    unsigned int L = (_n-r)/2;  // filter semi-length
+
+    // specific filter variables
+    float epsilon, Gp, Gs, ep, es;
+
+    // compute zeros and poles of analog prototype
+    switch (_ftype) {
+    case LIQUID_IIRDES_BUTTER:
+        // Butterworth filter design : no zeros, _n poles
+        nza = 0;
+        k0 = 1.0f;
+        butter_azpkf(_n,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY1:
+        // Cheby-I filter design : no zeros, _n poles, pass-band ripple
+        nza = 0;
+        epsilon = sqrtf( powf(10.0f, _Ap / 10.0f) - 1.0f );
+        k0 = r ? 1.0f : 1.0f / sqrt(1.0f + epsilon*epsilon);
+        cheby1_azpkf(_n,epsilon,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_CHEBY2:
+        // Cheby-II filter design : _n-r zeros, _n poles, stop-band ripple
+        nza = 2*L;
+        epsilon = powf(10.0f, -_As/20.0f);
+        k0 = 1.0f;
+        cheby2_azpkf(_n,epsilon,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_ELLIP:
+        // elliptic filter design : _n-r zeros, _n poles, pass/stop-band ripple
+        nza = 2*L;
+        Gp = powf(10.0f, -_Ap / 20.0f);     // pass-band gain
+        Gs = powf(10.0f, -_As / 20.0f);     // stop-band gain
+        ep = sqrtf(1.0f/(Gp*Gp) - 1.0f);    // pass-band epsilon
+        es = sqrtf(1.0f/(Gs*Gs) - 1.0f);    // stop-band epsilon
+        k0 = r ? 1.0f : 1.0f / sqrt(1.0f + ep*ep);
+        ellip_azpkf(_n,ep,es,za,pa,&ka);
+        break;
+    case LIQUID_IIRDES_BESSEL:
+        // Bessel filter design : no zeros, _n poles
+        nza = 0;
+        k0 = 1.0f;
+        bessel_azpkf(_n,za,pa,&ka);
+        break;
+    default:
+        fprintf(stderr,"error: liquid_iirdes(), unknown filter type\n");
+        exit(1);
+    }
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+    unsigned int i;
+
+    printf("poles (analog):\n");
+    for (i=0; i<npa; i++)
+        printf("  pa[%3u] = %12.8f + j*%12.8f\n", i, crealf(pa[i]), cimagf(pa[i]));
+    printf("zeros (analog):\n");
+    for (i=0; i<nza; i++)
+        printf("  za[%3u] = %12.8f + j*%12.8f\n", i, crealf(za[i]), cimagf(za[i]));
+    printf("gain (analog):\n");
+    printf("  ka : %12.8f + j*%12.8f\n", crealf(ka), cimagf(ka));
+#endif
+
+    // complex digital poles/zeros/gain
+    // NOTE: allocated double the filter order to cover band-pass, band-stop cases
+    float complex zd[2*_n];
+    float complex pd[2*_n];
+    float complex kd;
+    float m = iirdes_freqprewarp(_btype,_fc,_f0);
+    //printf("m : %12.8f\n", m);
+    bilinear_zpkf(za,    nza,
+                  pa,    npa,
+                  k0,    m,
+                  zd, pd, &kd);
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+    printf("zeros (digital, low-pass prototype):\n");
+    for (i=0; i<_n; i++)
+        printf("  zd[%3u] = %12.4e + j*%12.4e;\n", i, crealf(zd[i]), cimagf(zd[i]));
+    printf("poles (digital, low-pass prototype):\n");
+    for (i=0; i<_n; i++)
+        printf("  pd[%3u] = %12.4e + j*%12.4e;\n", i, crealf(pd[i]), cimagf(pd[i]));
+    printf("gain (digital):\n");
+    printf("  kd : %12.8f + j*%12.8f\n", crealf(kd), cimagf(kd));
+#endif
+
+    // negate zeros, poles for high-pass and band-stop cases
+    if (_btype == LIQUID_IIRDES_HIGHPASS ||
+        _btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        // run transform, place resulting zeros, poles
+        // back in same original arrays
+        iirdes_dzpk_lp2hp(zd, pd, _n, zd, pd);
+    }
+
+    // transform zeros, poles in band-pass, band-stop cases
+    // NOTE: this also doubles the filter order
+    if (_btype == LIQUID_IIRDES_BANDPASS ||
+        _btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        // allocate memory for transformed zeros, poles
+        float complex zd1[2*_n];
+        float complex pd1[2*_n];
+
+        // run zeros, poles low-pass -> band-pass trasform
+        iirdes_dzpk_lp2bp(zd, pd,   // low-pass prototype zeros, poles
+                          _n,       // filter order
+                          _f0,      // center frequency
+                          zd1,pd1); // transformed zeros, poles (length: 2*n)
+
+        // copy transformed zeros, poles
+        memmove(zd, zd1, 2*_n*sizeof(float complex));
+        memmove(pd, pd1, 2*_n*sizeof(float complex));
+
+        // update paramters; filter order doubles which changes the
+        // number of second-order sections and forces there to never
+        // be any remainder (r=0 always).
+        _n =  2*_n;     // _n is now even
+#if LIQUID_IIRDES_DEBUG_PRINT
+        r  = _n % 2;    //  r is now zero
+        L  = (_n-r)/2;  //  L is now the original value of _n
+#endif
+    }
+
+    if (_format == LIQUID_IIRDES_TF) {
+        // convert complex digital poles/zeros/gain into transfer
+        // function : H(z) = B(z) / A(z)
+        // where length(B,A) = low/high-pass ? _n + 1 : 2*_n + 1
+        iirdes_dzpk2tff(zd,pd,_n,kd,_B,_A);
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+        // print coefficients
+        for (i=0; i<=_n; i++) printf("b[%3u] = %12.8f;\n", i, _B[i]);
+        for (i=0; i<=_n; i++) printf("a[%3u] = %12.8f;\n", i, _A[i]);
+#endif
+    } else {
+        // convert complex digital poles/zeros/gain into second-
+        // order sections form :
+        // H(z) = prod { (b0 + b1*z^-1 + b2*z^-2) / (a0 + a1*z^-1 + a2*z^-2) }
+        // where size(B,A) = low|high-pass  : [3]x[L+r]
+        //                   band-pass|stop : [3]x[2*L]
+        iirdes_dzpk2sosf(zd,pd,_n,kd,_B,_A);
+
+#if LIQUID_IIRDES_DEBUG_PRINT
+        // print coefficients
+        printf("B [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", _B[3*i+0], _B[3*i+1], _B[3*i+2]);
+        printf("A [%u x 3] :\n", L+r);
+        for (i=0; i<L+r; i++)
+            printf("  %12.8f %12.8f %12.8f\n", _A[3*i+0], _A[3*i+1], _A[3*i+2]);
+#endif
+
+    }
+}
+
+// checks stability of iir filter
+//  _b      :   feed-forward coefficients [size: _n x 1]
+//  _a      :   feed-back coefficients [size: _n x 1]
+//  _n      :   number of coefficients
+int iirdes_isstable(float * _b,
+                    float * _a,
+                    unsigned int _n)
+{
+    // validate input
+    if (_n < 2) {
+        fprintf(stderr,"error: iirdes_isstable(), filter order too low\n");
+        exit(1);
+    }
+    unsigned int i;
+
+    // flip denominator, left to right
+    float a_hat[_n];
+    for (i=0; i<_n; i++)
+        a_hat[i] = _a[_n-i-1];
+
+    // compute poles (roots of denominator)
+    float complex roots[_n-1];
+    polyf_findroots_bairstow(a_hat, _n, roots);
+
+#if 0
+    // print roots
+    printf("\nroots:\n");
+    for (i=0; i<_n-1; i++)
+        printf("  r[%3u] = %12.8f + j *%12.8f\n", i, crealf(roots[i]), cimagf(roots[i]));
+#endif
+
+    // compute magnitude of poles
+    for (i=0; i<_n-1; i++) {
+        if (cabsf(roots[i]) > 1.0)
+            return 0;
+    }
+
+    return 1;
+}
+
+
diff --git a/src/filter/src/iirdes.pll.c b/src/filter/src/iirdes.pll.c
new file mode 100644
index 0000000..1b2bd98
--- /dev/null
+++ b/src/filter/src/iirdes.pll.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// 2nd-order iir (infinite impulse response) phase-locked loop filter design
+//
+// References:
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// design 2nd-order IIR filter (active lag)
+//          1 + t2 * s
+//  F(s) = ------------
+//          1 + t1 * s
+//
+//  _w      :   filter bandwidth
+//  _zeta   :   damping factor (1/sqrt(2) suggested)
+//  _K      :   loop gain (1000 suggested)
+//  _b      :   output feed-forward coefficients [size: 3 x 1]
+//  _a      :   output feed-back coefficients [size: 3 x 1]
+void iirdes_pll_active_lag(float _w,
+                           float _zeta,
+                           float _K,
+                           float * _b,
+                           float * _a)
+{
+    // validate input
+    if (_w <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (_zeta <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), damping factor must be greater than 0\n");
+        exit(1);
+    } else if (_K <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), gain must be greater than 0\n");
+        exit(1);
+    }
+
+    float wn = _w;                  // natural frequency
+    float t1 = _K/(wn*wn);          // 
+    float t2 = 2*_zeta/wn - 1/_K;   //
+
+    _b[0] = 2*_K*(1.+t2/2.0f);
+    _b[1] = 2*_K*2.;
+    _b[2] = 2*_K*(1.-t2/2.0f);
+
+    _a[0] =  1 + t1/2.0f;
+    _a[1] = -t1;
+    _a[2] = -1 + t1/2.0f;
+}
+
+// design 2nd-order IIR filter (active PI)
+//          1 + t2 * s
+//  F(s) = ------------
+//           t1 * s
+//
+//  _w      :   filter bandwidth
+//  _zeta   :   damping factor (1/sqrt(2) suggested)
+//  _K      :   loop gain (1000 suggested)
+//  _b      :   output feed-forward coefficients [size: 3 x 1]
+//  _a      :   output feed-back coefficients [size: 3 x 1]
+void iirdes_pll_active_PI(float _w,
+                          float _zeta,
+                          float _K,
+                          float * _b,
+                          float * _a)
+{
+    // validate input
+    if (_w <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), bandwidth must be greater than 0\n");
+        exit(1);
+    } else if (_zeta <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), damping factor must be greater than 0\n");
+        exit(1);
+    } else if (_K <= 0.0f) {
+        fprintf(stderr,"error: iirdes_pll_active_lag(), gain must be greater than 0\n");
+        exit(1);
+    }
+
+    // loop filter (active lag)
+    float wn = _w;          // natural frequency
+    float t1 = _K/(wn*wn);  //
+    float t2 = 2*_zeta/wn;  //
+
+    _b[0] = 2*_K*(1.+t2/2.0f);
+    _b[1] = 2*_K*2.;
+    _b[2] = 2*_K*(1.-t2/2.0f);
+
+    _a[0] =  t1/2.0f;
+    _a[1] = -t1;
+    _a[2] =  t1/2.0f;
+}
+
+
diff --git a/src/filter/src/iirfilt.c b/src/filter/src/iirfilt.c
new file mode 100644
index 0000000..910f259
--- /dev/null
+++ b/src/filter/src/iirfilt.c
@@ -0,0 +1,656 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt : Infinite impulse response filter
+//
+// References:
+//  [Pintelon:1990] Rik Pintelon and Johan Schoukens, "Real-Time
+//      Integration and Differentiation of Analog Signals by Means of
+//      Digital Filtering," IEEE Transactions on Instrumentation and
+//      Measurement, vol 39 no. 6, December 1990.
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// defined:
+//  IIRFILT()       name-mangling macro
+//  TO              output type
+//  TC              coefficients type
+//  TI              input type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+// use structured dot product? 0:no, 1:yes
+#define LIQUID_IIRFILT_USE_DOTPROD   (0)
+
+struct IIRFILT(_s) {
+    TC * b;             // numerator (feed-forward coefficients)
+    TC * a;             // denominator (feed-back coefficients)
+    TI * v;             // internal filter state (buffer)
+    unsigned int n;     // filter length (order+1)
+
+    unsigned int nb;    // numerator length
+    unsigned int na;    // denominator length
+
+    // filter structure type
+    enum {
+        IIRFILT_TYPE_NORM=0,
+        IIRFILT_TYPE_SOS
+    } type;
+
+#if LIQUID_IIRFILT_USE_DOTPROD
+    DOTPROD() dpb;      // numerator dot product
+    DOTPROD() dpa;      // denominator dot product
+#endif
+
+    // second-order sections 
+    IIRFILTSOS() * qsos;    // second-order sections filters
+    unsigned int nsos;      // number of second-order sections
+};
+
+// create iirfilt (infinite impulse response filter) object
+//  _b      :   numerator, feed-forward coefficients [size: _nb x 1]
+//  _nb     :   length of numerator
+//  _a      :   denominator, feed-back coefficients [size: _na x 1]
+//  _na     :   length of denominator
+IIRFILT() IIRFILT(_create)(TC *         _b,
+                           unsigned int _nb,
+                           TC *         _a,
+                           unsigned int _na)
+{
+    // validate input
+    if (_nb == 0) {
+        fprintf(stderr,"error: iirfilt_%s_create(), numerator length cannot be zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_na == 0) {
+        fprintf(stderr,"error: iirfilt_%s_create(), denominator length cannot be zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create structure and initialize
+    IIRFILT() q = (IIRFILT()) malloc(sizeof(struct IIRFILT(_s)));
+    q->nb = _nb;
+    q->na = _na;
+    q->n = (q->na > q->nb) ? q->na : q->nb;
+    q->type = IIRFILT_TYPE_NORM;
+
+    // allocate memory for numerator, denominator
+    q->a = (TC *) malloc((q->na)*sizeof(TC));
+    q->b = (TC *) malloc((q->nb)*sizeof(TC));
+
+    // normalize coefficients to _a[0]
+    TC a0 = _a[0];
+
+    unsigned int i;
+#if 0
+    // read values in reverse order
+    for (i=0; i<q->nb; i++)
+        q->b[i] = _b[q->nb - i - 1];
+
+    for (i=0; i<q->na; i++)
+        q->a[i] = _a[q->na - i - 1];
+#else
+    for (i=0; i<q->nb; i++)
+        q->b[i] = _b[i] / a0;
+
+    for (i=0; i<q->na; i++)
+        q->a[i] = _a[i] / a0;
+#endif
+
+    // create buffer and initialize
+    q->v = (TI *) malloc((q->n)*sizeof(TI));
+
+#if LIQUID_IIRFILT_USE_DOTPROD
+    q->dpa = DOTPROD(_create)(q->a+1, q->na-1);
+    q->dpb = DOTPROD(_create)(q->b,   q->nb);
+#endif
+
+    // reset internal state
+    IIRFILT(_reset)(q);
+    
+    // return iirfilt object
+    return q;
+}
+
+// create iirfilt (infinite impulse response filter) object based
+// on second-order sections form
+//  _B      :   numerator, feed-forward coefficients [size: _nsos x 3]
+//  _A      :   denominator, feed-back coefficients  [size: _nsos x 3]
+//  _nsos   :   number of second-order sections
+//
+// NOTE: The number of second-order sections can be computed from the
+// filter's order, n, as such:
+//   r = n % 2
+//   L = (n-r)/2
+//   nsos = L+r
+IIRFILT() IIRFILT(_create_sos)(TC *         _B,
+                               TC *         _A,
+                               unsigned int _nsos)
+{
+    // validate input
+    if (_nsos == 0) {
+        fprintf(stderr,"error: iirfilt_%s_create_sos(), filter must have at least one 2nd-order section\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create structure and initialize
+    IIRFILT() q = (IIRFILT()) malloc(sizeof(struct IIRFILT(_s)));
+    q->type = IIRFILT_TYPE_SOS;
+    q->nsos = _nsos;
+    q->qsos = (IIRFILTSOS()*) malloc( (q->nsos)*sizeof(IIRFILTSOS()) );
+    q->n = _nsos * 2;
+
+    // create coefficients array and copy over
+    q->b = (TC *) malloc(3*(q->nsos)*sizeof(TC));
+    q->a = (TC *) malloc(3*(q->nsos)*sizeof(TC));
+    memmove(q->b, _B, 3*(q->nsos)*sizeof(TC));
+    memmove(q->a, _A, 3*(q->nsos)*sizeof(TC));
+
+    TC at[3];
+    TC bt[3];
+    unsigned int i,k;
+    for (i=0; i<q->nsos; i++) {
+        for (k=0; k<3; k++) {
+            at[k] = q->a[3*i+k];
+            bt[k] = q->b[3*i+k];
+        }
+        q->qsos[i] = IIRFILTSOS(_create)(bt,at);
+        //q->qsos[i] = IIRFILT(_create)(q->b+3*i,3,q->a+3*i,3);
+    }
+    return q;
+}
+
+// create iirfilt (infinite impulse response filter) object based
+// on prototype
+//  _ftype      :   filter type (e.g. LIQUID_IIRDES_BUTTER)
+//  _btype      :   band type (e.g. LIQUID_IIRDES_BANDPASS)
+//  _format     :   coefficients format (e.g. LIQUID_IIRDES_SOS)
+//  _order      :   filter order
+//  _fc         :   low-pass prototype cut-off frequency
+//  _f0         :   center frequency (band-pass, band-stop)
+//  _Ap         :   pass-band ripple in dB
+//  _As         :   stop-band ripple in dB
+IIRFILT() IIRFILT(_create_prototype)(liquid_iirdes_filtertype _ftype,
+                                     liquid_iirdes_bandtype   _btype,
+                                     liquid_iirdes_format     _format,
+                                     unsigned int _order,
+                                     float _fc,
+                                     float _f0,
+                                     float _Ap,
+                                     float _As)
+{
+    // derived values : compute filter length
+    unsigned int N = _order; // effective order
+    // filter order effectively doubles for band-pass, band-stop
+    // filters due to doubling the number of poles and zeros as
+    // a result of filter transformation
+    if (_btype == LIQUID_IIRDES_BANDPASS ||
+        _btype == LIQUID_IIRDES_BANDSTOP)
+    {
+        N *= 2;
+    }
+    unsigned int r = N%2;       // odd/even order
+    unsigned int L = (N-r)/2;   // filter semi-length
+
+    // allocate memory for filter coefficients
+    unsigned int h_len = (_format == LIQUID_IIRDES_SOS) ? 3*(L+r) : N+1;
+    float B[h_len];
+    float A[h_len];
+
+    // design filter (compute coefficients)
+    liquid_iirdes(_ftype, _btype, _format, _order, _fc, _f0, _Ap, _As, B, A);
+
+    // move coefficients to type-specific arrays (e.g. float complex)
+    TC Bc[h_len];
+    TC Ac[h_len];
+    unsigned int i;
+    for (i=0; i<h_len; i++) {
+        Bc[i] = B[i];
+        Ac[i] = A[i];
+    }
+
+    // create filter object
+    IIRFILT() q = NULL;
+    if (_format == LIQUID_IIRDES_SOS)
+        q = IIRFILT(_create_sos)(Bc, Ac, L+r);
+    else
+        q = IIRFILT(_create)(Bc, h_len, Ac, h_len);
+
+    // return filter object
+    return q;
+}
+
+// create simplified low-pass Butterworth IIR filter
+//  _n      : filter order
+//  _fc     : low-pass prototype cut-off frequency
+IIRFILT() IIRFILT(_create_lowpass)(
+            unsigned int _order,
+            float        _fc)
+{
+    return IIRFILT(_create_prototype)(LIQUID_IIRDES_BUTTER,
+                                      LIQUID_IIRDES_LOWPASS,
+                                      LIQUID_IIRDES_SOS,
+                                      _order,
+                                      _fc,
+                                      0.0f,      // center
+                                      0.1f,      // pass-band ripple
+                                      60.0f);    // stop-band attenuation
+}
+
+// create 8th-order integrating filter
+IIRFILT() IIRFILT(_create_integrator)()
+{
+    // 
+    // integrator digital zeros/poles/gain, [Pintelon:1990] Table II
+    //
+    // zeros, digital, integrator
+    float complex zdi[8] = {
+        1.175839 * -1.0f,
+        3.371020 * cexpf(_Complex_I * M_PI / 180.0f * -125.1125f),
+        3.371020 * cexpf(_Complex_I * M_PI / 180.0f *  125.1125f),
+        4.549710 * cexpf(_Complex_I * M_PI / 180.0f *  -80.96404f),
+        4.549710 * cexpf(_Complex_I * M_PI / 180.0f *   80.96404f),
+        5.223966 * cexpf(_Complex_I * M_PI / 180.0f *  -40.09347f),
+        5.223966 * cexpf(_Complex_I * M_PI / 180.0f *   40.09347f),
+        5.443743,};
+    // poles, digital, integrator
+    float complex pdi[8] = {
+        0.5805235f * -1.0f,
+        0.2332021f * cexpf(_Complex_I * M_PI / 180.0f * -114.0968f),
+        0.2332021f * cexpf(_Complex_I * M_PI / 180.0f *  114.0968f),
+        0.1814755f * cexpf(_Complex_I * M_PI / 180.0f *  -66.33969f),
+        0.1814755f * cexpf(_Complex_I * M_PI / 180.0f *   66.33969f),
+        0.1641457f * cexpf(_Complex_I * M_PI / 180.0f *  -21.89539f),
+        0.1641457f * cexpf(_Complex_I * M_PI / 180.0f *   21.89539f),
+        1.0f,};
+    // gain, digital, integrator
+    float complex kdi = -1.89213380759321e-05f;
+
+    // second-order sections
+    // allocate 12 values for 4 second-order sections each with
+    // 2 roots (order 8), e.g. (1 + r0 z^-1)(1 + r1 z^-1)
+    float Bi[12];
+    float Ai[12];
+    iirdes_dzpk2sosf(zdi, pdi, 8, kdi, Bi, Ai);
+
+    // copy to type-specific array
+    TC B[12];
+    TC A[12];
+    unsigned int i;
+    for (i=0; i<12; i++) {
+        B[i] = (TC) (Bi[i]);
+        A[i] = (TC) (Ai[i]);
+    }
+
+    // create and return filter object
+    return IIRFILT(_create_sos)(B,A,4);
+}
+
+// create 8th-order differentiation filter
+IIRFILT() IIRFILT(_create_differentiator)()
+{
+    // 
+    // differentiator digital zeros/poles/gain, [Pintelon:1990] Table IV
+    //
+    // zeros, digital, differentiator
+    float complex zdd[8] = {
+        1.702575f * -1.0f,
+        5.877385f * cexpf(_Complex_I * M_PI / 180.0f * -221.4063f),
+        5.877385f * cexpf(_Complex_I * M_PI / 180.0f *  221.4063f),
+        4.197421f * cexpf(_Complex_I * M_PI / 180.0f * -144.5972f),
+        4.197421f * cexpf(_Complex_I * M_PI / 180.0f *  144.5972f),
+        5.350284f * cexpf(_Complex_I * M_PI / 180.0f *  -66.88802f),
+        5.350284f * cexpf(_Complex_I * M_PI / 180.0f *   66.88802f),
+        1.0f,};
+    // poles, digital, differentiator
+    float complex pdd[8] = {
+        0.8476936f * -1.0f,
+        0.2990781f * cexpf(_Complex_I * M_PI / 180.0f * -125.5188f),
+        0.2990781f * cexpf(_Complex_I * M_PI / 180.0f *  125.5188f),
+        0.2232427f * cexpf(_Complex_I * M_PI / 180.0f *  -81.52326f),
+        0.2232427f * cexpf(_Complex_I * M_PI / 180.0f *   81.52326f),
+        0.1958670f * cexpf(_Complex_I * M_PI / 180.0f *  -40.51510f),
+        0.1958670f * cexpf(_Complex_I * M_PI / 180.0f *   40.51510f),
+        0.1886088f,};
+    // gain, digital, differentiator
+    float complex kdd = 2.09049284907492e-05f;
+
+    // second-order sections
+    // allocate 12 values for 4 second-order sections each with
+    // 2 roots (order 8), e.g. (1 + r0 z^-1)(1 + r1 z^-1)
+    float Bd[12];
+    float Ad[12];
+    iirdes_dzpk2sosf(zdd, pdd, 8, kdd, Bd, Ad);
+
+    // copy to type-specific array
+    TC B[12];
+    TC A[12];
+    unsigned int i;
+    for (i=0; i<12; i++) {
+        B[i] = (TC) (Bd[i]);
+        A[i] = (TC) (Ad[i]);
+    }
+
+    // create and return filter object
+    return IIRFILT(_create_sos)(B,A,4);
+}
+
+// create DC-blocking filter
+//
+//          1 -          z^-1
+//  H(z) = ------------------
+//          1 - (1-alpha)z^-1
+IIRFILT() IIRFILT(_create_dc_blocker)(float _alpha)
+{
+    // compute DC-blocking filter coefficients
+    float bf[2] = {1.0f, -1.0f  };
+    float af[2] = {1.0f, -1.0f + _alpha};
+
+    // convert to type-specific array
+    TC b[2] = {(TC)bf[0], (TC)bf[1]};
+    TC a[2] = {(TC)af[0], (TC)af[1]};
+    return IIRFILT(_create)(b,2,a,2);
+}
+
+// create phase-locked loop iirfilt object
+//  _w      :   filter bandwidth
+//  _zeta   :   damping factor (1/sqrt(2) suggested)
+//  _K      :   loop gain (1000 suggested)
+IIRFILT() IIRFILT(_create_pll)(float _w,
+                               float _zeta,
+                               float _K)
+{
+    // validate input
+    if (_w <= 0.0f || _w >= 1.0f) {
+        fprintf(stderr,"error: iirfilt_%s_create_pll(), bandwidth must be in (0,1)\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_zeta <= 0.0f || _zeta >= 1.0f) {
+        fprintf(stderr,"error: iirfilt_%s_create_pll(), damping factor must be in (0,1)\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_K <= 0.0f) {
+        fprintf(stderr,"error: iirfilt_%s_create_pll(), loop gain must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute loop filter coefficients
+    float bf[3];
+    float af[3];
+    iirdes_pll_active_lag(_w, _zeta, _K, bf, af);
+
+    // copy to type-specific array
+    TC b[3];
+    TC a[3];
+    b[0] = bf[0];   b[1] = bf[1];   b[2] = bf[2];
+    a[0] = af[0];   a[1] = af[1];   a[2] = af[2];
+
+    // create and return filter object
+    return IIRFILT(_create_sos)(b,a,1);
+}
+
+// destroy iirfilt object
+void IIRFILT(_destroy)(IIRFILT() _q)
+{
+#if LIQUID_IIRFILT_USE_DOTPROD
+    DOTPROD(_destroy)(_q->dpa);
+    DOTPROD(_destroy)(_q->dpb);
+#endif
+    free(_q->b);
+    free(_q->a);
+    // if filter is comprised of cascaded second-order sections,
+    // delete sub-filters separately
+    if (_q->type == IIRFILT_TYPE_SOS) {
+        unsigned int i;
+        for (i=0; i<_q->nsos; i++)
+            IIRFILTSOS(_destroy)(_q->qsos[i]);
+        free(_q->qsos);
+    } else {
+        free(_q->v);
+    }
+
+    free(_q);
+}
+
+// print iirfilt object internals
+void IIRFILT(_print)(IIRFILT() _q)
+{
+    printf("iir filter [%s]:\n", _q->type == IIRFILT_TYPE_NORM ? "normal" : "sos");
+    unsigned int i;
+
+    if (_q->type == IIRFILT_TYPE_SOS) {
+        for (i=0; i<_q->nsos; i++)
+            IIRFILTSOS(_print)(_q->qsos[i]);
+    } else {
+
+        printf("  b :");
+        for (i=0; i<_q->nb; i++)
+            PRINTVAL_TC(_q->b[i],%12.8f);
+        printf("\n");
+
+        printf("  a :");
+        for (i=0; i<_q->na; i++)
+            PRINTVAL_TC(_q->a[i],%12.8f);
+        printf("\n");
+
+#if 0
+        printf("  v :");
+        for (i=0; i<_q->n; i++)
+            PRINTVAL(_q->v[i]);
+        printf("\n");
+#endif
+    }
+}
+
+// clear/reset iirfilt object internals
+void IIRFILT(_reset)(IIRFILT() _q)
+{
+    unsigned int i;
+
+    if (_q->type == IIRFILT_TYPE_SOS) {
+        // clear second-order sections
+        for (i=0; i<_q->nsos; i++) {
+            IIRFILTSOS(_reset)(_q->qsos[i]);
+        }
+    } else {
+        // set internal buffer to zero
+        for (i=0; i<_q->n; i++)
+            _q->v[i] = 0;
+    }
+}
+
+// execute normal iir filter using traditional numerator/denominator
+// form (not second-order sections form)
+//  _q      :   iirfilt object
+//  _x      :   input sample
+//  _y      :   output sample
+void IIRFILT(_execute_norm)(IIRFILT() _q,
+                            TI _x,
+                            TO *_y)
+{
+    unsigned int i;
+
+    // advance buffer
+    for (i=_q->n-1; i>0; i--)
+        _q->v[i] = _q->v[i-1];
+
+#if LIQUID_IIRFILT_USE_DOTPROD
+    // compute new v
+    TI v0;
+    DOTPROD(_execute)(_q->dpa, _q->v+1, & v0);
+    v0 = _x - v0;
+    _q->v[0] = v0;
+
+    // compute new y
+    DOTPROD(_execute)(_q->dpb, _q->v, _y);
+#else
+    // compute new v
+    TI v0 = _x;
+    for (i=1; i<_q->na; i++)
+        v0 -= _q->a[i] * _q->v[i];
+    _q->v[0] = v0;
+
+    // compute new y
+    TO y0 = 0;
+    for (i=0; i<_q->nb; i++)
+        y0 += _q->b[i] * _q->v[i];
+
+    // set return value
+    *_y = y0;
+#endif
+}
+
+// execute iir filter using second-order sections form
+//  _q      :   iirfilt object
+//  _x      :   input sample
+//  _y      :   output sample
+void IIRFILT(_execute_sos)(IIRFILT() _q,
+                           TI        _x,
+                           TO *      _y)
+{
+    TI t0 = _x;     // intermediate input
+    TO t1 = 0.;     // intermediate output
+    unsigned int i;
+    for (i=0; i<_q->nsos; i++) {
+        // run each filter separately
+        IIRFILTSOS(_execute)(_q->qsos[i], t0, &t1);
+
+        // output for filter n becomes input to filter n+1
+        t0 = t1;
+    }
+    *_y = t1;
+}
+
+// execute iir filter, switching to type-specific function
+//  _q      :   iirfilt object
+//  _x      :   input sample
+//  _y      :   output sample
+void IIRFILT(_execute)(IIRFILT() _q,
+                       TI        _x,
+                       TO *      _y)
+{
+    if (_q->type == IIRFILT_TYPE_NORM)
+        IIRFILT(_execute_norm)(_q,_x,_y);
+    else
+        IIRFILT(_execute_sos)(_q,_x,_y);
+}
+
+// execute the filter on a block of input samples; the
+// input and output buffers may be the same
+//  _q      : filter object
+//  _x      : pointer to input array [size: _n x 1]
+//  _n      : number of input, output samples
+//  _y      : pointer to output array [size: _n x 1]
+void IIRFILT(_execute_block)(IIRFILT()    _q,
+                             TI *         _x,
+                             unsigned int _n,
+                             TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        // compute output sample
+        IIRFILT(_execute)(_q, _x[i], &_y[i]);
+}
+
+
+// get filter length (order + 1)
+unsigned int IIRFILT(_get_length)(IIRFILT() _q)
+{
+    return _q->n;
+}
+
+// compute complex frequency response
+//  _q      :   filter object
+//  _fc     :   frequency
+//  _H      :   output frequency response
+void IIRFILT(_freqresponse)(IIRFILT()       _q,
+                            float           _fc,
+                            float complex * _H)
+{
+    unsigned int i;
+    float complex H = 0.0f;
+
+    if (_q->type == IIRFILT_TYPE_NORM) {
+        // 
+        float complex Ha = 0.0f;
+        float complex Hb = 0.0f;
+
+        for (i=0; i<_q->nb; i++)
+            Hb += _q->b[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+
+        for (i=0; i<_q->na; i++)
+            Ha += _q->a[i] * cexpf(_Complex_I*2*M_PI*_fc*i);
+
+        // TODO : check to see if we need to take conjugate
+        H = Hb / Ha;
+    } else {
+
+        H = 1.0f;
+
+        // compute 3-point DFT for each second-order section
+        for (i=0; i<_q->nsos; i++) {
+            float complex Hb =  _q->b[3*i+0] * cexpf(_Complex_I*2*M_PI*_fc*0) +
+                                _q->b[3*i+1] * cexpf(_Complex_I*2*M_PI*_fc*1) +
+                                _q->b[3*i+2] * cexpf(_Complex_I*2*M_PI*_fc*2);
+
+            float complex Ha =  _q->a[3*i+0] * cexpf(_Complex_I*2*M_PI*_fc*0) +
+                                _q->a[3*i+1] * cexpf(_Complex_I*2*M_PI*_fc*1) +
+                                _q->a[3*i+2] * cexpf(_Complex_I*2*M_PI*_fc*2);
+
+            // TODO : check to see if we need to take conjugate
+            H *= Hb / Ha;
+        }
+    }
+
+    // set return value
+    *_H = H;
+}
+
+// compute group delay in samples
+//  _q      :   filter object
+//  _fc     :   frequency
+float IIRFILT(_groupdelay)(IIRFILT() _q,
+                           float     _fc)
+{
+    float groupdelay = 0;
+    unsigned int i;
+
+    if (_q->type == IIRFILT_TYPE_NORM) {
+        // compute group delay from regular transfer function form
+
+        // copy coefficients
+        float b[_q->nb];
+        float a[_q->na];
+        for (i=0; i<_q->nb; i++) b[i] = crealf(_q->b[i]);
+        for (i=0; i<_q->na; i++) a[i] = crealf(_q->a[i]);
+        groupdelay = iir_group_delay(b, _q->nb, a, _q->na, _fc);
+    } else {
+        // accumulate group delay from second-order sections
+        for (i=0; i<_q->nsos; i++)
+            groupdelay += IIRFILTSOS(_groupdelay)(_q->qsos[i], _fc) - 2;
+    }
+
+    return groupdelay;
+}
+
diff --git a/src/filter/src/iirfiltsos.c b/src/filter/src/iirfiltsos.c
new file mode 100644
index 0000000..25cd99a
--- /dev/null
+++ b/src/filter/src/iirfiltsos.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Infinite impulse response filter (second-order section)
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+// defined:
+//  IIRFILTSOS()    name-mangling macro
+//  TO              output type
+//  TC              coefficients type
+//  TI              input type
+//  PRINTVAL()      print macro(s)
+
+// create iirfiltsos object
+IIRFILTSOS() IIRFILTSOS(_create)(TC * _b,
+                                 TC * _a)
+{
+    // create filter object
+    IIRFILTSOS() q = (IIRFILTSOS()) malloc(sizeof(struct IIRFILTSOS(_s)));
+
+    // set the internal coefficients
+    IIRFILTSOS(_set_coefficients)(q, _b, _a);
+
+    // clear filter state
+    IIRFILTSOS(_reset)(q);
+
+    return q;
+}
+
+// set internal filter coefficients
+// NOTE : this does not reset the internal state of the filter and
+//        could result in instability if executed on existing filter!
+// explicitly set 2nd-order IIR filter coefficients
+//  _q      : iirfiltsos object
+//  _b      : feed-forward coefficients [size: _3 x 1]
+//  _a      : feed-back coefficients    [size: _3 x 1]
+void IIRFILTSOS(_set_coefficients)(IIRFILTSOS() _q,
+                                   TC *         _b,
+                                   TC *         _a)
+{
+    // retain a0 coefficient for normalization
+    TC a0 = _a[0];
+
+    // copy feed-forward coefficients (numerator)
+    _q->b[0] = _b[0] / a0;
+    _q->b[1] = _b[1] / a0;
+    _q->b[2] = _b[2] / a0;
+
+    // copy feed-back coefficients (denominator)
+    _q->a[0] = _a[0] / a0;
+    _q->a[1] = _a[1] / a0;
+    _q->a[2] = _a[2] / a0;
+}
+
+// destroy iirfiltsos object, freeing all internal memory
+void IIRFILTSOS(_destroy)(IIRFILTSOS() _q)
+{
+    free(_q);
+}
+
+// print iirfiltsos object properties to stdout
+void IIRFILTSOS(_print)(IIRFILTSOS() _q)
+{
+    printf("iir filter | sos:\n");
+
+    printf("  b : ");
+    PRINTVAL_TC(_q->b[0],%12.8f); printf(",");
+    PRINTVAL_TC(_q->b[1],%12.8f); printf(",");
+    PRINTVAL_TC(_q->b[2],%12.8f); printf("\n");
+
+    printf("  a : ");
+    PRINTVAL_TC(_q->a[0],%12.8f); printf(",");
+    PRINTVAL_TC(_q->a[1],%12.8f); printf(",");
+    PRINTVAL_TC(_q->a[2],%12.8f); printf("\n");
+}
+
+// clear/reset iirfiltsos object internals
+void IIRFILTSOS(_reset)(IIRFILTSOS() _q)
+{
+    // set to zero
+    _q->v[0] = 0;
+    _q->v[1] = 0;
+    _q->v[2] = 0;
+
+    _q->x[0] = 0;
+    _q->x[1] = 0;
+    _q->x[2] = 0;
+
+    _q->y[0] = 0;
+    _q->y[1] = 0;
+    _q->y[2] = 0;
+
+}
+
+// compute filter output
+//  _q      : iirfiltsos object
+//  _x      : input sample
+//  _y      : output sample pointer
+void IIRFILTSOS(_execute)(IIRFILTSOS() _q,
+                          TI           _x,
+                          TO *         _y)
+{
+    // execute type-specific code
+    IIRFILTSOS(_execute_df2)(_q,_x,_y);
+}
+
+
+// compute filter output, direct form I method
+//  _q      : iirfiltsos object
+//  _x      : input sample
+//  _y      : output sample pointer
+void IIRFILTSOS(_execute_df1)(IIRFILTSOS() _q,
+                              TI           _x,
+                              TO *         _y)
+{
+    // advance buffer x
+    _q->x[2] = _q->x[1];
+    _q->x[1] = _q->x[0];
+    _q->x[0] = _x;
+
+    // advance buffer y
+    _q->y[2] = _q->y[1];
+    _q->y[1] = _q->y[0];
+
+    // compute new v
+    TI v = _q->x[0] * _q->b[0] +
+           _q->x[1] * _q->b[1] +
+           _q->x[2] * _q->b[2];
+
+    // compute new y[0]
+    _q->y[0] = v -
+               _q->y[1] * _q->a[1] -
+               _q->y[2] * _q->a[2];
+
+    // set output
+    *_y = _q->y[0];
+}
+
+// compute filter output, direct form I method
+//  _q      : iirfiltsos object
+//  _x      : input sample
+//  _y      : output sample pointer
+void IIRFILTSOS(_execute_df2)(IIRFILTSOS() _q,
+                              TI           _x,
+                              TO *         _y)
+{
+    // advance buffer
+    _q->v[2] = _q->v[1];
+    _q->v[1] = _q->v[0];
+
+    // compute new v[0]
+    _q->v[0] = _x - 
+               _q->a[1]*_q->v[1] -
+               _q->a[2]*_q->v[2];
+
+    // compute output _y
+    *_y = _q->b[0]*_q->v[0] +
+          _q->b[1]*_q->v[1] +
+          _q->b[2]*_q->v[2];
+}
+
+// compute group delay in samples
+//  _q      :   filter object
+//  _fc     :   frequency
+float IIRFILTSOS(_groupdelay)(IIRFILTSOS() _q,
+                              float        _fc)
+{
+    // copy coefficients
+    float b[3];
+    float a[3];
+    unsigned int i;
+    for (i=0; i<3; i++) {
+        b[i] = crealf(_q->b[i]);
+        a[i] = crealf(_q->a[i]);
+    }
+    return iir_group_delay(b, 3, a, 3, _fc) + 2.0;
+}
+
diff --git a/src/filter/src/iirinterp.c b/src/filter/src/iirinterp.c
new file mode 100644
index 0000000..80da94e
--- /dev/null
+++ b/src/filter/src/iirinterp.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// infinite impulse response interpolator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct IIRINTERP(_s) {
+    unsigned int M;     // interpolation factor
+
+    // TODO: use IIR polyphase filterbank
+    IIRFILT() iirfilt;  // filter object
+
+};
+
+// create interpolator from external coefficients
+//  _M      : interpolation factor
+//  _b      : feed-back coefficients [size: _nb x 1]
+//  _nb     : feed-back coefficients length
+//  _a      : feed-forward coefficients [size: _na x 1]
+//  _na     : feed-forward coefficients length
+IIRINTERP() IIRINTERP(_create)(unsigned int _M,
+                               TC *         _b,
+                               unsigned int _nb,
+                               TC *         _a,
+                               unsigned int _na)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: iirinterp_%s_create(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and set internal parameters
+    IIRINTERP() q = (IIRINTERP()) malloc(sizeof(struct IIRINTERP(_s)));
+    q->M = _M;
+
+    // create filter
+    q->iirfilt = IIRFILT(_create)(_b, _nb, _a, _na);
+
+    // return interpolator object
+    return q;
+}
+
+// create decimator with default Butterworth prototype
+//  _M      : decimation factor
+//  _order  : filter order
+IIRINTERP() IIRINTERP(_create_default)(unsigned int _M,
+                                       unsigned int _order)
+{
+    return IIRINTERP(_create_prototype)(_M,
+                                        LIQUID_IIRDES_BUTTER,
+                                        LIQUID_IIRDES_LOWPASS,
+                                        LIQUID_IIRDES_SOS,
+                                        _order,
+                                        0.5f / (float)_M,    // fc
+                                        0.0f,                // f0
+                                        0.1f,                // pass-band ripple,
+                                        60.0f);              // stop-band attenuation
+}
+
+// create interpolator from prototype
+//  _M      :   interpolation factor
+IIRINTERP() IIRINTERP(_create_prototype)(unsigned int _M,
+                                         liquid_iirdes_filtertype _ftype,
+                                         liquid_iirdes_bandtype   _btype,
+                                         liquid_iirdes_format     _format,
+                                         unsigned int _order,
+                                         float _fc,
+                                         float _f0,
+                                         float _Ap,
+                                         float _As)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: iirinterp_%s_create_prototype(), interp factor must be greater than 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and set internal parameters
+    IIRINTERP() q = (IIRINTERP()) malloc(sizeof(struct IIRINTERP(_s)));
+    q->M = _M;
+
+    // create filter
+    q->iirfilt = IIRFILT(_create_prototype)(_ftype, _btype, _format, _order, _fc, _f0, _Ap, _As);
+
+    // return interpolator object
+    return q;
+}
+
+// destroy interpolator object
+void IIRINTERP(_destroy)(IIRINTERP() _q)
+{
+    IIRFILT(_destroy)(_q->iirfilt);
+    free(_q);
+}
+
+// print interpolator state
+void IIRINTERP(_print)(IIRINTERP() _q)
+{
+    printf("interp():\n");
+    printf("    M       :   %u\n", _q->M);
+    IIRFILT(_print)(_q->iirfilt);
+}
+
+// clear internal state
+void IIRINTERP(_reset)(IIRINTERP() _q)
+{
+    IIRFILT(_reset)(_q->iirfilt);
+}
+
+// execute interpolator
+//  _q      :   interpolator object
+//  _x      :   input sample
+//  _y      :   output array [size: 1 x _M]
+void IIRINTERP(_execute)(IIRINTERP() _q,
+                         TI          _x,
+                         TO *        _y)
+{
+    // TODO: use iirpfb
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        IIRFILT(_execute)(_q->iirfilt, i==0 ? _x : 0.0f, &_y[i]);
+}
+
+// execute interpolation on block of input samples
+//  _q      : iirinterp object
+//  _x      : input array [size: _n x 1]
+//  _n      : size of input array
+//  _y      : output sample array [size: _M*_n x 1]
+void IIRINTERP(_execute_block)(IIRINTERP()  _q,
+                               TI *         _x,
+                               unsigned int _n,
+                               TO *         _y)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // execute one input at a time with an output stride _M
+        IIRINTERP(_execute)(_q, _x[i], &_y[i*_q->M]);
+    }
+}
+
+// get system group delay at frequency _fc
+//  _q      :   interpolator object
+//  _f      :   frequency
+float IIRINTERP(_groupdelay)(IIRINTERP() _q,
+                             float       _fc)
+{
+    return IIRFILT(_groupdelay)(_q->iirfilt, _fc) / (float) (_q->M);
+}
+
diff --git a/src/filter/src/lpc.c b/src/filter/src/lpc.c
new file mode 100644
index 0000000..8547940
--- /dev/null
+++ b/src/filter/src/lpc.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// lpc.c
+//
+// linear prediction coefficients
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_LEVINSON_MAXORDER (256)
+
+// compute the linear prediction coefficients for an input signal _x
+//  _x      :   input signal [size: _n x 1]
+//  _p      :   prediction filter order
+//  _a      :   prediction filter [size: _p+1 x 1]
+//  _e      :   prediction error variance [size: _p+1 x 1]
+void liquid_lpc(float * _x,
+                unsigned int _n,
+                unsigned int _p,
+                float * _a,
+                float * _g)
+{
+    // validate input
+    if (_p > _n) {
+        fprintf(stderr,"error: liquid_lpc(), prediction filter length cannot exceed input signal length\n");
+        exit(1);
+    }
+
+    // compute auto-correlation with lags
+    float r[_p+1];    // auto-correlation array
+
+    unsigned int i;
+    for (i=0; i<_p+1; i++) {
+        unsigned int lag = i;
+        unsigned int j;
+        r[i] = 0.0f;
+        for (j=lag; j<_n; j++)
+            r[i] += _x[j] * _x[j-lag];
+        printf("r[%3u] = %12.8f\n", i, r[i]);
+    }
+
+    // solve the Toeplitz inversion using Levinson-Durbin recursion
+    liquid_levinson(r,_p,_a,_g);
+}
+
+
+// solve the Yule-Walker equations using Levinson-Durbin recursion
+// for _symmetric_ autocorrelation
+//  _r      :   autocorrelation array [size: _p+1 x 1]
+//  _p      :   filter order
+//  _a      :   output coefficients [size: _p+1 x 1]
+//  _e      :   error variance [size: _p+1 x 1]
+//
+// NOTES:
+//  By definition _a[0] = 1.0
+void liquid_levinson(float * _r,
+                     unsigned int _p,
+                     float * _a,
+                     float * _e)
+{
+    // check allocation length
+    if (_p > LIQUID_LEVINSON_MAXORDER) {
+        fprintf(stderr,"error: liquid_levinson(), filter order (%u) exceeds maximum (%u)\n",
+            _p, LIQUID_LEVINSON_MAXORDER);
+        exit(1);
+    }
+
+    // allocate arrays
+    float a0[_p+1]; // temporary coefficients array, index [n]
+    float a1[_p+1]; // temporary coefficients array, index [n-1]
+    float e[_p+1];  // prediction error
+    float k[_p+1];  // reflection coefficients
+
+    // initialize
+    k[0] = 1.0f;
+    e[0] = _r[0];
+
+    unsigned int i;
+    for (i=0; i<_p+1; i++) {
+        a0[i] = (i==0) ? 1.0f : 0.0f;
+        a1[i] = (i==0) ? 1.0f : 0.0f;
+    }
+
+    unsigned int n;
+    for (n=1; n<_p+1; n++) {
+
+        float q = 0.0f;
+        for (i=0; i<n; i++)
+            q += a0[i]*_r[n-i];
+
+        k[n] = -q/e[n-1];
+        e[n] = e[n-1]*(1.0f - k[n]*k[n]);
+
+        // compute new coefficients
+        for (i=0; i<n; i++)
+            a1[i] = a0[i] + k[n]*a0[n-i];
+
+        a1[n] = k[n];
+#if 0
+        printf("iteration [n=%u]\n", n);
+        for (i=0; i<n; i++)
+            printf("  ** i = %3u, n-i = %3u\n", i, n-i);
+
+        // print results
+        printf("  k   = %12.8f\n", k[n]);
+        printf("  q   = %12.8f\n", q);
+        printf("  e   = %12.8f\n", e[n]);
+
+        printf("  a_%u = {", n-1);
+        for (i=0; i<n; i++)
+            printf("%6.3f, ", a0[i]);
+        printf("}\n");
+
+        printf("  a_%u = {", n);
+        for (i=0; i<n+1; i++)
+            printf("%6.3f, ", a1[i]);
+        printf("}\n");
+#endif
+
+        // copy temporary vector a1 to a0
+        memmove(a0, a1, (_p+1)*sizeof(float));
+
+    }
+
+    // copy results to output
+    memmove(_a, a1, (_p+1)*sizeof(float));
+    memmove(_e,  e, (_p+1)*sizeof(float));
+}
+
diff --git a/src/filter/src/msresamp.c b/src/filter/src/msresamp.c
new file mode 100644
index 0000000..4b88d4e
--- /dev/null
+++ b/src/filter/src/msresamp.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// multi-stage arbitrary resampler
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// forward declaration of internal methods
+//
+
+// execute multi-stage interpolation
+void MSRESAMP(_interp_execute)(MSRESAMP()    _q,
+                              TI *           _x,
+                              unsigned int   _nx,
+                              TO *           _y,
+                              unsigned int * _num_written);
+
+// execute multi-stage decimation
+void MSRESAMP(_decim_execute)(MSRESAMP()     _q,
+                              TI *           _x,
+                              unsigned int   _nx,
+                              TO *           _y,
+                              unsigned int * _num_written);
+
+
+struct MSRESAMP(_s) {
+    // user-defined parameters
+    float rate;                         // re-sampling rate
+    float As;                           // filter stop-band attenuation [dB]
+
+    // type: interpolator or decimator
+    int type;                           // run half-band resamplers as interp or decim
+
+    // half-band resampler parameters
+    unsigned int num_halfband_stages;   // number of halfband stages
+    MSRESAMP2() halfband_resamp;        // multi-stage halfband resampler
+    float rate_halfband;                // halfband rate
+
+    // arbitrary resampler parameters
+    RESAMP() arbitrary_resamp;          // arbitrary resampling object
+    float rate_arbitrary;               // clean-up resampling rate, in (0.5, 2.0)
+
+    // internal buffers (ping-pong)
+    unsigned int buffer_len;            // length of each buffer
+    T * buffer;                         // buffer[0]
+    unsigned int buffer_index;          // index of buffer
+};
+
+// create msresamp object
+//  TODO: add signal bandwidth parameter?
+//  TODO: add center frequency parameter; facilitates DDS object synthesis
+//  _r              :   resampling rate [output/input]
+//  _As             :   stop-band attenuation
+MSRESAMP() MSRESAMP(_create)(float _r,
+                             float _As)
+{
+    // validate input
+    if (_r <= 0.0f) {
+        fprintf(stderr,"error: msresamp_%s_create(), resampling rate must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create object
+    MSRESAMP() q = (MSRESAMP()) malloc(sizeof(struct MSRESAMP(_s)));
+
+    // set internal properties
+    q->rate = _r;       // composite rate
+    q->As   = _As;      // stop-band suppression
+
+    // decimation or interpolation?
+    q->type = (q->rate > 1.0f) ? LIQUID_RESAMP_INTERP : LIQUID_RESAMP_DECIM;
+
+    // compute derived values
+    q->rate_arbitrary = q->rate;
+    q->rate_halfband  = 1.0f;
+    q->num_halfband_stages = 0;
+    switch(q->type) {
+        case LIQUID_RESAMP_INTERP:
+            while (q->rate_arbitrary > 2.0f) {
+                q->num_halfband_stages++;
+                q->rate_halfband  *= 2.0f;
+                q->rate_arbitrary *= 0.5f;
+            }
+            break;
+        case LIQUID_RESAMP_DECIM:
+            while (q->rate_arbitrary < 0.5f) {
+                q->num_halfband_stages++;
+                q->rate_halfband  *= 0.5f;
+                q->rate_arbitrary *= 2.0f;
+            }
+            break;
+        default:;
+    }
+
+    // allocate memory for buffer
+    q->buffer_len = 4 + (1 << q->num_halfband_stages);
+    q->buffer = (T*) malloc( q->buffer_len*sizeof(T) );
+
+    // create single multi-stage half-band resampler object
+    // TODO: compute appropriate cut-off frequency
+    q->halfband_resamp = MSRESAMP2(_create)(q->type,
+                                            q->num_halfband_stages,
+                                            0.4f, 0.0f, q->As);
+
+    // create arbitrary resampler object
+    // TODO: compute appropriate parameters
+    q->arbitrary_resamp = RESAMP(_create)(q->rate_arbitrary, 7, 0.4f, q->As, 64);
+
+    // reset object
+    MSRESAMP(_reset)(q);
+
+    // return main object
+    return q;
+}
+
+// destroy msresamp object, freeing all internally-allocated memory
+void MSRESAMP(_destroy)(MSRESAMP() _q)
+{
+    // free buffer
+    free(_q->buffer);
+
+    // destroy arbitrary resampler
+    RESAMP(_destroy)(_q->arbitrary_resamp);
+
+    // destroy multi-stage half-band resampler object
+    MSRESAMP2(_destroy)(_q->halfband_resamp);
+
+    // destroy main object
+    free(_q);
+}
+
+// print msresamp object internals
+void MSRESAMP(_print)(MSRESAMP() _q)
+{
+    printf("multi-stage resampler\n");
+    printf("    composite rate      : %12.10f\n", _q->rate);
+    printf("    type                : %s\n", _q->type == LIQUID_RESAMP_INTERP ? "interp" : "decim");
+    printf("    num halfband stages : %u\n", _q->num_halfband_stages);
+    printf("    halfband rate       : %s%u\n", _q->type == LIQUID_RESAMP_INTERP ? "" : "1/",
+                                                 1<<_q->num_halfband_stages);
+    printf("    arbitrary rate      : %12.10f\n", _q->rate_arbitrary);
+    printf("    stages:\n");
+    
+    //float delay_halfband  = MSRESAMP2(_get_delay)(_q->halfband_resamp);
+    //float delay_arbitrary = RESAMP(_get_delay)(_q->arbitrary_resamp);
+
+    // print each stage
+    unsigned int stage=0;
+    float r = 1.0f; // accumulated rate
+    
+    // arbitrary (interpolator)
+    if (_q->type == LIQUID_RESAMP_INTERP) {
+        float rate = _q->rate_arbitrary;
+        r *= rate;  // update rate
+        printf("    [%2u, r=%11.7f] : arbitrary, r=%12.8f\n", stage, r, rate);
+        stage++;
+    }
+
+    // print stages of half-band resampling
+    unsigned int i;
+    for (i=0; i<_q->num_halfband_stages; i++) {
+        float rate = _q->type == LIQUID_RESAMP_INTERP ? 2.0f : 0.5f;
+        r *= rate;
+        printf("    [%2u, r=%11.7f] : halfband,  r=%5.1f\n", stage, r, rate);
+        stage++;
+    }
+
+    // arbitrary (decimator)
+    if (_q->type == LIQUID_RESAMP_DECIM) {
+        float rate = _q->rate_arbitrary;
+        r *= rate;
+        printf("    [%2u, r=%11.7f] : arbitrary, r=%12.8f\n", stage, r, rate);
+        stage++;
+    }
+}
+
+// reset msresamp object internals, clear filters and nco phase
+void MSRESAMP(_reset)(MSRESAMP() _q)
+{
+    // reset multi-stage half-band resampler object
+    MSRESAMP2(_reset)(_q->halfband_resamp);
+
+    // reset arbitrary resampler
+    RESAMP(_reset)(_q->arbitrary_resamp);
+
+    // reset buffer write pointer
+    _q->buffer_index = 0;
+
+    // TODO: clear internal buffers?
+}
+
+// get filter delay (output samples)
+float MSRESAMP(_get_delay)(MSRESAMP() _q)
+{
+    float delay_halfband = MSRESAMP2(_get_delay)(_q->halfband_resamp);
+    float delay_arbitrary = RESAMP(_get_delay)(_q->arbitrary_resamp);
+
+#if 0
+    printf("halfband    : rate=%12.8f, delay=%12.8f\n", (float)(1<<_q->num_halfband_stages), delay_halfband);
+    printf("arbitrary   : rate=%12.8f, delay=%12.8f\n", _q->rate_arbitrary, delay_arbitrary);
+#endif
+
+    // compute delay based on interpolation or decimation type
+    if (_q->num_halfband_stages == 0) {
+        // no half-band stages; just arbitrary resampler delay
+        return delay_arbitrary;
+    } else if (_q->type == LIQUID_RESAMP_INTERP) {
+        // interpolation
+        return delay_halfband / _q->rate_arbitrary + delay_arbitrary;
+    } else {
+        // decimation
+        unsigned int M = 1 << _q->num_halfband_stages;
+        return delay_halfband + M*delay_arbitrary;
+    }
+
+    return 0.0f;
+}
+
+// execute multi-stage resampler
+//  _q      :   msresamp object
+//  _x      :   input sample array
+//  _y      :   output sample array
+//  _ny     :   number of samples written to _y
+void MSRESAMP(_execute)(MSRESAMP()     _q,
+                        TI *           _x,
+                        unsigned int   _nx,
+                        TO *           _y,
+                        unsigned int * _ny)
+{
+    switch(_q->type) {
+    case LIQUID_RESAMP_INTERP:
+        MSRESAMP(_interp_execute)(_q, _x, _nx, _y, _ny);
+        break;
+    case LIQUID_RESAMP_DECIM:
+        MSRESAMP(_decim_execute)(_q, _x, _nx, _y, _ny);
+        break;
+    default:;
+    }
+}
+
+// 
+// internal methods
+//
+
+// execute multi-stage resampler as interpolator
+//  _q      :   msresamp object
+//  _x      :   input sample array
+//  _y      :   output sample array
+//  _nw     :   number of samples written to _y
+void MSRESAMP(_interp_execute)(MSRESAMP()     _q,
+                               TI *           _x,
+                               unsigned int   _nx,
+                               TO *           _y,
+                               unsigned int * _ny)
+{
+    unsigned int i;
+    unsigned int nw;
+    unsigned int ny = 0;
+
+    // operate one sample at a time so that we don't overflow the internal
+    // buffer
+    for (i=0; i<_nx; i++) {
+        // run arbitrary resampler
+        RESAMP(_execute)(_q->arbitrary_resamp, _x[i], _q->buffer, &nw);
+
+        // run multi-stage half-band resampler on each output sample
+        unsigned int k;
+        for (k=0; k<nw; k++) {
+            MSRESAMP2(_execute)(_q->halfband_resamp, &_q->buffer[k], &_y[ny]);
+
+            // increase output counter by halfband interpolation rate
+            ny += 1 << _q->num_halfband_stages;
+        }
+    }
+
+    // set return value for number of samples written
+    *_ny = ny;
+}
+
+// execute multi-stage resampler as decimator
+//  _q      :   msresamp object
+//  _x      :   input sample array
+//  _y      :   output sample array
+//  _nw     :   number of samples written to _y
+void MSRESAMP(_decim_execute)(MSRESAMP()     _q,
+                              TI *           _x,
+                              unsigned int   _nx,
+                              TO *           _y,
+                              unsigned int * _ny)
+{
+    unsigned int i;
+    unsigned int M = 1 << _q->num_halfband_stages;
+    unsigned int nw;        // number of samples written for arbitrary resamp
+    unsigned int ny = 0;    // running counter of output samples
+    TO halfband_output;     // single half-band decimator output sample
+
+    // write samples to buffer until it contains 2^num_halfband_stages
+    // TODO: write block of samples
+    for (i=0; i<_nx; i++) {
+        // push sample into buffer
+        _q->buffer[_q->buffer_index++] = _x[i];
+
+        // check if buffer has 'M' elements
+        if (_q->buffer_index == M) {
+            // run half-band decimation, producing a single output
+            MSRESAMP2(_execute)(_q->halfband_resamp, _q->buffer, &halfband_output);
+
+            // run resulting sample through arbitrary resampler
+            RESAMP(_execute)(_q->arbitrary_resamp, halfband_output, &_y[ny], &nw);
+
+            // increment output counter
+            ny += nw;
+
+            // reset buffer index
+            _q->buffer_index = 0;
+        }
+    }
+
+    // set return value for number of samples written
+    *_ny = ny;
+}
+
diff --git a/src/filter/src/msresamp2.c b/src/filter/src/msresamp2.c
new file mode 100644
index 0000000..a403a5c
--- /dev/null
+++ b/src/filter/src/msresamp2.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// multi-stage half-band resampler
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// forward declaration of internal methods
+//
+
+struct MSRESAMP2(_s) {
+    // user-defined parameters
+    liquid_resamp_type type;    // resampler type (e.g. LIQUID_RESAMP_INTERP)
+    unsigned int num_stages;    // number of half-band stages
+    float        fc;            // initial cut-off frequency
+    float        f0;            // initial center frequency
+    float        As;            // stop-band attenuation
+
+    // derived values
+    unsigned int M;             // integer resampling rate: 2^num_stages
+
+    // half-band resamplers
+    float * fc_stage;           // cut-off frequency for each stage
+    float * f0_stage;           // center frequency for each stage
+    float * As_stage;           // stop-band attenuation for each stage
+    unsigned int * m_stage;     // filter semi-length for each stage
+    RESAMP2() * resamp2;        // array of half-band resamplers
+    T * buffer0;                // buffer[0]
+    T * buffer1;                // buffer[1]
+    unsigned int buffer_index;  // index of buffer
+    float zeta;                 // scaling factor
+};
+
+// execute multi-stage resampler as interpolator
+//  _q      : msresamp object
+//  _x      : input sample
+//  _y      : output sample array  [size:2^_num_stages x 1]
+void MSRESAMP2(_interp_execute)(MSRESAMP2() _q,
+                                TI          _x,
+                                TO *        _y);
+
+// execute multi-stage resampler as decimator
+//  _q      : msresamp object
+//  _x      : input sample array  [size: 2^_num_stages x 1]
+//  _y      : output sample pointer
+void MSRESAMP2(_decim_execute)(MSRESAMP2() _q,
+                               TI *        _x,
+                               TO *        _y);
+
+// create multi-stage half-band resampler
+//  _type       : resampler type (e.g. LIQUID_RESAMP_DECIM)
+//  _num_stages : number of resampling stages
+//  _fc         : filter cut-off frequency 0 < _fc < 0.5
+//  _f0         : filter center frequency
+//  _As         : stop-band attenuation [dB]
+MSRESAMP2() MSRESAMP2(_create)(int          _type,
+                               unsigned int _num_stages,
+                               float        _fc,
+                               float        _f0,
+                               float        _As)
+{
+    // validate input
+    if (_num_stages > 16) {
+        fprintf(stderr,"error: msresamp2_%s_create(), number of stages should not exceed 16\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // ensure cut-off frequency is valid
+    if ( _fc <= 0.0f || _fc >= 0.5f ) {
+        fprintf(stderr,"error: msresamp2_%s_create(), cut-off frequency must be in (0,0.5)\n", EXTENSION_FULL);
+        exit(1);
+    } else if ( _fc > 0.45f ) {
+        fprintf(stderr,"warning: msresamp2_%s_create(), cut-off frequency greater than 0.45\n", EXTENSION_FULL);
+        fprintf(stderr,"    >> truncating to 0.45\n");
+        _fc = 0.45f;
+    }
+
+    // check center frequency
+    if ( _f0 != 0. ) {
+        fprintf(stderr,"warning: msresamp2_%s_create(), non-zero center frequency not yet supported\n", EXTENSION_FULL);
+        _f0 = 0.;
+    }
+
+    unsigned int i;
+
+    // create object
+    MSRESAMP2() q = (MSRESAMP2()) malloc(sizeof(struct MSRESAMP2(_s)));
+
+    // set internal properties
+    q->type       = _type == LIQUID_RESAMP_INTERP ? LIQUID_RESAMP_INTERP : LIQUID_RESAMP_DECIM;
+    q->num_stages = _num_stages;
+    q->fc         = _fc;
+    q->f0         = _f0;
+    q->As         = _As;
+
+    // derived values
+    q->M    = 1 << q->num_stages;
+    q->zeta = 1.0f / (float)(q->M);
+
+    // allocate memory for buffers
+    q->buffer0 = (T*) malloc( q->M * sizeof(T) );
+    q->buffer1 = (T*) malloc( q->M * sizeof(T) );
+
+    // allocate arrays for half-band resampler parameters
+    q->fc_stage = (float*)        malloc(q->num_stages*sizeof(float)       );
+    q->f0_stage = (float*)        malloc(q->num_stages*sizeof(float)       );
+    q->As_stage = (float*)        malloc(q->num_stages*sizeof(float)       );
+    q->m_stage  = (unsigned int*) malloc(q->num_stages*sizeof(unsigned int));
+
+    // determine half-band resampler parameters
+    float fc = q->fc;
+    float f0 = q->f0;
+    for (i=0; i<q->num_stages; i++) {
+        // compute parameters based on filter requirements;
+        f0 = 0.5f*f0;   // update center frequency
+        fc = 0.5f*fc;   // update cutoff frequency
+
+        // estiamte required filter length
+        float ft = (0.5f - fc)/2.0f;
+        unsigned int h_len = estimate_req_filter_len(ft, q->As);
+        unsigned int m = ceilf( (float)(h_len-1) / 4.0f );
+
+        q->fc_stage[i] = fc;            // filter cut-of
+        q->f0_stage[i] = f0;            // filter center frequency
+        q->As_stage[i] = q->As;         // filter stop-band attenuation
+        q->m_stage[i]  = m < 3 ? 3 : m; // minimum 2
+    }
+
+    // create half-band resampler objects
+    q->resamp2 = (RESAMP2()*) malloc(q->num_stages*sizeof(RESAMP2()));
+    for (i=0; i<q->num_stages; i++) {
+        // create half-band resampler
+        q->resamp2[i] = RESAMP2(_create)(q->m_stage[i],
+                                         q->f0_stage[i],
+                                         q->As_stage[i]);
+    }
+
+    // reset object
+    MSRESAMP2(_reset)(q);
+
+    // return main object
+    return q;
+}
+
+// destroy msresamp2 object, freeing all internally-allocated memory
+void MSRESAMP2(_destroy)(MSRESAMP2() _q)
+{
+    // free buffers
+    free(_q->buffer0);
+    free(_q->buffer1);
+
+    // free half-band resampler design parameter arrays
+    free(_q->fc_stage);
+    free(_q->f0_stage);
+    free(_q->As_stage);
+    free(_q->m_stage);
+
+    // destroy/free half-band resampler objects
+    unsigned int i;
+    for (i=0; i<_q->num_stages; i++)
+        RESAMP2(_destroy)(_q->resamp2[i]);
+
+    // free half-band resampler array
+    free(_q->resamp2);
+
+    // destroy main object
+    free(_q);
+}
+
+// print msresamp2 object internals
+void MSRESAMP2(_print)(MSRESAMP2() _q)
+{
+    printf("multi-stage half-band resampler:\n");
+    printf("    type                    : %s\n", _q->type == LIQUID_RESAMP_DECIM ? "decimator" : "interpolator");
+    printf("    number of stages        : %u stage%s\n", _q->num_stages, _q->num_stages == 1 ? "" : "s");
+    printf("    cut-off frequency, fc   : %12.8f Fs\n",  _q->fc);
+    printf("    center frequency, f0    : %12.8f Fs\n",  _q->f0);
+    printf("    stop-band attenuation   : %.2f dB\n",    _q->As);
+
+    // print each stage
+    unsigned int i;
+    for (i=0; i<_q->num_stages; i++) {
+        printf("    stage[%2u]  {m=%3u, As=%6.2f dB, fc=%6.3f, f0=%6.3f}\n",
+                    i, _q->m_stage[i], _q->As_stage[i], _q->fc_stage[i], _q->f0_stage[i]);
+    }
+}
+
+// reset msresamp2 object internals, clear filters and nco phase
+void MSRESAMP2(_reset)(MSRESAMP2() _q)
+{
+    // reset half-band resampler objects
+    unsigned int i;
+    for (i=0; i<_q->num_stages; i++)
+        RESAMP2(_clear)(_q->resamp2[i]);
+
+    // reset buffer write pointer
+    _q->buffer_index = 0;
+    
+    // NOTE: not necessary to clear internal buffers
+}
+
+// get group delay (number of output samples)
+float MSRESAMP2(_get_delay)(MSRESAMP2() _q)
+{
+    // initialize delay
+    float delay = 0;
+    unsigned int i;
+
+    // add half-band resampling delay
+    if (_q->type == LIQUID_RESAMP_INTERP) {
+        // interpolator
+        for (i=0; i<_q->num_stages; i++) {
+            // filter semi-length
+            unsigned int m = _q->m_stage[i];
+
+            delay *= 0.5f;
+            delay += m;
+        }
+    } else {
+        // decimator
+        for (i=0; i<_q->num_stages; i++) {
+            // filter semi-length
+            unsigned int m = _q->m_stage[_q->num_stages-i-1];
+
+            delay *= 2;
+            delay += 2*m - 1;
+        }
+    }
+
+    return delay;
+}
+
+// execute multi-stage resampler as interpolator            
+//  _q      : msresamp object                               
+//  _x      : input sample                                  
+//  _y      : output sample array  [size:2^_num_stages x 1] 
+void MSRESAMP2(_execute)(MSRESAMP2() _q,
+                         TI *        _x,
+                         TO *        _y)
+{
+    // switch resampling method based on type
+    if (_q->num_stages == 0) {
+        // pass through
+        _y[0] = _x[0];
+        return;
+    } else if (_q->type == LIQUID_RESAMP_INTERP) {
+        // execute multi-stage resampler as interpolator
+        MSRESAMP2(_interp_execute)(_q, _x[0], _y);
+    } else {
+        // execute multi-stage resampler as decimator
+        MSRESAMP2(_decim_execute)(_q, _x, _y);
+    }
+}
+
+//
+// internal methods
+//
+
+// execute multi-stage resampler as interpolator            
+//  _q      : msresamp object                               
+//  _x      : input sample                                  
+//  _y      : output sample array  [size:2^_num_stages x 1] 
+void MSRESAMP2(_interp_execute)(MSRESAMP2() _q,
+                                TI          _x,
+                                TO *        _y)
+{
+    // buffer pointers (initialize BOTH to _q->buffer0);
+    T * b0 = _q->buffer0;   // input buffer pointer
+    T * b1 = _q->buffer1;   // output buffer pointer
+
+    // set input sample in first buffer
+    b0[0] = _x;
+
+    unsigned int s;         // half-band decimator stage counter
+    unsigned int k;         // number of inputs for this stage
+    for (s=0; s<_q->num_stages; s++) {
+        // compute number of inputs for this stage
+        k = 1 << s;
+
+        // set final stage output as supplied output pointer
+        if (s == _q->num_stages-1)
+            b1 = _y;
+
+        // run half-band stages as interpolators
+        unsigned int i;
+        unsigned int g = _q->num_stages-s-1;    // reversed resampler index
+        for (i=0; i<k; i++)
+            RESAMP2(_interp_execute)(_q->resamp2[g], b0[i], &b1[2*i]);
+
+        // toggle output buffer pointers
+        b0 = (s % 2) == 0 ? _q->buffer1 : _q->buffer0;
+        b1 = (s % 2) == 0 ? _q->buffer0 : _q->buffer1;
+    }
+}
+
+// execute multi-stage resampler as decimator               
+//  _q      : msresamp object                               
+//  _x      : input sample array  [size: 2^_num_stages x 1] 
+//  _y      : output sample pointer                         
+void MSRESAMP2(_decim_execute)(MSRESAMP2() _q,
+                               TI *        _x,
+                               TO *        _y)
+{
+    // buffer pointers (initialize BOTH to _q->buffer0);
+    T * b0 = _x;            // input buffer pointer
+    T * b1 = _q->buffer1;   // output buffer pointer
+
+    unsigned int s;         // half-band decimator stage counter
+    unsigned int k;         // number of outputs for this stage
+    for (s=0; s<_q->num_stages; s++) {
+        // compute number of outputs for this stage
+        k = 1 << (_q->num_stages - s - 1);
+
+        // run half-band stages as decimators
+        unsigned int i;
+        for (i=0; i<k; i++)
+            RESAMP2(_decim_execute)(_q->resamp2[s], &b0[2*i], &b1[i]);
+
+        // toggle output buffer pointers
+        b0 = (s % 2) == 0 ? _q->buffer1 : _q->buffer0;
+        b1 = (s % 2) == 0 ? _q->buffer0 : _q->buffer1;
+    }
+
+    // set single output sample and scale appropriately
+    *_y = b0[0] * _q->zeta;
+}
+
diff --git a/src/filter/src/rcos.c b/src/filter/src/rcos.c
new file mode 100644
index 0000000..b55de61
--- /dev/null
+++ b/src/filter/src/rcos.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Design raised-cosine filter
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// Design Nyquist raised-cosine filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rcos(unsigned int _k,
+                        unsigned int _m,
+                        float _beta,
+                        float _dt,
+                        float * _h)
+{
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rcos(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rcos(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_rcos(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    unsigned int n;
+    float z, t1, t2, t3;
+
+    float nf, kf, mf;
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    // Calculate filter coefficients
+    for (n=0; n<h_len; n++) {
+        nf = (float) n;
+        kf = (float) _k;
+        mf = (float) _m;
+
+        z = (nf+_dt)/kf-mf;
+        t1 = cosf(_beta*M_PI*z);
+        t2 = sincf(z);
+        t3 = 1 - 4.0f*_beta*_beta*z*z;
+
+        // check for special condition where 4*_beta^2*z^2 equals 1
+        if ( fabsf(t3) < 1e-3f )
+            _h[n] = sinf(M_PI/(2.0*_beta))*_beta*0.5f;
+        else
+            _h[n] = t1*t2/t3;
+    }
+}
+
diff --git a/src/filter/src/resamp.c b/src/filter/src/resamp.c
new file mode 100644
index 0000000..c31f3c7
--- /dev/null
+++ b/src/filter/src/resamp.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Arbitrary resampler
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+// defined:
+//  TO          output data type
+//  TC          coefficient data type
+//  TI          input data type
+//  RESAMP()    name-mangling macro
+//  FIRPFB()    firpfb macro
+
+// enable run-time debug printing of resampler
+#define DEBUG_RESAMP_PRINT              0
+
+// internal: update timing
+void RESAMP(_update_timing_state)(RESAMP() _q);
+
+struct RESAMP(_s) {
+    // filter design parameters
+    unsigned int m;     // filter semi-length, h_len = 2*m + 1
+    float As;           // filter stop-band attenuation
+    float fc;           // filter cutoff frequency
+
+    // resampling properties/states
+    float rate;         // resampling rate (ouput/input)
+    float del;          // fractional delay step
+
+    // floating-point phase
+    float tau;          // accumulated timing phase, 0 <= tau < 1
+    float bf;           // soft filterbank index, bf = tau*npfb = b + mu
+    int b;              // base filterbank index, 0 <= b < npfb
+    float mu;           // fractional filterbank interpolation value, 0 <= mu < 1
+    TO y0;              // filterbank output at index b
+    TO y1;              // filterbank output at index b+1
+
+    // polyphase filterbank properties/object
+    unsigned int npfb;  // number of filters in the bank
+    FIRPFB() f;         // filterbank object (interpolator)
+
+    enum {
+        RESAMP_STATE_BOUNDARY, // boundary between input samples
+        RESAMP_STATE_INTERP,   // regular interpolation
+    } state;
+};
+
+// create arbitrary resampler
+//  _rate   :   resampling rate
+//  _m      :   prototype filter semi-length
+//  _fc     :   prototype filter cutoff frequency, fc in (0, 0.5)
+//  _As     :   prototype filter stop-band attenuation [dB] (e.g. 60)
+//  _npfb   :   number of filters in polyphase filterbank
+RESAMP() RESAMP(_create)(float        _rate,
+                         unsigned int _m,
+                         float        _fc,
+                         float        _As,
+                         unsigned int _npfb)
+{
+    // validate input
+    if (_rate <= 0) {
+        fprintf(stderr,"error: resamp_%s_create(), resampling rate must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: resamp_%s_create(), filter semi-length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_npfb == 0) {
+        fprintf(stderr,"error: resamp_%s_create(), number of filter banks must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fc <= 0.0f || _fc >= 0.5f) {
+        fprintf(stderr,"error: resamp_%s_create(), filter cutoff must be in (0,0.5)\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_As <= 0.0f) {
+        fprintf(stderr,"error: resamp_%s_create(), filter stop-band suppression must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate memory for resampler
+    RESAMP() q = (RESAMP()) malloc(sizeof(struct RESAMP(_s)));
+
+    // set rate using formal method (specifies output stride
+    // value 'del')
+    RESAMP(_set_rate)(q, _rate);
+
+    // set properties
+    q->m    = _m;       // prototype filter semi-length
+    q->fc   = _fc;      // prototype filter cutoff frequency
+    q->As   = _As;      // prototype filter stop-band attenuation
+    q->npfb = _npfb;    // number of filters in bank
+
+    // design filter
+    unsigned int n = 2*q->m*q->npfb+1;
+    float hf[n];
+    TC h[n];
+    liquid_firdes_kaiser(n,q->fc/((float)(q->npfb)),q->As,0.0f,hf);
+
+    // normalize filter coefficients by DC gain
+    unsigned int i;
+    float gain=0.0f;
+    for (i=0; i<n; i++)
+        gain += hf[i];
+    gain = (q->npfb)/(gain);
+
+    // copy to type-specific array, applying gain
+    for (i=0; i<n; i++)
+        h[i] = hf[i]*gain;
+    q->f = FIRPFB(_create)(q->npfb,h,n-1);
+
+    // reset object and return
+    RESAMP(_reset)(q);
+    return q;
+}
+
+// create arbitrary resampler object with a specified input
+// resampling rate and default parameters
+//  m (filter semi-length) = 7
+//  fc (filter cutoff frequency) = 0.25
+//  As (filter stop-band attenuation) = 60 dB
+//  npfb (number of filters in the bank) = 64
+RESAMP() RESAMP(_create_default)(float _rate)
+{
+    // validate input
+    if (_rate <= 0) {
+        fprintf(stderr,"error: resamp_%s_create_default(), resampling rate must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // det default parameters
+    unsigned int m    = 7;
+    float        fc   = 0.25f;
+    float        As   = 60.0f;
+    unsigned int npfb = 64;
+
+    // create and return resamp object
+    return RESAMP(_create)(_rate, m, fc, As, npfb);
+}
+
+// free arbitrary resampler object
+void RESAMP(_destroy)(RESAMP() _q)
+{
+    // free polyphase filterbank
+    FIRPFB(_destroy)(_q->f);
+
+    // free main object memory
+    free(_q);
+}
+
+// print resampler object
+void RESAMP(_print)(RESAMP() _q)
+{
+    printf("resampler [rate: %f]\n", _q->rate);
+    FIRPFB(_print)(_q->f);
+}
+
+// reset resampler object
+void RESAMP(_reset)(RESAMP() _q)
+{
+    // clear filterbank
+    FIRPFB(_reset)(_q->f);
+
+    // reset states
+    _q->state = RESAMP_STATE_INTERP;// input/output sample state
+    _q->tau   = 0.0f;           // accumulated timing phase
+    _q->bf    = 0.0f;           // soft-valued filterbank index
+    _q->b     = 0;              // base filterbank index
+    _q->mu    = 0.0f;           // fractional filterbank interpolation value
+
+    _q->y0    = 0;              // filterbank output at index b
+    _q->y1    = 0;              // filterbank output at index b+1
+}
+
+// get resampler filter delay (semi-length m)
+unsigned int RESAMP(_get_delay)(RESAMP() _q)
+{
+    return _q->m;
+}
+
+// set resampling rate
+void RESAMP(_set_rate)(RESAMP() _q,
+                       float    _rate)
+{
+    if (_rate <= 0) {
+        fprintf(stderr,"error: resamp_%s_set_rate(), resampling rate must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // set internal rate
+    _q->rate = _rate;
+
+    // set output stride
+    _q->del = 1.0f / _q->rate;
+}
+
+// adjust resampling rate
+void RESAMP(_adjust_rate)(RESAMP() _q,
+                          float    _delta)
+{
+    if (_delta > 0.1f || _delta < -0.1f) {
+        fprintf(stderr,"error: resamp_%s_adjust_rate(), resampling rate must be in [-0.1,0.1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // adjust internal rate
+    _q->rate += _delta;
+
+    // clip rate
+    if (_q->rate >  0.5f) _q->rate =  0.5f;
+    if (_q->rate < -0.5f) _q->rate = -0.5f;
+
+    // set output stride
+    _q->del = 1.0f / _q->rate;
+}
+
+
+// run arbitrary resampler
+//  _q          :   resampling object
+//  _x          :   single input sample
+//  _y          :   output array
+//  _num_written:   number of samples written to output
+void RESAMP(_execute)(RESAMP()       _q,
+                      TI             _x,
+                      TO *           _y,
+                      unsigned int * _num_written)
+{
+    // push input sample into filterbank
+    FIRPFB(_push)(_q->f, _x);
+    unsigned int n=0;
+    
+    while (_q->b < _q->npfb) {
+
+#if DEBUG_RESAMP_PRINT
+        printf("  [%2u] : s=%1u, tau=%12.8f, b : %12.8f (%4d + %8.6f)\n", n+1, _q->state, _q->tau, _q->bf, _q->b, _q->mu);
+#endif
+        switch (_q->state) {
+        case RESAMP_STATE_BOUNDARY:
+            // compute filterbank output
+            FIRPFB(_execute)(_q->f, 0, &_q->y1);
+
+            // interpolate
+            _y[n++] = (1.0f - _q->mu)*_q->y0 + _q->mu*_q->y1;
+        
+            // update timing state
+            RESAMP(_update_timing_state)(_q);
+
+            _q->state = RESAMP_STATE_INTERP;
+            break;
+
+        case RESAMP_STATE_INTERP:
+            // compute output at base index
+            FIRPFB(_execute)(_q->f, _q->b, &_q->y0);
+
+            // check to see if base index is last filter in the bank, in
+            // which case the resampler needs an additional input sample
+            // to finish the linear interpolation process
+            if (_q->b == _q->npfb-1) {
+                // last filter: need additional input sample
+                _q->state = RESAMP_STATE_BOUNDARY;
+            
+                // set index to indicate new sample is needed
+                _q->b = _q->npfb;
+            } else {
+                // do not need additional input sample; compute
+                // output at incremented base index
+                FIRPFB(_execute)(_q->f, _q->b+1, &_q->y1);
+
+                // perform linear interpolation between filterbank outputs
+                _y[n++] = (1.0f - _q->mu)*_q->y0 + _q->mu*_q->y1;
+
+                // update timing state
+                RESAMP(_update_timing_state)(_q);
+            }
+            break;
+        default:
+            fprintf(stderr,"error: resamp_%s_execute(), invalid/unknown state\n", EXTENSION_FULL);
+            exit(1);
+        }
+    }
+
+    // decrement timing phase by one sample
+    _q->tau -= 1.0f;
+    _q->bf  -= (float)(_q->npfb);
+    _q->b   -= _q->npfb;
+
+    // specify number of samples written
+    *_num_written = n;
+}
+
+// execute arbitrary resampler on a block of samples
+//  _q              :   resamp object
+//  _x              :   input buffer [size: _nx x 1]
+//  _nx             :   input buffer
+//  _y              :   output sample array (pointer)
+//  _ny             :   number of samples written to _y
+void RESAMP(_execute_block)(RESAMP()       _q,
+                            TI *           _x,
+                            unsigned int   _nx,
+                            TO *           _y,
+                            unsigned int * _ny)
+{
+    // initialize number of output samples to zero
+    unsigned int ny = 0;
+
+    // number of samples written for each individual iteration
+    unsigned int num_written;
+
+    // iterate over each input sample
+    unsigned int i;
+    for (i=0; i<_nx; i++) {
+        // run resampler on single input
+        RESAMP(_execute)(_q, _x[i], &_y[ny], &num_written);
+
+        // update output counter
+        ny += num_written;
+    }
+
+    // set return value for number of output samples written
+    *_ny = ny;
+}
+
+
+//
+// internal methods
+// 
+
+// update timing state; increment output timing stride and
+// quantize filterbank indices
+void RESAMP(_update_timing_state)(RESAMP() _q)
+{
+    // update high-resolution timing phase
+    _q->tau += _q->del;
+
+    // convert to high-resolution filterbank index 
+    _q->bf  = _q->tau * (float)(_q->npfb);
+
+    // split into integer filterbank index and fractional interpolation
+    _q->b   = (int)floorf(_q->bf);      // base index
+    _q->mu  = _q->bf - (float)(_q->b);  // fractional index
+}
+
diff --git a/src/filter/src/resamp.fixed.c b/src/filter/src/resamp.fixed.c
new file mode 100644
index 0000000..6afc24d
--- /dev/null
+++ b/src/filter/src/resamp.fixed.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Arbitrary resampler (fixed-point phase verions)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define DEBUG_RESAMP_PRINT  0
+
+// defined:
+//  TO          output data type
+//  TC          coefficient data type
+//  TI          input data type
+//  RESAMP()    name-mangling macro
+//  FIRPFB()    firpfb macro
+
+struct RESAMP(_s) {
+    TC * h;
+    unsigned int h_len;
+    float As;       // stop-band attenuation
+    float fc;       // filter cutoff
+
+    float r;        // rate
+    int b;          // filterbank index
+    float del;      // fractional delay step
+
+    // fixed-point phase
+    unsigned int theta;             // sampling phase
+    unsigned int d_theta;           // phase differential
+
+    unsigned int num_bits_phase;    // number of bits in phase
+    unsigned int max_phase;         // maximum phase value
+    unsigned int num_bits_npfb;     // number of bits in npfb
+    unsigned int num_shift_bits;    // number of bits to shift to compute b
+
+    unsigned int npfb;
+    FIRPFB() f;
+
+#if 0
+    fir_prototype p;    // prototype object
+#endif
+};
+
+RESAMP() RESAMP(_create)(float _r,
+                         unsigned int _h_len,
+                         float _fc,
+                         float _As,
+                         unsigned int _npfb)
+{
+    // validate input
+    if (_r <= 0) {
+        fprintf(stderr,"error: resamp_%s_create(), resampling rate must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_h_len == 0) {
+        fprintf(stderr,"error: resamp_%s_create(), filter length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_npfb == 0) {
+        fprintf(stderr,"error: resamp_%s_create(), number of filter banks must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_fc <= 0.0f || _fc >= 0.5f) {
+        fprintf(stderr,"error: resamp_%s_create(), filter cutoff must be in (0,0.5)\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_As <= 0.0f) {
+        fprintf(stderr,"error: resamp_%s_create(), filter stop-band suppression must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    RESAMP() q = (RESAMP()) malloc(sizeof(struct RESAMP(_s)));
+    q->r     = _r;
+    q->As    = _As;
+    q->fc    = _fc;
+    q->h_len = _h_len;
+
+    q->b   = 0;
+    q->del = 1.0f / q->r;
+
+    q->num_bits_npfb = liquid_nextpow2(_npfb);
+    q->npfb = 1<<q->num_bits_npfb;
+    q->num_bits_phase = 20;
+    q->max_phase = 1 << q->num_bits_phase;
+    q->num_shift_bits = q->num_bits_phase - q->num_bits_npfb;
+    q->theta = 0;
+    q->d_theta = (unsigned int)( q->max_phase * q->del );
+
+    // design filter
+    unsigned int n = 2*_h_len*q->npfb+1;
+    float hf[n];
+    TC h[n];
+    liquid_firdes_kaiser(n,q->fc/((float)(q->npfb)),q->As,0.0f,hf);
+
+    // normalize filter coefficients by DC gain
+    unsigned int i;
+    float gain=0.0f;
+    for (i=0; i<n; i++)
+        gain += hf[i];
+    gain = (q->npfb)/(gain);
+
+    // copy to type-specific array
+    for (i=0; i<n; i++)
+        h[i] = hf[i]*gain;
+    q->f = FIRPFB(_create)(q->npfb,h,n-1);
+
+    //for (i=0; i<n; i++)
+    //    PRINTVAL_TC(h[i],%12.8f);
+    //exit(0);
+
+    return q;
+}
+
+void RESAMP(_destroy)(RESAMP() _q)
+{
+    FIRPFB(_destroy)(_q->f);
+    free(_q);
+}
+
+void RESAMP(_print)(RESAMP() _q)
+{
+    printf("resampler [rate: %f]\n", _q->r);
+    FIRPFB(_print)(_q->f);
+}
+
+void RESAMP(_reset)(RESAMP() _q)
+{
+    FIRPFB(_reset)(_q->f);
+    _q->b   = 0;
+
+    _q->theta = 0;
+}
+
+void RESAMP(_setrate)(RESAMP() _q, float _rate)
+{
+    // TODO : validate rate, validate this method
+    _q->r = _rate;
+    _q->del = 1.0f / _q->r;
+
+    _q->d_theta = (unsigned int)( _q->max_phase * _q->del );
+}
+
+void RESAMP(_execute)(RESAMP() _q,
+                      TI _x,
+                      TO * _y,
+                      unsigned int *_num_written)
+{
+    FIRPFB(_push)(_q->f, _x);
+    unsigned int n=0;
+    
+    //while (_q->tau < 1.0f) {
+    while (_q->theta < _q->max_phase) {
+
+#if DEBUG_RESAMP_PRINT
+        printf("  [%2u] : theta = %6u, b : %6u\n", n, _q->theta, _q->b);
+#endif
+        FIRPFB(_execute)(_q->f, _q->b, &_y[n]);
+
+        _q->theta += _q->d_theta;
+        _q->b = _q->theta >> _q->num_shift_bits;
+        n++;
+    }
+
+    _q->theta -= _q->max_phase;
+    _q->b = _q->theta >> _q->num_shift_bits;
+    *_num_written = n;
+}
+
diff --git a/src/filter/src/resamp2.c b/src/filter/src/resamp2.c
new file mode 100644
index 0000000..5b2e77a
--- /dev/null
+++ b/src/filter/src/resamp2.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Halfband resampler (interpolator/decimator)
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+// defined:
+//  RESAMP2()       name-mangling macro
+//  TO              output data type
+//  TC              coefficient data type
+//  TI              input data type
+//  WINDOW()        window macro
+//  DOTPROD()       dotprod macro
+//  PRINTVAL()      print macro
+
+struct RESAMP2(_s) {
+    TC * h;                 // filter prototype
+    unsigned int m;         // primitive filter length
+    unsigned int h_len;     // actual filter length: h_len = 4*m+1
+    float f0;               // center frequency [-1.0 <= f0 <= 1.0]
+    float As;               // stop-band attenuation [dB]
+
+    // filter component
+    TC * h1;                // filter branch coefficients
+    DOTPROD() dp;           // inner dot product object
+    unsigned int h1_len;    // filter length (2*m)
+
+    // input buffers
+    WINDOW() w0;            // input buffer (even samples)
+    WINDOW() w1;            // input buffer (odd samples)
+
+    // halfband filter operation
+    unsigned int toggle;
+};
+
+// create a resamp2 object
+//  _m      :   filter semi-length (effective length: 4*_m+1)
+//  _f0     :   center frequency of half-band filter
+//  _As     :   stop-band attenuation [dB], _As > 0
+RESAMP2() RESAMP2(_create)(unsigned int _m,
+                           float        _f0,
+                           float        _As)
+{
+    // validate input
+    if (_m < 2) {
+        fprintf(stderr,"error: resamp2_%s_create(), filter semi-length must be at least 2\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    RESAMP2() q = (RESAMP2()) malloc(sizeof(struct RESAMP2(_s)));
+    q->m  = _m;
+    q->f0 = _f0;
+    q->As = _As;
+    if ( q->f0 < -0.5f || q->f0 > 0.5f ) {
+        fprintf(stderr,"error: resamp2_%s_create(), f0 (%12.4e) must be in (-1,1)\n", EXTENSION_FULL, q->f0);
+        exit(1);
+    }
+
+    // change filter length as necessary
+    q->h_len = 4*(q->m) + 1;
+    q->h = (TC *) malloc((q->h_len)*sizeof(TC));
+
+    q->h1_len = 2*(q->m);
+    q->h1 = (TC *) malloc((q->h1_len)*sizeof(TC));
+
+    // design filter prototype
+    unsigned int i;
+    float t, h1, h2;
+    TC h3;
+    float beta = kaiser_beta_As(q->As);
+    for (i=0; i<q->h_len; i++) {
+        t = (float)i - (float)(q->h_len-1)/2.0f;
+        h1 = sincf(t/2.0f);
+        h2 = kaiser(i,q->h_len,beta,0);
+#if TC_COMPLEX == 1
+        h3 = cosf(2.0f*M_PI*t*q->f0) + _Complex_I*sinf(2.0f*M_PI*t*q->f0);
+#else
+        h3 = cosf(2.0f*M_PI*t*q->f0);
+#endif
+        q->h[i] = h1*h2*h3;
+    }
+
+    // resample, alternate sign, [reverse direction]
+    unsigned int j=0;
+    for (i=1; i<q->h_len; i+=2)
+        q->h1[j++] = q->h[q->h_len - i - 1];
+
+    // create dotprod object
+    q->dp = DOTPROD(_create)(q->h1, 2*q->m);
+
+    // create window buffers
+    q->w0 = WINDOW(_create)(2*(q->m));
+    q->w1 = WINDOW(_create)(2*(q->m));
+
+    RESAMP2(_clear)(q);
+
+    return q;
+}
+
+// re-create a resamp2 object with new properties
+//  _q          :   original resamp2 object
+//  _m          :   filter semi-length (effective length: 4*_m+1)
+//  _f0         :   center frequency of half-band filter
+//  _As         :   stop-band attenuation [dB], _As > 0
+RESAMP2() RESAMP2(_recreate)(RESAMP2()    _q,
+                             unsigned int _m,
+                             float        _f0,
+                             float        _As)
+{
+    // only re-design filter if necessary
+    if (_m != _q->m) {
+        // destroy resampler and re-create from scratch
+        RESAMP2(_destroy)(_q);
+        _q = RESAMP2(_create)(_m, _f0, _As);
+
+    } else {
+        // re-design filter prototype
+        unsigned int i;
+        float t, h1, h2;
+        TC h3;
+        float beta = kaiser_beta_As(_q->As);
+        for (i=0; i<_q->h_len; i++) {
+            t = (float)i - (float)(_q->h_len-1)/2.0f;
+            h1 = sincf(t/2.0f);
+            h2 = kaiser(i,_q->h_len,beta,0);
+#if TC_COMPLEX == 1
+            h3 = cosf(2.0f*M_PI*t*_q->f0) + _Complex_I*sinf(2.0f*M_PI*t*_q->f0);
+#else
+            h3 = cosf(2.0f*M_PI*t*_q->f0);
+#endif
+            _q->h[i] = h1*h2*h3;
+        }
+
+        // resample, alternate sign, [reverse direction]
+        unsigned int j=0;
+        for (i=1; i<_q->h_len; i+=2)
+            _q->h1[j++] = _q->h[_q->h_len - i - 1];
+
+        // create dotprod object
+        _q->dp = DOTPROD(_recreate)(_q->dp, _q->h1, 2*_q->m);
+    }
+    return _q;
+}
+
+// destroy a resamp2 object, clearing up all allocated memory
+void RESAMP2(_destroy)(RESAMP2() _q)
+{
+    // destroy dotprod object
+    DOTPROD(_destroy)(_q->dp);
+
+    // destroy window buffers
+    WINDOW(_destroy)(_q->w0);
+    WINDOW(_destroy)(_q->w1);
+
+    // free arrays
+    free(_q->h);
+    free(_q->h1);
+
+    // free main object memory
+    free(_q);
+}
+
+// print a resamp2 object's internals
+void RESAMP2(_print)(RESAMP2() _q)
+{
+    printf("fir half-band resampler: [%u taps, f0=%12.8f]\n",
+            _q->h_len,
+            _q->f0);
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++) {
+        printf("  h(%4u) = ", i+1);
+        PRINTVAL_TC(_q->h[i],%12.8f);
+        printf(";\n");
+    }
+    printf("---\n");
+    for (i=0; i<_q->h1_len; i++) {
+        printf("  h1(%4u) = ", i+1);
+        PRINTVAL_TC(_q->h1[i],%12.8f);
+        printf(";\n");
+    }
+}
+
+// clear internal buffer
+void RESAMP2(_clear)(RESAMP2() _q)
+{
+    WINDOW(_clear)(_q->w0);
+    WINDOW(_clear)(_q->w1);
+
+    _q->toggle = 0;
+}
+
+// get filter delay (samples)
+unsigned int RESAMP2(_get_delay)(RESAMP2() _q)
+{
+    return 2*_q->m - 1;
+}
+
+// execute resamp2 as half-band filter
+//  _q      :   resamp2 object
+//  _x      :   input sample
+//  _y0     :   output sample pointer (low frequency)
+//  _y1     :   output sample pointer (high frequency)
+void RESAMP2(_filter_execute)(RESAMP2() _q,
+                              TI        _x,
+                              TO *      _y0,
+                              TO *      _y1)
+{
+    TI * r;     // buffer read pointer
+    TO yi;      // delay branch
+    TO yq;      // filter branch
+
+    if ( _q->toggle == 0 ) {
+        // push sample into upper branch
+        WINDOW(_push)(_q->w0, _x);
+
+        // upper branch (delay)
+        WINDOW(_index)(_q->w0, _q->m-1, &yi);
+
+        // lower branch (filter)
+        WINDOW(_read)(_q->w1, &r);
+        DOTPROD(_execute)(_q->dp, r, &yq);
+    } else {
+        // push sample into lower branch
+        WINDOW(_push)(_q->w1, _x);
+
+        // upper branch (delay)
+        WINDOW(_index)(_q->w1, _q->m-1, &yi);
+
+        // lower branch (filter)
+        WINDOW(_read)(_q->w0, &r);
+        DOTPROD(_execute)(_q->dp, r, &yq);
+    }
+
+    // toggle flag
+    _q->toggle = 1 - _q->toggle;
+
+    // set return values, normalizing gain
+    *_y0 = 0.5f*(yi + yq);  // lower band
+    *_y1 = 0.5f*(yi - yq);  // upper band
+}
+
+// execute analysis half-band filterbank
+//  _q      :   resamp2 object
+//  _x      :   input array [size: 2 x 1]
+//  _y      :   output array [size: 2 x 1]
+void RESAMP2(_analyzer_execute)(RESAMP2() _q,
+                                TI *      _x,
+                                TO *      _y)
+{
+    TI * r;     // buffer read pointer
+    TO y0;      // delay branch
+    TO y1;      // filter branch
+
+    // compute filter branch
+    WINDOW(_push)(_q->w1, 0.5*_x[0]);
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dp, r, &y1);
+
+    // compute delay branch
+    WINDOW(_push)(_q->w0, 0.5*_x[1]);
+    WINDOW(_index)(_q->w0, _q->m-1, &y0);
+
+    // set return value
+    _y[0] = y1 + y0;
+    _y[1] = y1 - y0;
+}
+
+// execute synthesis half-band filterbank
+//  _q      :   resamp2 object
+//  _x      :   input array [size: 2 x 1]
+//  _y      :   output array [size: 2 x 1]
+void RESAMP2(_synthesizer_execute)(RESAMP2() _q,
+                                   TI *      _x,
+                                   TO *      _y)
+{
+    TI * r;                 // buffer read pointer
+    TI x0 = _x[0] + _x[1];  // delay branch input
+    TI x1 = _x[0] - _x[1];  // filter branch input
+
+    // compute delay branch
+    WINDOW(_push)(_q->w0, x0);
+    WINDOW(_index)(_q->w0, _q->m-1, &_y[0]);
+
+    // compute second branch (filter)
+    WINDOW(_push)(_q->w1, x1);
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dp, r, &_y[1]);
+}
+
+
+// execute half-band decimation
+//  _q      :   resamp2 object
+//  _x      :   input array [size: 2 x 1]
+//  _y      :   output sample pointer
+void RESAMP2(_decim_execute)(RESAMP2() _q,
+                             TI *      _x,
+                             TO *      _y)
+{
+    TI * r;     // buffer read pointer
+    TO y0;      // delay branch
+    TO y1;      // filter branch
+
+    // compute filter branch
+    WINDOW(_push)(_q->w1, _x[0]);
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dp, r, &y1);
+
+    // compute delay branch
+    WINDOW(_push)(_q->w0, _x[1]);
+    WINDOW(_index)(_q->w0, _q->m-1, &y0);
+
+    // set return value
+    *_y = y0 + y1;
+}
+
+// execute half-band interpolation
+//  _q      :   resamp2 object
+//  _x      :   input sample
+//  _y      :   output array [size: 2 x 1]
+void RESAMP2(_interp_execute)(RESAMP2() _q,
+                              TI        _x,
+                              TO *      _y)
+{
+    TI * r;  // buffer read pointer
+
+    // compute delay branch
+    WINDOW(_push)(_q->w0, _x);
+    WINDOW(_index)(_q->w0, _q->m-1, &_y[0]);
+
+    // compute second branch (filter)
+    WINDOW(_push)(_q->w1, _x);
+    WINDOW(_read)(_q->w1, &r);
+    DOTPROD(_execute)(_q->dp, r, &_y[1]);
+}
+
diff --git a/src/filter/src/rkaiser.c b/src/filter/src/rkaiser.c
new file mode 100644
index 0000000..0bf3f6f
--- /dev/null
+++ b/src/filter/src/rkaiser.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Design root-Nyquist Kaiser filter
+//
+// References
+//  [Vaidyanathan:1993] Vaidyanathan, P. P., "Multirate Systems and
+//      Filter Banks," 1993, Prentice Hall, Section 3.2.1
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_RKAISER 0
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+void liquid_firdes_rkaiser(unsigned int _k,
+                           unsigned int _m,
+                           float _beta,
+                           float _dt,
+                           float * _h)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), k must be at least 2\n");
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), m must be at least 1\n");
+        exit(1);
+    } else if (_beta <= 0.0f || _beta >= 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), beta must be in (0,1)\n");
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser(), dt must be in [-1,1]\n");
+        exit(1);
+    }
+
+    // simply call internal method and ignore output rho value
+    float rho;
+    //liquid_firdes_rkaiser_bisection(_k,_m,_beta,_dt,_h,&rho);
+    liquid_firdes_rkaiser_quadratic(_k,_m,_beta,_dt,_h,&rho);
+}
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc using approximation for rho.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+void liquid_firdes_arkaiser(unsigned int _k,
+                            unsigned int _m,
+                            float _beta,
+                            float _dt,
+                            float * _h)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: liquid_firdes_arkaiser(), k must be at least 2\n");
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: liquid_firdes_arkaiser(), m must be at least 1\n");
+        exit(1);
+    } else if (_beta <= 0.0f || _beta >= 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_arkaiser(), beta must be in (0,1)\n");
+        exit(1);
+    } else if (_dt < -1.0f || _dt > 1.0f) {
+        fprintf(stderr,"error: liquid_firdes_arkaiser(), dt must be in [-1,1]\n");
+        exit(1);
+    }
+
+#if 0
+    // compute bandwidth adjustment estimate
+    float rho_hat = rkaiser_approximate_rho(_m,_beta);  // bandwidth correction factor
+#else
+    // rho ~ c0 + c1*log(_beta) + c2*log^2(_beta)
+
+    // c0 ~ 0.762886 + 0.067663*log(m)
+    // c1 ~ 0.065515
+    // c2 ~ log( 1 - 0.088*m^-1.6 )
+
+    float c0 = 0.762886 + 0.067663*logf(_m);
+    float c1 = 0.065515;
+    float c2 = logf( 1 - 0.088*powf(_m,-1.6 ) );
+
+    float log_beta = logf(_beta);
+
+    float rho_hat = c0 + c1*log_beta + c2*log_beta*log_beta;
+
+    // ensure range is valid
+    if (rho_hat <= 0.0f || rho_hat >= 1.0f)
+        rho_hat = rkaiser_approximate_rho(_m,_beta);
+#endif
+
+    unsigned int n=2*_k*_m+1;                       // filter length
+    float kf = (float)_k;                           // samples/symbol (float)
+    float del = _beta*rho_hat / kf;                 // transition bandwidth
+    float As = estimate_req_filter_As(del, n);      // stop-band suppression
+    float fc  = 0.5f*(1 + _beta*(1.0f-rho_hat))/kf; // filter cutoff
+    
+#if DEBUG_RKAISER
+    printf("rho-hat : %12.8f (compare to %12.8f)\n", rho_hat, rkaiser_approximate_rho(_m,_beta));
+    printf("fc      : %12.8f\n", fc);
+    printf("delta-f : %12.8f\n", del);
+    printf("As      : %12.8f dB\n", As);
+    printf("alpha   : %12.8f\n", kaiser_beta_As(As));
+#endif
+
+    // compute filter coefficients
+    liquid_firdes_kaiser(n,fc,As,_dt,_h);
+
+    // normalize coefficients
+    float e2 = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) e2 += _h[i]*_h[i];
+    for (i=0; i<n; i++) _h[i] *= sqrtf(_k/e2);
+}
+
+// Find approximate bandwidth adjustment factor rho based on
+// filter delay and desired excess bandwdith factor.
+//
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+float rkaiser_approximate_rho(unsigned int _m,
+                              float _beta)
+{
+    if ( _m < 1 ) {
+        fprintf(stderr,"error: rkaiser_approximate_rho(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: rkaiser_approximate_rho(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    // compute bandwidth adjustment estimate
+    float c0=0.0f, c1=0.0f, c2=0.0f;
+    switch (_m) {
+    case 1:     c0=0.75749731;  c1=0.06134303;  c2=-0.08729663; break;
+    case 2:     c0=0.81151861;  c1=0.07437658;  c2=-0.01427088; break;
+    case 3:     c0=0.84249538;  c1=0.07684185;  c2=-0.00536879; break;
+    case 4:     c0=0.86140782;  c1=0.07144126;  c2=-0.00558652; break;
+    case 5:     c0=0.87457740;  c1=0.06578694;  c2=-0.00650447; break;
+    case 6:     c0=0.88438797;  c1=0.06074265;  c2=-0.00736405; break;
+    case 7:     c0=0.89216620;  c1=0.05669236;  c2=-0.00791222; break;
+    case 8:     c0=0.89874983;  c1=0.05361696;  c2=-0.00815301; break;
+    case 9:     c0=0.90460032;  c1=0.05167952;  c2=-0.00807893; break;
+    case 10:    c0=0.91034430;  c1=0.05130753;  c2=-0.00746192; break;
+    case 11:    c0=0.91587675;  c1=0.05180436;  c2=-0.00670711; break;
+    case 12:    c0=0.92121875;  c1=0.05273801;  c2=-0.00588351; break;
+    case 13:    c0=0.92638195;  c1=0.05400764;  c2=-0.00508452; break;
+    case 14:    c0=0.93123555;  c1=0.05516163;  c2=-0.00437306; break;
+    case 15:    c0=0.93564993;  c1=0.05596561;  c2=-0.00388152; break;
+    case 16:    c0=0.93976742;  c1=0.05662274;  c2=-0.00348280; break;
+    case 17:    c0=0.94351703;  c1=0.05694120;  c2=-0.00318821; break;
+    case 18:    c0=0.94557273;  c1=0.05227591;  c2=-0.00400676; break;
+    case 19:    c0=0.95001614;  c1=0.05681641;  c2=-0.00300628; break;
+    case 20:    c0=0.95281708;  c1=0.05637607;  c2=-0.00304790; break;
+    case 21:    c0=0.95536256;  c1=0.05575880;  c2=-0.00312988; break;
+    case 22:    c0=0.95754206;  c1=0.05426060;  c2=-0.00385945; break;
+    default:
+        c0 =  0.056873*logf(_m+1e-3f) + 0.781388;
+        c1 =  0.05426f;
+        c2 = -0.00386f;
+    }
+
+    float b = logf(_beta);
+    float rho_hat = c0 + c1*b + c2*b*b;
+
+    // ensure estimate is in [0,1]
+    if (rho_hat < 0.0f) {
+        rho_hat = 0.0f;
+    } else if (rho_hat > 1.0f) {
+        rho_hat = 1.0f;
+    }
+
+    return rho_hat;
+}
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+void liquid_firdes_rkaiser_bisection(unsigned int _k,
+                                     unsigned int _m,
+                                     float _beta,
+                                     float _dt,
+                                     float * _h,
+                                     float * _rho)
+{
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_bisection(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_bisection(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_bisection(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    // algorithm:
+    //  1. choose three initial points [x0, x1, x2] where x0 < x1 < x2
+    //  2. choose xa = 0.5*(x0 + x1), bisection of [x0 x1]
+    //  3. choose xb = 0.5*(x1 + x2), bisection of [x1 x2]
+    //  4. x0 < xa < x1 < xb < x2
+    //  5. evaluate points to obtain [y0, ya, y1, yb, y2]
+    //  6. find minimum of three midpoints [ya, y1, yb]
+    //  7. update initial points [x0, x1, x2] and go to step 2
+
+    unsigned int i;
+
+    unsigned int n=2*_k*_m+1;   // filter length
+
+    // compute bandwidth adjustment estimate
+    float rho_hat = rkaiser_approximate_rho(_m,_beta);
+
+    // bandwidth adjustment
+    float x0 = 0.5f * rho_hat;  // lower bound, old: rho_hat*0.9f;
+    float x1 = rho_hat;         // midpoint: use initial estimate
+    float x2 = 1.0f;            // upper bound, old: 1.0f - 0.9f*(1.0f-rho_hat);
+    //x1 = 0.5f*(x0 + x2);      // bisect [x0,x1]
+
+    // evaluate performance (ISI) of each bandwidth adjustment
+    float y0 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x0,_h);
+    float y1 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x1,_h);
+    float y2 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x2,_h);
+
+    // run parabolic search to find bandwidth adjustment x_hat which
+    // minimizes the inter-symbol interference of the filter
+    unsigned int p, pmax=14;
+    float x_hat = rho_hat;
+    float xa, xb;
+    float ya, yb;
+#if DEBUG_RKAISER
+    FILE * fid = fopen("rkaiser_debug.m", "w");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"x = [%12.4e %12.4e %12.4e];\n", x0, x1, x2);
+    fprintf(fid,"y = [%12.4e %12.4e %12.4e];\n", y0, y1, y2);
+#endif
+    for (p=0; p<pmax; p++) {
+        // check bounding conditions: y1 should be less than y0 and y2
+        if (y1 > y0 || y1 > y2)
+            fprintf(stderr,"warning: liquid_firdes_rkaiser_bisection(): bounding region is ill-conditioned\n");
+
+        // choose midway points xa, xb and compute ISI
+        xa = 0.5f*(x0 + x1);    // bisect [x0,x1]
+        xb = 0.5f*(x1 + x2);    // bisect [x1,x2]
+        ya = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,xa,_h);
+        yb = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,xb,_h);
+
+#if DEBUG_RKAISER
+        fprintf(fid,"x = [x %12.4e %12.4e];\n", xa, xb);
+        fprintf(fid,"y = [y %12.4e %12.4e];\n", ya, yb);
+#endif
+
+        // find the minimum of [ya,y1,yb], update bounds
+        if (y1 < ya && y1 < yb) {
+            x0 = xa;    y0 = ya;
+            x2 = xb;    y2 = yb;
+        } else if (ya < yb) {
+            x2 = x1;    y2 = y1;
+            x1 = xa;    y1 = ya;
+        } else {
+            x0 = x1;    y0 = y1;
+            x1 = xb;    y1 = yb;
+        }
+
+        x_hat = x1;
+#if DEBUG_RKAISER
+        float y_hat = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x_hat,_h);
+        printf("  %4u : rho=%12.8f, isi=%12.6f dB\n", p+1, x_hat, 20*log10f(y_hat));
+#endif
+    };
+#if DEBUG_RKAISER
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(x,20*log10(y),'x');\n");
+    fclose(fid);
+    printf("results written to rkaiser_debug.m\n");
+#endif
+
+    // re-design filter with optimal value for rho
+    liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x_hat,_h);
+
+    // normalize filter magnitude
+    float e2 = 0.0f;
+    for (i=0; i<n; i++) e2 += _h[i]*_h[i];
+    for (i=0; i<n; i++) _h[i] *= sqrtf(_k/e2);
+
+    // save trasition bandwidth adjustment
+    *_rho = x_hat;
+}
+
+// Design frequency-shifted root-Nyquist filter based on
+// the Kaiser-windowed sinc using the quadratic search method.
+//
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _h      :   resulting filter [size: 2*_k*_m+1]
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+void liquid_firdes_rkaiser_quadratic(unsigned int _k,
+                                     unsigned int _m,
+                                     float _beta,
+                                     float _dt,
+                                     float * _h,
+                                     float * _rho)
+{
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_quadratic(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_quadratic(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_rkaiser_quadratic(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    // algorithm:
+    //  1. choose initial bounding points [x0,x2] where x0 < x2
+    //  2. choose x1 as bisection of [x0,x2]: x1 = 0.5*(x0+x2)
+    //  3. choose x_hat as solution to quadratic equation (x0,y0), (x1,y1), (x2,y2)
+    //  4. re-select boundary: (x0,y0) <- (x1,y1)   if x_hat > x1
+    //                         (x2,y2) <- (x1,y1)   otherwise
+    //  5. go to step 2
+
+    unsigned int i;
+
+    unsigned int n=2*_k*_m+1;   // filter length
+
+    // compute bandwidth adjustment estimate
+    float rho_hat = rkaiser_approximate_rho(_m,_beta);
+    float rho_opt = rho_hat;
+
+    // bandwidth adjustment
+    float x0;           // lower bound
+    float x1 = rho_hat; // initial estimate
+    float x2;           // upper bound
+
+    // evaluate performance (ISI) of each bandwidth adjustment
+    float y0;
+    float y1;
+    float y2;
+    float y_opt = 0.0f;
+
+    // run parabolic search to find bandwidth adjustment x_hat which
+    // minimizes the inter-symbol interference of the filter
+    unsigned int p, pmax=14;
+    float x_hat = rho_hat;
+    float dx  = 0.2f;       // bounding size
+    float del = 0.0f;       // computed step size
+    float tol = 1e-6f;      // tolerance
+#if DEBUG_RKAISER
+    FILE * fid = fopen("rkaiser_debug.m", "w");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"x = [];\n");
+    fprintf(fid,"y = [];\n");
+#endif
+    for (p=0; p<pmax; p++) {
+        // choose boundary points
+        x0 = x1 - dx;
+        x2 = x1 + dx;
+
+        // ensure boundary points are valid
+        if (x0 <= 0.0f) x0 = 0.01f;
+        if (x2 >= 1.0f) x2 = 0.99f;
+
+        // evaluate all points
+        y0 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x0,_h);
+        y1 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x1,_h);
+        y2 = liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,x2,_h);
+
+        // save optimum
+        if (p==0 || y1 < y_opt) {
+            rho_opt = x1;
+            y_opt   = y1;
+        }
+
+#if DEBUG_RKAISER
+        fprintf(fid,"x = [x %12.4e %12.4e %12.4e];\n", x0, x1, x2);
+        fprintf(fid,"y = [y %12.4e %12.4e %12.4e];\n", y0, y1, y2);
+        //printf("  %4u : [%8.4f %8.4f %8.4f] rho=%12.8f, isi=%12.6f dB\n", p+1, x0, x1, x2, x1, 20*log10f(y1));
+        printf("  %4u : [%8.4f,%8.2f] [%8.4f,%8.2f] [%8.4f,%8.2f]\n",
+                p+1,
+                x0, 20*log10f(y0),
+                x1, 20*log10f(y1),
+                x2, 20*log10f(y2));
+#endif
+        // compute minimum of quadratic function
+        double ta = y0*(x1*x1 - x2*x2) +
+                    y1*(x2*x2 - x0*x0) +
+                    y2*(x0*x0 - x1*x1);
+
+        double tb = y0*(x1 - x2) +
+                    y1*(x2 - x0) +
+                    y2*(x0 - x1);
+
+        // update estimate
+        x_hat = 0.5f * ta / tb;
+
+        // ensure x_hat is within boundary (this will fail if y1 > y0 || y1 > y2)
+        if (x_hat < x0 || x_hat > x2) {
+            //fprintf(stderr,"warning: liquid_firdes_rkaiser_quadratic(), quadratic minimum outside boundary\n");
+            break;
+        }
+
+        // break if step size is sufficiently small
+        del = x_hat - x1;
+        if (p > 3 && fabsf(del) < tol)
+            break;
+
+        // update estimate, reduce bound
+        x1 = x_hat;
+        dx *= 0.5f;
+    };
+#if DEBUG_RKAISER
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(x,20*log10(y),'x');\n");
+    fclose(fid);
+    printf("results written to rkaiser_debug.m\n");
+#endif
+
+    // re-design filter with optimal value for rho
+    liquid_firdes_rkaiser_internal_isi(_k,_m,_beta,_dt,rho_opt,_h);
+
+    // normalize filter magnitude
+    float e2 = 0.0f;
+    for (i=0; i<n; i++) e2 += _h[i]*_h[i];
+    for (i=0; i<n; i++) _h[i] *= sqrtf(_k/e2);
+
+    // save trasition bandwidth adjustment
+    *_rho = rho_opt;
+}
+
+// compute filter coefficients and determine resulting ISI
+//  
+//  _k      :   filter over-sampling rate (samples/symbol)
+//  _m      :   filter delay (symbols)
+//  _beta   :   filter excess bandwidth factor (0,1)
+//  _dt     :   filter fractional sample delay
+//  _rho    :   transition bandwidth adjustment, 0 < _rho < 1
+//  _h      :   filter buffer [size: 2*_k*_m+1]
+float liquid_firdes_rkaiser_internal_isi(unsigned int _k,
+                                         unsigned int _m,
+                                         float _beta,
+                                         float _dt,
+                                         float _rho,
+                                         float * _h)
+{
+    // validate input
+    if (_rho < 0.0f) {
+        fprintf(stderr,"warning: liquid_firdes_rkaiser_internal_isi(), rho < 0\n");
+    } else if (_rho > 1.0f) {
+        fprintf(stderr,"warning: liquid_firdes_rkaiser_internal_isi(), rho > 1\n");
+    }
+
+    unsigned int n=2*_k*_m+1;                   // filter length
+    float kf = (float)_k;                       // samples/symbol (float)
+    float del = _beta*_rho / kf;                // transition bandwidth
+    float As = estimate_req_filter_As(del, n);  // stop-band suppression
+    float fc = 0.5f*(1 + _beta*(1.0f-_rho))/kf; // filter cutoff
+
+    // evaluate performance (ISI)
+    float isi_max;
+    float isi_rms;
+
+    // compute filter
+    liquid_firdes_kaiser(n,fc,As,_dt,_h);
+
+    // compute filter ISI
+    liquid_filter_isi(_h,_k,_m,&isi_rms,&isi_max);
+
+    // return RMS of ISI
+    return isi_rms;
+}
+
+
diff --git a/src/filter/src/rrcos.c b/src/filter/src/rrcos.c
new file mode 100644
index 0000000..c707475
--- /dev/null
+++ b/src/filter/src/rrcos.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Design root raised-cosine filter
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Design root-Nyquist raised-cosine filter
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _dt     : fractional sample delay
+//  _h      : output coefficient buffer (length: 2*k*m+1)
+void liquid_firdes_rrcos(unsigned int _k,
+                         unsigned int _m,
+                         float _beta,
+                         float _dt,
+                         float * _h)
+{
+    if ( _k < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rrcos(): k must be greater than 0\n");
+        exit(1);
+    } else if ( _m < 1 ) {
+        fprintf(stderr,"error: liquid_firdes_rrcos(): m must be greater than 0\n");
+        exit(1);
+    } else if ( (_beta < 0.0f) || (_beta > 1.0f) ) {
+        fprintf(stderr,"error: liquid_firdes_rrcos(): beta must be in [0,1]\n");
+        exit(1);
+    } else;
+
+    unsigned int n;
+    float z, t1, t2, t3, t4, T=1.0f;
+
+    float nf, kf, mf;
+
+    unsigned int h_len = 2*_k*_m + 1;
+
+    // Calculate filter coefficients
+    for (n=0; n<h_len; n++) {
+        nf = (float) n;
+        kf = (float) _k;
+        mf = (float) _m;
+
+        z = (nf+_dt)/kf-mf;
+        t1 = cosf((1+_beta)*M_PI*z);
+        t2 = sinf((1-_beta)*M_PI*z);
+
+        // Check for special condition where z equals zero
+        if ( fabsf(z) < 1e-5 ) {
+            _h[n] = 1 - _beta + 4*_beta/M_PI;
+        } else {
+            t3 = 1/((4*_beta*z));
+
+            float g = 1-16*_beta*_beta*z*z;
+            g *= g;
+
+            // Check for special condition where 16*_beta^2*z^2 equals 1
+            if ( g < 1e-5 ) {
+                float g1, g2, g3, g4;
+                g1 = 1 + 2.0f/M_PI;
+                g2 = sinf(0.25f*M_PI/_beta);
+                g3 = 1 - 2.0f/M_PI;
+                g4 = cosf(0.25f*M_PI/_beta);
+                _h[n] = _beta/sqrtf(2.0f)*(g1*g2 + g3*g4);
+            } else {
+                t4 = 4*_beta/(M_PI*sqrtf(T)*(1-(16*_beta*_beta*z*z)));
+                _h[n] = t4*( t1 + (t2*t3) );
+            }
+        }
+    }
+}
+
diff --git a/src/filter/src/symsync.c b/src/filter/src/symsync.c
new file mode 100644
index 0000000..e709b04
--- /dev/null
+++ b/src/filter/src/symsync.c
@@ -0,0 +1,698 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Symbol synchronizer
+//
+// References:
+//  [Mengali:1997] Umberto Mengali and Aldo N. D'Andrea,
+//      "Synchronization Techniques for Digital Receivers,"
+//      Plenum Press, New York & London, 1997.
+//  [harris:2001] frederic j. harris and Michael Rice,
+//      "Multirate Digital Filters for Symbol Timing Synchronization
+//      in Software Defined Radios," IEEE Journal on Selected Areas
+//      of Communications, vol. 19, no. 12, December, 2001, pp.
+//      2346-2357.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define DEBUG_SYMSYNC           0
+#define DEBUG_SYMSYNC_PRINT     0
+#define DEBUG_SYMSYNC_FILENAME  "symsync_internal_debug.m"
+#define DEBUG_BUFFER_LEN        (1024)
+
+// 
+// forward declaration of internal methods
+//
+
+// step synchronizer
+//  _q      : symsync object
+//  _x      : input sample
+//  _y      : output sample array pointer
+//  _ny     : number of output samples written
+void SYMSYNC(_step)(SYMSYNC()      _q,
+                    TI             _x,
+                    TO *           _y,
+                    unsigned int * _ny);
+
+// advance synchronizer's internal loop filter
+//  _q      : synchronizer object
+//  _mf     : matched-filter output
+//  _dmf    : derivative matched-filter output
+void SYMSYNC(_advance_internal_loop)(SYMSYNC() _q,
+                                     TO        _mf,
+                                     TO        _dmf);
+
+// print results to output debugging file
+//  _q          : synchronizer object
+//  _filename   : output filename
+void SYMSYNC(_output_debug_file)(SYMSYNC()    _q,
+                                 const char * _filename);
+
+// internal structure
+struct SYMSYNC(_s) {
+    unsigned int h_len;         // matched filter length
+    unsigned int k;             // samples/symbol (input)
+    unsigned int k_out;         // samples/symbol (output)
+
+    unsigned int decim_counter; // decimation counter
+    int is_locked;              // synchronizer locked flag
+
+    float rate;                 // internal resampling rate
+    float del;                  // fractional delay step
+
+    // floating-point timing phase
+    float tau;                  // accumulated timing phase (0 <= tau <= 1)
+    float tau_decim;            // timing phase, retained for get_tau() method
+    float bf;                   // soft filterbank index
+    int   b;                    // filterbank index
+
+    // loop filter
+    float q;                    // instantaneous timing error
+    float q_hat;                // filtered timing error
+    float B[3];                 // loop filter feed-forward coefficients
+    float A[3];                 // loop filter feed-back coefficients
+    iirfiltsos_rrrf pll;        // loop filter object (iir filter)
+    float rate_adjustment;      // internal rate adjustment factor
+
+    unsigned int npfb;          // number of filters in the bank
+    FIRPFB()      mf;           // matched filter
+    FIRPFB()     dmf;           // derivative matched filter
+
+#if DEBUG_SYMSYNC
+    windowf debug_rate;
+    windowf debug_del;
+    windowf debug_tau;
+    windowf debug_bsoft;
+    windowf debug_b;
+    windowf debug_q_hat;
+#endif
+};
+
+// create synchronizer object from external coefficients
+//  _k      : samples per symbol
+//  _M      : number of filters in the bank
+//  _h      : matched filter coefficients
+//  _h_len  : length of matched filter
+SYMSYNC() SYMSYNC(_create)(unsigned int _k,
+                           unsigned int _M,
+                           TC *         _h,
+                           unsigned int _h_len)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: symsync_%s_create(), input sample rate must be at least 2\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_h_len == 0) {
+        fprintf(stderr,"error: symsync_%s_create(), filter length must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if ( (_h_len-1) % _M ) {
+        fprintf(stderr,"error: symsync_%s_create(), filter length must be of the form: h_len = m*_k*_M + 1 \n", EXTENSION_FULL);
+        exit(1);
+    } else if (_M == 0) {
+        fprintf(stderr,"error: symsync_%s_create(), number of filter banks must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create main object
+    SYMSYNC() q = (SYMSYNC()) malloc(sizeof(struct SYMSYNC(_s)));
+
+    // set internal properties
+    q->k    = _k;  // input samples per symbol
+    q->npfb = _M;  // number of filters in the polyphase filter bank
+
+    // set output rate (nominally 1, full decimation)
+    SYMSYNC(_set_output_rate)(q, 1);
+
+    // set internal sub-filter length
+    q->h_len = (_h_len-1)/q->npfb;
+    
+    // compute derivative filter
+    TC dh[_h_len];
+    float hdh_max = 0.0f;
+    unsigned int i;
+    for (i=0; i<_h_len; i++) {
+        if (i==0) {
+            dh[i] = _h[i+1] - _h[_h_len-1];
+        } else if (i==_h_len-1) {
+            dh[i] = _h[0]   - _h[i-1];
+        } else {
+            dh[i] = _h[i+1] - _h[i-1];
+        }
+
+        // find maximum of h*dh
+        if ( fabsf(_h[i]*dh[i]) > hdh_max || i==0 )
+            hdh_max = fabsf(_h[i]*dh[i]);
+    }
+
+    // apply scaling factor for normalized response
+    for (i=0; i<_h_len; i++)
+        dh[i] *= 0.06f / hdh_max;
+    
+    q->mf  = FIRPFB(_create)(q->npfb, _h, _h_len);
+    q->dmf = FIRPFB(_create)(q->npfb, dh, _h_len);
+
+    // reset state and initialize loop filter
+    q->A[0] = 1.0f;     q->B[0] = 0.0f;
+    q->A[1] = 0.0f;     q->B[1] = 0.0f;
+    q->A[2] = 0.0f;     q->B[2] = 0.0f;
+    q->pll = iirfiltsos_rrrf_create(q->B, q->A);
+    SYMSYNC(_reset)(q);
+    SYMSYNC(_set_lf_bw)(q, 0.01f);
+
+    // set output rate nominally at 1 sample/symbol (full decimation)
+    SYMSYNC(_set_output_rate)(q, 1);
+
+    // unlock loop control
+    SYMSYNC(_unlock)(q);
+
+#if DEBUG_SYMSYNC
+    q->debug_rate  = windowf_create(DEBUG_BUFFER_LEN);
+    q->debug_del   = windowf_create(DEBUG_BUFFER_LEN);
+    q->debug_tau   = windowf_create(DEBUG_BUFFER_LEN);
+    q->debug_bsoft = windowf_create(DEBUG_BUFFER_LEN);
+    q->debug_b     = windowf_create(DEBUG_BUFFER_LEN);
+    q->debug_q_hat = windowf_create(DEBUG_BUFFER_LEN);
+#endif
+
+    // return main object
+    return q;
+}
+
+// create square-root Nyquist symbol synchronizer
+//  _type   : filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _k      : samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor (0 < beta <= 1)
+//  _M      : number of filters in the bank
+SYMSYNC() SYMSYNC(_create_rnyquist)(int          _type,
+                                    unsigned int _k,
+                                    unsigned int _m,
+                                    float        _beta,
+                                    unsigned int _M)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: symsync_%s_create_rnyquist(), samples/symbol must be at least 2\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: symsync_%s_create_rnyquist(), filter delay (m) must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: symsync_%s_create_rnyquist(), filter excess bandwidth must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate memory for filter coefficients
+    unsigned int H_len = 2*_M*_k*_m + 1;
+    float Hf[H_len];
+
+    // design square-root Nyquist pulse-shaping filter
+    liquid_firdes_prototype(_type, _k*_M, _m, _beta, 0, Hf);
+
+    // copy coefficients to type-specific array
+    TC H[H_len];
+    unsigned int i;
+    for (i=0; i<H_len; i++)
+        H[i] = Hf[i];
+
+    // create object and return
+    return SYMSYNC(_create)(_k, _M, H, H_len);
+}
+
+// create symsync using Kaiser filter interpolator; useful
+// when the input signal has matched filter applied already
+//  _k      : input samples/symbol
+//  _m      : symbol delay
+//  _beta   : rolloff factor, beta in (0,1]
+//  _M      : number of filters in the bank
+SYMSYNC() SYMSYNC(_create_kaiser)(unsigned int _k,
+                                  unsigned int _m,
+                                  float        _beta,
+                                  unsigned int _M)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: symsync_%s_create_kaiser(), samples/symbol must be at least 2\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: symsync_%s_create_kaiser(), filter delay (m) must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: symsync_%s_create_kaiser(), filter excess bandwidth must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate memory for filter coefficients
+    unsigned int H_len = 2*_M*_k*_m + 1;
+    float Hf[H_len];
+
+    // design interpolating filter whose bandwidth is outside the cut-off
+    // frequency of input signal
+    // TODO: use _beta to compute more accurate cut-off frequency
+    float fc = 0.75f;   // filter cut-off frequency (nominal)
+    float As = 40.0f;   // filter stop-band attenuation
+    liquid_firdes_kaiser(H_len, fc / (float)(_k*_M), As, 0.0f, Hf);
+
+    // copy coefficients to type-specific array, adjusting to relative
+    // filter gain
+    unsigned int i;
+    TC H[H_len];
+    for (i=0; i<H_len; i++)
+        H[i] = Hf[i] * 2.0f * fc;
+
+    // create object and return
+    return SYMSYNC(_create)(_k, _M, H, H_len);
+}
+
+// destroy symsync object, freeing all internal memory
+void SYMSYNC(_destroy)(SYMSYNC() _q)
+{
+#if DEBUG_SYMSYNC
+    // output debugging file
+    SYMSYNC(_output_debug_file)(_q, DEBUG_SYMSYNC_FILENAME);
+
+    // destroy internal window objects
+    windowf_destroy(_q->debug_rate);
+    windowf_destroy(_q->debug_del);
+    windowf_destroy(_q->debug_tau);
+    windowf_destroy(_q->debug_bsoft);
+    windowf_destroy(_q->debug_b);
+    windowf_destroy(_q->debug_q_hat);
+#endif
+
+    // destroy filterbank objects
+    FIRPFB(_destroy)(_q->mf);
+    FIRPFB(_destroy)(_q->dmf);
+
+    // destroy timing phase-locked loop filter
+    iirfiltsos_rrrf_destroy(_q->pll);
+
+    // free main object memory
+    free(_q);
+}
+
+// print symsync object's parameters
+void SYMSYNC(_print)(SYMSYNC() _q)
+{
+    printf("symsync_%s [rate: %f]\n", EXTENSION_FULL, _q->rate);
+    FIRPFB(_print)(_q->mf);
+}
+
+// reset symsync internal state
+void SYMSYNC(_reset)(SYMSYNC() _q)
+{
+    // reset polyphase filterbank
+    FIRPFB(_reset)(_q->mf);
+
+    // reset counters, etc.
+    _q->rate          = (float)_q->k / (float)_q->k_out;
+    _q->del           = _q->rate;
+    _q->b             =   0;    // filterbank index
+    _q->bf            = 0.0f;   // filterbank index (soft value)
+    _q->tau           = 0.0f;   // instantaneous timing estimate
+    _q->tau_decim     = 0.0f;   // instantaneous timing estaimte (decimated)
+    _q->q             = 0.0f;   // timing error
+    _q->q_hat         = 0.0f;   // filtered timing error
+    _q->decim_counter = 0;      // decimated output counter
+
+    // reset timing phase-locked loop filter
+    iirfiltsos_rrrf_reset(_q->pll);
+}
+
+// lock synchronizer object
+void SYMSYNC(_lock)(SYMSYNC() _q)
+{
+    _q->is_locked = 1;
+}
+
+// unlock synchronizer object
+void SYMSYNC(_unlock)(SYMSYNC() _q)
+{
+    _q->is_locked = 0;
+}
+
+// set synchronizer output rate (samples/symbol)
+//  _q      :   synchronizer object
+//  _k_out  :   output samples/symbol
+void SYMSYNC(_set_output_rate)(SYMSYNC()    _q,
+                               unsigned int _k_out)
+{
+    // validate input
+    if (_k_out == 0) {
+        fprintf(stderr,"error: symsync_%s_output_rate(), output rate must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // set output rate
+    _q->k_out = _k_out;
+
+    _q->rate = (float)_q->k / (float)_q->k_out;
+    _q->del  = _q->rate;
+}
+
+// set synchronizer loop filter bandwidth
+//  _q      :   synchronizer object
+//  _bt     :   loop bandwidth
+void SYMSYNC(_set_lf_bw)(SYMSYNC() _q,
+                         float     _bt)
+{
+    // validate input
+    if (_bt < 0.0f || _bt > 1.0f) {
+        fprintf(stderr,"error: symsync_%s_set_lf_bt(), bandwidth must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute filter coefficients from bandwidth
+    float alpha = 1.000f - _bt;
+    float beta  = 0.220f * _bt;
+    float a     = 0.500f;
+    float b     = 0.495f;
+
+    _q->B[0] = beta;
+    _q->B[1] = 0.00f;
+    _q->B[2] = 0.00f;
+
+    _q->A[0] = 1.00f - a*alpha;
+    _q->A[1] = -b*alpha;
+    _q->A[2] = 0.00f;
+
+    // set internal parameters of 2nd-order IIR filter
+    iirfiltsos_rrrf_set_coefficients(_q->pll, _q->B, _q->A);
+    
+    // update rate adjustment factor
+    _q->rate_adjustment = 0.5*_bt;
+}
+
+// return instantaneous fractional timing offset estimate
+float SYMSYNC(_get_tau)(SYMSYNC() _q)
+{
+    return _q->tau_decim;
+}
+
+// execute synchronizer on input data array
+//  _q      : synchronizer object
+//  _x      : input data array
+//  _nx     : number of input samples
+//  _y      : output data array
+//  _ny     : number of samples written to output buffer
+void SYMSYNC(_execute)(SYMSYNC()      _q,
+                       TI *           _x,
+                       unsigned int   _nx,
+                       TO *           _y,
+                       unsigned int * _ny)
+{
+    unsigned int i, ny=0, k=0;
+    for (i=0; i<_nx; i++) {
+        SYMSYNC(_step)(_q, _x[i], &_y[ny], &k);
+        ny += k;
+        //printf("%u\n",k);
+    }
+    *_ny = ny;
+}
+
+//
+// internal methods
+//
+
+// step synchronizer with single input sample
+//  _q      : symsync object
+//  _x      : input sample
+//  _y      : output sample array pointer
+//  _ny     : number of output samples written
+void SYMSYNC(_step)(SYMSYNC()      _q,
+                    TI             _x,
+                    TO *           _y,
+                    unsigned int * _ny)
+{
+    // push sample into MF and dMF filterbanks
+    FIRPFB(_push)(_q->mf,  _x);
+    FIRPFB(_push)(_q->dmf, _x);
+    
+    // matched and derivative matched-filter outputs
+    TO  mf; // matched filter output
+    TO dmf; // derivative matched filter output
+
+    unsigned int n=0;
+    
+    // continue loop until filterbank index rolls over
+    while (_q->b < _q->npfb) {
+
+#if DEBUG_SYMSYNC_PRINT
+        printf("  [%2u] : tau : %12.8f, b : %4u (%12.8f)\n", n, _q->tau, _q->b, _q->bf);
+#endif
+
+        // compute filterbank output
+        FIRPFB(_execute)(_q->mf, _q->b, &mf);
+
+        // scale output by samples/symbol
+        _y[n] = mf / (float)(_q->k);
+
+        // check output count and determine if this is 'ideal' timing output
+        if (_q->decim_counter == _q->k_out) {
+            // reset counter
+            _q->decim_counter = 0;
+
+#if DEBUG_SYMSYNC
+            // save debugging variables
+            windowf_push(_q->debug_rate,   _q->rate);
+            windowf_push(_q->debug_del,    _q->del);
+            windowf_push(_q->debug_tau,    _q->tau);
+            windowf_push(_q->debug_bsoft,  _q->bf);
+            windowf_push(_q->debug_b,      _q->b);
+            windowf_push(_q->debug_q_hat,  _q->q_hat);
+#endif
+
+            // if synchronizer is locked, don't update internal timing offset
+            if (_q->is_locked)
+                continue;
+
+            // compute dMF output
+            FIRPFB(_execute)(_q->dmf, _q->b, &dmf);
+            
+            // update internal state
+            SYMSYNC(_advance_internal_loop)(_q, mf, dmf);
+            _q->tau_decim = _q->tau;    // save return value
+        }
+
+        // increment decimation counter
+        _q->decim_counter++;
+
+        // update states
+        _q->tau += _q->del;                     // instantaneous fractional offset
+        _q->bf  = _q->tau * (float)(_q->npfb);  // filterbank index (soft)
+        _q->b   = (int)roundf(_q->bf);          // filterbank index
+        n++;                                    // number of output samples
+    }
+
+    // filterbank index rolled over; update states
+    _q->tau -= 1.0f;                // instantaneous fractional offset
+    _q->bf  -= (float)(_q->npfb);   // filterbank index (soft)
+    _q->b   -= _q->npfb;            // filterbank index
+
+    // set output number of samples written
+    *_ny = n;
+}
+
+// advance synchronizer's internal loop filter
+//  _q      : synchronizer object
+//  _mf     : matched-filter output
+//  _dmf    : derivative matched-filter output
+void SYMSYNC(_advance_internal_loop)(SYMSYNC() _q,
+                                     TO        _mf,
+                                     TO        _dmf)
+{
+    //  1. compute timing error signal, clipping large levels
+#if 0
+    _q->q = crealf(_mf)*crealf(_dmf) + cimagf(_mf)*cimagf(_dmf);
+#else
+    // TODO : use more efficient method to compute this
+    _q->q = crealf( conjf(_mf)*_dmf );  // [Mengali:1997] Eq.~(8.3.5)
+#endif
+    // constrain timing error
+    if      (_q->q >  1.0f) _q->q =  1.0f;  // clip large positive values
+    else if (_q->q < -1.0f) _q->q = -1.0f;  // clip large negative values
+
+    //  2. filter error signal through timing loop filter: retain large
+    //     portion of old estimate and small percent of new estimate
+    iirfiltsos_rrrf_execute(_q->pll, _q->q, &_q->q_hat);
+
+    // 3. update rate and timing phase
+    _q->rate += _q->rate_adjustment * _q->q_hat;
+    _q->del   = _q->rate + _q->q_hat;
+
+#if DEBUG_SYMSYNC_PRINT
+    printf("q : %12.8f, rate : %12.8f, del : %12.8f, q_hat : %12.8f\n", _q->q, _q->rate, _q->del, _q->q_hat);
+#endif
+}
+
+// print results to output debugging file
+//  _q          : synchronizer object
+//  _filename   : output filename
+void SYMSYNC(_output_debug_file)(SYMSYNC()    _q,
+                                 const char * _filename)
+{
+    FILE * fid = fopen(_filename, "w");
+    if (!fid) {
+        fprintf(stderr,"error: symsync_%s_output_debug_file(), could not open '%s' for writing\n", EXTENSION_FULL, _filename);
+        return;
+    }
+    fprintf(fid,"%% %s, auto-generated file\n\n", DEBUG_SYMSYNC_FILENAME);
+    fprintf(fid,"\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+
+    fprintf(fid,"npfb = %u;\n",_q->npfb);
+    fprintf(fid,"k    = %u;\n",_q->k);
+    fprintf(fid,"\n\n");
+
+#if DEBUG_SYMSYNC
+    fprintf(fid,"n = %u;\n", DEBUG_BUFFER_LEN);
+    float * r;
+    unsigned int i;
+
+    // save filter responses
+    FIRPFB(_reset)(_q->mf);
+    FIRPFB(_reset)(_q->dmf);
+    fprintf(fid,"h = [];\n");
+    fprintf(fid,"dh = [];\n");
+    fprintf(fid,"h_len = %u;\n", _q->h_len);
+    for (i=0; i<_q->h_len; i++) {
+        // push impulse
+        if (i==0) {
+            FIRPFB(_push)(_q->mf,  1.0f);
+            FIRPFB(_push)(_q->dmf, 1.0f);
+        } else {
+            FIRPFB(_push)(_q->mf,  0.0f);
+            FIRPFB(_push)(_q->dmf, 0.0f);
+        }
+
+        // compute output for all filters
+        TO  mf;     // matched filter output
+        TO dmf;     // derivative matched filter output
+
+        unsigned int n;
+        for (n=0; n<_q->npfb; n++) {
+            FIRPFB(_execute)(_q->mf,  n, &mf);
+            FIRPFB(_execute)(_q->dmf, n, &dmf);
+
+            fprintf(fid,"h(%4u) = %12.8f; dh(%4u) = %12.8f;\n", i*_q->npfb+n+1, crealf(mf), i*_q->npfb+n+1, crealf(dmf));
+        }
+    }
+    // plot response
+    fprintf(fid,"\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"th = [0:(h_len*npfb-1)]/(k*npfb) - h_len/(2*k);\n");
+    fprintf(fid,"m  = abs(round(th(1)));\n");
+    //fprintf(fid,"plot(t,h,t,dh);\n");
+    fprintf(fid,"subplot(3,1,1),\n");
+    fprintf(fid,"  plot(th, h, 'LineWidth', 2, 'Color', [0 0.5 0.2]);\n");
+    fprintf(fid,"  ylabel('MF');\n");
+    fprintf(fid,"  axis([-m m -0.25 1.25]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2),\n");
+    fprintf(fid,"  plot(th, dh, 'LineWidth', 2, 'Color', [0 0.2 0.5]);\n");
+    fprintf(fid,"  ylabel('dMF');\n");
+    fprintf(fid,"  axis([-m m -0.10 0.10]);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3),\n");
+    fprintf(fid,"  plot(th,-h.*dh, 'LineWidth', 2, 'Color', [0.5 0 0]);\n");
+    fprintf(fid,"  xlabel('Symbol Index');\n");
+    fprintf(fid,"  ylabel('-MF*dMF');\n");
+    fprintf(fid,"  axis([-m m -0.08 0.08]);\n");
+    fprintf(fid,"  grid on;\n");
+
+    // print rate buffer
+    fprintf(fid,"rate = zeros(1,n);\n");
+    windowf_read(_q->debug_rate, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"rate(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    // print del buffer
+    fprintf(fid,"del = zeros(1,n);\n");
+    windowf_read(_q->debug_del, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"del(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    // print tau buffer
+    fprintf(fid,"tau = zeros(1,n);\n");
+    windowf_read(_q->debug_tau, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"tau(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    // print bsoft buffer
+    fprintf(fid,"bf = zeros(1,n);\n");
+    windowf_read(_q->debug_bsoft, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"bf(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    // print b (filterbank index) buffer
+    fprintf(fid,"b = zeros(1,n);\n");
+    windowf_read(_q->debug_b, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"b(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    // print filtered error signal
+    fprintf(fid,"q_hat = zeros(1,n);\n");
+    windowf_read(_q->debug_q_hat, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"q_hat(%4u) = %12.8f;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"t=1:n;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,b,'Color',[0.5 0.5 0.5]);\n");
+    fprintf(fid,"  plot(t,bf,'LineWidth',2,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([t(1) t(end) -1 npfb]);\n");
+    fprintf(fid,"  legend('b','b (soft)',0);\n");
+    fprintf(fid,"  xlabel('Symbol Index')\n");
+    fprintf(fid,"  ylabel('Polyphase Filter Index')\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  hold on;\n");
+    fprintf(fid,"  plot(t,rate,'LineWidth',2,'Color',[0 0.25 0.5]);\n");
+    fprintf(fid,"  hold off;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  axis([t(1) t(end) 0.99 1.01]);\n");
+    fprintf(fid,"  xlabel('Symbol Index')\n");
+    fprintf(fid,"  ylabel('Rate')\n");
+    fprintf(fid,"%% done.\n");
+#endif
+
+    fclose(fid);
+    printf("symsync: internal results written to '%s'\n", _filename);
+}
+
+
diff --git a/src/filter/tests/data/fftfilt_cccf_data_h13x256.c b/src/filter/tests/data/fftfilt_cccf_data_h13x256.c
new file mode 100644
index 0000000..e6d507f
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_cccf_data_h13x256.c
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_cccf_data_h13x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float complex fftfilt_cccf_data_h13x256_h[] = {
+    0.101274943352 +  -0.122793912888*_Complex_I,
+    0.058305418491 +  -0.133282411098*_Complex_I,
+    0.040646758676 +  -0.024372637272*_Complex_I,
+    0.079661983252 +  -0.054072910547*_Complex_I,
+    0.001840994880 +   0.061087465286*_Complex_I,
+   -0.087824678421 +  -0.017600339651*_Complex_I,
+    0.218935108185 +  -0.191448485851*_Complex_I,
+    0.063519352674 +   0.238570642471*_Complex_I,
+    0.062901532650 +   0.024227905273*_Complex_I,
+    0.047201344371 +  -0.122910189629*_Complex_I,
+   -0.052710837126 +  -0.028337442875*_Complex_I,
+    0.155722010136 +  -0.206684422493*_Complex_I,
+    0.158807659149 +  -0.032457110286*_Complex_I};
+
+float complex fftfilt_cccf_data_h13x256_x[] = {
+    0.033812573552 +   0.167574334145*_Complex_I,
+   -0.180972433090 +   0.003268009797*_Complex_I,
+   -0.158811461926 +   0.096975576878*_Complex_I,
+    0.089864820242 +  -0.129931354523*_Complex_I,
+   -0.048388853669 +   0.209818410873*_Complex_I,
+   -0.174670159817 +  -0.000558722531*_Complex_I,
+    0.184307169914 +  -0.097096782923*_Complex_I,
+    0.055431371927 +  -0.085229063034*_Complex_I,
+   -0.005678167194 +  -0.008018184453*_Complex_I,
+   -0.069053018093 +   0.055740833282*_Complex_I,
+   -0.064427644014 +   0.147326159477*_Complex_I,
+    0.257856726646 +  -0.008164577186*_Complex_I,
+   -0.001989185810 +  -0.019182929397*_Complex_I,
+    0.034131622314 +  -0.019393232465*_Complex_I,
+    0.107628858089 +   0.122120463848*_Complex_I,
+    0.023927879333 +   0.069706928730*_Complex_I,
+   -0.053020107746 +  -0.083087205887*_Complex_I,
+   -0.080838572979 +  -0.011525543034*_Complex_I,
+    0.031260085106 +   0.001133395731*_Complex_I,
+   -0.001233685296 +  -0.189275312424*_Complex_I,
+   -0.038783121109 +   0.028758850694*_Complex_I,
+    0.036648643017 +  -0.011882914603*_Complex_I,
+   -0.020449468493 +   0.139192247391*_Complex_I,
+   -0.075582921505 +  -0.002107951231*_Complex_I,
+   -0.181470632553 +  -0.014771178365*_Complex_I,
+    0.026783564687 +  -0.128984892368*_Complex_I,
+   -0.047539860010 +   0.222328519821*_Complex_I,
+    0.005170624331 +   0.030745416880*_Complex_I,
+    0.053824990988 +  -0.026686629653*_Complex_I,
+    0.099266970158 +  -0.031245106459*_Complex_I,
+    0.039606907964 +  -0.065418809652*_Complex_I,
+   -0.127475917339 +   0.064139616489*_Complex_I,
+   -0.042834937572 +  -0.150583517551*_Complex_I,
+    0.139883732796 +   0.082940441370*_Complex_I,
+   -0.064364117384 +  -0.044677674770*_Complex_I,
+    0.055855596066 +   0.017247803509*_Complex_I,
+   -0.173977994919 +  -0.052533900738*_Complex_I,
+    0.027730098367 +   0.080870586634*_Complex_I,
+   -0.091971349716 +  -0.051875424385*_Complex_I,
+    0.051230835915 +  -0.061945867538*_Complex_I,
+   -0.044583868980 +  -0.032021436095*_Complex_I,
+    0.068678015471 +  -0.015937516093*_Complex_I,
+    0.019641043246 +   0.090001666546*_Complex_I,
+   -0.050730788708 +  -0.067964708805*_Complex_I,
+   -0.117348599434 +   0.225804829597*_Complex_I,
+   -0.024700947106 +  -0.226277422905*_Complex_I,
+    0.044463178515 +   0.038473030925*_Complex_I,
+    0.146118962765 +   0.228452873230*_Complex_I,
+    0.148312568665 +  -0.089482551813*_Complex_I,
+    0.002765571699 +   0.218297338486*_Complex_I,
+   -0.058677202463 +  -0.040143096447*_Complex_I,
+    0.151873219013 +   0.038929876685*_Complex_I,
+   -0.054030978680 +   0.087771093845*_Complex_I,
+    0.053254860640 +  -0.107342064381*_Complex_I,
+   -0.004281488806 +  -0.147849392891*_Complex_I,
+    0.124097406864 +  -0.161590623856*_Complex_I,
+   -0.083864957094 +   0.077308541536*_Complex_I,
+    0.091537797451 +   0.140995931625*_Complex_I,
+   -0.099840551615 +   0.043124458194*_Complex_I,
+   -0.121636021137 +   0.081380259991*_Complex_I,
+   -0.011938279122 +   0.019365566969*_Complex_I,
+   -0.029302325845 +  -0.002548613772*_Complex_I,
+    0.039761963487 +   0.151991939545*_Complex_I,
+    0.057192808390 +  -0.059029465914*_Complex_I,
+   -0.095872926712 +  -0.044790551066*_Complex_I,
+    0.095753777027 +  -0.164024686813*_Complex_I,
+    0.037644055486 +  -0.033171021938*_Complex_I,
+    0.071704179049 +   0.047472149134*_Complex_I,
+   -0.072857999802 +   0.045551717281*_Complex_I,
+   -0.004142929241 +  -0.048290520906*_Complex_I,
+   -0.180688798428 +   0.006293178350*_Complex_I,
+    0.038382813334 +   0.122165417671*_Complex_I,
+   -0.050295007229 +  -0.127239310741*_Complex_I,
+   -0.109994089603 +  -0.176806294918*_Complex_I,
+    0.085634392500 +  -0.091329562664*_Complex_I,
+   -0.137377023697 +   0.066401362419*_Complex_I,
+    0.109237027168 +  -0.000195262639*_Complex_I,
+   -0.111181199551 +   0.181575310230*_Complex_I,
+   -0.088704961538 +   0.101208889484*_Complex_I,
+    0.227800679207 +   0.017428271472*_Complex_I,
+   -0.072724056244 +  -0.158883297443*_Complex_I,
+   -0.046088048816 +   0.034102421999*_Complex_I,
+   -0.086277353764 +   0.048207068443*_Complex_I,
+    0.189791500568 +   0.111536753178*_Complex_I,
+    0.043753921986 +   0.042386168242*_Complex_I,
+    0.175305640697 +  -0.077729618549*_Complex_I,
+   -0.090353775024 +  -0.086115002632*_Complex_I,
+    0.163218414783 +   0.005952451378*_Complex_I,
+   -0.010694872588 +   0.037079387903*_Complex_I,
+   -0.067783647776 +   0.015387859941*_Complex_I,
+   -0.001508052554 +   0.005860465765*_Complex_I,
+   -0.103225553036 +  -0.091570162773*_Complex_I,
+   -0.181873428822 +  -0.033351799846*_Complex_I,
+    0.014190819860 +  -0.107816028595*_Complex_I,
+    0.049625259638 +  -0.131252551079*_Complex_I,
+   -0.003888563812 +  -0.083181303740*_Complex_I,
+   -0.044093203545 +   0.053719127178*_Complex_I,
+   -0.059636509418 +  -0.062606734037*_Complex_I,
+    0.043757092953 +   0.143932354450*_Complex_I,
+    0.023032055795 +  -0.073675203323*_Complex_I,
+    0.074079465866 +  -0.133735966682*_Complex_I,
+   -0.016778314114 +  -0.077572107315*_Complex_I,
+   -0.213720870018 +  -0.010327737033*_Complex_I,
+   -0.011057607085 +   0.039916378260*_Complex_I,
+    0.108575832844 +  -0.097652089596*_Complex_I,
+    0.169485592842 +   0.195236229897*_Complex_I,
+   -0.094353854656 +  -0.053460478783*_Complex_I,
+   -0.111175501347 +  -0.177497363091*_Complex_I,
+    0.004201298580 +  -0.125308763981*_Complex_I,
+    0.051966518164 +   0.123850774765*_Complex_I,
+   -0.096300393343 +  -0.013914050162*_Complex_I,
+    0.106172311306 +  -0.164844048023*_Complex_I,
+    0.124734151363 +  -0.008266538382*_Complex_I,
+   -0.008394107223 +   0.042837706208*_Complex_I,
+   -0.269948434830 +  -0.133680295944*_Complex_I,
+    0.077266824245 +   0.018269205093*_Complex_I,
+   -0.018016268313 +  -0.040084049106*_Complex_I,
+    0.095163732767 +   0.081313228607*_Complex_I,
+   -0.208993816376 +  -0.040263220668*_Complex_I,
+    0.064939141273 +  -0.018769615889*_Complex_I,
+   -0.037253284454 +  -0.092296826839*_Complex_I,
+    0.123162364960 +   0.068240380287*_Complex_I,
+   -0.132501530647 +  -0.228929615021*_Complex_I,
+    0.168357527256 +  -0.092901027203*_Complex_I,
+    0.088688296080 +  -0.001608422026*_Complex_I,
+    0.221683001518 +   0.066785657406*_Complex_I,
+    0.083319008350 +  -0.164619421959*_Complex_I,
+   -0.057560896873 +   0.080689120293*_Complex_I,
+    0.017244935036 +  -0.052605807781*_Complex_I,
+   -0.103263401985 +   0.128316771984*_Complex_I,
+   -0.048902520537 +   0.101460754871*_Complex_I,
+   -0.101036417484 +   0.059622770548*_Complex_I,
+   -0.006965886056 +   0.062334090471*_Complex_I,
+    0.098089504242 +   0.189659333229*_Complex_I,
+   -0.170798206329 +   0.049062070251*_Complex_I,
+    0.165300846100 +   0.029215490818*_Complex_I,
+   -0.075640136003 +  -0.087369698286*_Complex_I,
+   -0.013823843002 +   0.127059864998*_Complex_I,
+    0.095502263308 +   0.068228989840*_Complex_I,
+    0.171380794048 +  -0.077519750595*_Complex_I,
+    0.129892742634 +  -0.012643009424*_Complex_I,
+    0.023339818418 +   0.093716734648*_Complex_I,
+   -0.058398854733 +   0.106715428829*_Complex_I,
+   -0.013301552832 +   0.002579919435*_Complex_I,
+    0.088806277514 +  -0.297042322159*_Complex_I,
+   -0.046128720045 +   0.126318335533*_Complex_I,
+    0.051016080379 +  -0.158195829391*_Complex_I,
+   -0.137489342690 +   0.012126408517*_Complex_I,
+    0.076124078035 +   0.094534236193*_Complex_I,
+    0.055480271578 +   0.034476712346*_Complex_I,
+    0.197368288040 +  -0.003630393744*_Complex_I,
+   -0.132637274265 +   0.016389763355*_Complex_I,
+   -0.018736995757 +   0.069434946775*_Complex_I,
+    0.024380022287 +   0.032195645571*_Complex_I,
+   -0.110287964344 +   0.105914473534*_Complex_I,
+    0.180047070980 +   0.122122013569*_Complex_I,
+    0.021164430678 +  -0.021511299908*_Complex_I,
+   -0.083895099163 +  -0.094187325239*_Complex_I,
+   -0.085609924793 +  -0.089779102802*_Complex_I,
+   -0.026364889741 +  -0.034728026390*_Complex_I,
+   -0.039741188288 +   0.061549043655*_Complex_I,
+   -0.182061326504 +  -0.117300724983*_Complex_I,
+    0.051411151886 +  -0.039550358057*_Complex_I,
+    0.115115880966 +   0.132340645790*_Complex_I,
+   -0.172311210632 +   0.157257282734*_Complex_I,
+   -0.066816413403 +   0.050810027122*_Complex_I,
+    0.075493675470 +  -0.024358917773*_Complex_I,
+    0.037579393387 +  -0.105334246159*_Complex_I,
+   -0.032166060805 +   0.304082226753*_Complex_I,
+   -0.115465772152 +  -0.083099174500*_Complex_I,
+    0.213583827019 +  -0.033120486140*_Complex_I,
+    0.049020007253 +   0.103692758083*_Complex_I,
+    0.039526313543 +   0.159559214115*_Complex_I,
+   -0.014911358058 +  -0.009031812847*_Complex_I,
+    0.140197467804 +   0.188218057156*_Complex_I,
+   -0.014860840142 +  -0.114006638527*_Complex_I,
+    0.111025202274 +  -0.101571118832*_Complex_I,
+   -0.031162160635 +  -0.044938939810*_Complex_I,
+   -0.088898491859 +  -0.196167945862*_Complex_I,
+    0.074709993601 +  -0.042812746763*_Complex_I,
+    0.020372083783 +  -0.102141439915*_Complex_I,
+    0.036488103867 +   0.061840379238*_Complex_I,
+    0.113381445408 +  -0.077737921476*_Complex_I,
+    0.003184536844 +   0.024977919459*_Complex_I,
+   -0.130429613590 +  -0.062496966124*_Complex_I,
+   -0.109437787533 +   0.033421832323*_Complex_I,
+   -0.107781338692 +  -0.036825361848*_Complex_I,
+   -0.121548032761 +  -0.071893042326*_Complex_I,
+   -0.032831138372 +   0.036911919713*_Complex_I,
+    0.101628661156 +   0.116993212700*_Complex_I,
+   -0.052929800749 +  -0.061160600185*_Complex_I,
+   -0.155849313736 +  -0.126586461067*_Complex_I,
+   -0.093626201153 +  -0.085899829865*_Complex_I,
+    0.093120694160 +   0.010586068779*_Complex_I,
+   -0.071643483639 +   0.044553604722*_Complex_I,
+    0.096362644434 +  -0.005238156021*_Complex_I,
+   -0.191972267628 +   0.062410986423*_Complex_I,
+    0.175240826607 +   0.055103266239*_Complex_I,
+    0.217493343353 +   0.057524204254*_Complex_I,
+    0.137563228607 +   0.136677563190*_Complex_I,
+   -0.002121994831 +  -0.160035789013*_Complex_I,
+   -0.106702613831 +   0.129322338104*_Complex_I,
+    0.077257323265 +   0.190817809105*_Complex_I,
+    0.030118304491 +   0.068840289116*_Complex_I,
+    0.001969731040 +  -0.019872169197*_Complex_I,
+    0.038756659627 +  -0.110168564320*_Complex_I,
+   -0.094096481800 +   0.178715145588*_Complex_I,
+   -0.045212453604 +  -0.001988123916*_Complex_I,
+   -0.050746786594 +  -0.038362777233*_Complex_I,
+    0.085383939743 +   0.041049334407*_Complex_I,
+   -0.044858980179 +   0.073414093256*_Complex_I,
+    0.074703049660 +   0.103927171230*_Complex_I,
+    0.055366307497 +  -0.115881478786*_Complex_I,
+    0.133115530014 +  -0.054990023375*_Complex_I,
+   -0.060879433155 +  -0.052990645170*_Complex_I,
+   -0.014458604157 +   0.024620576203*_Complex_I,
+    0.035869082808 +   0.070715308189*_Complex_I,
+    0.001854417846 +  -0.032330670953*_Complex_I,
+    0.217453384399 +  -0.031029167771*_Complex_I,
+   -0.012186873704 +   0.007021924853*_Complex_I,
+    0.082451862097 +  -0.078747928143*_Complex_I,
+   -0.079927939177 +  -0.026517114043*_Complex_I,
+   -0.049421241879 +  -0.003672285378*_Complex_I,
+   -0.050635516644 +  -0.204359102249*_Complex_I,
+    0.114336001873 +  -0.075574308634*_Complex_I,
+    0.017275553942 +  -0.004209178686*_Complex_I,
+    0.093194973469 +  -0.117229437828*_Complex_I,
+    0.055422365665 +   0.058078074455*_Complex_I,
+    0.051127582788 +  -0.056875407696*_Complex_I,
+   -0.033710032701 +  -0.024449241161*_Complex_I,
+    0.098715597391 +   0.075275701284*_Complex_I,
+    0.049691435695 +  -0.010312996805*_Complex_I,
+   -0.143938088417 +  -0.057803511620*_Complex_I,
+    0.004402134567 +  -0.171495795250*_Complex_I,
+    0.050372958183 +  -0.030280271173*_Complex_I,
+   -0.066968792677 +   0.049716952443*_Complex_I,
+   -0.030198466778 +  -0.093219310045*_Complex_I,
+    0.020674671233 +   0.049302938581*_Complex_I,
+   -0.082176977396 +   0.004003078118*_Complex_I,
+    0.051144361496 +  -0.025944027305*_Complex_I,
+   -0.136934876442 +   0.030611222982*_Complex_I,
+    0.072055798769 +   0.072714412212*_Complex_I,
+    0.006607705355 +  -0.154940152168*_Complex_I,
+   -0.055419456959 +   0.142084217072*_Complex_I,
+    0.127882242203 +  -0.068437719345*_Complex_I,
+   -0.028541901708 +   0.125126791000*_Complex_I,
+   -0.071173512936 +   0.003831871226*_Complex_I,
+   -0.040117982030 +  -0.071510893106*_Complex_I,
+   -0.036725261807 +   0.037219762802*_Complex_I,
+    0.018186755478 +   0.046204718947*_Complex_I,
+    0.145269620419 +   0.059547924995*_Complex_I,
+    0.040549764037 +   0.051019173861*_Complex_I,
+    0.017368020117 +   0.015167059004*_Complex_I,
+    0.018691918254 +  -0.096917903423*_Complex_I,
+    0.085063648224 +   0.024469487369*_Complex_I,
+    0.107211554050 +  -0.066898560524*_Complex_I};
+
+float complex fftfilt_cccf_data_h13x256_y[] = {
+    0.024001474660 +   0.012819102986*_Complex_I,
+    0.006379486345 +   0.027817151043*_Complex_I,
+   -0.008833116513 +   0.059620513709*_Complex_I,
+    0.001290360957 +   0.018691901680*_Complex_I,
+   -0.019720088289 +   0.027870569701*_Complex_I,
+   -0.000089104333 +   0.022554509191*_Complex_I,
+    0.048983753430 +   0.008870419475*_Complex_I,
+   -0.059738782036 +   0.031503141884*_Complex_I,
+   -0.072004160761 +   0.015777738903*_Complex_I,
+   -0.011979216083 +  -0.114153406857*_Complex_I,
+    0.084719374355 +   0.122759620341*_Complex_I,
+   -0.002652524124 +   0.074081920456*_Complex_I,
+   -0.010647425116 +  -0.055247615062*_Complex_I,
+    0.009826036454 +   0.077018691837*_Complex_I,
+    0.043050527194 +  -0.030195436945*_Complex_I,
+    0.096977257426 +   0.014196001707*_Complex_I,
+   -0.069749349540 +   0.084922275308*_Complex_I,
+   -0.020592821729 +  -0.078815687497*_Complex_I,
+    0.015344750806 +   0.046911643882*_Complex_I,
+    0.003215160982 +  -0.044654666098*_Complex_I,
+    0.045065388666 +  -0.014762372700*_Complex_I,
+   -0.000649914548 +   0.078685026348*_Complex_I,
+    0.004242245469 +  -0.018256519858*_Complex_I,
+    0.068463378080 +   0.002186618045*_Complex_I,
+   -0.013886532699 +  -0.006164365808*_Complex_I,
+   -0.023002266230 +  -0.024231940574*_Complex_I,
+    0.059444806653 +   0.063727454712*_Complex_I,
+    0.005199838368 +  -0.013523702394*_Complex_I,
+   -0.014439329476 +   0.017624416202*_Complex_I,
+   -0.021969811821 +   0.031968738864*_Complex_I,
+   -0.099775723814 +  -0.032339826558*_Complex_I,
+   -0.023202788664 +  -0.102402077252*_Complex_I,
+    0.039130993156 +   0.049187762643*_Complex_I,
+   -0.024993065771 +   0.041659056463*_Complex_I,
+   -0.035177096744 +   0.034267402820*_Complex_I,
+   -0.012813786527 +   0.035585893453*_Complex_I,
+   -0.010166359548 +  -0.030098941996*_Complex_I,
+    0.035012509492 +   0.104480427481*_Complex_I,
+   -0.067925226605 +  -0.033727903935*_Complex_I,
+    0.038150929611 +  -0.034144316119*_Complex_I,
+   -0.031836563804 +   0.003970737127*_Complex_I,
+    0.022331990340 +  -0.044052273250*_Complex_I,
+   -0.024273008320 +   0.027787498353*_Complex_I,
+   -0.035557076930 +  -0.024109903696*_Complex_I,
+   -0.015439201027 +   0.010120871946*_Complex_I,
+   -0.005667692817 +   0.009808534446*_Complex_I,
+   -0.023993809759 +  -0.001976323260*_Complex_I,
+    0.033939569756 +   0.006322746502*_Complex_I,
+    0.038339628046 +  -0.021022149468*_Complex_I,
+    0.000846072793 +   0.018029554697*_Complex_I,
+    0.053339414921 +   0.071529172443*_Complex_I,
+   -0.104230849664 +  -0.080895763503*_Complex_I,
+    0.071647820798 +  -0.030732758724*_Complex_I,
+    0.101421268066 +   0.054142700304*_Complex_I,
+   -0.070017141172 +  -0.048691110375*_Complex_I,
+    0.071783266356 +   0.132628048489*_Complex_I,
+   -0.125892768778 +  -0.020392469132*_Complex_I,
+    0.077534371443 +  -0.106386873570*_Complex_I,
+    0.118899511278 +   0.091045477209*_Complex_I,
+    0.019035177150 +   0.019334344232*_Complex_I,
+    0.072542950409 +   0.021775500164*_Complex_I,
+    0.018913391291 +  -0.008423771096*_Complex_I,
+    0.068880105631 +   0.006805725914*_Complex_I,
+    0.070165826853 +  -0.001076549347*_Complex_I,
+   -0.092150026143 +   0.032247576962*_Complex_I,
+   -0.065407227079 +  -0.006168603538*_Complex_I,
+   -0.051509544946 +  -0.131615847220*_Complex_I,
+   -0.001170276180 +  -0.019427264753*_Complex_I,
+    0.083364051792 +   0.041323212038*_Complex_I,
+    0.007295606110 +   0.057364049960*_Complex_I,
+   -0.057103564464 +   0.118719947312*_Complex_I,
+    0.004531748675 +  -0.012147464062*_Complex_I,
+    0.012749373222 +  -0.030480391526*_Complex_I,
+    0.011125930632 +   0.028825825693*_Complex_I,
+   -0.043938238280 +   0.011854121979*_Complex_I,
+   -0.075321232264 +  -0.018064459135*_Complex_I,
+   -0.060886977242 +  -0.042110048390*_Complex_I,
+    0.056409867179 +  -0.045351450134*_Complex_I,
+   -0.018186918127 +   0.057805159108*_Complex_I,
+   -0.000535255779 +   0.017342658310*_Complex_I,
+    0.044187556819 +  -0.080806819744*_Complex_I,
+   -0.077107500264 +   0.074314966560*_Complex_I,
+   -0.000316037199 +  -0.033151523412*_Complex_I,
+    0.004502285827 +   0.096977958968*_Complex_I,
+   -0.093482268512 +   0.004941127824*_Complex_I,
+    0.021543628024 +  -0.127138737110*_Complex_I,
+   -0.024144738766 +   0.045799607631*_Complex_I,
+    0.073683578652 +  -0.012636723694*_Complex_I,
+    0.037103315594 +  -0.019525859806*_Complex_I,
+   -0.000690801053 +   0.046026880071*_Complex_I,
+    0.026954746660 +   0.048691313027*_Complex_I,
+    0.007353638085 +   0.001105524364*_Complex_I,
+   -0.054855624724 +   0.041421970378*_Complex_I,
+    0.022781842217 +  -0.038200366123*_Complex_I,
+    0.008043522754 +  -0.024654798773*_Complex_I,
+   -0.026166674166 +   0.009031999284*_Complex_I,
+    0.015652619885 +  -0.077945632696*_Complex_I,
+   -0.049664412838 +  -0.015226397097*_Complex_I,
+   -0.011383210898 +  -0.016385255644*_Complex_I,
+    0.020234061901 +  -0.066780782463*_Complex_I,
+   -0.046755003222 +  -0.039668549878*_Complex_I,
+   -0.003720962630 +  -0.011626088440*_Complex_I,
+   -0.053663894051 +   0.033112538916*_Complex_I,
+   -0.121787017031 +   0.003208574048*_Complex_I,
+   -0.028376450272 +  -0.019370775859*_Complex_I,
+   -0.047890954007 +  -0.041473896341*_Complex_I,
+    0.011519092293 +  -0.078183218332*_Complex_I,
+    0.028829438437 +   0.001487290342*_Complex_I,
+   -0.075450909852 +   0.021430470386*_Complex_I,
+   -0.034355836889 +  -0.040885483718*_Complex_I,
+   -0.058002545755 +  -0.068146319622*_Complex_I,
+    0.075219933002 +  -0.010912041314*_Complex_I,
+   -0.036651133000 +   0.019765597603*_Complex_I,
+   -0.098981225134 +  -0.018643142319*_Complex_I,
+   -0.032500295138 +  -0.084155976688*_Complex_I,
+    0.049578122325 +  -0.016429877737*_Complex_I,
+   -0.013361520737 +   0.029138302084*_Complex_I,
+   -0.043356913875 +  -0.030952278560*_Complex_I,
+    0.014132503851 +  -0.028805299647*_Complex_I,
+   -0.017867267512 +   0.026284047653*_Complex_I,
+   -0.083047255042 +  -0.017096821986*_Complex_I,
+    0.008417904103 +  -0.048597238033*_Complex_I,
+   -0.089743880041 +  -0.096283355401*_Complex_I,
+    0.035975823890 +  -0.056271631450*_Complex_I,
+   -0.015269531402 +   0.017064247022*_Complex_I,
+   -0.053012454761 +  -0.057229106407*_Complex_I,
+   -0.020365053756 +  -0.125161538208*_Complex_I,
+    0.066674345673 +   0.011158699255*_Complex_I,
+   -0.036133831454 +  -0.011201495817*_Complex_I,
+    0.016361003153 +  -0.025706876818*_Complex_I,
+    0.044163879016 +   0.016897283579*_Complex_I,
+    0.016023914791 +   0.021215167172*_Complex_I,
+    0.029810334754 +   0.009766734291*_Complex_I,
+    0.035636470208 +   0.060987445145*_Complex_I,
+   -0.006302550511 +  -0.115013574847*_Complex_I,
+    0.051720848730 +  -0.009461663454*_Complex_I,
+    0.047973920047 +  -0.016849009904*_Complex_I,
+   -0.046701499921 +   0.016919642862*_Complex_I,
+    0.043467132678 +  -0.016102822186*_Complex_I,
+    0.067871787511 +   0.010066831600*_Complex_I,
+   -0.035710415892 +   0.082788003225*_Complex_I,
+    0.069725504265 +  -0.005507023393*_Complex_I,
+    0.015433685804 +   0.073521250471*_Complex_I,
+    0.063438026419 +   0.060901474404*_Complex_I,
+    0.041153827196 +  -0.018596637068*_Complex_I,
+   -0.048641595104 +   0.055630921150*_Complex_I,
+    0.070785738691 +  -0.021001547792*_Complex_I,
+    0.030058519696 +   0.002719657788*_Complex_I,
+    0.033781571029 +   0.057641496421*_Complex_I,
+   -0.011861512115 +  -0.000934312027*_Complex_I,
+    0.026823353697 +  -0.148020696569*_Complex_I,
+    0.149407622218 +  -0.001917493861*_Complex_I,
+    0.031564264895 +  -0.033648864447*_Complex_I,
+    0.013500408643 +   0.049606336179*_Complex_I,
+    0.018200592161 +   0.054843460925*_Complex_I,
+   -0.042220409610 +  -0.057168354901*_Complex_I,
+    0.087247514708 +  -0.025386591035*_Complex_I,
+   -0.028531902133 +   0.071536103147*_Complex_I,
+   -0.025160173288 +  -0.020151250712*_Complex_I,
+   -0.022772795332 +  -0.017187718098*_Complex_I,
+   -0.029078837193 +   0.069628451313*_Complex_I,
+    0.049111894335 +  -0.041621311215*_Complex_I,
+   -0.023619794976 +   0.077908526801*_Complex_I,
+    0.000333911391 +   0.053474867787*_Complex_I,
+    0.029475675088 +  -0.009019730084*_Complex_I,
+    0.034174066620 +   0.017898879791*_Complex_I,
+    0.065544425508 +   0.045084590196*_Complex_I,
+   -0.090015977785 +   0.028850540224*_Complex_I,
+   -0.012871094837 +  -0.054608369039*_Complex_I,
+    0.047544364344 +   0.008538257328*_Complex_I,
+   -0.063104697961 +   0.090294378190*_Complex_I,
+   -0.011929275791 +   0.022601352310*_Complex_I,
+   -0.047521622205 +   0.007445085519*_Complex_I,
+    0.022958962838 +  -0.062917965176*_Complex_I,
+    0.214158563544 +   0.108130217870*_Complex_I,
+   -0.073961792459 +   0.071479678221*_Complex_I,
+   -0.015320084620 +  -0.052303661814*_Complex_I,
+    0.077538995397 +   0.033838920671*_Complex_I,
+   -0.034556396252 +   0.000838447098*_Complex_I,
+    0.000060541807 +  -0.004333615826*_Complex_I,
+    0.019595150038 +   0.059919909742*_Complex_I,
+   -0.060268723573 +  -0.056864079389*_Complex_I,
+    0.125955282742 +  -0.089792980055*_Complex_I,
+    0.104088003218 +   0.030894546232*_Complex_I,
+   -0.057778045852 +  -0.020383254417*_Complex_I,
+    0.072179136454 +  -0.043422298997*_Complex_I,
+   -0.033946126046 +   0.018129630965*_Complex_I,
+   -0.049055572484 +  -0.016390359113*_Complex_I,
+   -0.025066786874 +  -0.031503746350*_Complex_I,
+   -0.027009840982 +   0.016252291912*_Complex_I,
+   -0.042713013603 +  -0.046577467289*_Complex_I,
+   -0.046395432817 +  -0.042034197104*_Complex_I,
+   -0.066951975959 +  -0.006293309275*_Complex_I,
+   -0.076584206559 +  -0.038789936400*_Complex_I,
+    0.005278362238 +  -0.031661049359*_Complex_I,
+    0.006878243906 +   0.019853143294*_Complex_I,
+   -0.074683265419 +   0.092712646012*_Complex_I,
+   -0.048535905143 +   0.066894323514*_Complex_I,
+   -0.011718308326 +  -0.093940704974*_Complex_I,
+    0.054231403971 +  -0.067545703916*_Complex_I,
+    0.027390269161 +   0.013522266374*_Complex_I,
+    0.027113327798 +   0.025183265482*_Complex_I,
+   -0.041823544233 +   0.108944472361*_Complex_I,
+   -0.030438012256 +  -0.063572748831*_Complex_I,
+    0.066074070111 +  -0.020231915298*_Complex_I,
+    0.069140337649 +   0.114893641537*_Complex_I,
+   -0.005919906768 +   0.004293077149*_Complex_I,
+    0.056489424450 +   0.068597409742*_Complex_I,
+    0.027928885781 +  -0.031072362986*_Complex_I,
+    0.033715956662 +   0.016500309835*_Complex_I,
+    0.066646875440 +   0.052841945423*_Complex_I,
+    0.059048505781 +  -0.044714082918*_Complex_I,
+    0.083521538493 +   0.040432491516*_Complex_I,
+   -0.015140885802 +   0.007398722932*_Complex_I,
+   -0.018685866288 +  -0.005688072534*_Complex_I,
+    0.037444281102 +  -0.001275030615*_Complex_I,
+    0.009248759930 +   0.000917465357*_Complex_I,
+    0.037078087936 +   0.054897922621*_Complex_I,
+   -0.032804989991 +  -0.001205514200*_Complex_I,
+    0.049475279806 +  -0.034629284571*_Complex_I,
+    0.040449266799 +  -0.025094164230*_Complex_I,
+    0.028840625614 +  -0.034271290637*_Complex_I,
+    0.022502076079 +   0.028207110264*_Complex_I,
+   -0.095209370089 +  -0.024287776579*_Complex_I,
+    0.036853780217 +  -0.112642535014*_Complex_I,
+    0.011055463457 +   0.014755419711*_Complex_I,
+   -0.007470671462 +  -0.082840060740*_Complex_I,
+    0.049493876654 +  -0.029503227741*_Complex_I,
+    0.003287878646 +  -0.011760309552*_Complex_I,
+   -0.052454911751 +  -0.135685038742*_Complex_I,
+    0.100557451271 +  -0.060727168658*_Complex_I,
+    0.030460767615 +  -0.025297334320*_Complex_I,
+   -0.041900949710 +  -0.044189282191*_Complex_I,
+    0.002239369339 +   0.018583017194*_Complex_I,
+   -0.094251314287 +  -0.045638715401*_Complex_I,
+   -0.043687223316 +  -0.073236757137*_Complex_I,
+    0.042678054726 +  -0.053958008845*_Complex_I,
+   -0.000338320251 +  -0.031016295417*_Complex_I,
+   -0.017513369486 +   0.046863199684*_Complex_I,
+   -0.017742806904 +  -0.098421945495*_Complex_I,
+    0.035630421170 +  -0.042774040828*_Complex_I,
+    0.023161832990 +   0.056833847742*_Complex_I,
+   -0.035822048154 +  -0.064590977441*_Complex_I,
+   -0.008387640915 +   0.040501176809*_Complex_I,
+   -0.068025770572 +  -0.021755292254*_Complex_I,
+    0.001359162188 +  -0.081310455090*_Complex_I,
+    0.012404768734 +   0.083535778559*_Complex_I,
+   -0.038725035226 +  -0.008917515519*_Complex_I,
+   -0.038625918697 +  -0.011541140189*_Complex_I,
+    0.023371955995 +   0.096714602422*_Complex_I,
+    0.017691840656 +  -0.095198285220*_Complex_I,
+    0.045385440171 +   0.068921408368*_Complex_I,
+    0.002806803285 +   0.044993613294*_Complex_I,
+   -0.033553704471 +  -0.075678324986*_Complex_I,
+    0.033838944413 +   0.008301990809*_Complex_I,
+    0.001278998079 +  -0.054009422018*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_cccf_data_h23x256.c b/src/filter/tests/data/fftfilt_cccf_data_h23x256.c
new file mode 100644
index 0000000..01479da
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_cccf_data_h23x256.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_cccf_data_h23x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float complex fftfilt_cccf_data_h23x256_h[] = {
+   -0.113265895844 +  -0.022710527480*_Complex_I,
+    0.017113091052 +  -0.012686794996*_Complex_I,
+   -0.115593445301 +  -0.088176310062*_Complex_I,
+   -0.013192173839 +   0.146348237991*_Complex_I,
+   -0.103383064270 +  -0.047336477041*_Complex_I,
+    0.075577318668 +  -0.044972211123*_Complex_I,
+    0.142312300205 +  -0.064133465290*_Complex_I,
+   -0.221127843857 +   0.014621888101*_Complex_I,
+   -0.058339691162 +  -0.140489292145*_Complex_I,
+   -0.038768473268 +   0.063463032246*_Complex_I,
+   -0.066909837723 +  -0.194865047932*_Complex_I,
+    0.113466823101 +  -0.236079573631*_Complex_I,
+    0.025802928209 +  -0.126112377644*_Complex_I,
+   -0.025553426147 +  -0.205878305435*_Complex_I,
+   -0.036806601286 +   0.039826345444*_Complex_I,
+   -0.069977194071 +  -0.241963529587*_Complex_I,
+    0.067285501957 +  -0.138619136810*_Complex_I,
+   -0.216540384293 +  -0.094396740198*_Complex_I,
+    0.053344154358 +  -0.034197300673*_Complex_I,
+   -0.094768559933 +  -0.246735548973*_Complex_I,
+    0.026606693864 +  -0.040954247117*_Complex_I,
+    0.048448944092 +   0.068329632282*_Complex_I,
+    0.053360801935 +   0.035232502222*_Complex_I};
+
+float complex fftfilt_cccf_data_h23x256_x[] = {
+    0.045492258668 +   0.087591451406*_Complex_I,
+   -0.360275936127 +  -0.244660735130*_Complex_I,
+   -0.076566338539 +   0.085396862030*_Complex_I,
+   -0.010159610212 +  -0.099646502733*_Complex_I,
+    0.106244933605 +  -0.035118606687*_Complex_I,
+    0.004351182655 +   0.104901087284*_Complex_I,
+    0.060583090782 +   0.090451103449*_Complex_I,
+   -0.009854796529 +  -0.159225010872*_Complex_I,
+    0.123082661629 +  -0.013109473884*_Complex_I,
+   -0.042733341455 +   0.038194873929*_Complex_I,
+    0.059678620100 +  -0.141551160812*_Complex_I,
+   -0.004473050311 +   0.106543672085*_Complex_I,
+    0.099794340134 +  -0.143291473389*_Complex_I,
+   -0.044146090746 +   0.066736018658*_Complex_I,
+   -0.059895306826 +   0.166889667511*_Complex_I,
+    0.076762962341 +  -0.058477646112*_Complex_I,
+   -0.031721797585 +  -0.070060622692*_Complex_I,
+   -0.030031725764 +   0.018004868925*_Complex_I,
+   -0.132683742046 +   0.047249066830*_Complex_I,
+   -0.017683801055 +  -0.110559308529*_Complex_I,
+   -0.089177983999 +   0.128147161007*_Complex_I,
+   -0.058403855562 +  -0.253948712349*_Complex_I,
+    0.155546200275 +  -0.033433157206*_Complex_I,
+    0.019463591278 +   0.087873625755*_Complex_I,
+    0.175985527039 +   0.134090995789*_Complex_I,
+    0.005070775002 +   0.023215842247*_Complex_I,
+   -0.183340942860 +   0.061269021034*_Complex_I,
+   -0.282386708260 +   0.050984662771*_Complex_I,
+   -0.150776374340 +   0.111193597317*_Complex_I,
+   -0.092933952808 +   0.014530903101*_Complex_I,
+   -0.025000396371 +  -0.020265173912*_Complex_I,
+    0.130480492115 +   0.031612247229*_Complex_I,
+    0.100867581367 +  -0.229012298584*_Complex_I,
+   -0.082832515240 +   0.091525465250*_Complex_I,
+    0.048790007830 +   0.014641028643*_Complex_I,
+   -0.072996783257 +  -0.022627115250*_Complex_I,
+    0.076681035757 +  -0.090442115068*_Complex_I,
+   -0.036189466715 +  -0.120588958263*_Complex_I,
+   -0.100714707375 +  -0.106518495083*_Complex_I,
+   -0.321345973015 +  -0.129095542431*_Complex_I,
+   -0.041321143508 +  -0.097576838732*_Complex_I,
+   -0.074389326572 +   0.045334511995*_Complex_I,
+    0.136498391628 +   0.096301412582*_Complex_I,
+    0.191800594330 +  -0.083850991726*_Complex_I,
+    0.163151848316 +  -0.054458898306*_Complex_I,
+   -0.001362276729 +  -0.030826401711*_Complex_I,
+   -0.030661305785 +  -0.240707612038*_Complex_I,
+   -0.255475115776 +   0.166176271439*_Complex_I,
+   -0.087870460749 +   0.138075506687*_Complex_I,
+    0.063871967793 +  -0.167130613327*_Complex_I,
+   -0.167199540138 +  -0.085400974751*_Complex_I,
+    0.050435560942 +  -0.040156632662*_Complex_I,
+    0.057380825281 +   0.090105038881*_Complex_I,
+   -0.040529885888 +  -0.006345084310*_Complex_I,
+    0.112770903111 +   0.106182515621*_Complex_I,
+    0.128458249569 +   0.066667920351*_Complex_I,
+    0.083549374342 +  -0.086842793226*_Complex_I,
+    0.144279420376 +   0.133334076405*_Complex_I,
+   -0.137241375446 +   0.032534021139*_Complex_I,
+   -0.023988795280 +   0.049458268285*_Complex_I,
+    0.076303368807 +   0.020058257878*_Complex_I,
+   -0.154448091984 +  -0.050609207153*_Complex_I,
+    0.165292298794 +   0.070755618811*_Complex_I,
+    0.005886135250 +  -0.036164498329*_Complex_I,
+   -0.037918183208 +  -0.019724342227*_Complex_I,
+    0.026809751987 +  -0.032598829269*_Complex_I,
+    0.063324218988 +   0.098319518566*_Complex_I,
+   -0.047398203611 +  -0.114408671856*_Complex_I,
+    0.121957802773 +   0.028821185231*_Complex_I,
+   -0.135715365410 +   0.048073148727*_Complex_I,
+    0.029720014334 +   0.097452402115*_Complex_I,
+   -0.148619377613 +   0.177626144886*_Complex_I,
+   -0.122573924065 +   0.032197031379*_Complex_I,
+   -0.005472518504 +   0.130435466766*_Complex_I,
+    0.055357205868 +   0.036867898703*_Complex_I,
+   -0.037471351027 +   0.078105151653*_Complex_I,
+   -0.080862498283 +  -0.009199683368*_Complex_I,
+   -0.132427692413 +  -0.173398303986*_Complex_I,
+   -0.048274517059 +  -0.028256762028*_Complex_I,
+    0.063550901413 +  -0.019138570130*_Complex_I,
+    0.018642249703 +   0.105512928963*_Complex_I,
+   -0.034507310390 +   0.165678203106*_Complex_I,
+    0.157479083538 +  -0.098934185505*_Complex_I,
+    0.110079026222 +   0.190422582626*_Complex_I,
+   -0.123567688465 +  -0.026369869709*_Complex_I,
+    0.010789130628 +  -0.029063615203*_Complex_I,
+   -0.083237224817 +   0.123224353790*_Complex_I,
+    0.008147761226 +   0.157242572308*_Complex_I,
+   -0.115900576115 +   0.016479349136*_Complex_I,
+    0.014385309815 +  -0.114978981018*_Complex_I,
+   -0.010936866701 +   0.001918938756*_Complex_I,
+   -0.042773860693 +  -0.096163535118*_Complex_I,
+    0.159630239010 +  -0.090808361769*_Complex_I,
+    0.099759966135 +   0.003752164543*_Complex_I,
+    0.082647228241 +   0.061170494556*_Complex_I,
+    0.068049144745 +   0.166794586182*_Complex_I,
+   -0.025365993381 +  -0.024766857922*_Complex_I,
+    0.069112539291 +  -0.027834153175*_Complex_I,
+   -0.044352179766 +  -0.044707950950*_Complex_I,
+    0.067422634363 +  -0.017061285675*_Complex_I,
+    0.070048046112 +   0.109582054615*_Complex_I,
+    0.037835836411 +   0.180136477947*_Complex_I,
+    0.032625645399 +   0.116918706894*_Complex_I,
+    0.049541047215 +   0.161158871651*_Complex_I,
+    0.027160745859 +  -0.168793261051*_Complex_I,
+   -0.052759623528 +  -0.072317802906*_Complex_I,
+    0.184534084797 +   0.022903186083*_Complex_I,
+   -0.010724669695 +   0.036121314764*_Complex_I,
+    0.245871329308 +   0.039303827286*_Complex_I,
+   -0.024940526485 +   0.096528834105*_Complex_I,
+   -0.078127902746 +  -0.096756339073*_Complex_I,
+    0.043826240301 +  -0.125324332714*_Complex_I,
+    0.009783142805 +  -0.003001630120*_Complex_I,
+    0.064857578278 +  -0.042588630319*_Complex_I,
+    0.081423562765 +   0.121582865715*_Complex_I,
+   -0.118639636040 +   0.128098034859*_Complex_I,
+   -0.111134576797 +   0.038534373045*_Complex_I,
+   -0.289731216431 +  -0.080021494627*_Complex_I,
+    0.069585120678 +   0.060569906235*_Complex_I,
+   -0.051440721750 +  -0.150054514408*_Complex_I,
+   -0.039673271775 +   0.039142814279*_Complex_I,
+   -0.017449636757 +  -0.103529226780*_Complex_I,
+    0.046975213289 +  -0.208591985703*_Complex_I,
+    0.141377413273 +   0.023652508855*_Complex_I,
+   -0.061699587107 +  -0.151958966255*_Complex_I,
+    0.007653100044 +  -0.046074271202*_Complex_I,
+    0.135512828827 +   0.166977787018*_Complex_I,
+   -0.042983511090 +   0.140523970127*_Complex_I,
+   -0.174354147911 +   0.157390367985*_Complex_I,
+   -0.180938804150 +  -0.007540450245*_Complex_I,
+   -0.006038622186 +   0.114250767231*_Complex_I,
+   -0.183260941505 +  -0.041695398092*_Complex_I,
+   -0.087769675255 +  -0.009365759790*_Complex_I,
+   -0.077232390642 +  -0.019897325337*_Complex_I,
+   -0.017668469250 +  -0.107637941837*_Complex_I,
+    0.186912357807 +   0.078892260790*_Complex_I,
+    0.199635422230 +   0.003066605330*_Complex_I,
+    0.005723394454 +  -0.087536334991*_Complex_I,
+    0.036157646775 +   0.010726630688*_Complex_I,
+   -0.013097931445 +   0.137133157253*_Complex_I,
+   -0.200704836845 +  -0.067425280809*_Complex_I,
+   -0.018093538284 +  -0.002605117485*_Complex_I,
+   -0.046316248178 +   0.047676995397*_Complex_I,
+    0.065687131882 +  -0.272353625298*_Complex_I,
+    0.182847750187 +   0.136976885796*_Complex_I,
+   -0.060609722137 +   0.063969159126*_Complex_I,
+    0.065259736776 +  -0.030771258473*_Complex_I,
+    0.093584728241 +  -0.088571476936*_Complex_I,
+    0.055487519503 +  -0.092523062229*_Complex_I,
+    0.040360233188 +  -0.237069821358*_Complex_I,
+   -0.054889243841 +   0.007436647266*_Complex_I,
+    0.146980488300 +  -0.112567687035*_Complex_I,
+    0.033167552948 +   0.077278125286*_Complex_I,
+   -0.123007047176 +  -0.234244012833*_Complex_I,
+   -0.023877808452 +  -0.034760150313*_Complex_I,
+   -0.073663645983 +  -0.067232269049*_Complex_I,
+   -0.021728573740 +   0.229374289513*_Complex_I,
+    0.075998270512 +  -0.019796457887*_Complex_I,
+   -0.117255151272 +   0.168271052837*_Complex_I,
+    0.157391369343 +  -0.156538987160*_Complex_I,
+   -0.144393324852 +   0.064253067970*_Complex_I,
+    0.182558035851 +   0.176998484135*_Complex_I,
+    0.022407060862 +   0.102678549290*_Complex_I,
+    0.053511500359 +   0.111569952965*_Complex_I,
+    0.035490038991 +   0.067579591274*_Complex_I,
+   -0.069553595781 +   0.028308334947*_Complex_I,
+   -0.077684468031 +   0.165951395035*_Complex_I,
+   -0.171819615364 +   0.051840746403*_Complex_I,
+   -0.169814157486 +  -0.009480411559*_Complex_I,
+    0.002353400178 +   0.097526848316*_Complex_I,
+   -0.035275432467 +   0.051336318254*_Complex_I,
+    0.073788845539 +  -0.159921205044*_Complex_I,
+   -0.062105762959 +   0.139204311371*_Complex_I,
+    0.046410915256 +   0.076092356443*_Complex_I,
+    0.015324105322 +   0.056624919176*_Complex_I,
+   -0.032998785377 +   0.030960384011*_Complex_I,
+   -0.033524742723 +  -0.072809416056*_Complex_I,
+    0.034249174595 +   0.142128837109*_Complex_I,
+   -0.017137683928 +   0.000054139068*_Complex_I,
+   -0.152735805511 +  -0.015746916831*_Complex_I,
+    0.076812589169 +   0.113819742203*_Complex_I,
+   -0.269065928459 +   0.047783342004*_Complex_I,
+   -0.006360206753 +  -0.290388703346*_Complex_I,
+    0.056451249123 +  -0.141728675365*_Complex_I,
+   -0.074260264635 +   0.112913572788*_Complex_I,
+   -0.097910881042 +   0.009142446518*_Complex_I,
+    0.008521752059 +  -0.165642762184*_Complex_I,
+   -0.068459671736 +  -0.177522385120*_Complex_I,
+   -0.057727193832 +  -0.061166942120*_Complex_I,
+   -0.014851769805 +   0.012171116471*_Complex_I,
+   -0.106043231487 +   0.214376616478*_Complex_I,
+   -0.056457644701 +   0.133102154732*_Complex_I,
+   -0.001181100123 +  -0.025020095706*_Complex_I,
+    0.013895004988 +   0.055028027296*_Complex_I,
+   -0.094381314516 +  -0.076402390003*_Complex_I,
+    0.059590637684 +  -0.081801712513*_Complex_I,
+    0.004246252403 +   0.056055116653*_Complex_I,
+   -0.044613176584 +  -0.091410827637*_Complex_I,
+   -0.037342968583 +  -0.024862080812*_Complex_I,
+   -0.190127372742 +   0.041265439987*_Complex_I,
+   -0.006639320403 +   0.097943222523*_Complex_I,
+    0.138125526905 +  -0.064016056061*_Complex_I,
+   -0.098870986700 +  -0.145059263706*_Complex_I,
+    0.114810085297 +  -0.059086716175*_Complex_I,
+    0.000181899476 +  -0.094249367714*_Complex_I,
+    0.086499345303 +  -0.031394767761*_Complex_I,
+    0.131707870960 +   0.043413007259*_Complex_I,
+   -0.162042534351 +   0.021535064280*_Complex_I,
+    0.103580415249 +  -0.146205651760*_Complex_I,
+   -0.052903598547 +   0.023014034331*_Complex_I,
+   -0.198558521271 +  -0.064298307896*_Complex_I,
+    0.111735153198 +  -0.045897883177*_Complex_I,
+   -0.166142654419 +  -0.066890668869*_Complex_I,
+    0.135389423370 +   0.158303833008*_Complex_I,
+   -0.067218714952 +  -0.269701981544*_Complex_I,
+   -0.029925772548 +  -0.056027936935*_Complex_I,
+   -0.090524566174 +  -0.035552933812*_Complex_I,
+   -0.095090520382 +  -0.213347554207*_Complex_I,
+    0.065363866091 +  -0.078422772884*_Complex_I,
+    0.142836868763 +   0.045737281442*_Complex_I,
+   -0.066659629345 +   0.091496753693*_Complex_I,
+   -0.050436902046 +   0.038852605224*_Complex_I,
+    0.056302917004 +   0.035658785701*_Complex_I,
+   -0.261968922615 +   0.072266608477*_Complex_I,
+   -0.000278761820 +  -0.066401851177*_Complex_I,
+   -0.191342055798 +   0.103529000282*_Complex_I,
+   -0.049295476079 +   0.036229848862*_Complex_I,
+   -0.042302805185 +  -0.063734227419*_Complex_I,
+   -0.050776159763 +   0.063683301210*_Complex_I,
+   -0.091268771887 +   0.292417860031*_Complex_I,
+   -0.054572415352 +   0.059356063604*_Complex_I,
+    0.058717763424 +   0.045334947109*_Complex_I,
+    0.004559407011 +  -0.066553068161*_Complex_I,
+   -0.037161919475 +   0.062294793129*_Complex_I,
+   -0.234697723389 +  -0.031746768951*_Complex_I,
+    0.045789924264 +  -0.090054887533*_Complex_I,
+    0.107681131363 +  -0.112842118740*_Complex_I,
+    0.136971771717 +  -0.095520716906*_Complex_I,
+    0.093264913559 +   0.091368728876*_Complex_I,
+   -0.091999745369 +   0.012484084070*_Complex_I,
+    0.023904198408 +   0.167766880989*_Complex_I,
+    0.192737889290 +  -0.014556410909*_Complex_I,
+    0.106241250038 +   0.018227040768*_Complex_I,
+    0.024921812117 +   0.071124422550*_Complex_I,
+    0.052874428034 +  -0.094483900070*_Complex_I,
+    0.060024052858 +  -0.120348191261*_Complex_I,
+    0.151742827892 +   0.044160097837*_Complex_I,
+   -0.035461589694 +   0.040138626099*_Complex_I,
+    0.068723130226 +   0.084313547611*_Complex_I,
+    0.089995771646 +   0.131840264797*_Complex_I,
+   -0.138497865200 +   0.083961331844*_Complex_I,
+    0.012439595908 +   0.069563353062*_Complex_I,
+    0.032783246040 +   0.022058516741*_Complex_I,
+   -0.022213153541 +  -0.033352825046*_Complex_I,
+   -0.111178684235 +   0.004780802876*_Complex_I,
+   -0.256052374840 +  -0.011545106769*_Complex_I};
+
+float complex fftfilt_cccf_data_h23x256_y[] = {
+   -0.003163473368 +  -0.010954277402*_Complex_I,
+    0.037140370260 +   0.036815583414*_Complex_I,
+    0.003807251283 +  -0.021686181791*_Complex_I,
+    0.005314097986 +   0.079501236423*_Complex_I,
+    0.042112632196 +  -0.063838542514*_Complex_I,
+    0.017204891832 +   0.033074981292*_Complex_I,
+   -0.018245137527 +  -0.013306853145*_Complex_I,
+   -0.071746576920 +   0.012804254640*_Complex_I,
+    0.038716740288 +   0.025468078675*_Complex_I,
+   -0.021154025874 +   0.034169668261*_Complex_I,
+    0.077447054430 +  -0.009547926751*_Complex_I,
+   -0.016338142895 +   0.132246349074*_Complex_I,
+   -0.116081376657 +   0.043341402708*_Complex_I,
+    0.006899278182 +   0.004113954837*_Complex_I,
+   -0.096053480940 +   0.077818095714*_Complex_I,
+    0.036459684176 +  -0.026251748476*_Complex_I,
+    0.009672547486 +   0.040691758623*_Complex_I,
+   -0.055593734133 +   0.042208131016*_Complex_I,
+    0.040988977939 +   0.032815266125*_Complex_I,
+    0.003252391576 +  -0.027032799374*_Complex_I,
+   -0.056555124191 +   0.108792895823*_Complex_I,
+    0.027780168593 +  -0.058523157109*_Complex_I,
+   -0.023467358131 +  -0.032855820386*_Complex_I,
+   -0.160897508497 +  -0.123678772528*_Complex_I,
+    0.089287852799 +  -0.007143354758*_Complex_I,
+   -0.016099534679 +   0.002581265491*_Complex_I,
+   -0.000986708318 +   0.015164729493*_Complex_I,
+   -0.064404987562 +  -0.057663923465*_Complex_I,
+    0.132601153086 +  -0.001252362087*_Complex_I,
+   -0.048228851544 +   0.135111186832*_Complex_I,
+    0.134924898703 +  -0.062012201527*_Complex_I,
+   -0.074853441754 +   0.026496948899*_Complex_I,
+   -0.129226783274 +  -0.004790572963*_Complex_I,
+    0.103893760072 +   0.022506218947*_Complex_I,
+   -0.035460676841 +   0.047688216908*_Complex_I,
+    0.178180257360 +  -0.013109362819*_Complex_I,
+    0.018161301104 +   0.109487688153*_Complex_I,
+    0.094671736324 +   0.013921482693*_Complex_I,
+   -0.048072372913 +   0.137711693833*_Complex_I,
+    0.062880576088 +   0.175933352879*_Complex_I,
+   -0.009910181192 +   0.111994156930*_Complex_I,
+    0.044512102038 +   0.006301021902*_Complex_I,
+    0.066098606944 +  -0.028081191959*_Complex_I,
+    0.087466319032 +   0.008238268753*_Complex_I,
+   -0.001367800660 +   0.038967292120*_Complex_I,
+   -0.067421987215 +   0.145539459508*_Complex_I,
+    0.075984112068 +   0.175244098743*_Complex_I,
+   -0.027482814927 +   0.090963593261*_Complex_I,
+   -0.061982865075 +  -0.001782549283*_Complex_I,
+   -0.051388866943 +   0.091286306431*_Complex_I,
+   -0.125038294673 +   0.006669602361*_Complex_I,
+   -0.266499054163 +   0.031401157713*_Complex_I,
+   -0.005097555860 +   0.090581995126*_Complex_I,
+   -0.055547492377 +   0.112501253551*_Complex_I,
+    0.012659187846 +   0.026503690407*_Complex_I,
+   -0.054554809518 +  -0.047656993760*_Complex_I,
+   -0.092937843107 +   0.022881558123*_Complex_I,
+   -0.072930225245 +   0.008805379158*_Complex_I,
+    0.001689423379 +   0.180348978422*_Complex_I,
+   -0.127921044291 +  -0.059030091461*_Complex_I,
+    0.022023172094 +   0.056579583536*_Complex_I,
+   -0.121613219261 +  -0.108837221417*_Complex_I,
+   -0.067796732417 +  -0.119754222994*_Complex_I,
+    0.022160633596 +   0.172479032134*_Complex_I,
+   -0.021149818992 +  -0.088048810494*_Complex_I,
+    0.061930559225 +   0.033755437249*_Complex_I,
+    0.009953383045 +   0.044238688316*_Complex_I,
+    0.122661863617 +  -0.104781716997*_Complex_I,
+    0.038138937124 +  -0.063310924046*_Complex_I,
+   -0.085849386817 +   0.029133930890*_Complex_I,
+    0.062239172444 +  -0.126085383157*_Complex_I,
+    0.056320988469 +  -0.024245019681*_Complex_I,
+    0.008584244317 +  -0.122838573365*_Complex_I,
+    0.076110822106 +  -0.085361591999*_Complex_I,
+    0.020719355889 +  -0.055555782582*_Complex_I,
+    0.000653411740 +  -0.069873018399*_Complex_I,
+    0.066178767484 +  -0.026066404349*_Complex_I,
+    0.028671545965 +   0.078431187056*_Complex_I,
+    0.065121750679 +  -0.050089915985*_Complex_I,
+    0.057324656071 +  -0.014133997391*_Complex_I,
+    0.030288156492 +   0.026307763098*_Complex_I,
+    0.111873889736 +  -0.050023831315*_Complex_I,
+    0.004746208895 +   0.091571031089*_Complex_I,
+    0.010352530872 +  -0.069569373081*_Complex_I,
+    0.103204479415 +   0.119216440805*_Complex_I,
+    0.100504774459 +  -0.026045891638*_Complex_I,
+    0.137774605021 +   0.123356174384*_Complex_I,
+   -0.002645216707 +  -0.014199931832*_Complex_I,
+    0.171062973870 +   0.005021494304*_Complex_I,
+    0.017869576969 +   0.051069516929*_Complex_I,
+    0.012047989391 +  -0.064461915154*_Complex_I,
+    0.127717671510 +   0.063350643190*_Complex_I,
+    0.004050235012 +   0.103983488965*_Complex_I,
+    0.032899906611 +  -0.046472299982*_Complex_I,
+    0.065807936567 +  -0.000393539350*_Complex_I,
+    0.028480795150 +  -0.035085883796*_Complex_I,
+    0.020035985982 +   0.094697203202*_Complex_I,
+   -0.032243593542 +  -0.034371463214*_Complex_I,
+    0.130710337771 +  -0.076446903504*_Complex_I,
+   -0.000290058651 +   0.063994722962*_Complex_I,
+    0.029367046198 +  -0.059754919406*_Complex_I,
+   -0.013496796316 +   0.021741636806*_Complex_I,
+   -0.030554614083 +  -0.122446936946*_Complex_I,
+    0.076444606322 +  -0.050687378688*_Complex_I,
+   -0.098301236592 +  -0.042289531263*_Complex_I,
+    0.110549679961 +  -0.093097947505*_Complex_I,
+   -0.006461769800 +   0.063465854253*_Complex_I,
+    0.009417608596 +  -0.044048779916*_Complex_I,
+   -0.064257503698 +  -0.070341963349*_Complex_I,
+   -0.059122879906 +  -0.064698906559*_Complex_I,
+   -0.042900235147 +  -0.196073457626*_Complex_I,
+    0.040029224882 +  -0.029654181156*_Complex_I,
+    0.032819760175 +  -0.111413861187*_Complex_I,
+    0.113350177210 +  -0.032359925542*_Complex_I,
+    0.126474793881 +  -0.075277469455*_Complex_I,
+   -0.036790802258 +  -0.012416828849*_Complex_I,
+   -0.013341048450 +  -0.171411636672*_Complex_I,
+    0.071226482453 +  -0.028621067255*_Complex_I,
+    0.025788824745 +  -0.122676045849*_Complex_I,
+    0.138882083097 +  -0.112136069734*_Complex_I,
+    0.079803890033 +  -0.056689245330*_Complex_I,
+   -0.083660222940 +   0.021037488864*_Complex_I,
+    0.038027034035 +  -0.045614580284*_Complex_I,
+   -0.173039872932 +  -0.036333257565*_Complex_I,
+    0.109185737875 +  -0.018739181874*_Complex_I,
+   -0.011116564421 +  -0.012659167774*_Complex_I,
+    0.027848471825 +   0.042226423184*_Complex_I,
+    0.009983998202 +   0.009652509701*_Complex_I,
+    0.034939989540 +   0.049003327190*_Complex_I,
+   -0.003271145352 +   0.117508812071*_Complex_I,
+   -0.089674168530 +   0.022069310635*_Complex_I,
+    0.075219548196 +   0.009686118261*_Complex_I,
+   -0.009264365949 +   0.096686949696*_Complex_I,
+   -0.015282640313 +  -0.022970528637*_Complex_I,
+    0.056453107756 +   0.038101951682*_Complex_I,
+   -0.075215301245 +  -0.009549458275*_Complex_I,
+    0.016786191325 +   0.119063467526*_Complex_I,
+   -0.063461158126 +  -0.044766698681*_Complex_I,
+    0.013715934436 +   0.023246048562*_Complex_I,
+   -0.007187144049 +   0.139644946690*_Complex_I,
+    0.001093160636 +   0.038143526761*_Complex_I,
+    0.089982542957 +   0.104701345525*_Complex_I,
+    0.006438088218 +   0.016301123978*_Complex_I,
+    0.024550427000 +   0.007223414838*_Complex_I,
+    0.001998203885 +   0.128506017977*_Complex_I,
+    0.027388452832 +  -0.021862288446*_Complex_I,
+    0.121924019581 +  -0.034718387256*_Complex_I,
+    0.065573770757 +   0.084008890431*_Complex_I,
+   -0.010812132830 +   0.041404645847*_Complex_I,
+   -0.001516024489 +  -0.046108309651*_Complex_I,
+    0.021914893505 +   0.163215448624*_Complex_I,
+   -0.168800712098 +   0.059209648111*_Complex_I,
+    0.043105911919 +  -0.060137086361*_Complex_I,
+   -0.189708356977 +   0.061129252073*_Complex_I,
+   -0.012670229735 +  -0.164593804221*_Complex_I,
+   -0.027215975709 +   0.001464776717*_Complex_I,
+   -0.032021988907 +  -0.005641085087*_Complex_I,
+    0.084676334643 +   0.050890486174*_Complex_I,
+   -0.025518610180 +  -0.026683394670*_Complex_I,
+   -0.196695068885 +  -0.118334531757*_Complex_I,
+    0.005134576502 +   0.041058905955*_Complex_I,
+   -0.234700560083 +  -0.074871902832*_Complex_I,
+   -0.003394269991 +  -0.025828554844*_Complex_I,
+   -0.095398073839 +  -0.137794198564*_Complex_I,
+   -0.115844189635 +  -0.016811394953*_Complex_I,
+   -0.086433951444 +  -0.096085417783*_Complex_I,
+   -0.135882412437 +   0.086996893482*_Complex_I,
+    0.086188594393 +  -0.052027988864*_Complex_I,
+   -0.032660999389 +   0.079110677785*_Complex_I,
+    0.048555777064 +  -0.071192412508*_Complex_I,
+    0.002384862118 +  -0.024747258783*_Complex_I,
+    0.103707359507 +  -0.071240112258*_Complex_I,
+    0.066653972959 +   0.034897982205*_Complex_I,
+    0.104312888231 +  -0.035974080403*_Complex_I,
+    0.138638211048 +  -0.049974123076*_Complex_I,
+    0.194165435108 +  -0.049828755020*_Complex_I,
+    0.053154926316 +  -0.036358363591*_Complex_I,
+    0.220675128214 +  -0.030961835182*_Complex_I,
+   -0.023703599067 +   0.107776938666*_Complex_I,
+    0.144905982977 +   0.004212434062*_Complex_I,
+    0.095439650368 +   0.010795170803*_Complex_I,
+    0.121879552506 +  -0.015505102444*_Complex_I,
+    0.116627097083 +   0.007939429200*_Complex_I,
+    0.121158143520 +   0.140391060505*_Complex_I,
+    0.023773742208 +  -0.003915558406*_Complex_I,
+    0.249689399352 +   0.106009483142*_Complex_I,
+    0.042459283114 +   0.116551334180*_Complex_I,
+   -0.032000595442 +   0.043069200952*_Complex_I,
+    0.083807567711 +  -0.048271370782*_Complex_I,
+    0.106133277971 +   0.107195381079*_Complex_I,
+   -0.032758866155 +   0.055589876920*_Complex_I,
+    0.083113455874 +   0.056268033253*_Complex_I,
+    0.062805062701 +   0.052981580201*_Complex_I,
+   -0.072300786192 +  -0.022099642616*_Complex_I,
+   -0.023071207539 +   0.069870175640*_Complex_I,
+   -0.028038066616 +   0.115260700432*_Complex_I,
+    0.064595548137 +   0.179516005676*_Complex_I,
+   -0.096265563941 +   0.042171225905*_Complex_I,
+   -0.009166713003 +   0.047707628475*_Complex_I,
+   -0.074476545197 +   0.078481428604*_Complex_I,
+    0.020402514454 +   0.097206670167*_Complex_I,
+    0.009603470518 +   0.137727809796*_Complex_I,
+   -0.061724978115 +   0.088460708343*_Complex_I,
+   -0.018787857770 +   0.022957577259*_Complex_I,
+    0.060840793373 +   0.153201719382*_Complex_I,
+   -0.090761100879 +   0.063269553381*_Complex_I,
+    0.048643003688 +   0.073091682104*_Complex_I,
+    0.042574315610 +  -0.003350831677*_Complex_I,
+   -0.040962206275 +  -0.040240568604*_Complex_I,
+    0.091461005901 +   0.091406021898*_Complex_I,
+   -0.072777125035 +   0.062929145258*_Complex_I,
+    0.050350001022 +   0.069452557434*_Complex_I,
+   -0.053002534268 +   0.128785684713*_Complex_I,
+   -0.138610817453 +  -0.082977870570*_Complex_I,
+    0.003075555607 +   0.064767936471*_Complex_I,
+   -0.086047615737 +   0.023981557378*_Complex_I,
+   -0.050138686326 +  -0.027308987428*_Complex_I,
+    0.076446905787 +   0.037104333404*_Complex_I,
+   -0.133192170848 +   0.118044932479*_Complex_I,
+    0.031944935119 +  -0.022005963244*_Complex_I,
+   -0.145840989397 +  -0.032141552759*_Complex_I,
+   -0.094264666257 +   0.034400727372*_Complex_I,
+   -0.108437970582 +   0.098296799443*_Complex_I,
+   -0.077001723208 +  -0.045921410112*_Complex_I,
+    0.064828536127 +   0.008649034614*_Complex_I,
+   -0.049541743659 +   0.128627939884*_Complex_I,
+   -0.054927512176 +  -0.017342218621*_Complex_I,
+   -0.045962424214 +   0.135024314150*_Complex_I,
+   -0.103035351086 +  -0.015043817667*_Complex_I,
+   -0.037360852174 +   0.060401543729*_Complex_I,
+   -0.015995655894 +  -0.111740685081*_Complex_I,
+    0.032349781494 +   0.177898564863*_Complex_I,
+   -0.021316438598 +  -0.060576452528*_Complex_I,
+    0.017301344278 +   0.053102820051*_Complex_I,
+   -0.014246232246 +   0.154624061894*_Complex_I,
+    0.059824941825 +   0.104996100396*_Complex_I,
+    0.011889122729 +   0.118361813218*_Complex_I,
+    0.043309441161 +  -0.055415600686*_Complex_I,
+    0.074309063231 +   0.029407405713*_Complex_I,
+    0.062659426432 +   0.086580925759*_Complex_I,
+    0.141493640931 +   0.127665018203*_Complex_I,
+    0.091935455312 +   0.098230420374*_Complex_I,
+    0.153585132414 +   0.041819978162*_Complex_I,
+   -0.036769194301 +  -0.001909617998*_Complex_I,
+    0.081816172969 +   0.145221737468*_Complex_I,
+   -0.074164721966 +   0.103001561704*_Complex_I,
+   -0.001976310011 +  -0.051318733615*_Complex_I,
+   -0.009827887129 +  -0.043930466173*_Complex_I,
+    0.054370258297 +  -0.141481458488*_Complex_I,
+   -0.015471769405 +   0.047001209676*_Complex_I,
+   -0.056006669211 +  -0.098678088820*_Complex_I,
+    0.009040689393 +  -0.031336560460*_Complex_I,
+   -0.039683698505 +  -0.042155900675*_Complex_I,
+    0.017353066695 +  -0.086277701489*_Complex_I,
+   -0.002902182702 +  -0.059776504802*_Complex_I,
+   -0.044568379057 +  -0.090648234378*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_cccf_data_h4x256.c b/src/filter/tests/data/fftfilt_cccf_data_h4x256.c
new file mode 100644
index 0000000..f322397
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_cccf_data_h4x256.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_cccf_data_h4x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float complex fftfilt_cccf_data_h4x256_h[] = {
+   -0.135231339931 +   0.062977480888*_Complex_I,
+   -0.157240438461 +   0.037726294994*_Complex_I,
+    0.169742667675 +  -0.043306457996*_Complex_I,
+   -0.001679791883 +  -0.031882059574*_Complex_I};
+
+float complex fftfilt_cccf_data_h4x256_x[] = {
+   -0.145331311226 +   0.109500586987*_Complex_I,
+    0.008576134592 +  -0.169542956352*_Complex_I,
+    0.047844916582 +   0.149058306217*_Complex_I,
+    0.177120888233 +   0.170329642296*_Complex_I,
+   -0.004175573960 +   0.072688925266*_Complex_I,
+   -0.027152365446 +   0.035960051417*_Complex_I,
+    0.094344460964 +  -0.033645594120*_Complex_I,
+   -0.010391099006 +  -0.035192406178*_Complex_I,
+   -0.180138766766 +  -0.031795871258*_Complex_I,
+   -0.203700375557 +  -0.138529646397*_Complex_I,
+   -0.019561575353 +  -0.144657015800*_Complex_I,
+   -0.118808639050 +  -0.027752715349*_Complex_I,
+    0.056749093533 +  -0.025054290891*_Complex_I,
+    0.116542947292 +  -0.007720955461*_Complex_I,
+   -0.074969446659 +   0.100591087341*_Complex_I,
+    0.001408090629 +  -0.042336615920*_Complex_I,
+    0.240100383759 +  -0.161245095730*_Complex_I,
+   -0.085952699184 +   0.084794622660*_Complex_I,
+   -0.015006904304 +   0.035609281063*_Complex_I,
+    0.000034478516 +  -0.187320244312*_Complex_I,
+    0.052339464426 +  -0.004798505455*_Complex_I,
+    0.093824869394 +  -0.070545089245*_Complex_I,
+    0.149086785316 +   0.108725130558*_Complex_I,
+   -0.156761336327 +   0.005509132892*_Complex_I,
+   -0.125493848324 +  -0.043598395586*_Complex_I,
+   -0.121326863766 +   0.007096276432*_Complex_I,
+    0.003544984013 +   0.166835379601*_Complex_I,
+   -0.037431293726 +  -0.020329789817*_Complex_I,
+   -0.085911118984 +  -0.079901820421*_Complex_I,
+    0.122742509842 +   0.014822798967*_Complex_I,
+    0.110778486729 +  -0.071227872372*_Complex_I,
+   -0.007605559379 +  -0.128052246571*_Complex_I,
+   -0.066244971752 +  -0.047825807333*_Complex_I,
+   -0.030502566695 +  -0.254250669479*_Complex_I,
+    0.157666409016 +  -0.093531721830*_Complex_I,
+    0.043991112709 +   0.067102664709*_Complex_I,
+   -0.063667005301 +   0.055537760258*_Complex_I,
+    0.035860368609 +  -0.217314362526*_Complex_I,
+   -0.154697167873 +  -0.051928877831*_Complex_I,
+   -0.154008638859 +   0.137628376484*_Complex_I,
+   -0.021988071501 +  -0.136372375488*_Complex_I,
+    0.118345403671 +  -0.299838876724*_Complex_I,
+    0.066200459003 +   0.107628118992*_Complex_I,
+   -0.022814218700 +   0.042800396681*_Complex_I,
+    0.039931696653 +   0.066364485025*_Complex_I,
+   -0.038531854749 +  -0.120103299618*_Complex_I,
+    0.017913374305 +   0.016456928849*_Complex_I,
+    0.001270754635 +   0.030759117007*_Complex_I,
+    0.235941290855 +   0.024339741468*_Complex_I,
+    0.074259299040 +   0.054985857010*_Complex_I,
+   -0.117393791676 +  -0.024225908518*_Complex_I,
+    0.195164024830 +  -0.100901556015*_Complex_I,
+   -0.074234390259 +  -0.041768211126*_Complex_I,
+    0.042385265231 +  -0.228795623779*_Complex_I,
+   -0.089356112480 +   0.027445358038*_Complex_I,
+   -0.273647761345 +  -0.094072991610*_Complex_I,
+    0.067754048109 +  -0.066324621439*_Complex_I,
+   -0.071163922548 +   0.011088901013*_Complex_I,
+    0.002260415070 +  -0.033153522015*_Complex_I,
+   -0.163043868542 +   0.147372353077*_Complex_I,
+    0.130338788033 +   0.113823711872*_Complex_I,
+   -0.018929108977 +  -0.134858226776*_Complex_I,
+    0.005879180133 +   0.110552978516*_Complex_I,
+   -0.177987158298 +  -0.145397603512*_Complex_I,
+    0.112013733387 +  -0.243533611298*_Complex_I,
+   -0.065134996176 +  -0.161895787716*_Complex_I,
+   -0.100013601780 +  -0.185575771332*_Complex_I,
+   -0.039732664824 +  -0.033335423470*_Complex_I,
+   -0.004159386829 +  -0.118040883541*_Complex_I,
+    0.101805281639 +   0.084620487690*_Complex_I,
+   -0.150429344177 +  -0.084105199575*_Complex_I,
+    0.062221693993 +   0.047454631329*_Complex_I,
+    0.138085842133 +  -0.076058888435*_Complex_I,
+    0.254889655113 +  -0.163386595249*_Complex_I,
+   -0.025463542342 +  -0.060935062170*_Complex_I,
+    0.061728596687 +  -0.006239769980*_Complex_I,
+   -0.083425503969 +   0.069602411985*_Complex_I,
+    0.039149901271 +  -0.118299651146*_Complex_I,
+    0.103384292126 +  -0.022438082099*_Complex_I,
+   -0.008195941895 +   0.049835348129*_Complex_I,
+   -0.049642279744 +  -0.038572207093*_Complex_I,
+    0.112285923958 +   0.046880367398*_Complex_I,
+   -0.052668154240 +   0.054064834118*_Complex_I,
+   -0.009626910090 +  -0.040556022525*_Complex_I,
+    0.002097596042 +  -0.216656374931*_Complex_I,
+    0.057758331299 +  -0.002327759005*_Complex_I,
+   -0.008833542466 +  -0.014824745059*_Complex_I,
+    0.040581482649 +   0.045436677337*_Complex_I,
+    0.194773435593 +   0.044354414940*_Complex_I,
+   -0.028676524758 +  -0.018726617098*_Complex_I,
+   -0.019215075672 +  -0.038972333074*_Complex_I,
+    0.200048685074 +   0.008784489334*_Complex_I,
+   -0.116677081585 +  -0.295071172714*_Complex_I,
+   -0.077746075392 +  -0.031134122610*_Complex_I,
+    0.078498488665 +   0.007961921394*_Complex_I,
+   -0.081279265881 +   0.114860057831*_Complex_I,
+   -0.014656324685 +  -0.145551848412*_Complex_I,
+    0.129554140568 +   0.086291736364*_Complex_I,
+    0.123636543751 +  -0.061629927158*_Complex_I,
+    0.040432214737 +   0.095078992844*_Complex_I,
+    0.021615064144 +   0.087291020155*_Complex_I,
+   -0.036054989696 +   0.058331966400*_Complex_I,
+    0.031770968437 +   0.150715994835*_Complex_I,
+    0.036789077520 +   0.006291696429*_Complex_I,
+    0.207829833031 +  -0.195549118519*_Complex_I,
+   -0.069425356388 +   0.034547394514*_Complex_I,
+    0.121964550018 +  -0.175282227993*_Complex_I,
+   -0.076088452339 +   0.189335298538*_Complex_I,
+   -0.238275814056 +   0.164852106571*_Complex_I,
+   -0.105275332928 +   0.048049694300*_Complex_I,
+    0.025219294429 +   0.148977017403*_Complex_I,
+   -0.116340065002 +   0.047728052735*_Complex_I,
+   -0.001223530248 +   0.002220938914*_Complex_I,
+   -0.095514726639 +   0.177518546581*_Complex_I,
+   -0.024929241836 +   0.051844847202*_Complex_I,
+    0.003478102759 +  -0.047746166587*_Complex_I,
+   -0.179150342941 +  -0.217488384247*_Complex_I,
+   -0.004914051294 +  -0.154762375355*_Complex_I,
+    0.097842323780 +  -0.024203261733*_Complex_I,
+   -0.103857576847 +  -0.032823368907*_Complex_I,
+   -0.092790353298 +   0.061397796869*_Complex_I,
+    0.047670701146 +   0.110239303112*_Complex_I,
+    0.073129659891 +   0.008005330712*_Complex_I,
+    0.215736103058 +  -0.160301959515*_Complex_I,
+    0.012318083644 +   0.165701925755*_Complex_I,
+   -0.092335480452 +   0.060041588545*_Complex_I,
+    0.057762295008 +   0.212985706329*_Complex_I,
+   -0.116756439209 +   0.080165004730*_Complex_I,
+   -0.016070105135 +   0.102566015720*_Complex_I,
+   -0.031425985694 +   0.048826861382*_Complex_I,
+   -0.138180553913 +  -0.015630686283*_Complex_I,
+   -0.040541607141 +   0.086674833298*_Complex_I,
+   -0.017452953756 +  -0.181320679188*_Complex_I,
+    0.011673089117 +   0.072657525539*_Complex_I,
+   -0.015449254215 +   0.112104630470*_Complex_I,
+    0.071193879843 +  -0.032887244225*_Complex_I,
+    0.016030588746 +  -0.007359164953*_Complex_I,
+    0.007357457280 +   0.054825830460*_Complex_I,
+   -0.068202400208 +  -0.091296017170*_Complex_I,
+    0.030967503786 +  -0.193203377724*_Complex_I,
+    0.036832565069 +  -0.081849122047*_Complex_I,
+   -0.046027070284 +  -0.027470356226*_Complex_I,
+   -0.125437974930 +   0.032441046834*_Complex_I,
+    0.011070625484 +  -0.092022657394*_Complex_I,
+    0.137458860874 +   0.151134181023*_Complex_I,
+   -0.117661428452 +  -0.008656930178*_Complex_I,
+    0.163053572178 +  -0.070776551962*_Complex_I,
+   -0.119681096077 +  -0.012249100208*_Complex_I,
+    0.127253675461 +   0.107909095287*_Complex_I,
+   -0.051333457232 +  -0.116273534298*_Complex_I,
+   -0.071138489246 +   0.072735458612*_Complex_I,
+    0.037040272355 +  -0.050955241919*_Complex_I,
+   -0.200804090500 +  -0.217231678963*_Complex_I,
+   -0.109212982655 +   0.012372082472*_Complex_I,
+   -0.029450660944 +   0.118574059010*_Complex_I,
+    0.099285334349 +  -0.000237483322*_Complex_I,
+    0.276025676727 +  -0.017251548171*_Complex_I,
+    0.088983309269 +   0.013136875629*_Complex_I,
+    0.163659727573 +  -0.066418147087*_Complex_I,
+    0.104050731659 +   0.028489127755*_Complex_I,
+    0.016449892521 +  -0.020746667683*_Complex_I,
+   -0.126559376717 +  -0.070659190416*_Complex_I,
+   -0.237141442299 +   0.089611142874*_Complex_I,
+    0.068004012108 +   0.173722803593*_Complex_I,
+   -0.047145104408 +  -0.175941777229*_Complex_I,
+   -0.123525714874 +  -0.166570901871*_Complex_I,
+    0.001423638687 +   0.157464015484*_Complex_I,
+    0.079405152798 +  -0.025422385335*_Complex_I,
+    0.081756842136 +   0.121069538593*_Complex_I,
+   -0.128136157990 +   0.139459896088*_Complex_I,
+    0.007735489309 +  -0.027935725451*_Complex_I,
+   -0.092589050531 +  -0.109762561321*_Complex_I,
+   -0.016260780394 +  -0.015316626430*_Complex_I,
+   -0.158776199818 +   0.127178168297*_Complex_I,
+   -0.026575049758 +   0.238629913330*_Complex_I,
+    0.152634930611 +  -0.165786969662*_Complex_I,
+    0.108250701427 +  -0.051711326838*_Complex_I,
+    0.076149952412 +  -0.090032291412*_Complex_I,
+    0.074367189407 +  -0.029565215111*_Complex_I,
+    0.061409753561 +  -0.112582445145*_Complex_I,
+   -0.097367572784 +  -0.113927245140*_Complex_I,
+    0.026321750879 +   0.138302946091*_Complex_I,
+   -0.001476241183 +  -0.069936299324*_Complex_I,
+   -0.114703190327 +   0.070511698723*_Complex_I,
+   -0.006401912868 +  -0.010170778632*_Complex_I,
+    0.082021260262 +  -0.036617666483*_Complex_I,
+    0.381905221939 +   0.054933649302*_Complex_I,
+   -0.047172623873 +  -0.016221418977*_Complex_I,
+    0.075080448389 +   0.044649630785*_Complex_I,
+    0.006743746251 +  -0.057219368219*_Complex_I,
+    0.072658914328 +  -0.113842511177*_Complex_I,
+    0.063461345434 +   0.108319532871*_Complex_I,
+   -0.085157209635 +   0.127499580383*_Complex_I,
+   -0.110658645630 +   0.026664611697*_Complex_I,
+    0.087542557716 +  -0.198326361179*_Complex_I,
+   -0.187630629539 +  -0.076032394171*_Complex_I,
+    0.158300364017 +  -0.183700358868*_Complex_I,
+   -0.117931878567 +  -0.157972729206*_Complex_I,
+    0.048598474264 +  -0.121435344219*_Complex_I,
+    0.090523117781 +  -0.092027544975*_Complex_I,
+   -0.126598548889 +  -0.115896356106*_Complex_I,
+   -0.050347799063 +   0.063965880871*_Complex_I,
+    0.009061803669 +  -0.003273813799*_Complex_I,
+   -0.138404893875 +  -0.058004939556*_Complex_I,
+   -0.027039816976 +   0.123541855812*_Complex_I,
+   -0.260724592209 +   0.070059484243*_Complex_I,
+   -0.071726894379 +   0.118393385410*_Complex_I,
+   -0.103354132175 +  -0.048656904697*_Complex_I,
+   -0.079942697287 +  -0.007439111173*_Complex_I,
+    0.010002358258 +   0.217011165619*_Complex_I,
+   -0.107096755505 +  -0.032441377640*_Complex_I,
+   -0.038857096434 +  -0.075556772947*_Complex_I,
+    0.103407859802 +  -0.010233432055*_Complex_I,
+   -0.017742788792 +  -0.016240037978*_Complex_I,
+   -0.061273831129 +   0.003164015338*_Complex_I,
+   -0.120957195759 +   0.053525984287*_Complex_I,
+   -0.102713096142 +  -0.067174482346*_Complex_I,
+    0.033584815264 +   0.006909974664*_Complex_I,
+   -0.067435938120 +  -0.001113843173*_Complex_I,
+    0.151518130302 +  -0.022716298699*_Complex_I,
+   -0.092082822323 +  -0.080135297775*_Complex_I,
+    0.027139824629 +   0.071895372868*_Complex_I,
+   -0.115053045750 +   0.071924138069*_Complex_I,
+    0.011123746634 +  -0.015718665719*_Complex_I,
+    0.101550316811 +   0.003213942051*_Complex_I,
+    0.087435191870 +  -0.074827837944*_Complex_I,
+    0.098427033424 +   0.153796470165*_Complex_I,
+   -0.030665528774 +   0.086529731750*_Complex_I,
+   -0.015693670511 +  -0.002731614932*_Complex_I,
+    0.133912920952 +   0.264183211327*_Complex_I,
+   -0.000968740415 +   0.156215214729*_Complex_I,
+   -0.018345800042 +  -0.029062193632*_Complex_I,
+   -0.049218568206 +  -0.058449655771*_Complex_I,
+    0.045136275887 +   0.035753569007*_Complex_I,
+    0.011435703933 +   0.002002427541*_Complex_I,
+    0.021104848385 +  -0.311103820801*_Complex_I,
+    0.005841723830 +  -0.030350989103*_Complex_I,
+   -0.064211606979 +   0.203191661835*_Complex_I,
+   -0.071606880426 +   0.014993095398*_Complex_I,
+    0.072429609299 +   0.168000137806*_Complex_I,
+   -0.038637900352 +  -0.050586193800*_Complex_I,
+    0.168560516834 +   0.112749493122*_Complex_I,
+    0.140708494186 +  -0.139942800999*_Complex_I,
+    0.044472098351 +   0.057332044840*_Complex_I,
+   -0.113584566116 +   0.036734291911*_Complex_I,
+   -0.115594518185 +   0.096241426468*_Complex_I,
+   -0.132571578026 +   0.076662874222*_Complex_I,
+   -0.088810175657 +   0.180961203575*_Complex_I,
+   -0.134494102001 +   0.108674275875*_Complex_I,
+   -0.066532063484 +   0.080732357502*_Complex_I,
+   -0.001597549021 +   0.199119567871*_Complex_I,
+   -0.171998977661 +   0.006639727950*_Complex_I,
+    0.157607150078 +  -0.054489070177*_Complex_I,
+   -0.109499049187 +  -0.023027896881*_Complex_I,
+   -0.172086226940 +   0.121059966087*_Complex_I,
+    0.119662201405 +  -0.004805976152*_Complex_I};
+
+float complex fftfilt_cccf_data_h4x256_y[] = {
+    0.012757276827 +  -0.023960510977*_Complex_I,
+    0.028238533774 +   0.000766892287*_Complex_I,
+   -0.030736578281 +   0.034719058493*_Complex_I,
+   -0.049977138490 +  -0.058212812777*_Complex_I,
+   -0.029132843102 +  -0.006952447936*_Complex_I,
+    0.041434724419 +   0.001305867219*_Complex_I,
+   -0.000154542372 +   0.010398927205*_Complex_I,
+   -0.010671035217 +   0.020245277403*_Complex_I,
+    0.045073710539 +  -0.010894785162*_Complex_I,
+    0.061276520757 +  -0.004366387666*_Complex_I,
+    0.015952866275 +   0.035222286704*_Complex_I,
+   -0.014939304199 +   0.009382470283*_Complex_I,
+   -0.000027323374 +  -0.010136557087*_Complex_I,
+   -0.049199930734 +   0.015765195506*_Complex_I,
+   -0.006368245652 +  -0.015589575903*_Complex_I,
+    0.029022969544 +  -0.020956220654*_Complex_I,
+   -0.029749689836 +   0.060255058887*_Complex_I,
+   -0.023648414328 +   0.012506271275*_Complex_I,
+    0.042523255991 +  -0.060078254361*_Complex_I,
+   -0.003653249612 +   0.029899901014*_Complex_I,
+    0.002128348980 +   0.042692956028*_Complex_I,
+   -0.023239979421 +  -0.013201242246*_Complex_I,
+   -0.036395878395 +   0.006550682393*_Complex_I,
+    0.005937946446 +  -0.039787300481*_Complex_I,
+    0.071765960434 +   0.000338344221*_Complex_I,
+    0.014183242345 +  -0.003691402540*_Complex_I,
+   -0.014927275586 +  -0.025008346274*_Complex_I,
+   -0.021975526648 +  -0.015174616793*_Complex_I,
+    0.031559403980 +   0.039201077146*_Complex_I,
+   -0.002930033110 +   0.012825110045*_Complex_I,
+   -0.048982592713 +   0.010293962046*_Complex_I,
+    0.013434665977 +   0.032290621601*_Complex_I,
+    0.033982791965 +   0.001317652206*_Complex_I,
+    0.023064195315 +   0.012663899061*_Complex_I,
+   -0.018428432947 +   0.056613928236*_Complex_I,
+   -0.049039654309 +  -0.025292660627*_Complex_I,
+    0.010320821686 +  -0.041716388907*_Complex_I,
+    0.023878576192 +   0.025126876135*_Complex_I,
+    0.020413632469 +   0.043472532575*_Complex_I,
+    0.036996565632 +  -0.062485539135*_Complex_I,
+   -0.004910190919 +  -0.013287275246*_Complex_I,
+   -0.010096052144 +   0.103664702556*_Complex_I,
+   -0.028018937736 +   0.023708913702*_Complex_I,
+   -0.011287655176 +  -0.076741200437*_Complex_I,
+   -0.001467109023 +  -0.001917661318*_Complex_I,
+    0.005293144320 +   0.010848041931*_Complex_I,
+    0.018185964149 +   0.026525130926*_Complex_I,
+   -0.015239532128 +  -0.026094016726*_Complex_I,
+   -0.034810813866 +   0.010226757752*_Complex_I,
+   -0.049480455827 +   0.006882194797*_Complex_I,
+    0.045731930549 +  -0.016140007390*_Complex_I,
+    0.014701121326 +   0.023870809774*_Complex_I,
+   -0.033559313643 +   0.022713731523*_Complex_I,
+    0.050108362232 +   0.015581007741*_Complex_I,
+   -0.005632120264 +   0.018308311284*_Complex_I,
+    0.052024517749 +  -0.050433653230*_Complex_I,
+    0.020247348485 +   0.025265853477*_Complex_I,
+   -0.048724807310 +   0.005688999390*_Complex_I,
+    0.018642648578 +  -0.005112453077*_Complex_I,
+   -0.000164879106 +  -0.021983711442*_Complex_I,
+   -0.005295938220 +  -0.039983291504*_Complex_I,
+   -0.036089961107 +   0.036124313440*_Complex_I,
+    0.032332494429 +   0.024537918792*_Complex_I,
+    0.022487700299 +  -0.035126580906*_Complex_I,
+    0.035179302371 +   0.075476289976*_Complex_I,
+   -0.022415275427 +   0.042965211229*_Complex_I,
+    0.045691989716 +   0.001526190095*_Complex_I,
+    0.004179917382 +  -0.000409395803*_Complex_I,
+   -0.014563762228 +  -0.005376713067*_Complex_I,
+   -0.027925684190 +   0.012934585830*_Complex_I,
+   -0.000374846557 +  -0.026098719210*_Complex_I,
+    0.032612571524 +   0.015336583912*_Complex_I,
+   -0.052107302856 +   0.002717865097*_Complex_I,
+   -0.032834640384 +   0.065614047849*_Complex_I,
+   -0.005080334534 +   0.020989805546*_Complex_I,
+    0.031881162655 +  -0.029694607868*_Complex_I,
+   -0.015170864143 +  -0.028448915975*_Complex_I,
+    0.020955734615 +   0.001553519861*_Complex_I,
+   -0.025709904149 +   0.043093492818*_Complex_I,
+   -0.013558366662 +  -0.019060063112*_Complex_I,
+    0.021290614364 +  -0.015390877095*_Complex_I,
+   -0.008998068093 +   0.010479790493*_Complex_I,
+   -0.024201210119 +  -0.017983452805*_Complex_I,
+    0.030041436100 +  -0.000867601449*_Complex_I,
+    0.011111929170 +   0.043244016918*_Complex_I,
+   -0.001398590030 +   0.033219709577*_Complex_I,
+   -0.017169337706 +  -0.032498137528*_Complex_I,
+   -0.003608831698 +  -0.004190317460*_Complex_I,
+   -0.039540681605 +  -0.003316647474*_Complex_I,
+   -0.018843962328 +   0.007361859229*_Complex_I,
+    0.046631096565 +   0.003646629910*_Complex_I,
+   -0.027706090091 +   0.008592625414*_Complex_I,
+   -0.002924156406 +   0.033883252714*_Complex_I,
+    0.075079733805 +   0.034815167989*_Complex_I,
+   -0.030357014457 +  -0.045596609600*_Complex_I,
+   -0.032642221938 +  -0.016644239862*_Complex_I,
+    0.032402948921 +  -0.001883868205*_Complex_I,
+   -0.023858913130 +   0.039323855789*_Complex_I,
+   -0.041457486487 +  -0.014233573157*_Complex_I,
+   -0.007459151809 +   0.013792354132*_Complex_I,
+    0.002485997247 +  -0.043959018212*_Complex_I,
+    0.003318269056 +  -0.012519460724*_Complex_I,
+    0.000093169055 +  -0.016480871965*_Complex_I,
+   -0.016900119114 +  -0.010406925321*_Complex_I,
+   -0.007971836056 +   0.065190143824*_Complex_I,
+   -0.006820219290 +   0.027753423502*_Complex_I,
+    0.031106376763 +  -0.020043621536*_Complex_I,
+   -0.031071266484 +   0.004339971697*_Complex_I,
+    0.040991447377 +  -0.102820217268*_Complex_I,
+    0.031948560681 +  -0.016199124342*_Complex_I,
+   -0.025194033136 +   0.010324013232*_Complex_I,
+   -0.006991588793 +  -0.016219922197*_Complex_I,
+    0.028959571857 +   0.015200024042*_Complex_I,
+   -0.011128094874 +  -0.018331258759*_Complex_I,
+    0.010033408771 +  -0.036038577315*_Complex_I,
+   -0.003951805923 +   0.031887379901*_Complex_I,
+    0.043011761476 +   0.038394544247*_Complex_I,
+    0.047003249222 +   0.040511060053*_Complex_I,
+   -0.046451986806 +   0.004395040215*_Complex_I,
+   -0.012529114947 +  -0.014584931709*_Complex_I,
+    0.036884371074 +  -0.020832501744*_Complex_I,
+   -0.021101612528 +  -0.029213061508*_Complex_I,
+   -0.036011838357 +   0.005793889985*_Complex_I,
+   -0.015900637518 +   0.056267590724*_Complex_I,
+   -0.023781635996 +   0.008199383119*_Complex_I,
+    0.030327057536 +  -0.078422708418*_Complex_I,
+   -0.005177131563 +  -0.017104662921*_Complex_I,
+   -0.014188045903 +  -0.035985384502*_Complex_I,
+    0.032146074117 +   0.004602106645*_Complex_I,
+   -0.009821272669 +  -0.008851465116*_Complex_I,
+    0.027236042210 +   0.006241896038*_Complex_I,
+    0.022418389996 +  -0.007040618186*_Complex_I,
+   -0.005638389798 +   0.012513644070*_Complex_I,
+    0.000036227705 +   0.039661942810*_Complex_I,
+   -0.017530818413 +  -0.055992409201*_Complex_I,
+   -0.009980130825 +   0.003409352124*_Complex_I,
+   -0.007128899287 +   0.029065621717*_Complex_I,
+    0.007569712793 +  -0.013550165086*_Complex_I,
+    0.012981701430 +  -0.004450356742*_Complex_I,
+    0.025509754347 +   0.048348739912*_Complex_I,
+   -0.011201724949 +   0.032065969619*_Complex_I,
+   -0.000655974814 +  -0.016732426150*_Complex_I,
+    0.019689483222 +  -0.025854936465*_Complex_I,
+    0.011124510152 +  -0.000398237810*_Complex_I,
+   -0.047061637319 +   0.015558558157*_Complex_I,
+   -0.011720140733 +  -0.036972797597*_Complex_I,
+    0.028160356014 +   0.036264888172*_Complex_I,
+   -0.021771924689 +   0.010389295245*_Complex_I,
+    0.019810005650 +  -0.024476888982*_Complex_I,
+   -0.033191779573 +  -0.001651714148*_Complex_I,
+    0.043581774082 +   0.018672171512*_Complex_I,
+   -0.003880433403 +  -0.026649182116*_Complex_I,
+    0.024387701876 +   0.043399010529*_Complex_I,
+    0.060278802711 +   0.009923457700*_Complex_I,
+   -0.031958233338 +  -0.053227946939*_Complex_I,
+   -0.037844853987 +   0.000125803799*_Complex_I,
+   -0.051129681909 +   0.048362994750*_Complex_I,
+   -0.034939691771 +   0.013353255240*_Complex_I,
+    0.013495424238 +   0.002533014678*_Complex_I,
+   -0.024433814805 +   0.008923172086*_Complex_I,
+    0.006819321217 +  -0.017933176852*_Complex_I,
+    0.036263988877 +   0.000691266276*_Complex_I,
+    0.051618724746 +  -0.028316128600*_Complex_I,
+   -0.011460974644 +  -0.049249657052*_Complex_I,
+   -0.038203479805 +   0.025707308664*_Complex_I,
+    0.063567349796 +   0.074586008729*_Complex_I,
+    0.005400590167 +  -0.029955963800*_Complex_I,
+   -0.049012787288 +  -0.037393461959*_Complex_I,
+   -0.028249615420 +   0.026654301221*_Complex_I,
+    0.008517579272 +  -0.050945600372*_Complex_I,
+    0.033776936755 +  -0.007976718780*_Complex_I,
+    0.007283043274 +   0.040108239951*_Complex_I,
+    0.026628003403 +   0.013587396181*_Complex_I,
+   -0.004776533268 +  -0.040224248409*_Complex_I,
+    0.001966163699 +  -0.058690841656*_Complex_I,
+   -0.036928601088 +   0.022515059406*_Complex_I,
+   -0.018983384206 +   0.092142143476*_Complex_I,
+    0.006683303356 +  -0.005118852801*_Complex_I,
+   -0.006178795549 +   0.007657773084*_Complex_I,
+   -0.004596093747 +   0.004602055322*_Complex_I,
+    0.023277809619 +   0.018778205222*_Complex_I,
+    0.011819463018 +  -0.026895362189*_Complex_I,
+   -0.029906188199 +  -0.028279608383*_Complex_I,
+    0.020930036213 +   0.019813673820*_Complex_I,
+    0.017967997243 +  -0.027321140175*_Complex_I,
+   -0.026039058237 +   0.028575862467*_Complex_I,
+   -0.065707150276 +   0.027564172049*_Complex_I,
+   -0.042699371771 +  -0.004553530531*_Complex_I,
+    0.060963634866 +  -0.010306512109*_Complex_I,
+   -0.018398404569 +  -0.009004454205*_Complex_I,
+    0.012682078281 +   0.035081253992*_Complex_I,
+   -0.022569581502 +  -0.002483099277*_Complex_I,
+   -0.005011260632 +  -0.059832394695*_Complex_I,
+    0.033576764841 +  -0.020322773572*_Complex_I,
+    0.011459245426 +   0.047090396734*_Complex_I,
+    0.010458010526 +   0.044772209285*_Complex_I,
+    0.029840259776 +   0.005715793160*_Complex_I,
+   -0.033675797644 +   0.041554808951*_Complex_I,
+    0.042385066649 +   0.007945594638*_Complex_I,
+   -0.042488207826 +   0.012628049489*_Complex_I,
+    0.011808820611 +   0.006893400628*_Complex_I,
+    0.034485946031 +  -0.019260139612*_Complex_I,
+   -0.025110072757 +  -0.027865545511*_Complex_I,
+    0.011809921801 +   0.017253366676*_Complex_I,
+    0.023347768774 +  -0.013960810328*_Complex_I,
+    0.004312140053 +  -0.050475400734*_Complex_I,
+    0.039740495297 +  -0.014728639995*_Complex_I,
+   -0.013385038021 +   0.002586440528*_Complex_I,
+    0.024990035834 +   0.031120474272*_Complex_I,
+   -0.017924227058 +  -0.032258326607*_Complex_I,
+   -0.008503405616 +  -0.030526995261*_Complex_I,
+    0.039069831028 +   0.047795375707*_Complex_I,
+   -0.017060972790 +   0.016758758851*_Complex_I,
+   -0.023173891050 +  -0.001084414363*_Complex_I,
+    0.026255359508 +  -0.007252038830*_Complex_I,
+    0.018286656103 +  -0.022933031391*_Complex_I,
+    0.024368811798 +  -0.006580655348*_Complex_I,
+   -0.004301769439 +   0.024140294549*_Complex_I,
+   -0.014786161819 +  -0.007103599240*_Complex_I,
+   -0.004382808422 +   0.013351241103*_Complex_I,
+   -0.016799676739 +   0.015974782469*_Complex_I,
+    0.034117502850 +  -0.007152515247*_Complex_I,
+   -0.016030168590 +  -0.041660273222*_Complex_I,
+    0.020183324735 +   0.001275076977*_Complex_I,
+   -0.029259335107 +   0.025057117273*_Complex_I,
+   -0.019506766368 +   0.019348724623*_Complex_I,
+   -0.017064727586 +  -0.003715333530*_Complex_I,
+   -0.011048527087 +  -0.053833523722*_Complex_I,
+    0.024686830410 +   0.003799560503*_Complex_I,
+   -0.028896024708 +  -0.014835494620*_Complex_I,
+   -0.040702141102 +  -0.056626153309*_Complex_I,
+    0.032680942933 +   0.017723655112*_Complex_I,
+    0.029116465637 +   0.030527344293*_Complex_I,
+    0.002198173782 +   0.000971254919*_Complex_I,
+   -0.021900155002 +  -0.010625879853*_Complex_I,
+    0.022293892586 +   0.049298267413*_Complex_I,
+    0.012631626403 +   0.052532172470*_Complex_I,
+   -0.013732381568 +  -0.080618502666*_Complex_I,
+    0.000893373321 +  -0.046464687935*_Complex_I,
+   -0.012758518468 +   0.013919376550*_Complex_I,
+   -0.014235454439 +  -0.011924506441*_Complex_I,
+   -0.001743292107 +   0.029502693438*_Complex_I,
+   -0.044487788769 +   0.006911727783*_Complex_I,
+    0.005476613431 +   0.035516199946*_Complex_I,
+    0.025026369628 +  -0.054869348705*_Complex_I,
+    0.031378831930 +  -0.026801186271*_Complex_I,
+    0.011708942324 +  -0.028570090261*_Complex_I,
+    0.004475361885 +  -0.022218736038*_Complex_I,
+    0.002560834562 +  -0.032693273555*_Complex_I,
+    0.016389708983 +   0.001391247264*_Complex_I,
+   -0.017112703198 +  -0.015433616304*_Complex_I,
+    0.011474238974 +  -0.022409486851*_Complex_I,
+    0.019950525436 +   0.045615249829*_Complex_I,
+   -0.029025601300 +   0.019024103552*_Complex_I,
+    0.058627375247 +  -0.038320734588*_Complex_I,
+   -0.014973530138 +  -0.021441866237*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_cccf_data_h7x256.c b/src/filter/tests/data/fftfilt_cccf_data_h7x256.c
new file mode 100644
index 0000000..7c2d918
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_cccf_data_h7x256.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_cccf_data_h7x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float complex fftfilt_cccf_data_h7x256_h[] = {
+   -0.007281364501 +   0.082790547609*_Complex_I,
+    0.009967260063 +  -0.094733065367*_Complex_I,
+    0.081317949295 +  -0.022636219859*_Complex_I,
+   -0.065424275398 +  -0.051597696543*_Complex_I,
+    0.014696654677 +  -0.083596301079*_Complex_I,
+   -0.061433798075 +   0.010713580251*_Complex_I,
+   -0.225753474236 +   0.128715121746*_Complex_I};
+
+float complex fftfilt_cccf_data_h7x256_x[] = {
+   -0.224034833908 +  -0.008699086308*_Complex_I,
+   -0.049732953310 +  -0.031826138496*_Complex_I,
+    0.076697158813 +   0.011335625499*_Complex_I,
+   -0.036047935486 +   0.081799823046*_Complex_I,
+   -0.034496423602 +   0.116949427128*_Complex_I,
+    0.041598823667 +   0.101615536213*_Complex_I,
+    0.210013437271 +  -0.035565614700*_Complex_I,
+   -0.012152856588 +  -0.011570352316*_Complex_I,
+   -0.028147217631 +   0.166729998589*_Complex_I,
+   -0.167404901981 +   0.187454140186*_Complex_I,
+   -0.026512432098 +   0.261313009262*_Complex_I,
+    0.102972781658 +  -0.004534423724*_Complex_I,
+    0.059284800291 +   0.183526933193*_Complex_I,
+    0.030326950550 +  -0.089217764139*_Complex_I,
+   -0.018447282910 +  -0.049126222730*_Complex_I,
+    0.113261175156 +   0.002915558405*_Complex_I,
+   -0.135392773151 +   0.014876940846*_Complex_I,
+    0.004361338913 +   0.021383246779*_Complex_I,
+   -0.202443695068 +  -0.054077798128*_Complex_I,
+   -0.004843016341 +   0.108202099800*_Complex_I,
+    0.001668977737 +   0.194661521912*_Complex_I,
+   -0.010195496678 +   0.196535933018*_Complex_I,
+    0.034973055124 +  -0.061964601278*_Complex_I,
+   -0.102277493477 +   0.223646140099*_Complex_I,
+   -0.023419928551 +   0.070746564865*_Complex_I,
+   -0.159975051880 +  -0.097339767218*_Complex_I,
+    0.106308436394 +  -0.067826884985*_Complex_I,
+    0.009907814115 +  -0.101035916805*_Complex_I,
+    0.091246986389 +  -0.126393759251*_Complex_I,
+    0.155365455151 +   0.032811716199*_Complex_I,
+   -0.050400662422 +   0.005999959260*_Complex_I,
+    0.098249650002 +   0.119454848766*_Complex_I,
+    0.043273428082 +   0.037401539087*_Complex_I,
+   -0.023780041933 +  -0.059200626612*_Complex_I,
+   -0.123366844654 +   0.054930090904*_Complex_I,
+    0.036521813273 +  -0.093353188038*_Complex_I,
+   -0.127803146839 +  -0.129888510704*_Complex_I,
+    0.010239485651 +  -0.006050923839*_Complex_I,
+    0.073504078388 +   0.019415560365*_Complex_I,
+   -0.071594107151 +   0.259088206291*_Complex_I,
+   -0.164907193184 +  -0.230266833305*_Complex_I,
+   -0.047658449411 +  -0.051974463463*_Complex_I,
+    0.126409828663 +   0.080523037910*_Complex_I,
+   -0.015694612265 +  -0.041679582000*_Complex_I,
+    0.247366762161 +   0.014814090729*_Complex_I,
+   -0.128671801090 +   0.043787026405*_Complex_I,
+    0.059505617619 +  -0.051679652929*_Complex_I,
+   -0.031817299128 +  -0.116028845310*_Complex_I,
+   -0.059748482704 +   0.052395486832*_Complex_I,
+    0.057849615812 +   0.112650203705*_Complex_I,
+    0.024142844975 +  -0.127626419067*_Complex_I,
+   -0.021054366231 +  -0.034706163406*_Complex_I,
+    0.115030443668 +  -0.228685379028*_Complex_I,
+    0.046825522184 +  -0.056672191620*_Complex_I,
+    0.033698257804 +   0.121798670292*_Complex_I,
+    0.054840600491 +  -0.057236319780*_Complex_I,
+    0.099697828293 +   0.019323211908*_Complex_I,
+   -0.155612981319 +  -0.004370240867*_Complex_I,
+   -0.004620010406 +  -0.084638983011*_Complex_I,
+    0.070532739162 +   0.011928464472*_Complex_I,
+   -0.109211897850 +  -0.102859199047*_Complex_I,
+   -0.081594198942 +  -0.078812837601*_Complex_I,
+    0.043022164702 +  -0.170342290401*_Complex_I,
+   -0.121134638786 +   0.000720720459*_Complex_I,
+   -0.113152539730 +   0.049016854167*_Complex_I,
+   -0.044530594349 +  -0.051119738817*_Complex_I,
+    0.072675555944 +   0.022612531483*_Complex_I,
+    0.079078042507 +  -0.004720374569*_Complex_I,
+    0.139414632320 +  -0.062684261799*_Complex_I,
+    0.047556161880 +   0.004797710106*_Complex_I,
+    0.099456727505 +  -0.072699415684*_Complex_I,
+    0.042401811481 +   0.080890959501*_Complex_I,
+    0.153871464729 +  -0.013893789053*_Complex_I,
+    0.071284359694 +   0.021137169003*_Complex_I,
+   -0.167169976234 +  -0.071725249290*_Complex_I,
+   -0.107850408554 +  -0.217037105560*_Complex_I,
+   -0.074640941620 +  -0.056393194199*_Complex_I,
+   -0.088496351242 +  -0.011706933379*_Complex_I,
+   -0.055197536945 +   0.114396464825*_Complex_I,
+   -0.016207906604 +   0.147190082073*_Complex_I,
+    0.027984252572 +  -0.018767827749*_Complex_I,
+   -0.159589684010 +  -0.021223792434*_Complex_I,
+    0.258048582077 +  -0.011464864016*_Complex_I,
+   -0.028931617737 +   0.055535191298*_Complex_I,
+    0.090275204182 +  -0.104534256458*_Complex_I,
+    0.007160897553 +  -0.012574975193*_Complex_I,
+    0.074187409878 +  -0.067596536875*_Complex_I,
+   -0.129586255550 +  -0.095341777802*_Complex_I,
+    0.025683063269 +   0.037984248996*_Complex_I,
+   -0.097140216827 +   0.072665137053*_Complex_I,
+   -0.060703277588 +  -0.020677544177*_Complex_I,
+   -0.111789143085 +   0.069817501307*_Complex_I,
+    0.058083856106 +   0.050827920437*_Complex_I,
+    0.050110381842 +  -0.096382743120*_Complex_I,
+   -0.003128097579 +   0.086557382345*_Complex_I,
+   -0.162541246414 +  -0.290272450447*_Complex_I,
+    0.149705743790 +  -0.195581960678*_Complex_I,
+   -0.037328532338 +  -0.068566280603*_Complex_I,
+   -0.028459328413 +  -0.020298738778*_Complex_I,
+    0.030010712147 +  -0.021110333502*_Complex_I,
+    0.040339571238 +   0.037839868665*_Complex_I,
+    0.014134916663 +  -0.117352914810*_Complex_I,
+   -0.077330631018 +  -0.119114518166*_Complex_I,
+   -0.022596450150 +   0.134884607792*_Complex_I,
+    0.031885313988 +  -0.092574226856*_Complex_I,
+    0.050890713930 +  -0.056063300371*_Complex_I,
+   -0.065839445591 +   0.059127074480*_Complex_I,
+    0.234607529640 +  -0.133058774471*_Complex_I,
+    0.102069032192 +  -0.076372599602*_Complex_I,
+   -0.075891935825 +  -0.020809832215*_Complex_I,
+   -0.090930736065 +  -0.085748946667*_Complex_I,
+    0.017342594266 +   0.031213536859*_Complex_I,
+    0.038657885790 +   0.025738736987*_Complex_I,
+    0.060605341196 +  -0.119861590862*_Complex_I,
+    0.073895943165 +  -0.044689002633*_Complex_I,
+    0.105450510979 +  -0.160455989838*_Complex_I,
+   -0.124542450905 +   0.046032539010*_Complex_I,
+    0.045331561565 +   0.000319873891*_Complex_I,
+    0.024264129996 +   0.026674240828*_Complex_I,
+   -0.105825412273 +  -0.020621740818*_Complex_I,
+    0.088798046112 +  -0.188584482670*_Complex_I,
+   -0.106230950356 +   0.102003884315*_Complex_I,
+    0.094149869680 +   0.040696659684*_Complex_I,
+   -0.034001356363 +   0.107437086105*_Complex_I,
+   -0.018224966526 +   0.112718844414*_Complex_I,
+    0.017100249231 +  -0.115852773190*_Complex_I,
+   -0.033193737268 +  -0.000597832398*_Complex_I,
+    0.000483878236 +  -0.106654667854*_Complex_I,
+   -0.058922708035 +   0.047018918395*_Complex_I,
+   -0.062014073133 +  -0.023304754496*_Complex_I,
+    0.044369766116 +  -0.168211150169*_Complex_I,
+    0.130730128288 +  -0.172116661072*_Complex_I,
+    0.145810830593 +  -0.078202575445*_Complex_I,
+    0.064167177677 +  -0.028775659204*_Complex_I,
+   -0.080559664965 +   0.030702465773*_Complex_I,
+    0.099988013506 +   0.068486881256*_Complex_I,
+    0.037929025292 +  -0.016449454427*_Complex_I,
+    0.170670747757 +  -0.075250750780*_Complex_I,
+    0.063665509224 +   0.095736193657*_Complex_I,
+   -0.004959807545 +   0.029583230615*_Complex_I,
+    0.001406790875 +   0.107474470139*_Complex_I,
+   -0.118572568893 +  -0.044207367301*_Complex_I,
+    0.006175531074 +   0.128024518490*_Complex_I,
+   -0.140851879120 +  -0.023605427146*_Complex_I,
+   -0.107514488697 +   0.124532020092*_Complex_I,
+    0.002869207598 +  -0.137480175495*_Complex_I,
+    0.107076263428 +   0.171094465256*_Complex_I,
+   -0.154599738121 +   0.276871156693*_Complex_I,
+    0.101584804058 +   0.051078712940*_Complex_I,
+    0.080567914248 +   0.072715389729*_Complex_I,
+   -0.118250894547 +   0.203802919388*_Complex_I,
+   -0.204971146584 +  -0.016665045917*_Complex_I,
+   -0.066708135605 +  -0.131536126137*_Complex_I,
+   -0.121210551262 +   0.006252479553*_Complex_I,
+    0.061139965057 +   0.027922019362*_Complex_I,
+   -0.019852848351 +   0.013758929074*_Complex_I,
+    0.124678170681 +   0.068077403307*_Complex_I,
+    0.114459848404 +  -0.033342263103*_Complex_I,
+    0.177140033245 +  -0.072824108601*_Complex_I,
+    0.044942480326 +  -0.185283625126*_Complex_I,
+    0.130411398411 +   0.241176652908*_Complex_I,
+    0.184130918980 +  -0.040936833620*_Complex_I,
+   -0.000141406199 +   0.054715901613*_Complex_I,
+   -0.069603371620 +   0.036669918895*_Complex_I,
+   -0.061578559875 +   0.032855123281*_Complex_I,
+    0.056589269638 +  -0.058769983053*_Complex_I,
+    0.182259237766 +   0.049186679721*_Complex_I,
+    0.065936774015 +   0.027898868918*_Complex_I,
+   -0.065557408333 +  -0.070153760910*_Complex_I,
+    0.054138237238 +  -0.027161026001*_Complex_I,
+   -0.018398490548 +  -0.110003650188*_Complex_I,
+    0.002755430155 +   0.078435707092*_Complex_I,
+    0.013758489490 +  -0.113180506229*_Complex_I,
+   -0.051234912872 +  -0.005474814028*_Complex_I,
+   -0.104593336582 +   0.080550593138*_Complex_I,
+    0.007267350703 +  -0.125861871243*_Complex_I,
+    0.107477843761 +   0.046790626645*_Complex_I,
+    0.039212217927 +  -0.116215384007*_Complex_I,
+    0.124826478958 +  -0.081632637978*_Complex_I,
+    0.125280117989 +   0.320632147789*_Complex_I,
+   -0.007471156120 +   0.104660058022*_Complex_I,
+   -0.115239381790 +  -0.076022726297*_Complex_I,
+   -0.094622492790 +  -0.057151579857*_Complex_I,
+   -0.168905770779 +  -0.250946950912*_Complex_I,
+   -0.108807468414 +   0.001907045953*_Complex_I,
+    0.070568650961 +   0.086386668682*_Complex_I,
+   -0.001869313978 +   0.037941047549*_Complex_I,
+   -0.025408759713 +   0.082414060831*_Complex_I,
+    0.144938981533 +   0.203817605972*_Complex_I,
+    0.069982367754 +   0.049768888950*_Complex_I,
+    0.016490152478 +   0.074179810286*_Complex_I,
+    0.105687451363 +  -0.110984444618*_Complex_I,
+    0.113551545143 +   0.109880757332*_Complex_I,
+   -0.057247632742 +   0.134226346016*_Complex_I,
+   -0.147579395771 +   0.089595174789*_Complex_I,
+   -0.066095471382 +  -0.101449275017*_Complex_I,
+   -0.099719625711 +   0.069632160664*_Complex_I,
+   -0.010588133335 +   0.072132295370*_Complex_I,
+    0.121390247345 +   0.038519486785*_Complex_I,
+   -0.018545143306 +  -0.181026077271*_Complex_I,
+    0.063769525290 +  -0.064973735809*_Complex_I,
+   -0.069635564089 +  -0.033288177848*_Complex_I,
+   -0.191137075424 +  -0.041628292203*_Complex_I,
+    0.078501302004 +   0.084461838007*_Complex_I,
+    0.057879024744 +  -0.055903303623*_Complex_I,
+   -0.024872413278 +  -0.012924948335*_Complex_I,
+    0.030974438787 +   0.053028428555*_Complex_I,
+    0.108443212509 +   0.096264117956*_Complex_I,
+    0.020256328583 +   0.141622221470*_Complex_I,
+   -0.014323671162 +   0.037487837672*_Complex_I,
+   -0.054792886972 +  -0.018909129500*_Complex_I,
+    0.112955272198 +  -0.105722856522*_Complex_I,
+    0.076327735186 +  -0.023400066793*_Complex_I,
+    0.050041192770 +  -0.111338639259*_Complex_I,
+   -0.152574229240 +   0.112567448616*_Complex_I,
+   -0.100790119171 +   0.151508641243*_Complex_I,
+   -0.132981181145 +  -0.022067332268*_Complex_I,
+   -0.039436492324 +  -0.142156898975*_Complex_I,
+    0.272883749008 +  -0.021410278976*_Complex_I,
+    0.016576641798 +   0.044247716665*_Complex_I,
+   -0.043560856581 +   0.035046094656*_Complex_I,
+    0.048125803471 +   0.009931682050*_Complex_I,
+    0.067209833860 +   0.107492733002*_Complex_I,
+   -0.046075564623 +   0.075197333097*_Complex_I,
+   -0.102803206444 +   0.086919504404*_Complex_I,
+    0.082356423140 +   0.014404250681*_Complex_I,
+   -0.008545895666 +   0.062289041281*_Complex_I,
+   -0.137773501873 +  -0.077222770452*_Complex_I,
+    0.011148673296 +   0.004276630655*_Complex_I,
+   -0.013848714530 +   0.030436140299*_Complex_I,
+   -0.032711941004 +  -0.004091787338*_Complex_I,
+   -0.068230164051 +  -0.041420990229*_Complex_I,
+   -0.153997719288 +  -0.011947461963*_Complex_I,
+    0.104870522022 +   0.007879497856*_Complex_I,
+   -0.012260202318 +   0.097705692053*_Complex_I,
+    0.105240559578 +   0.005197756737*_Complex_I,
+   -0.052444481850 +   0.019592680037*_Complex_I,
+   -0.111304521561 +  -0.082899254560*_Complex_I,
+    0.143180394173 +   0.084308052063*_Complex_I,
+   -0.081846255064 +   0.013113263249*_Complex_I,
+   -0.046875402331 +   0.172497475147*_Complex_I,
+   -0.101886129379 +   0.027100220323*_Complex_I,
+   -0.120501518250 +  -0.011151956767*_Complex_I,
+   -0.069285804033 +  -0.066505169868*_Complex_I,
+    0.135213494301 +  -0.155834245682*_Complex_I,
+    0.120262145996 +  -0.073802947998*_Complex_I,
+    0.083161532879 +   0.151243543625*_Complex_I,
+    0.032715103030 +   0.113134217262*_Complex_I,
+    0.029656037688 +   0.049728840590*_Complex_I,
+   -0.002572274208 +  -0.116174411774*_Complex_I,
+   -0.028744161129 +   0.043948829174*_Complex_I,
+   -0.039567849040 +   0.071218341589*_Complex_I,
+   -0.008260916919 +  -0.064960545301*_Complex_I,
+    0.065007799864 +  -0.006339413673*_Complex_I,
+   -0.060093468428 +   0.035650527477*_Complex_I,
+    0.056396996975 +   0.005192263052*_Complex_I};
+
+float complex fftfilt_cccf_data_h7x256_y[] = {
+    0.002351481406 +  -0.018484625365*_Complex_I,
+   -0.000060077369 +   0.017251119786*_Complex_I,
+   -0.023422599244 +   0.015025306572*_Complex_I,
+    0.004772403706 +  -0.000066271937*_Complex_I,
+    0.002043987108 +   0.022957322244*_Complex_I,
+    0.006971825136 +   0.011730423896*_Complex_I,
+    0.075044907061 +  -0.010178051817*_Complex_I,
+    0.030543599209 +  -0.014597024085*_Complex_I,
+   -0.004187564049 +  -0.012444862513*_Complex_I,
+   -0.008030018509 +  -0.052716125275*_Complex_I,
+   -0.014464683212 +  -0.025509129789*_Complex_I,
+   -0.010946291967 +   0.010946463449*_Complex_I,
+   -0.019083957948 +   0.052439959673*_Complex_I,
+    0.066017066062 +  -0.011801681075*_Complex_I,
+    0.012681132555 +  -0.044881829563*_Complex_I,
+    0.013761162913 +  -0.101272173584*_Complex_I,
+   -0.025754029961 +  -0.084604999250*_Complex_I,
+   -0.029077685715 +   0.014810788777*_Complex_I,
+   -0.052203914324 +  -0.045501878664*_Complex_I,
+    0.002605824684 +   0.042542814124*_Complex_I,
+   -0.020042313554 +   0.020106358559*_Complex_I,
+   -0.001164073801 +   0.033904434250*_Complex_I,
+    0.054491738690 +   0.009310176063*_Complex_I,
+    0.008515506861 +  -0.011753395733*_Complex_I,
+    0.094828569883 +  -0.026525642698*_Complex_I,
+    0.008210310591 +  -0.020111342169*_Complex_I,
+   -0.019624361168 +  -0.039339738841*_Complex_I,
+   -0.014390113878 +  -0.046568094370*_Complex_I,
+    0.022417994509 +   0.019882474081*_Complex_I,
+   -0.042352866908 +  -0.063076024765*_Complex_I,
+    0.006168688437 +  -0.049455732212*_Complex_I,
+    0.024885397093 +   0.019187383987*_Complex_I,
+   -0.027570701811 +   0.012482396648*_Complex_I,
+    0.034892258750 +   0.024754673210*_Complex_I,
+   -0.019859377031 +   0.024455238102*_Complex_I,
+   -0.017573930231 +   0.012206492330*_Complex_I,
+   -0.000030036343 +  -0.018861530511*_Complex_I,
+   -0.047303454990 +  -0.008979343421*_Complex_I,
+   -0.032862144074 +   0.013096445775*_Complex_I,
+   -0.003290051529 +   0.000967363362*_Complex_I,
+    0.056330016511 +  -0.016207422066*_Complex_I,
+   -0.009913108504 +   0.058827181838*_Complex_I,
+    0.034154084481 +  -0.006877816529*_Complex_I,
+    0.020659033034 +   0.020329190951*_Complex_I,
+   -0.033737500188 +   0.029784749533*_Complex_I,
+   -0.014787869426 +  -0.101188110643*_Complex_I,
+    0.104978906788 +   0.041126486668*_Complex_I,
+   -0.014250954609 +  -0.012495039834*_Complex_I,
+   -0.033482494554 +  -0.025221829498*_Complex_I,
+   -0.021831072247 +   0.022276580945*_Complex_I,
+   -0.039699732339 +   0.032109615994*_Complex_I,
+    0.015186398989 +  -0.019223783117*_Complex_I,
+    0.015617783227 +   0.023435548030*_Complex_I,
+    0.008665069769 +   0.010930457080*_Complex_I,
+   -0.019797730380 +  -0.050654536825*_Complex_I,
+   -0.031473081271 +  -0.002299268225*_Complex_I,
+   -0.012475915927 +   0.033630086770*_Complex_I,
+    0.012117088154 +  -0.022047521436*_Complex_I,
+    0.018953771446 +   0.084550384232*_Complex_I,
+   -0.038474316372 +   0.008379280035*_Complex_I,
+   -0.004152321333 +  -0.040424140749*_Complex_I,
+   -0.015770397680 +   0.041094196479*_Complex_I,
+   -0.032229291824 +   0.007648330888*_Complex_I,
+    0.017449905676 +  -0.027618778546*_Complex_I,
+   -0.006186370902 +   0.022452120667*_Complex_I,
+   -0.030800615243 +   0.036816080788*_Complex_I,
+    0.022326504033 +   0.029342855626*_Complex_I,
+    0.033861836665 +   0.027757022091*_Complex_I,
+    0.033294890809 +   0.063126561257*_Complex_I,
+    0.026195622962 +  -0.034339699352*_Complex_I,
+    0.036201082469 +  -0.036428336260*_Complex_I,
+   -0.008587770526 +  -0.012485601384*_Complex_I,
+   -0.015622310942 +  -0.008490102192*_Complex_I,
+   -0.031068211782 +   0.008958937995*_Complex_I,
+   -0.007527617323 +  -0.009025029679*_Complex_I,
+   -0.003515003755 +   0.009061780897*_Complex_I,
+   -0.050680655889 +   0.006806632206*_Complex_I,
+   -0.037393296454 +  -0.018572129400*_Complex_I,
+   -0.068556095005 +   0.055060185831*_Complex_I,
+   -0.034772327965 +   0.025392795717*_Complex_I,
+    0.068529963519 +   0.033603853922*_Complex_I,
+    0.068141793595 +   0.036809357702*_Complex_I,
+    0.044397919943 +   0.034591392907*_Complex_I,
+    0.016558611126 +  -0.038538049919*_Complex_I,
+    0.039037787845 +  -0.030382584037*_Complex_I,
+   -0.047508232653 +  -0.037136167083*_Complex_I,
+    0.022633104346 +  -0.021161468084*_Complex_I,
+    0.019417536232 +  -0.025786181928*_Complex_I,
+   -0.073279279420 +   0.029450944763*_Complex_I,
+   -0.028480466565 +  -0.024506456063*_Complex_I,
+    0.002785948604 +   0.049355785551*_Complex_I,
+   -0.027211045311 +   0.018218457773*_Complex_I,
+    0.010057004283 +   0.043364801389*_Complex_I,
+    0.052591845448 +   0.024521644529*_Complex_I,
+   -0.007103268409 +  -0.008581702223*_Complex_I,
+    0.054820608058 +  -0.043395183529*_Complex_I,
+    0.007024973331 +   0.024411987870*_Complex_I,
+   -0.021388284266 +  -0.082405595490*_Complex_I,
+   -0.016087426094 +   0.012722301935*_Complex_I,
+   -0.051447296267 +   0.037652130140*_Complex_I,
+   -0.020486080157 +  -0.013825054104*_Complex_I,
+    0.077051270037 +   0.059321440673*_Complex_I,
+   -0.007177045570 +   0.063268745704*_Complex_I,
+   -0.007356525502 +  -0.002336907547*_Complex_I,
+    0.015196504414 +   0.005533310901*_Complex_I,
+   -0.020595977716 +   0.027771059823*_Complex_I,
+   -0.024966648066 +  -0.018445080957*_Complex_I,
+    0.039144080425 +   0.064591816908*_Complex_I,
+    0.010537647232 +  -0.002822061917*_Complex_I,
+    0.002315709356 +  -0.065572625222*_Complex_I,
+   -0.004442911821 +   0.023522751103*_Complex_I,
+   -0.037537461559 +   0.002025051546*_Complex_I,
+   -0.015345245866 +  -0.018745889894*_Complex_I,
+   -0.028196845561 +   0.086992050424*_Complex_I,
+   -0.020218420272 +   0.034974063899*_Complex_I,
+    0.039213934957 +  -0.014112442962*_Complex_I,
+    0.010679269036 +  -0.019758241192*_Complex_I,
+   -0.019147387264 +  -0.013125586740*_Complex_I,
+   -0.043295632029 +   0.009486453379*_Complex_I,
+    0.005284847258 +   0.018994059412*_Complex_I,
+   -0.002085765180 +   0.059479538243*_Complex_I,
+   -0.029236803469 +   0.019741499311*_Complex_I,
+    0.035349982194 +  -0.019550030169*_Complex_I,
+   -0.041053561980 +   0.019272334294*_Complex_I,
+    0.004781302105 +  -0.011012070475*_Complex_I,
+    0.045646671834 +   0.021045605276*_Complex_I,
+    0.012770406300 +   0.038111716908*_Complex_I,
+    0.027329675786 +  -0.046058315366*_Complex_I,
+   -0.039771978581 +   0.000217202102*_Complex_I,
+   -0.009660565931 +  -0.044728920362*_Complex_I,
+   -0.009225975226 +   0.004924878144*_Complex_I,
+    0.002703841656 +   0.032097754317*_Complex_I,
+    0.004813881897 +  -0.003380733447*_Complex_I,
+    0.004987905899 +   0.008151460089*_Complex_I,
+   -0.013488990428 +  -0.042016257678*_Complex_I,
+   -0.009691719041 +   0.004147526727*_Complex_I,
+   -0.001915632244 +   0.039683487703*_Complex_I,
+    0.003401771653 +   0.072636903863*_Complex_I,
+   -0.039215694844 +   0.021883077630*_Complex_I,
+    0.017168617098 +  -0.011912327212*_Complex_I,
+   -0.007318874653 +  -0.021289530606*_Complex_I,
+   -0.021564667106 +  -0.032135038803*_Complex_I,
+   -0.018800999207 +   0.028531206788*_Complex_I,
+   -0.021400100758 +   0.015792926127*_Complex_I,
+   -0.021993149924 +   0.008754610031*_Complex_I,
+    0.006936290706 +   0.000597376601*_Complex_I,
+   -0.021466822342 +   0.006078512969*_Complex_I,
+    0.032723812926 +  -0.038751398566*_Complex_I,
+    0.024881623268 +   0.028431503514*_Complex_I,
+    0.023419199688 +  -0.017541455668*_Complex_I,
+    0.050912833475 +  -0.066376947294*_Complex_I,
+    0.054697680345 +   0.030926451912*_Complex_I,
+   -0.032668768209 +  -0.026228308942*_Complex_I,
+   -0.011814848889 +  -0.099139861759*_Complex_I,
+   -0.019161603567 +   0.029704427790*_Complex_I,
+   -0.036750148181 +   0.005099535581*_Complex_I,
+    0.009675516891 +  -0.040182618193*_Complex_I,
+    0.058423255788 +  -0.009856816276*_Complex_I,
+    0.059012388904 +   0.021583146036*_Complex_I,
+    0.037149929970 +  -0.044837871667*_Complex_I,
+   -0.043274833420 +  -0.019633669057*_Complex_I,
+    0.003526349344 +  -0.033434222301*_Complex_I,
+   -0.050076212918 +  -0.003722989863*_Complex_I,
+   -0.025867292105 +  -0.013323971651*_Complex_I,
+   -0.021737697568 +   0.042999651371*_Complex_I,
+    0.007346070802 +   0.030458687290*_Complex_I,
+   -0.075005823056 +  -0.018314546864*_Complex_I,
+   -0.022152256082 +   0.019384502494*_Complex_I,
+    0.017499070564 +  -0.019824208197*_Complex_I,
+    0.001505234864 +  -0.027164808784*_Complex_I,
+    0.011030624309 +  -0.041209715189*_Complex_I,
+   -0.026294492121 +   0.019222017912*_Complex_I,
+   -0.050856019432 +   0.008735996115*_Complex_I,
+   -0.027403486053 +   0.008700019502*_Complex_I,
+    0.006815542339 +  -0.009666401504*_Complex_I,
+    0.006105705172 +   0.040222173395*_Complex_I,
+   -0.012075903008 +   0.033397015302*_Complex_I,
+    0.012019759958 +  -0.021944616302*_Complex_I,
+    0.017925084955 +   0.052323963578*_Complex_I,
+   -0.030587094207 +  -0.037431463625*_Complex_I,
+    0.042390892393 +  -0.046071078594*_Complex_I,
+    0.020356789621 +   0.037541180532*_Complex_I,
+   -0.029260492422 +  -0.016824122043*_Complex_I,
+    0.038403000345 +   0.018077265958*_Complex_I,
+   -0.050358282861 +   0.031195025615*_Complex_I,
+   -0.103007313184 +  -0.046673399500*_Complex_I,
+   -0.015001996713 +   0.007412936952*_Complex_I,
+    0.030614255358 +   0.024026842526*_Complex_I,
+    0.030514848064 +   0.031032426681*_Complex_I,
+    0.103356759873 +   0.027622431036*_Complex_I,
+    0.043667006406 +  -0.014369006727*_Complex_I,
+    0.002636002600 +  -0.019049360571*_Complex_I,
+   -0.002989929744 +  -0.026987627040*_Complex_I,
+   -0.001094610254 +  -0.070463253033*_Complex_I,
+   -0.052461850294 +  -0.027889438408*_Complex_I,
+   -0.019216539397 +  -0.007771079643*_Complex_I,
+   -0.022329844508 +  -0.013265024739*_Complex_I,
+   -0.001032810892 +   0.043621747952*_Complex_I,
+   -0.037166418731 +   0.024063055407*_Complex_I,
+    0.025171222560 +  -0.045359590807*_Complex_I,
+    0.034025739768 +  -0.022421529929*_Complex_I,
+    0.025417150018 +  -0.024301770390*_Complex_I,
+    0.015009353323 +  -0.045864470900*_Complex_I,
+   -0.057523653826 +   0.003583092215*_Complex_I,
+   -0.034434260403 +   0.016881949843*_Complex_I,
+    0.035644457860 +   0.058158734255*_Complex_I,
+   -0.011112332655 +   0.028514917481*_Complex_I,
+    0.027994412412 +  -0.000205339299*_Complex_I,
+    0.041965151118 +  -0.028140201223*_Complex_I,
+   -0.010670315886 +  -0.004680002358*_Complex_I,
+    0.008782107185 +   0.015284218025*_Complex_I,
+    0.025707610980 +  -0.002674567128*_Complex_I,
+   -0.018895101407 +  -0.019624170860*_Complex_I,
+   -0.019911769609 +  -0.024108802516*_Complex_I,
+   -0.050134730896 +  -0.049361961836*_Complex_I,
+   -0.012519484934 +  -0.027194287781*_Complex_I,
+    0.005428457404 +   0.015715697375*_Complex_I,
+   -0.005238127738 +   0.060099693736*_Complex_I,
+   -0.019908892893 +   0.058463294088*_Complex_I,
+    0.020429924866 +   0.006302169131*_Complex_I,
+    0.039448100593 +  -0.046219948910*_Complex_I,
+   -0.015700342290 +  -0.047106832460*_Complex_I,
+    0.029509140574 +  -0.026543641019*_Complex_I,
+    0.028367751154 +   0.020535031374*_Complex_I,
+   -0.052572208167 +   0.041630709393*_Complex_I,
+   -0.001077487262 +  -0.000376269684*_Complex_I,
+    0.009826813537 +  -0.019618490100*_Complex_I,
+    0.019631481777 +  -0.007463103449*_Complex_I,
+   -0.034282389268 +   0.002382911984*_Complex_I,
+   -0.002559574060 +  -0.045197738751*_Complex_I,
+    0.021242907296 +  -0.020034144666*_Complex_I,
+   -0.026802493199 +   0.013457289582*_Complex_I,
+    0.000954596220 +  -0.020243773142*_Complex_I,
+    0.034013053731 +   0.024387771984*_Complex_I,
+   -0.020040092793 +  -0.001711964654*_Complex_I,
+    0.022898948260 +   0.014147183418*_Complex_I,
+    0.004346236707 +  -0.000881433760*_Complex_I,
+    0.056049489865 +  -0.020165330024*_Complex_I,
+    0.010340548926 +   0.003943868642*_Complex_I,
+   -0.020562812005 +  -0.025528198571*_Complex_I,
+   -0.012419521606 +  -0.000626407753*_Complex_I,
+   -0.026985254984 +   0.006236327937*_Complex_I,
+    0.035672449990 +   0.000476818088*_Complex_I,
+    0.034040270392 +   0.009473149874*_Complex_I,
+   -0.021617320834 +   0.027702717665*_Complex_I,
+    0.010577232861 +  -0.016439967770*_Complex_I,
+   -0.018705783473 +  -0.051973816043*_Complex_I,
+    0.017231348174 +  -0.025014597024*_Complex_I,
+    0.027788678270 +  -0.014081809821*_Complex_I,
+    0.035390113297 +  -0.001427819683*_Complex_I,
+   -0.010424664995 +   0.044346140061*_Complex_I,
+   -0.018385369373 +   0.007862320395*_Complex_I,
+   -0.032248235088 +  -0.015582515953*_Complex_I,
+   -0.037693834890 +  -0.014701063462*_Complex_I,
+   -0.006787010773 +  -0.016420585935*_Complex_I,
+    0.026470441729 +   0.040610897283*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_crcf_data_h13x256.c b/src/filter/tests/data/fftfilt_crcf_data_h13x256.c
new file mode 100644
index 0000000..e5d1be8
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_crcf_data_h13x256.c
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_crcf_data_h13x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float fftfilt_crcf_data_h13x256_h[] = {
+    0.078415364027,
+   -0.038823145628,
+   -0.051182097197,
+   -0.126747608185,
+   -0.044354891777,
+    0.008586834371,
+    0.093218648434,
+    0.013351599872,
+    0.104246592522,
+   -0.147247779369,
+   -0.015137273073,
+   -0.079225271940,
+    0.060841810703};
+
+float complex fftfilt_crcf_data_h13x256_x[] = {
+    0.085537636280 +  -0.046111658216*_Complex_I,
+    0.225455975533 +  -0.212855386734*_Complex_I,
+   -0.057062256336 +   0.128235316277*_Complex_I,
+   -0.133161616325 +   0.161920166016*_Complex_I,
+   -0.111347568035 +   0.016286674142*_Complex_I,
+   -0.123166453838 +  -0.175749886036*_Complex_I,
+   -0.127994334698 +  -0.038678660989*_Complex_I,
+    0.001753777266 +   0.177034842968*_Complex_I,
+   -0.060360968113 +   0.043074116111*_Complex_I,
+   -0.019538708031 +   0.103789019585*_Complex_I,
+   -0.014221027493 +  -0.063071894646*_Complex_I,
+    0.068899226189 +  -0.026364141703*_Complex_I,
+    0.115704405308 +  -0.004594086856*_Complex_I,
+    0.007105919719 +  -0.147161698341*_Complex_I,
+   -0.108864045143 +   0.057467007637*_Complex_I,
+   -0.068811172247 +  -0.147289085388*_Complex_I,
+   -0.230341863632 +  -0.117713248730*_Complex_I,
+    0.212086534500 +   0.075316011906*_Complex_I,
+    0.111719250679 +  -0.054894649982*_Complex_I,
+   -0.058907771111 +   0.011760091782*_Complex_I,
+    0.040262156725 +   0.204210424423*_Complex_I,
+    0.056027442217 +  -0.155560147762*_Complex_I,
+    0.086805480719 +  -0.004523682222*_Complex_I,
+    0.036359512806 +   0.141219484806*_Complex_I,
+   -0.220814013481 +  -0.062183243036*_Complex_I,
+    0.153262519836 +  -0.046371394396*_Complex_I,
+    0.026782101393 +  -0.145832955837*_Complex_I,
+    0.065809303522 +   0.160269105434*_Complex_I,
+   -0.350135111809 +  -0.025641095638*_Complex_I,
+   -0.014225926995 +  -0.006095404923*_Complex_I,
+    0.025032842159 +   0.079940289259*_Complex_I,
+   -0.153848528862 +   0.047698283195*_Complex_I,
+   -0.074249881506 +  -0.036540001631*_Complex_I,
+   -0.058573323488 +  -0.038949799538*_Complex_I,
+   -0.004152575880 +   0.284297037125*_Complex_I,
+   -0.135746490955 +   0.021615330875*_Complex_I,
+    0.016607114673 +  -0.020308291912*_Complex_I,
+    0.152347302437 +   0.127957522869*_Complex_I,
+   -0.010453918576 +   0.027730533481*_Complex_I,
+    0.014759464562 +   0.026369267702*_Complex_I,
+    0.082868421078 +   0.018653041124*_Complex_I,
+    0.036655226350 +   0.110286545753*_Complex_I,
+   -0.089152860641 +  -0.036632573605*_Complex_I,
+    0.039231717587 +   0.077600806952*_Complex_I,
+   -0.081070268154 +  -0.003452447802*_Complex_I,
+    0.000562082976 +   0.017119739950*_Complex_I,
+    0.144655835629 +   0.104714202881*_Complex_I,
+    0.014049226046 +   0.166446399689*_Complex_I,
+    0.088486814499 +  -0.014644494653*_Complex_I,
+    0.057762199640 +  -0.093236494064*_Complex_I,
+    0.047099858522 +  -0.028719729185*_Complex_I,
+    0.106528282166 +   0.020991277695*_Complex_I,
+    0.138301646709 +  -0.257377123833*_Complex_I,
+    0.080518740416 +   0.154109323025*_Complex_I,
+   -0.024190089107 +  -0.061511921883*_Complex_I,
+    0.000458766706 +   0.001026612055*_Complex_I,
+    0.099718827009 +  -0.065688782930*_Complex_I,
+   -0.029233866930 +  -0.195329236984*_Complex_I,
+   -0.066604834795 +   0.089790678024*_Complex_I,
+   -0.068512433767 +   0.076835018396*_Complex_I,
+    0.050179117918 +   0.095668357611*_Complex_I,
+   -0.025827249885 +   0.161506593227*_Complex_I,
+   -0.044248852134 +  -0.043011960387*_Complex_I,
+   -0.065869921446 +  -0.124157130718*_Complex_I,
+   -0.022857676446 +   0.034576061368*_Complex_I,
+   -0.092982125282 +   0.081510609388*_Complex_I,
+    0.197520732880 +   0.040147519112*_Complex_I,
+    0.064278131723 +  -0.020518574119*_Complex_I,
+   -0.044479858875 +  -0.093625146151*_Complex_I,
+    0.006783248484 +   0.080368715525*_Complex_I,
+    0.015499952435 +   0.014531745017*_Complex_I,
+    0.097499859333 +   0.031322878599*_Complex_I,
+    0.075692665577 +   0.103051352501*_Complex_I,
+   -0.127897429466 +   0.147205972672*_Complex_I,
+   -0.227601456642 +  -0.086447989941*_Complex_I,
+    0.079460048676 +   0.009285532683*_Complex_I,
+   -0.104196500778 +  -0.062360990047*_Complex_I,
+   -0.003569463268 +  -0.159094834328*_Complex_I,
+   -0.015271052718 +   0.053798562288*_Complex_I,
+    0.038455364108 +   0.154173243046*_Complex_I,
+    0.055872535706 +  -0.233663058281*_Complex_I,
+   -0.008917537332 +   0.091611146927*_Complex_I,
+    0.086029797792 +  -0.039757794142*_Complex_I,
+    0.049558714032 +  -0.151712751389*_Complex_I,
+    0.080167847872 +   0.034949752688*_Complex_I,
+   -0.050086444616 +   0.050132060051*_Complex_I,
+    0.083901208639 +   0.046979528666*_Complex_I,
+   -0.132237958908 +   0.095516347885*_Complex_I,
+    0.091105806828 +   0.120565533638*_Complex_I,
+    0.043457311392 +  -0.141374385357*_Complex_I,
+   -0.013054405153 +  -0.100194096565*_Complex_I,
+   -0.159590744972 +   0.033074292541*_Complex_I,
+    0.000640281662 +  -0.032389008999*_Complex_I,
+    0.003084850125 +   0.021032336354*_Complex_I,
+   -0.185569190979 +   0.124299430847*_Complex_I,
+   -0.011914233118 +  -0.237578940392*_Complex_I,
+   -0.171687960625 +  -0.104329741001*_Complex_I,
+   -0.069582682848 +  -0.225686883926*_Complex_I,
+   -0.113897264004 +   0.090501463413*_Complex_I,
+    0.093735665083 +   0.154317796230*_Complex_I,
+    0.014358232915 +  -0.103527617455*_Complex_I,
+   -0.008946143836 +   0.126785087585*_Complex_I,
+   -0.137021541595 +  -0.133091437817*_Complex_I,
+   -0.093204009533 +  -0.052661812305*_Complex_I,
+   -0.017306408286 +   0.105603075027*_Complex_I,
+   -0.070221811533 +  -0.083750408888*_Complex_I,
+    0.026400086284 +  -0.231271290779*_Complex_I,
+   -0.067720073462 +   0.057201570272*_Complex_I,
+   -0.048747465014 +  -0.065235435963*_Complex_I,
+    0.043681365252 +  -0.104784011841*_Complex_I,
+   -0.090612548590 +  -0.275935935974*_Complex_I,
+   -0.003840988874 +   0.110235190392*_Complex_I,
+    0.175717639923 +   0.039627754688*_Complex_I,
+   -0.095855474472 +  -0.109507787228*_Complex_I,
+   -0.103805732727 +   0.047046291828*_Complex_I,
+    0.102693021297 +  -0.121691584587*_Complex_I,
+    0.205679154396 +   0.105767965317*_Complex_I,
+   -0.076863563061 +  -0.109635329247*_Complex_I,
+    0.008887568116 +  -0.008115684241*_Complex_I,
+    0.115569293499 +  -0.002244588919*_Complex_I,
+    0.114743447304 +   0.110550260544*_Complex_I,
+   -0.207589817047 +   0.076551246643*_Complex_I,
+    0.001820044219 +   0.039755198359*_Complex_I,
+    0.120840430260 +   0.207095527649*_Complex_I,
+    0.060907727480 +   0.108076405525*_Complex_I,
+   -0.068012183905 +  -0.004220475256*_Complex_I,
+   -0.075779396296 +   0.192010104656*_Complex_I,
+    0.176728832722 +  -0.014139008522*_Complex_I,
+    0.003537797555 +  -0.008619928360*_Complex_I,
+    0.105585181713 +  -0.044047874212*_Complex_I,
+   -0.042796334624 +   0.104850161076*_Complex_I,
+   -0.051997685432 +   0.156431221962*_Complex_I,
+    0.020244561136 +   0.167572557926*_Complex_I,
+    0.145088231564 +   0.027772811055*_Complex_I,
+    0.037466460466 +   0.109766936302*_Complex_I,
+   -0.098527204990 +  -0.032260373235*_Complex_I,
+    0.009477450699 +  -0.035027536750*_Complex_I,
+   -0.016112615168 +   0.085437601805*_Complex_I,
+    0.143263375759 +   0.075950628519*_Complex_I,
+    0.106226658821 +   0.103723526001*_Complex_I,
+    0.000303745759 +  -0.010642482340*_Complex_I,
+    0.108046162128 +  -0.032960096002*_Complex_I,
+   -0.003579319268 +   0.113042652607*_Complex_I,
+    0.004739668965 +  -0.090823930502*_Complex_I,
+   -0.075828909874 +  -0.005588034540*_Complex_I,
+   -0.085471099615 +  -0.016970762610*_Complex_I,
+    0.011292902380 +  -0.025727859139*_Complex_I,
+   -0.003472129628 +  -0.066497987509*_Complex_I,
+    0.052674955130 +   0.053410953283*_Complex_I,
+    0.171188580990 +   0.164189565182*_Complex_I,
+   -0.031832432747 +  -0.106380403042*_Complex_I,
+    0.104630780220 +  -0.005379130691*_Complex_I,
+    0.146008169651 +  -0.016912537813*_Complex_I,
+    0.001029302273 +   0.191088676453*_Complex_I,
+   -0.189716017246 +  -0.017046442628*_Complex_I,
+   -0.098682647943 +   0.001545182616*_Complex_I,
+    0.017514893413 +  -0.091537868977*_Complex_I,
+    0.046101492643 +  -0.087318551540*_Complex_I,
+    0.006703664362 +  -0.168205010891*_Complex_I,
+   -0.017618760467 +   0.027586823702*_Complex_I,
+   -0.131682169437 +   0.043349444866*_Complex_I,
+    0.050538945198 +   0.176133990288*_Complex_I,
+   -0.053573626280 +  -0.126223480701*_Complex_I,
+    0.196893954277 +  -0.158274137974*_Complex_I,
+   -0.148222410679 +   0.046666064858*_Complex_I,
+    0.095723485947 +   0.036118236184*_Complex_I,
+   -0.072401177883 +  -0.081512874365*_Complex_I,
+    0.057588219643 +   0.215086269379*_Complex_I,
+    0.082906532288 +   0.065297931433*_Complex_I,
+   -0.031125253439 +   0.089076888561*_Complex_I,
+   -0.022213415802 +  -0.206439447403*_Complex_I,
+   -0.128299164772 +   0.021574760973*_Complex_I,
+    0.044257754087 +   0.042379784584*_Complex_I,
+    0.201319956779 +  -0.054105019569*_Complex_I,
+    0.028415796161 +  -0.068978685141*_Complex_I,
+   -0.011957987398 +   0.176460993290*_Complex_I,
+    0.090524059534 +  -0.115864157677*_Complex_I,
+   -0.107446074486 +  -0.129327297211*_Complex_I,
+    0.089880418777 +   0.060228496790*_Complex_I,
+   -0.030561709404 +   0.026375296712*_Complex_I,
+    0.098281121254 +  -0.215652251244*_Complex_I,
+   -0.007009872049 +  -0.074216008186*_Complex_I,
+   -0.004887212813 +   0.159088206291*_Complex_I,
+    0.056175363064 +   0.101569080353*_Complex_I,
+   -0.062076961994 +  -0.057750892639*_Complex_I,
+   -0.126010406017 +   0.065122300386*_Complex_I,
+   -0.049417442083 +   0.079578405619*_Complex_I,
+    0.104471850395 +  -0.222810673714*_Complex_I,
+    0.008511145413 +   0.109938132763*_Complex_I,
+    0.037150239944 +   0.084586590528*_Complex_I,
+    0.050007992983 +  -0.057158488035*_Complex_I,
+   -0.035429692268 +   0.058791869879*_Complex_I,
+   -0.155366706848 +   0.118630039692*_Complex_I,
+    0.047825142741 +  -0.105882799625*_Complex_I,
+   -0.109545922279 +  -0.002970626391*_Complex_I,
+    0.130073928833 +   0.085777258873*_Complex_I,
+   -0.039974737167 +   0.114924871922*_Complex_I,
+    0.082493311167 +  -0.066850924492*_Complex_I,
+    0.094225972891 +  -0.016298688948*_Complex_I,
+   -0.196156990528 +  -0.039484533668*_Complex_I,
+    0.221052432060 +   0.012097968161*_Complex_I,
+    0.011275211722 +  -0.101457715034*_Complex_I,
+    0.009068550915 +   0.102877616882*_Complex_I,
+    0.233158087730 +   0.028579771519*_Complex_I,
+    0.103334558010 +   0.013103367388*_Complex_I,
+    0.113383591175 +   0.299872016907*_Complex_I,
+    0.003663517535 +  -0.053204262257*_Complex_I,
+    0.016009953618 +  -0.204974889755*_Complex_I,
+   -0.129484927654 +  -0.081961399317*_Complex_I,
+    0.043841245770 +  -0.172690594196*_Complex_I,
+    0.016576626897 +   0.178835308552*_Complex_I,
+    0.021964907646 +  -0.018969631195*_Complex_I,
+    0.040968599916 +   0.013126873970*_Complex_I,
+   -0.139588427544 +   0.084010922909*_Complex_I,
+   -0.119654500484 +   0.023274496198*_Complex_I,
+   -0.069549763203 +   0.014241448045*_Complex_I,
+    0.061426883936 +   0.037087076902*_Complex_I,
+   -0.101361310482 +  -0.062185329199*_Complex_I,
+   -0.112329518795 +  -0.115670502186*_Complex_I,
+    0.134469640255 +   0.146243238449*_Complex_I,
+    0.069860440493 +  -0.059987205267*_Complex_I,
+    0.002678702772 +   0.026742336154*_Complex_I,
+   -0.100598180294 +   0.019916568696*_Complex_I,
+    0.112527644634 +   0.158179092407*_Complex_I,
+   -0.044148188829 +   0.048926886916*_Complex_I,
+   -0.036606773734 +  -0.143591856956*_Complex_I,
+    0.058808875084 +   0.019581055641*_Complex_I,
+    0.036360570788 +   0.014478833973*_Complex_I,
+    0.175481355190 +   0.010096323490*_Complex_I,
+    0.108694136143 +  -0.070673483610*_Complex_I,
+    0.090956181288 +  -0.025260141492*_Complex_I,
+   -0.051956391335 +  -0.102067518234*_Complex_I,
+   -0.136262392998 +  -0.047263675928*_Complex_I,
+   -0.003427351639 +  -0.120171773434*_Complex_I,
+   -0.056164652109 +   0.211695027351*_Complex_I,
+    0.122092783451 +   0.066175770760*_Complex_I,
+    0.058047491312 +   0.006997314841*_Complex_I,
+   -0.052954304218 +  -0.022754417360*_Complex_I,
+    0.142856597900 +   0.108723735809*_Complex_I,
+   -0.034428742528 +  -0.054634892941*_Complex_I,
+   -0.060924202204 +   0.031046575308*_Complex_I,
+   -0.200955986977 +  -0.117396569252*_Complex_I,
+   -0.088245415688 +   0.019958937168*_Complex_I,
+   -0.020239216089 +   0.041008698940*_Complex_I,
+    0.016120427847 +  -0.252910399437*_Complex_I,
+   -0.005489441007 +  -0.005252978206*_Complex_I,
+    0.146177339554 +  -0.124855351448*_Complex_I,
+    0.008751311898 +   0.021546928585*_Complex_I,
+   -0.045790722966 +  -0.055281382799*_Complex_I,
+    0.020690375566 +   0.076892906427*_Complex_I,
+    0.021331049502 +  -0.009237347543*_Complex_I,
+    0.055180394650 +  -0.023450747132*_Complex_I,
+   -0.200542330742 +  -0.096801882982*_Complex_I,
+    0.010742266476 +   0.088201242685*_Complex_I,
+    0.072365719080 +   0.038520815969*_Complex_I,
+    0.174543678761 +  -0.139873552322*_Complex_I};
+
+float complex fftfilt_crcf_data_h13x256_y[] = {
+    0.006707464887 +  -0.003615862465*_Complex_I,
+    0.014358372283 +  -0.014900933014*_Complex_I,
+   -0.017605463388 +   0.020679426057*_Complex_I,
+   -0.030607580793 +   0.024457457884*_Complex_I,
+   -0.033011059558 +   0.017451711642*_Complex_I,
+   -0.000552862047 +  -0.029909500040*_Complex_I,
+    0.029762551415 +  -0.029380501742*_Complex_I,
+    0.053098771667 +  -0.004223742190*_Complex_I,
+    0.027764068736 +   0.025733123329*_Complex_I,
+    0.019184165051 +   0.011649528617*_Complex_I,
+   -0.045468748657 +   0.015676561918*_Complex_I,
+   -0.015208315362 +  -0.029868068751*_Complex_I,
+   -0.005074731196 +  -0.025633738248*_Complex_I,
+    0.016953787198 +  -0.036545469115*_Complex_I,
+   -0.015051143400 +   0.040438104378*_Complex_I,
+   -0.000389921114 +   0.040583727841*_Complex_I,
+   -0.018476482937 +   0.001605043113*_Complex_I,
+    0.059276110787 +   0.008087486852*_Complex_I,
+    0.032014951529 +  -0.026016763072*_Complex_I,
+    0.028169341465 +   0.019691597843*_Complex_I,
+   -0.027575406956 +   0.014924607884*_Complex_I,
+   -0.044969293038 +  -0.034038723353*_Complex_I,
+   -0.035957879305 +   0.009864897267*_Complex_I,
+    0.015551833890 +  -0.025044401657*_Complex_I,
+   -0.025072135741 +   0.016447641694*_Complex_I,
+    0.067713809538 +   0.015047662589*_Complex_I,
+   -0.014827314512 +  -0.006159133526*_Complex_I,
+    0.017417617315 +   0.018940542198*_Complex_I,
+   -0.051497901101 +   0.013839514218*_Complex_I,
+    0.006425931128 +  -0.012107460374*_Complex_I,
+    0.003778314165 +   0.004734451148*_Complex_I,
+    0.024107174779 +  -0.007035831300*_Complex_I,
+   -0.010365520171 +  -0.022195343173*_Complex_I,
+    0.051580386373 +  -0.008087616648*_Complex_I,
+   -0.021776665000 +  -0.003115102667*_Complex_I,
+    0.023266451034 +   0.047344342773*_Complex_I,
+   -0.053848275959 +  -0.026819242730*_Complex_I,
+    0.063009256294 +  -0.009944000506*_Complex_I,
+    0.006746963810 +  -0.032158195839*_Complex_I,
+    0.003429372021 +  -0.000544080428*_Complex_I,
+   -0.021562011789 +  -0.003250402475*_Complex_I,
+   -0.014790441461 +  -0.003085695467*_Complex_I,
+    0.010008967449 +   0.023823269718*_Complex_I,
+   -0.008293948136 +  -0.022998494726*_Complex_I,
+    0.011452965518 +  -0.020435253906*_Complex_I,
+    0.025061888640 +  -0.008646415119*_Complex_I,
+    0.009417229303 +   0.002491707097*_Complex_I,
+   -0.001052856194 +   0.014974027706*_Complex_I,
+   -0.009405958690 +  -0.029993691331*_Complex_I,
+   -0.014721142637 +  -0.008638802539*_Complex_I,
+   -0.036054668791 +  -0.043505364136*_Complex_I,
+    0.002937162197 +   0.016396266668*_Complex_I,
+   -0.004075444532 +  -0.014475666608*_Complex_I,
+    0.011656769213 +   0.056296896866*_Complex_I,
+   -0.011068622206 +   0.001107236820*_Complex_I,
+   -0.029464500491 +   0.023806584861*_Complex_I,
+   -0.001234548257 +  -0.043088390782*_Complex_I,
+   -0.013592614491 +  -0.029832033410*_Complex_I,
+    0.009582371751 +   0.002336669503*_Complex_I,
+   -0.007665271794 +   0.050333730164*_Complex_I,
+    0.006935919041 +   0.000935532598*_Complex_I,
+   -0.003952523598 +   0.051259768228*_Complex_I,
+   -0.006392118058 +  -0.064666858821*_Complex_I,
+   -0.009497007771 +  -0.021680677945*_Complex_I,
+    0.009619420930 +  -0.042495568141*_Complex_I,
+   -0.014045975317 +   0.022418973657*_Complex_I,
+    0.028464744316 +   0.062481882056*_Complex_I,
+    0.001067687053 +   0.012760185564*_Complex_I,
+    0.016381591820 +  -0.014605527050*_Complex_I,
+   -0.034415502412 +  -0.026979376550*_Complex_I,
+   -0.017681907434 +  -0.024441169492*_Complex_I,
+   -0.005897651761 +   0.007001263045*_Complex_I,
+    0.033278467741 +   0.020304401627*_Complex_I,
+   -0.015372239067 +   0.016871371540*_Complex_I,
+    0.004123217473 +  -0.031815284855*_Complex_I,
+   -0.015306721649 +  -0.031111950511*_Complex_I,
+    0.004503865148 +  -0.033058697462*_Complex_I,
+    0.029142795487 +   0.022150281542*_Complex_I,
+    0.019772837268 +   0.022699222634*_Complex_I,
+    0.015820827834 +   0.046069967481*_Complex_I,
+   -0.023442270401 +  -0.016297278519*_Complex_I,
+   -0.026038769435 +   0.011214015864*_Complex_I,
+   -0.021468228990 +  -0.057542563457*_Complex_I,
+    0.034028743864 +  -0.002254875536*_Complex_I,
+   -0.006908951528 +   0.001680565517*_Complex_I,
+    0.007793730477 +   0.032660744962*_Complex_I,
+   -0.019663600279 +   0.025988218786*_Complex_I,
+   -0.003368100468 +   0.026542388675*_Complex_I,
+    0.013438824633 +  -0.047413054472*_Complex_I,
+   -0.004063114270 +  -0.015525778855*_Complex_I,
+    0.018830244631 +  -0.044104705319*_Complex_I,
+   -0.032245213510 +   0.016288658540*_Complex_I,
+    0.007245432060 +   0.024854569789*_Complex_I,
+   -0.027446572547 +   0.041850280938*_Complex_I,
+    0.029419010658 +   0.028727610740*_Complex_I,
+   -0.010452476889 +  -0.044041749123*_Complex_I,
+    0.030615778213 +  -0.021544600594*_Complex_I,
+   -0.006420674173 +  -0.051315896975*_Complex_I,
+    0.016696184929 +   0.047238754510*_Complex_I,
+    0.005689845758 +   0.063096109099*_Complex_I,
+    0.028255169043 +   0.036125238544*_Complex_I,
+    0.012724807027 +  -0.010983186848*_Complex_I,
+   -0.042681639075 +  -0.046834116871*_Complex_I,
+   -0.000989952691 +  -0.059638193499*_Complex_I,
+   -0.014509988842 +   0.030996292286*_Complex_I,
+    0.058136967808 +   0.004841843998*_Complex_I,
+    0.016711224472 +   0.056418115581*_Complex_I,
+    0.042240824058 +   0.015983263464*_Complex_I,
+   -0.022083897958 +  -0.024086928202*_Complex_I,
+   -0.001789727311 +   0.023860817657*_Complex_I,
+   -0.029894590649 +  -0.040496551187*_Complex_I,
+    0.018773801669 +   0.051847548401*_Complex_I,
+    0.031709831384 +   0.011373889090*_Complex_I,
+   -0.004023229281 +   0.020313196377*_Complex_I,
+   -0.001577893158 +  -0.019338873041*_Complex_I,
+   -0.016653147805 +   0.001029965867*_Complex_I,
+    0.022914141726 +  -0.002104645021*_Complex_I,
+    0.004474041805 +   0.010394883929*_Complex_I,
+   -0.007920863082 +  -0.015181966976*_Complex_I,
+   -0.013430955196 +   0.050824400632*_Complex_I,
+    0.008429368466 +   0.016663178667*_Complex_I,
+   -0.040384189162 +  -0.005524017450*_Complex_I,
+    0.002595592227 +  -0.002667437393*_Complex_I,
+    0.009304373200 +  -0.026400357126*_Complex_I,
+    0.048184754654 +   0.020873671678*_Complex_I,
+   -0.030489638011 +  -0.058480052355*_Complex_I,
+   -0.016679991101 +   0.018809245099*_Complex_I,
+   -0.008746544102 +  -0.035440693217*_Complex_I,
+    0.015062452407 +   0.019391877795*_Complex_I,
+   -0.022136074513 +  -0.020072882619*_Complex_I,
+    0.000436956008 +   0.007335145893*_Complex_I,
+   -0.008872275506 +   0.020549897080*_Complex_I,
+   -0.002241620183 +   0.006218861440*_Complex_I,
+   -0.001175415969 +  -0.040479462629*_Complex_I,
+    0.000083899519 +  -0.021882526156*_Complex_I,
+    0.025794092443 +  -0.065356864610*_Complex_I,
+   -0.035076022000 +  -0.002349257653*_Complex_I,
+   -0.003341037071 +  -0.006590144581*_Complex_I,
+   -0.014034503634 +   0.051948837186*_Complex_I,
+    0.030633439581 +   0.013809324577*_Complex_I,
+   -0.002962173263 +  -0.011885141436*_Complex_I,
+   -0.000277152583 +  -0.057872949989*_Complex_I,
+   -0.041128587589 +  -0.009986479197*_Complex_I,
+   -0.032666587947 +  -0.028631352014*_Complex_I,
+   -0.000937363608 +   0.018495486941*_Complex_I,
+    0.007753441209 +   0.008806508191*_Complex_I,
+    0.037266455064 +   0.010636476395*_Complex_I,
+    0.006587066115 +  -0.001705926814*_Complex_I,
+    0.003108159036 +  -0.006628853141*_Complex_I,
+    0.010939472752 +   0.006955405461*_Complex_I,
+   -0.035625089431 +   0.003442599966*_Complex_I,
+   -0.008902105887 +  -0.028853630235*_Complex_I,
+   -0.032226114328 +  -0.009004482236*_Complex_I,
+   -0.005180831436 +   0.005500644217*_Complex_I,
+   -0.013588249735 +   0.016986170136*_Complex_I,
+   -0.001619772072 +   0.000519931731*_Complex_I,
+    0.016514152427 +  -0.020852436461*_Complex_I,
+    0.046615752684 +  -0.000490623097*_Complex_I,
+    0.005417795160 +  -0.036933916369*_Complex_I,
+    0.007190900949 +   0.046477922845*_Complex_I,
+   -0.045185383594 +   0.017921427684*_Complex_I,
+   -0.014537612941 +   0.075949416617*_Complex_I,
+   -0.029141623074 +  -0.059957316195*_Complex_I,
+    0.049424271568 +  -0.033258736817*_Complex_I,
+    0.012029563939 +  -0.050271746506*_Complex_I,
+    0.023861869121 +   0.035334845394*_Complex_I,
+   -0.046542548410 +   0.016947426572*_Complex_I,
+    0.004060617093 +   0.071512899954*_Complex_I,
+   -0.014037223962 +  -0.012127913597*_Complex_I,
+    0.039889046850 +   0.005706366222*_Complex_I,
+   -0.028460519816 +  -0.096256824265*_Complex_I,
+    0.022664509438 +  -0.011714722989*_Complex_I,
+   -0.050769105818 +   0.012359859521*_Complex_I,
+    0.066087653016 +   0.054750882096*_Complex_I,
+   -0.020633352616 +   0.001005601365*_Complex_I,
+    0.024688652114 +   0.040909798080*_Complex_I,
+   -0.040302384002 +  -0.048949559759*_Complex_I,
+   -0.040499641156 +   0.001157352899*_Complex_I,
+    0.003027817515 +  -0.057483585836*_Complex_I,
+   -0.004425501836 +   0.047349196316*_Complex_I,
+    0.050482303632 +  -0.003841475384*_Complex_I,
+    0.006892893469 +   0.023406378381*_Complex_I,
+   -0.016436659880 +  -0.003310696286*_Complex_I,
+   -0.033973819400 +   0.045991336264*_Complex_I,
+   -0.006077224480 +  -0.023680985792*_Complex_I,
+   -0.024572808359 +  -0.010230339465*_Complex_I,
+    0.032481213736 +  -0.024751506834*_Complex_I,
+   -0.000140080804 +  -0.013756951783*_Complex_I,
+    0.045204061251 +  -0.001863065274*_Complex_I,
+   -0.014671178560 +   0.022868286772*_Complex_I,
+   -0.008200142546 +   0.040201740584*_Complex_I,
+   -0.028297364362 +   0.011407262105*_Complex_I,
+   -0.031979428685 +  -0.029902785287*_Complex_I,
+    0.007876129419 +  -0.034552491520*_Complex_I,
+    0.011879957596 +   0.002102523326*_Complex_I,
+    0.065685674192 +  -0.021696600034*_Complex_I,
+   -0.004436318677 +   0.044689761692*_Complex_I,
+    0.006444035464 +  -0.009625205044*_Complex_I,
+   -0.031610428157 +  -0.001954799840*_Complex_I,
+   -0.028459000697 +  -0.034725990869*_Complex_I,
+   -0.011174251949 +   0.010299896743*_Complex_I,
+    0.023757183405 +  -0.012151501352*_Complex_I,
+   -0.001899448573 +   0.033748924229*_Complex_I,
+    0.045121542184 +   0.003981235350*_Complex_I,
+   -0.039350108228 +   0.019225662049*_Complex_I,
+    0.000272360769 +  -0.022187283938*_Complex_I,
+   -0.039963184290 +  -0.025512380416*_Complex_I,
+   -0.048539833099 +  -0.045287358543*_Complex_I,
+    0.014614456311 +  -0.006244097705*_Complex_I,
+   -0.005955795663 +  -0.016811959796*_Complex_I,
+    0.034965200850 +   0.085058210904*_Complex_I,
+    0.019869783149 +   0.033565877203*_Complex_I,
+   -0.007450313153 +   0.019794672596*_Complex_I,
+   -0.024234527340 +  -0.013904685967*_Complex_I,
+   -0.056857838231 +  -0.065401336188*_Complex_I,
+    0.008081702581 +  -0.037938012149*_Complex_I,
+    0.013228639879 +   0.003618896346*_Complex_I,
+    0.047207180558 +   0.006639688435*_Complex_I,
+    0.005206139690 +   0.048614164213*_Complex_I,
+    0.012317627070 +  -0.009658873466*_Complex_I,
+   -0.008051197208 +   0.015338520918*_Complex_I,
+   -0.017767221557 +  -0.001246156194*_Complex_I,
+   -0.012973491018 +  -0.004457537375*_Complex_I,
+   -0.001247711756 +  -0.000324872382*_Complex_I,
+    0.015217577217 +  -0.018849976281*_Complex_I,
+   -0.000220920310 +  -0.022284388072*_Complex_I,
+    0.007337925412 +  -0.022911190749*_Complex_I,
+    0.026359365671 +   0.027517236286*_Complex_I,
+    0.008511664801 +  -0.000024345933*_Complex_I,
+   -0.006523939632 +   0.027431135887*_Complex_I,
+   -0.045640085613 +  -0.016594833325*_Complex_I,
+   -0.011370492419 +   0.008910655275*_Complex_I,
+   -0.045188661133 +  -0.014169350102*_Complex_I,
+    0.006126649841 +  -0.018848731487*_Complex_I,
+    0.020184591301 +   0.049393686574*_Complex_I,
+    0.050790767045 +   0.013516083838*_Complex_I,
+    0.030911878104 +   0.013274684518*_Complex_I,
+   -0.032128201429 +  -0.056563309721*_Complex_I,
+   -0.024604150985 +  -0.007862317966*_Complex_I,
+   -0.053074476432 +  -0.026948064488*_Complex_I,
+   -0.016714258122 +   0.036960212668*_Complex_I,
+    0.003641941365 +  -0.017652191591*_Complex_I,
+    0.014976615622 +   0.055005028639*_Complex_I,
+    0.047122774118 +  -0.019075872589*_Complex_I,
+    0.027958265929 +  -0.005155130760*_Complex_I,
+    0.006577988759 +  -0.021876641490*_Complex_I,
+    0.018186494518 +   0.020647216102*_Complex_I,
+   -0.047150707407 +   0.008796453305*_Complex_I,
+   -0.018231362190 +   0.025611832961*_Complex_I,
+   -0.044254892960 +  -0.000932158388*_Complex_I,
+    0.029538101318 +   0.008693714282*_Complex_I,
+    0.025472319724 +  -0.006973878349*_Complex_I,
+    0.012287778194 +  -0.046541397503*_Complex_I,
+   -0.003318140133 +   0.036850102200*_Complex_I,
+    0.015644777552 +  -0.006720827246*_Complex_I,
+    0.011761450619 +   0.045924263037*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_crcf_data_h23x256.c b/src/filter/tests/data/fftfilt_crcf_data_h23x256.c
new file mode 100644
index 0000000..50d4968
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_crcf_data_h23x256.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_crcf_data_h23x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float fftfilt_crcf_data_h23x256_h[] = {
+    0.175772392750,
+    0.016595666111,
+   -0.074416112900,
+    0.010717856884,
+   -0.091077792645,
+    0.152976071835,
+   -0.025270926952,
+    0.021845243871,
+   -0.001858575456,
+   -0.090402913094,
+   -0.054905104637,
+    0.135398101807,
+   -0.000730551640,
+    0.029069179296,
+   -0.117681956291,
+    0.088986778259,
+   -0.005193765089,
+    0.047553667426,
+   -0.093419462442,
+    0.060808116198,
+    0.138476681709,
+    0.104599690437,
+    0.007943090051};
+
+float complex fftfilt_crcf_data_h23x256_x[] = {
+    0.155103063583 +  -0.039989483356*_Complex_I,
+    0.089731889963 +   0.007783630490*_Complex_I,
+    0.031814819574 +  -0.092475223541*_Complex_I,
+   -0.052295821905 +   0.059911632538*_Complex_I,
+   -0.128601908684 +  -0.137423527241*_Complex_I,
+   -0.005442334712 +   0.136688292027*_Complex_I,
+   -0.110040140152 +   0.129731595516*_Complex_I,
+    0.070668441057 +   0.014537031949*_Complex_I,
+   -0.043719315529 +  -0.018183282018*_Complex_I,
+   -0.023593281209 +  -0.088085335493*_Complex_I,
+    0.079267203808 +  -0.050603461266*_Complex_I,
+    0.091274780035 +  -0.041612377763*_Complex_I,
+   -0.063425219059 +   0.028925007582*_Complex_I,
+    0.029070848227 +  -0.047291010618*_Complex_I,
+   -0.016231997311 +  -0.220043921471*_Complex_I,
+    0.001723764278 +  -0.110049223900*_Complex_I,
+    0.044714280963 +  -0.016450446844*_Complex_I,
+   -0.050305968523 +   0.038971278071*_Complex_I,
+   -0.118738901615 +  -0.027668058872*_Complex_I,
+   -0.065542882681 +  -0.056009286642*_Complex_I,
+    0.087729597092 +  -0.011164753139*_Complex_I,
+    0.078783208132 +  -0.036137032509*_Complex_I,
+   -0.024815648794 +  -0.187882483006*_Complex_I,
+    0.157861471176 +  -0.075281244516*_Complex_I,
+    0.002852580696 +  -0.062035644054*_Complex_I,
+    0.010516438633 +   0.137531483173*_Complex_I,
+    0.028980562091 +  -0.067520070076*_Complex_I,
+    0.022702263296 +   0.036881408095*_Complex_I,
+   -0.072563153505 +  -0.135415744781*_Complex_I,
+   -0.130733311176 +  -0.031472635269*_Complex_I,
+   -0.132395064831 +  -0.013236595690*_Complex_I,
+   -0.050313913822 +  -0.001110552903*_Complex_I,
+   -0.182336819172 +   0.011559166759*_Complex_I,
+    0.034110882878 +  -0.085457748175*_Complex_I,
+    0.010640498996 +   0.024029432237*_Complex_I,
+   -0.031135216355 +  -0.075218772888*_Complex_I,
+   -0.113621425629 +  -0.020260357857*_Complex_I,
+    0.035748475790 +  -0.047527408600*_Complex_I,
+    0.158889925480 +  -0.108263909817*_Complex_I,
+    0.058575540781 +   0.029335317016*_Complex_I,
+   -0.012806682289 +   0.062627220154*_Complex_I,
+    0.017389293015 +   0.082187730074*_Complex_I,
+   -0.155518913269 +  -0.130875742435*_Complex_I,
+   -0.110213851929 +   0.189839553833*_Complex_I,
+    0.052800035477 +   0.166894078255*_Complex_I,
+   -0.017228771746 +   0.002264509723*_Complex_I,
+    0.126624763012 +   0.014616450667*_Complex_I,
+    0.074029338360 +   0.130479073524*_Complex_I,
+    0.039149993658 +  -0.081848907471*_Complex_I,
+   -0.174016666412 +  -0.002245875075*_Complex_I,
+   -0.060786378384 +   0.087645614147*_Complex_I,
+    0.057222455740 +  -0.064347177744*_Complex_I,
+   -0.158683681488 +   0.064550727606*_Complex_I,
+    0.058903807402 +  -0.197414779663*_Complex_I,
+    0.022170741856 +   0.005642867088*_Complex_I,
+   -0.019022856653 +   0.026909428835*_Complex_I,
+    0.054943174124 +  -0.010510860384*_Complex_I,
+   -0.130231952667 +  -0.085019159317*_Complex_I,
+    0.057500022650 +  -0.036732053757*_Complex_I,
+    0.105791926384 +   0.016274939477*_Complex_I,
+    0.116283416748 +  -0.056632959843*_Complex_I,
+    0.009825258702 +   0.019144052267*_Complex_I,
+    0.196307218075 +  -0.048692828417*_Complex_I,
+    0.010651530325 +   0.008062589169*_Complex_I,
+    0.064581054449 +  -0.010377624631*_Complex_I,
+    0.089088356495 +   0.044110041857*_Complex_I,
+   -0.000476853596 +  -0.038994494081*_Complex_I,
+    0.105319905281 +  -0.132293283939*_Complex_I,
+    0.009918146580 +  -0.041363826394*_Complex_I,
+    0.036220628023 +  -0.030043822527*_Complex_I,
+    0.025852039456 +   0.067388910055*_Complex_I,
+   -0.083622449636 +  -0.035971945524*_Complex_I,
+   -0.100427985191 +   0.044513091445*_Complex_I,
+   -0.185684227943 +  -0.040250685811*_Complex_I,
+   -0.117641401291 +  -0.084515213966*_Complex_I,
+    0.041368806362 +   0.039818888903*_Complex_I,
+   -0.237114048004 +   0.125617718697*_Complex_I,
+   -0.054697531462 +   0.033353772759*_Complex_I,
+   -0.074546104670 +  -0.125889337063*_Complex_I,
+    0.033940449357 +   0.015348845720*_Complex_I,
+   -0.045566964149 +   0.039473927021*_Complex_I,
+   -0.015814815462 +  -0.096754157543*_Complex_I,
+    0.140625333786 +   0.088005954027*_Complex_I,
+   -0.109252071381 +  -0.058004480600*_Complex_I,
+    0.006840997189 +   0.123340046406*_Complex_I,
+   -0.043643641472 +   0.077158600092*_Complex_I,
+   -0.073954302073 +   0.095201927423*_Complex_I,
+   -0.024249210954 +   0.057317435741*_Complex_I,
+    0.018241013587 +   0.081239950657*_Complex_I,
+   -0.091524779797 +  -0.259817528725*_Complex_I,
+   -0.026682430506 +  -0.034324991703*_Complex_I,
+    0.088583648205 +  -0.007783739269*_Complex_I,
+    0.183037674427 +   0.111340200901*_Complex_I,
+   -0.076495563984 +  -0.089935213327*_Complex_I,
+    0.016016796231 +   0.185418581963*_Complex_I,
+    0.090234714746 +  -0.036095014215*_Complex_I,
+    0.016144131124 +   0.097066044807*_Complex_I,
+    0.012245001644 +   0.095291149616*_Complex_I,
+   -0.161877584457 +  -0.052310425043*_Complex_I,
+   -0.033721217513 +  -0.012814992666*_Complex_I,
+    0.091250944138 +   0.131859993935*_Complex_I,
+   -0.128745031357 +  -0.084740310907*_Complex_I,
+   -0.061492395401 +  -0.099420046806*_Complex_I,
+    0.012414894253 +  -0.015323466063*_Complex_I,
+    0.012217399478 +   0.012398956716*_Complex_I,
+    0.050523018837 +  -0.087095749378*_Complex_I,
+   -0.015820150077 +  -0.054119426012*_Complex_I,
+   -0.055258476734 +   0.020015104115*_Complex_I,
+    0.063105708361 +   0.042238712311*_Complex_I,
+    0.017720183730 +   0.017351190746*_Complex_I,
+   -0.141501569748 +  -0.117573750019*_Complex_I,
+   -0.006765382737 +   0.056032115221*_Complex_I,
+   -0.004831269756 +  -0.002968831547*_Complex_I,
+   -0.032051759958 +  -0.190994548798*_Complex_I,
+    0.036215576530 +   0.173437201977*_Complex_I,
+   -0.189757418633 +  -0.055692046881*_Complex_I,
+   -0.059237557650 +   0.126770603657*_Complex_I,
+    0.038132804632 +  -0.074801898003*_Complex_I,
+    0.088674974442 +  -0.053153961897*_Complex_I,
+   -0.078639388084 +   0.046550104022*_Complex_I,
+   -0.018440252542 +   0.040226161480*_Complex_I,
+   -0.001557452139 +   0.011683121324*_Complex_I,
+   -0.251441335678 +   0.073139393330*_Complex_I,
+   -0.205929875374 +   0.018797023594*_Complex_I,
+   -0.004785243422 +   0.018977609277*_Complex_I,
+   -0.008223478496 +   0.016689766943*_Complex_I,
+   -0.013542374969 +   0.098333865404*_Complex_I,
+    0.174354946613 +  -0.053679537773*_Complex_I,
+    0.070709151030 +   0.105750656128*_Complex_I,
+   -0.004860538989 +  -0.093827313185*_Complex_I,
+    0.008115487546 +   0.105573391914*_Complex_I,
+   -0.086253505945 +  -0.016359496117*_Complex_I,
+    0.260078501701 +   0.011332800239*_Complex_I,
+   -0.106663954258 +  -0.106564879417*_Complex_I,
+    0.103312873840 +  -0.263231492043*_Complex_I,
+   -0.054847794771 +   0.040892100334*_Complex_I,
+    0.110823535919 +  -0.184762966633*_Complex_I,
+   -0.071165961027 +  -0.055934780836*_Complex_I,
+   -0.178788268566 +  -0.044872501493*_Complex_I,
+   -0.046904963255 +  -0.092374777794*_Complex_I,
+    0.162701845169 +   0.020701634884*_Complex_I,
+   -0.101515638828 +   0.074363875389*_Complex_I,
+    0.057615011930 +  -0.117078483105*_Complex_I,
+    0.020440319180 +   0.002894190140*_Complex_I,
+   -0.066558498144 +   0.002631408162*_Complex_I,
+    0.016302645206 +   0.071249717474*_Complex_I,
+   -0.062377142906 +   0.063173556328*_Complex_I,
+    0.115204286575 +   0.057497209311*_Complex_I,
+    0.094178062677 +   0.013675969839*_Complex_I,
+    0.039668846130 +  -0.104888665676*_Complex_I,
+   -0.077536809444 +   0.030107039213*_Complex_I,
+    0.019500632584 +  -0.077639460564*_Complex_I,
+   -0.036470189691 +   0.141290163994*_Complex_I,
+   -0.202891373634 +  -0.006020304561*_Complex_I,
+    0.142597723007 +   0.001829937287*_Complex_I,
+    0.042515522242 +  -0.033903941512*_Complex_I,
+   -0.160332143307 +   0.167697465420*_Complex_I,
+    0.065906691551 +  -0.046222153306*_Complex_I,
+   -0.014665135741 +   0.029732593894*_Complex_I,
+   -0.161926984787 +  -0.039980190992*_Complex_I,
+    0.216635417938 +  -0.084111690521*_Complex_I,
+    0.111900138855 +  -0.088012528419*_Complex_I,
+    0.131331551075 +  -0.075983870029*_Complex_I,
+   -0.110518038273 +   0.217655158043*_Complex_I,
+   -0.019964502752 +   0.029798141122*_Complex_I,
+   -0.077271521091 +   0.017983873188*_Complex_I,
+    0.192190217972 +  -0.034995573759*_Complex_I,
+    0.121502757072 +  -0.071480178833*_Complex_I,
+    0.082723850012 +  -0.001845339499*_Complex_I,
+   -0.072242385149 +  -0.005906258896*_Complex_I,
+   -0.176676702499 +   0.110248458385*_Complex_I,
+    0.080035436153 +   0.107154047489*_Complex_I,
+    0.023038166761 +  -0.114933979511*_Complex_I,
+   -0.139963030815 +  -0.063580548763*_Complex_I,
+   -0.024152973294 +  -0.165445506573*_Complex_I,
+    0.175627219677 +   0.068415731192*_Complex_I,
+    0.062092590332 +  -0.063827121258*_Complex_I,
+    0.058578062057 +   0.007875456661*_Complex_I,
+    0.076061081886 +  -0.132551908493*_Complex_I,
+    0.104943084717 +  -0.084286916256*_Complex_I,
+    0.019696915150 +   0.003201137111*_Complex_I,
+    0.079270011187 +   0.105154144764*_Complex_I,
+    0.067746007442 +   0.087236845493*_Complex_I,
+   -0.095283645391 +  -0.162002611160*_Complex_I,
+   -0.049992722273 +   0.118607068062*_Complex_I,
+    0.107323861122 +  -0.174439251423*_Complex_I,
+   -0.132320475578 +   0.017735700309*_Complex_I,
+    0.010226617008 +  -0.197343289852*_Complex_I,
+   -0.065331441164 +  -0.169277667999*_Complex_I,
+   -0.067106610537 +   0.003877854347*_Complex_I,
+    0.149599671364 +  -0.130133831501*_Complex_I,
+   -0.082966232300 +  -0.140549242496*_Complex_I,
+   -0.121567571163 +   0.111494851112*_Complex_I,
+   -0.040895500779 +   0.242881941795*_Complex_I,
+    0.046610686183 +   0.190598750114*_Complex_I,
+   -0.241175961494 +   0.038187211752*_Complex_I,
+    0.091600197554 +   0.075727856159*_Complex_I,
+    0.138635289669 +   0.048851582408*_Complex_I,
+    0.142762672901 +   0.008544234186*_Complex_I,
+    0.129457914829 +   0.013422895968*_Complex_I,
+   -0.118289554119 +   0.265937256813*_Complex_I,
+    0.010878042877 +  -0.077647930384*_Complex_I,
+   -0.070324224234 +   0.039972990751*_Complex_I,
+   -0.041980904341 +   0.067246806622*_Complex_I,
+   -0.150996875763 +   0.028954780102*_Complex_I,
+    0.023831695318 +  -0.059671503305*_Complex_I,
+   -0.060499125719 +   0.022479967773*_Complex_I,
+    0.042781451344 +  -0.012739011645*_Complex_I,
+   -0.058961522579 +   0.108779168129*_Complex_I,
+   -0.123040950298 +  -0.084437525272*_Complex_I,
+   -0.031985121965 +   0.144360256195*_Complex_I,
+    0.009733268619 +  -0.105923128128*_Complex_I,
+   -0.133637535572 +  -0.006438682973*_Complex_I,
+    0.049610015750 +  -0.120102596283*_Complex_I,
+    0.112718105316 +   0.129998087883*_Complex_I,
+   -0.142268264294 +  -0.013222855330*_Complex_I,
+    0.119939064980 +   0.084782326221*_Complex_I,
+   -0.002139478363 +   0.143925952911*_Complex_I,
+   -0.017734520137 +   0.060474181175*_Complex_I,
+   -0.115593242645 +   0.266750621796*_Complex_I,
+   -0.122079384327 +   0.060140782595*_Complex_I,
+   -0.116746783257 +   0.058306437731*_Complex_I,
+   -0.035461139679 +  -0.199710798264*_Complex_I,
+   -0.012796324492 +  -0.006662363559*_Complex_I,
+   -0.168765580654 +  -0.080484443903*_Complex_I,
+    0.109565150738 +  -0.066066616774*_Complex_I,
+    0.167375874519 +  -0.095793098211*_Complex_I,
+    0.063125562668 +  -0.015197466314*_Complex_I,
+   -0.131772887707 +   0.043374121189*_Complex_I,
+    0.108676636219 +   0.016854281723*_Complex_I,
+   -0.158170187473 +   0.066611725092*_Complex_I,
+    0.186592674255 +  -0.119178080559*_Complex_I,
+    0.258357715607 +   0.035161945224*_Complex_I,
+   -0.267501235008 +   0.113799548149*_Complex_I,
+   -0.121099197865 +  -0.008966898173*_Complex_I,
+   -0.080716609955 +   0.092299455404*_Complex_I,
+    0.052158069611 +   0.009480424225*_Complex_I,
+    0.042302635312 +  -0.107537162304*_Complex_I,
+   -0.149109280109 +   0.086092722416*_Complex_I,
+   -0.047981733084 +   0.051883894205*_Complex_I,
+    0.129582881927 +   0.080048167706*_Complex_I,
+    0.020590978861 +   0.052324140072*_Complex_I,
+   -0.127634072304 +   0.003840797022*_Complex_I,
+   -0.015030093491 +   0.081028074026*_Complex_I,
+   -0.151265537739 +   0.073973071575*_Complex_I,
+    0.065107202530 +  -0.102953696251*_Complex_I,
+    0.020201592147 +  -0.043066096306*_Complex_I,
+   -0.041327267885 +  -0.084052354097*_Complex_I,
+    0.039087793231 +  -0.027260744572*_Complex_I,
+    0.149368381500 +   0.085757601261*_Complex_I,
+   -0.045472341776 +  -0.007366366684*_Complex_I,
+   -0.014300452173 +   0.070052427053*_Complex_I,
+   -0.030928212404 +   0.008410415053*_Complex_I,
+    0.102823138237 +   0.089631778002*_Complex_I,
+   -0.046347615123 +   0.012207016349*_Complex_I,
+   -0.022019737959 +   0.008490467072*_Complex_I};
+
+float complex fftfilt_crcf_data_h23x256_y[] = {
+    0.027262836609 +  -0.007029047174*_Complex_I,
+    0.018346427661 +   0.000704495242*_Complex_I,
+   -0.004460839644 +  -0.013149554871*_Complex_I,
+   -0.013679299641 +   0.007988293987*_Complex_I,
+   -0.039004795515 +  -0.012553764367*_Complex_I,
+    0.016696266867 +   0.009469516192*_Complex_I,
+   -0.003513172567 +   0.046564035001*_Complex_I,
+    0.020372582455 +  -0.027609901195*_Complex_I,
+    0.006199293336 +   0.013118588352*_Complex_I,
+   -0.042660178683 +  -0.048881324868*_Complex_I,
+    0.012162311731 +   0.006692219554*_Complex_I,
+    0.005999545171 +   0.012678334008*_Complex_I,
+    0.016925234916 +   0.011802413052*_Complex_I,
+    0.012643874018 +  -0.002379618949*_Complex_I,
+   -0.019843625969 +  -0.042091077177*_Complex_I,
+   -0.002447170334 +  -0.066361916522*_Complex_I,
+    0.027635184820 +   0.025233499033*_Complex_I,
+   -0.023950232537 +   0.018344717328*_Complex_I,
+   -0.001523383406 +   0.047987234347*_Complex_I,
+   -0.037208867735 +  -0.060587103130*_Complex_I,
+    0.040881005070 +  -0.012194712051*_Complex_I,
+    0.051353615432 +  -0.030052114039*_Complex_I,
+    0.039843962567 +  -0.018530512464*_Complex_I,
+   -0.011241859913 +   0.003314208877*_Complex_I,
+   -0.023931715066 +  -0.008536051326*_Complex_I,
+   -0.046474201238 +   0.019213392371*_Complex_I,
+    0.026420531301 +   0.008903961274*_Complex_I,
+   -0.003321242652 +  -0.005089987861*_Complex_I,
+    0.018094294321 +   0.009055610544*_Complex_I,
+   -0.065634402614 +  -0.053749750040*_Complex_I,
+   -0.023600007069 +   0.001972776975*_Complex_I,
+    0.029460287631 +  -0.022729601752*_Complex_I,
+   -0.005045917714 +   0.054578580699*_Complex_I,
+   -0.007148429271 +  -0.055204501924*_Complex_I,
+    0.010660749874 +  -0.059471864271*_Complex_I,
+   -0.028011323273 +  -0.066785744888*_Complex_I,
+    0.011622802421 +   0.013720590963*_Complex_I,
+   -0.036631828451 +   0.003833520561*_Complex_I,
+    0.047864856646 +  -0.017738859345*_Complex_I,
+   -0.007990385588 +  -0.035672288313*_Complex_I,
+    0.004986325822 +   0.029278794046*_Complex_I,
+   -0.016889528458 +  -0.015755301985*_Complex_I,
+   -0.010143232206 +  -0.021096571154*_Complex_I,
+   -0.009806462106 +  -0.047616138372*_Complex_I,
+    0.051880472016 +   0.035421818987*_Complex_I,
+    0.004878725946 +  -0.006588080806*_Complex_I,
+    0.059514317675 +   0.025245817016*_Complex_I,
+   -0.041213052806 +   0.001434752292*_Complex_I,
+   -0.031520998781 +  -0.026425494383*_Complex_I,
+   -0.042062234079 +  -0.023223262575*_Complex_I,
+   -0.031771373601 +  -0.001999239137*_Complex_I,
+    0.003008538314 +   0.007873584524*_Complex_I,
+   -0.044522016963 +   0.031453229897*_Complex_I,
+   -0.004123811253 +  -0.100618757520*_Complex_I,
+    0.000868379196 +  -0.007049767471*_Complex_I,
+   -0.035358534934 +   0.039935982082*_Complex_I,
+    0.001096892916 +  -0.008139914684*_Complex_I,
+   -0.045888282337 +  -0.028654012806*_Complex_I,
+    0.054241028654 +  -0.040641857373*_Complex_I,
+    0.076384681516 +  -0.009290596232*_Complex_I,
+   -0.014394459295 +   0.031937206347*_Complex_I,
+    0.029290098261 +   0.000294924311*_Complex_I,
+   -0.024684029149 +   0.000989628106*_Complex_I,
+   -0.016171486419 +   0.031195541518*_Complex_I,
+   -0.014868511208 +   0.014303327420*_Complex_I,
+    0.021852231291 +   0.016723227953*_Complex_I,
+    0.020272353403 +   0.015401209854*_Complex_I,
+    0.071528405043 +   0.017807463305*_Complex_I,
+   -0.020235058791 +  -0.039855986142*_Complex_I,
+   -0.034484804762 +   0.000496010405*_Complex_I,
+    0.007189500129 +   0.020495810669*_Complex_I,
+   -0.021931884508 +   0.027723824179*_Complex_I,
+   -0.045456425035 +  -0.019096529483*_Complex_I,
+   -0.017800090999 +  -0.040386165849*_Complex_I,
+   -0.031040929546 +  -0.045937861114*_Complex_I,
+    0.065367936599 +   0.021960412702*_Complex_I,
+   -0.060220212620 +   0.045242058344*_Complex_I,
+   -0.015791818114 +  -0.002107138847*_Complex_I,
+   -0.016776877211 +  -0.047286567469*_Complex_I,
+    0.014399266122 +  -0.042073854865*_Complex_I,
+    0.056679887935 +   0.012644277043*_Complex_I,
+   -0.008511751801 +   0.004971041195*_Complex_I,
+    0.081952669986 +   0.012577593191*_Complex_I,
+   -0.012764140374 +  -0.011967000266*_Complex_I,
+   -0.003336654686 +  -0.001262645566*_Complex_I,
+    0.016350375874 +   0.032350783323*_Complex_I,
+    0.010862760597 +  -0.035926563231*_Complex_I,
+    0.029863051593 +   0.043664156677*_Complex_I,
+   -0.012277218714 +  -0.028013771036*_Complex_I,
+   -0.021453932268 +  -0.061946366678*_Complex_I,
+    0.031514206628 +  -0.020671975000*_Complex_I,
+   -0.023541686556 +   0.038816659613*_Complex_I,
+    0.015568416177 +   0.034767821284*_Complex_I,
+   -0.053493562550 +   0.010727696220*_Complex_I,
+   -0.041010374462 +  -0.065258809696*_Complex_I,
+    0.005288272932 +   0.024477688872*_Complex_I,
+   -0.046902277134 +  -0.000092205057*_Complex_I,
+    0.001627461508 +   0.080137549979*_Complex_I,
+   -0.065128047455 +  -0.060262053915*_Complex_I,
+    0.006596006298 +   0.065336899466*_Complex_I,
+    0.000058721241 +  -0.040094018767*_Complex_I,
+   -0.036305854351 +  -0.003934363665*_Complex_I,
+    0.008450440041 +  -0.033595118529*_Complex_I,
+    0.029549628861 +   0.041310102659*_Complex_I,
+   -0.031905285980 +  -0.031000918152*_Complex_I,
+    0.011726181555 +   0.060113189533*_Complex_I,
+   -0.047367175488 +  -0.048891971737*_Complex_I,
+    0.019495535591 +   0.085142512409*_Complex_I,
+    0.018357043313 +  -0.001756917795*_Complex_I,
+   -0.044850116879 +  -0.007540707159*_Complex_I,
+   -0.040509844673 +  -0.092704738011*_Complex_I,
+    0.045015777649 +   0.044308907043*_Complex_I,
+    0.033412972066 +   0.006857384940*_Complex_I,
+   -0.013027623910 +  -0.030790116518*_Complex_I,
+   -0.000975157484 +   0.037733658737*_Complex_I,
+   -0.022753324517 +   0.023070309941*_Complex_I,
+    0.021078444893 +   0.032118433292*_Complex_I,
+    0.010443833730 +   0.016807510307*_Complex_I,
+   -0.038157141667 +  -0.083720289418*_Complex_I,
+    0.015884591663 +   0.080444457603*_Complex_I,
+   -0.014224915913 +   0.005135806833*_Complex_I,
+   -0.025232136903 +   0.004144801774*_Complex_I,
+   -0.077288630878 +  -0.005906892810*_Complex_I,
+   -0.034553210182 +  -0.017846341663*_Complex_I,
+    0.035746673427 +  -0.012259253660*_Complex_I,
+    0.036525883688 +  -0.010525857333*_Complex_I,
+   -0.014488296110 +  -0.014305379613*_Complex_I,
+   -0.015958243599 +   0.045987466851*_Complex_I,
+    0.003179382534 +  -0.018266667088*_Complex_I,
+    0.029305351113 +   0.000580102615*_Complex_I,
+   -0.043259878864 +  -0.037454506754*_Complex_I,
+   -0.028365132804 +   0.065377160248*_Complex_I,
+    0.070378428765 +  -0.056958149428*_Complex_I,
+    0.004867726670 +   0.005962596169*_Complex_I,
+   -0.037918405464 +  -0.091858574411*_Complex_I,
+   -0.019631672181 +   0.051212141219*_Complex_I,
+   -0.057622870703 +  -0.011592712757*_Complex_I,
+    0.039759648837 +   0.003839054841*_Complex_I,
+   -0.059496690769 +  -0.007464198304*_Complex_I,
+    0.020562373454 +  -0.038245564570*_Complex_I,
+    0.032578387575 +   0.008176063763*_Complex_I,
+   -0.022640669374 +   0.032133098133*_Complex_I,
+   -0.059754420752 +  -0.022200850890*_Complex_I,
+   -0.021917765961 +   0.061715182774*_Complex_I,
+   -0.070723789096 +  -0.036769285459*_Complex_I,
+    0.040897570713 +   0.020449142350*_Complex_I,
+   -0.078688142274 +   0.044106118254*_Complex_I,
+    0.132511709017 +  -0.002283572710*_Complex_I,
+    0.031006482035 +   0.023633310053*_Complex_I,
+    0.000719650304 +  -0.060425994891*_Complex_I,
+   -0.068488182320 +   0.011823413272*_Complex_I,
+    0.033571377809 +   0.001638620807*_Complex_I,
+    0.013415095820 +   0.063237008575*_Complex_I,
+    0.003843590843 +  -0.032020775100*_Complex_I,
+    0.005708962317 +  -0.073909950927*_Complex_I,
+    0.043060259091 +  -0.039410177301*_Complex_I,
+   -0.031531087064 +  -0.004760009337*_Complex_I,
+   -0.001506462543 +  -0.004643974469*_Complex_I,
+   -0.069117792716 +  -0.013243456525*_Complex_I,
+   -0.008999132078 +  -0.025213996567*_Complex_I,
+    0.076230704310 +  -0.038893791301*_Complex_I,
+   -0.016693801234 +   0.016189526184*_Complex_I,
+    0.053793442954 +  -0.046315507571*_Complex_I,
+   -0.022323859813 +   0.074471398757*_Complex_I,
+   -0.077019541390 +   0.000776612640*_Complex_I,
+    0.028852092849 +  -0.003754892925*_Complex_I,
+    0.030634254859 +  -0.031119630463*_Complex_I,
+    0.070978930378 +   0.018366156735*_Complex_I,
+    0.011314850738 +   0.020679865086*_Complex_I,
+   -0.009529937549 +   0.025881841685*_Complex_I,
+   -0.099579012075 +  -0.013132370673*_Complex_I,
+    0.044640476208 +   0.044583555144*_Complex_I,
+    0.016522002764 +  -0.063707510591*_Complex_I,
+    0.003035671136 +  -0.013751873371*_Complex_I,
+   -0.026798937752 +  -0.022117369500*_Complex_I,
+    0.007823238214 +   0.038428718991*_Complex_I,
+   -0.033760635042 +   0.056366436739*_Complex_I,
+    0.061213467638 +  -0.037917433069*_Complex_I,
+   -0.019400770671 +   0.002314675027*_Complex_I,
+    0.030275097259 +  -0.065680749303*_Complex_I,
+   -0.012116792287 +   0.011848118163*_Complex_I,
+    0.036218177449 +  -0.012568718012*_Complex_I,
+    0.060643006355 +   0.043892825588*_Complex_I,
+    0.017806428565 +  -0.022264383863*_Complex_I,
+   -0.048473906401 +   0.014501335500*_Complex_I,
+   -0.031370768130 +  -0.040223039246*_Complex_I,
+    0.019781480670 +   0.021758320109*_Complex_I,
+    0.063634947558 +  -0.007457977030*_Complex_I,
+    0.020848525728 +  -0.052882085833*_Complex_I,
+   -0.063823888094 +   0.004319709113*_Complex_I,
+    0.038427723737 +  -0.024772513569*_Complex_I,
+   -0.035206635151 +  -0.007553598958*_Complex_I,
+    0.005557463302 +   0.057023731153*_Complex_I,
+   -0.026365483732 +   0.001677431571*_Complex_I,
+   -0.027214665101 +   0.003259544495*_Complex_I,
+    0.006634141929 +  -0.021760848241*_Complex_I,
+    0.046788459295 +  -0.032219612690*_Complex_I,
+    0.049439176247 +   0.058001544517*_Complex_I,
+    0.045244742233 +  -0.058913353130*_Complex_I,
+    0.024261567778 +   0.012176684918*_Complex_I,
+   -0.050667381589 +   0.023733558523*_Complex_I,
+    0.045349821935 +   0.040847998957*_Complex_I,
+    0.026718102874 +  -0.059402465947*_Complex_I,
+   -0.038454788567 +  -0.000221349180*_Complex_I,
+   -0.000891207880 +  -0.009195003923*_Complex_I,
+    0.012516609339 +   0.056222305600*_Complex_I,
+   -0.035084845543 +  -0.052719626538*_Complex_I,
+   -0.021230335164 +  -0.038700055773*_Complex_I,
+   -0.042702491469 +  -0.012596339115*_Complex_I,
+    0.001037928882 +  -0.021134005956*_Complex_I,
+    0.021394370180 +  -0.031996808591*_Complex_I,
+   -0.018110589131 +  -0.004361916553*_Complex_I,
+   -0.042616962178 +  -0.045478200889*_Complex_I,
+    0.015593771645 +   0.075086774578*_Complex_I,
+    0.027412734674 +   0.021799498978*_Complex_I,
+   -0.092513052262 +   0.111578027869*_Complex_I,
+    0.023261460083 +  -0.018008166583*_Complex_I,
+   -0.005433496503 +   0.062765792378*_Complex_I,
+    0.077096470007 +  -0.046336297254*_Complex_I,
+    0.013100162132 +   0.106964173517*_Complex_I,
+   -0.057225839702 +   0.011100170701*_Complex_I,
+   -0.011950150953 +   0.042976543657*_Complex_I,
+    0.009438239329 +  -0.043586074813*_Complex_I,
+   -0.035007068103 +   0.014223415436*_Complex_I,
+   -0.044707188095 +  -0.007302556287*_Complex_I,
+   -0.006824766721 +   0.022952180881*_Complex_I,
+    0.017261515635 +  -0.031023982394*_Complex_I,
+    0.009883747796 +   0.006683863273*_Complex_I,
+   -0.030856485749 +  -0.017782855219*_Complex_I,
+   -0.009108501465 +   0.015353290050*_Complex_I,
+   -0.038083572506 +   0.029801645641*_Complex_I,
+    0.028040003683 +  -0.001971697544*_Complex_I,
+    0.034858486128 +  -0.004199899994*_Complex_I,
+   -0.047963189034 +  -0.015741737165*_Complex_I,
+   -0.009310592393 +   0.022863663510*_Complex_I,
+   -0.082864799372 +   0.027670042319*_Complex_I,
+    0.018363359405 +   0.035364489440*_Complex_I,
+    0.108637353728 +  -0.051587108396*_Complex_I,
+   -0.037281254062 +   0.067821136010*_Complex_I,
+   -0.061231969683 +   0.036908299475*_Complex_I,
+   -0.020972301615 +   0.096608850141*_Complex_I,
+   -0.079111323581 +   0.024466271096*_Complex_I,
+    0.045843637768 +  -0.077335246433*_Complex_I,
+   -0.011031555894 +   0.001143494701*_Complex_I,
+   -0.050696885867 +  -0.000991623122*_Complex_I,
+   -0.025631881174 +  -0.020905755095*_Complex_I,
+    0.042787270331 +  -0.022375533820*_Complex_I,
+    0.040540034730 +  -0.032482851465*_Complex_I,
+    0.045207009532 +  -0.015041815894*_Complex_I,
+   -0.041989439384 +   0.052327580958*_Complex_I,
+   -0.060932225937 +  -0.002610819103*_Complex_I,
+    0.056802692214 +   0.015432577360*_Complex_I,
+    0.066574027053 +  -0.021238051145*_Complex_I,
+   -0.000873772321 +   0.003555393272*_Complex_I,
+   -0.047364452139 +   0.039519457621*_Complex_I,
+   -0.075184690098 +   0.032775776752*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_crcf_data_h4x256.c b/src/filter/tests/data/fftfilt_crcf_data_h4x256.c
new file mode 100644
index 0000000..0ee622b
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_crcf_data_h4x256.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_crcf_data_h4x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float fftfilt_crcf_data_h4x256_h[] = {
+    0.155624401569,
+    0.118511450291,
+   -0.168462336063,
+    0.090250796080};
+
+float complex fftfilt_crcf_data_h4x256_x[] = {
+    0.038939061761 +   0.066255682707*_Complex_I,
+    0.119283056259 +   0.056196963787*_Complex_I,
+   -0.040920588374 +  -0.079775565863*_Complex_I,
+   -0.227900600433 +  -0.102529621124*_Complex_I,
+   -0.174213981628 +  -0.108522331715*_Complex_I,
+    0.153431594372 +  -0.030162647367*_Complex_I,
+    0.014127536118 +   0.246407699585*_Complex_I,
+   -0.018383954465 +   0.021154025197*_Complex_I,
+   -0.191331315041 +  -0.127723705769*_Complex_I,
+    0.010609115660 +   0.052530884743*_Complex_I,
+   -0.112432408333 +   0.010107874870*_Complex_I,
+    0.025136622787 +  -0.032320749760*_Complex_I,
+    0.018991956115 +   0.133092260361*_Complex_I,
+   -0.039405971766 +  -0.013498529792*_Complex_I,
+   -0.019806520641 +  -0.002817103267*_Complex_I,
+   -0.078604698181 +   0.026840734482*_Complex_I,
+    0.028549847007 +  -0.017006455362*_Complex_I,
+    0.020842596889 +  -0.121898579597*_Complex_I,
+   -0.055000007153 +  -0.101676058769*_Complex_I,
+    0.036740297079 +  -0.151041173935*_Complex_I,
+    0.067903089523 +  -0.193994557858*_Complex_I,
+    0.197223436832 +   0.051035022736*_Complex_I,
+   -0.015651342273 +   0.007457277179*_Complex_I,
+   -0.125625634193 +   0.145215332508*_Complex_I,
+    0.255927467346 +  -0.039666593075*_Complex_I,
+   -0.014654898643 +  -0.154769206047*_Complex_I,
+   -0.142057240009 +   0.149415183067*_Complex_I,
+   -0.015665143728 +  -0.008279455453*_Complex_I,
+   -0.024408347905 +  -0.026021143794*_Complex_I,
+   -0.062624055147 +  -0.200754070282*_Complex_I,
+   -0.056015121937 +   0.040550091863*_Complex_I,
+    0.070212632418 +   0.062178546190*_Complex_I,
+   -0.109208559990 +  -0.118423628807*_Complex_I,
+    0.086101102829 +   0.087447303534*_Complex_I,
+    0.121767687798 +   0.103217923641*_Complex_I,
+   -0.083848309517 +  -0.039586529136*_Complex_I,
+    0.051707124710 +   0.150204086304*_Complex_I,
+   -0.014533403516 +  -0.031855377555*_Complex_I,
+    0.009939355403 +   0.151125979424*_Complex_I,
+   -0.118441712856 +   0.029392868280*_Complex_I,
+    0.068562531471 +   0.169217157364*_Complex_I,
+   -0.035535854101 +  -0.091208106279*_Complex_I,
+    0.110526537895 +   0.046102252603*_Complex_I,
+   -0.004789924249 +   0.014031916857*_Complex_I,
+    0.207642221451 +   0.005212478340*_Complex_I,
+   -0.079558753967 +   0.078763872385*_Complex_I,
+    0.019427961111 +   0.118139207363*_Complex_I,
+    0.027059462667 +   0.004347201437*_Complex_I,
+   -0.150222861767 +  -0.019229063392*_Complex_I,
+    0.060692095757 +   0.119515848160*_Complex_I,
+   -0.025792115927 +  -0.000083122792*_Complex_I,
+    0.113421833515 +   0.125912785530*_Complex_I,
+   -0.001560413372 +  -0.150379800797*_Complex_I,
+    0.152954483032 +  -0.063023918867*_Complex_I,
+   -0.028765177727 +  -0.036037924886*_Complex_I,
+   -0.061254453659 +   0.052871614695*_Complex_I,
+    0.022947111726 +   0.146198511124*_Complex_I,
+   -0.103656053543 +   0.085682183504*_Complex_I,
+    0.153891849518 +  -0.088169473410*_Complex_I,
+   -0.321221590042 +   0.123748040199*_Complex_I,
+   -0.049486947060 +   0.162590062618*_Complex_I,
+    0.006993473321 +   0.179711341858*_Complex_I,
+    0.009347584099 +   0.202764987946*_Complex_I,
+    0.095177561045 +   0.070498406887*_Complex_I,
+   -0.122608697414 +   0.114908039570*_Complex_I,
+    0.143189716339 +  -0.046278029680*_Complex_I,
+    0.037571176887 +   0.071948003769*_Complex_I,
+   -0.095956790447 +  -0.205198740959*_Complex_I,
+    0.072236007452 +  -0.040830844641*_Complex_I,
+    0.122828471661 +  -0.117571306229*_Complex_I,
+    0.157875216007 +  -0.039508357644*_Complex_I,
+   -0.007820869237 +   0.116631805897*_Complex_I,
+   -0.002049682848 +  -0.058560395241*_Complex_I,
+   -0.101921010017 +  -0.107374680042*_Complex_I,
+   -0.195360040665 +  -0.201392173767*_Complex_I,
+    0.173241472244 +  -0.001343925763*_Complex_I,
+    0.028210636973 +  -0.149001765251*_Complex_I,
+   -0.095708668232 +   0.070712405443*_Complex_I,
+    0.155656027794 +  -0.047673809528*_Complex_I,
+   -0.113691759109 +  -0.093889063597*_Complex_I,
+    0.001799738780 +  -0.111104428768*_Complex_I,
+    0.115965354443 +  -0.018809084594*_Complex_I,
+   -0.019960752130 +  -0.186925411224*_Complex_I,
+    0.149933063984 +   0.044130250812*_Complex_I,
+    0.010008703917 +  -0.031899172068*_Complex_I,
+    0.085326123238 +  -0.005660134554*_Complex_I,
+   -0.209100413322 +   0.130333411694*_Complex_I,
+   -0.102446043491 +  -0.034337127209*_Complex_I,
+   -0.010648646951 +   0.011874027550*_Complex_I,
+    0.037817972898 +  -0.044123852253*_Complex_I,
+    0.101257121563 +   0.028343737125*_Complex_I,
+    0.007420848310 +   0.154019880295*_Complex_I,
+    0.141882646084 +   0.096156585217*_Complex_I,
+   -0.017362400889 +   0.036749580503*_Complex_I,
+    0.023864392936 +  -0.022486045957*_Complex_I,
+    0.149045646191 +  -0.053199940920*_Complex_I,
+    0.084901583195 +   0.083841896057*_Complex_I,
+   -0.027361977100 +  -0.115613818169*_Complex_I,
+   -0.133944833279 +   0.016144460440*_Complex_I,
+    0.111774182320 +  -0.136482322216*_Complex_I,
+   -0.044260403514 +   0.111417186260*_Complex_I,
+    0.001404425409 +  -0.104211735725*_Complex_I,
+    0.095043230057 +   0.095729255676*_Complex_I,
+   -0.143477213383 +  -0.080443674326*_Complex_I,
+    0.184766137600 +   0.017212320864*_Complex_I,
+   -0.045734372735 +  -0.227365469933*_Complex_I,
+    0.031908345222 +   0.080422180891*_Complex_I,
+    0.089475703239 +   0.117917668819*_Complex_I,
+   -0.143735373020 +   0.151164078712*_Complex_I,
+   -0.158408689499 +  -0.105965197086*_Complex_I,
+    0.041039618850 +  -0.014203602076*_Complex_I,
+   -0.084943604469 +  -0.084828513861*_Complex_I,
+   -0.032031357288 +  -0.010720796138*_Complex_I,
+   -0.019809050858 +  -0.082033938169*_Complex_I,
+   -0.297199916840 +   0.071534729004*_Complex_I,
+    0.041559013724 +  -0.176234781742*_Complex_I,
+    0.077450340986 +   0.043274927139*_Complex_I,
+    0.188104069233 +   0.034578007460*_Complex_I,
+    0.091451799870 +  -0.117644202709*_Complex_I,
+   -0.116885960102 +   0.047494077682*_Complex_I,
+    0.021910847723 +  -0.104368901253*_Complex_I,
+   -0.170243906975 +  -0.038744941354*_Complex_I,
+   -0.006665949523 +  -0.076677244902*_Complex_I,
+   -0.123132240772 +  -0.028003439307*_Complex_I,
+   -0.008512797952 +   0.119553911686*_Complex_I,
+    0.007796970755 +  -0.034775868058*_Complex_I,
+   -0.020194862783 +   0.033565041423*_Complex_I,
+    0.000060333178 +   0.190958213806*_Complex_I,
+   -0.126177227497 +  -0.069941896200*_Complex_I,
+   -0.031391653419 +  -0.108642828465*_Complex_I,
+    0.049086883664 +   0.125198578835*_Complex_I,
+   -0.098606723547 +   0.157826316357*_Complex_I,
+   -0.037816497684 +  -0.075405150652*_Complex_I,
+    0.154469418526 +  -0.071843087673*_Complex_I,
+   -0.180931842327 +   0.056550979614*_Complex_I,
+    0.049642735720 +   0.229578256607*_Complex_I,
+   -0.013776327670 +   0.000968252402*_Complex_I,
+   -0.131162738800 +   0.062030094862*_Complex_I,
+    0.115592253208 +  -0.122473180294*_Complex_I,
+   -0.143930578232 +   0.208317732811*_Complex_I,
+    0.025780442357 +   0.038679277897*_Complex_I,
+   -0.186121022701 +   0.000732171116*_Complex_I,
+    0.001518380083 +  -0.024754013121*_Complex_I,
+   -0.026928266883 +  -0.122094595432*_Complex_I,
+    0.102070283890 +   0.129774129391*_Complex_I,
+   -0.046998295188 +   0.174012136459*_Complex_I,
+    0.114306473732 +  -0.074029159546*_Complex_I,
+   -0.041995987296 +   0.163119518757*_Complex_I,
+    0.167517042160 +  -0.009892838448*_Complex_I,
+    0.114438354969 +   0.017925891280*_Complex_I,
+   -0.095674896240 +   0.076540869474*_Complex_I,
+   -0.062672561407 +  -0.038445192575*_Complex_I,
+   -0.217370772362 +  -0.030016162992*_Complex_I,
+    0.005625271797 +  -0.027812755108*_Complex_I,
+   -0.152692675591 +  -0.136305415630*_Complex_I,
+    0.084204858541 +   0.203144884109*_Complex_I,
+   -0.056856244802 +  -0.032040473819*_Complex_I,
+   -0.015420150757 +   0.110550451279*_Complex_I,
+    0.011531084776 +   0.136303079128*_Complex_I,
+    0.059582346678 +  -0.063924384117*_Complex_I,
+   -0.066680252552 +   0.154493486881*_Complex_I,
+    0.034608376026 +   0.051702314615*_Complex_I,
+    0.113737416267 +   0.039248380065*_Complex_I,
+    0.158740794659 +   0.019757434726*_Complex_I,
+   -0.039499655366 +  -0.000118957297*_Complex_I,
+   -0.054218810797 +   0.061152768135*_Complex_I,
+    0.020386171341 +  -0.226112961769*_Complex_I,
+    0.027872058749 +  -0.079996037483*_Complex_I,
+   -0.139904499054 +   0.015143483877*_Complex_I,
+    0.013777624071 +  -0.018682165444*_Complex_I,
+    0.005936894938 +  -0.001314791571*_Complex_I,
+    0.157291007042 +  -0.008614261448*_Complex_I,
+   -0.079033893347 +  -0.041999083757*_Complex_I,
+    0.084690928459 +   0.051340937614*_Complex_I,
+    0.001056097262 +   0.008219049126*_Complex_I,
+   -0.028290018439 +   0.131245112419*_Complex_I,
+    0.102844524384 +  -0.111723649502*_Complex_I,
+    0.123529279232 +  -0.021718944609*_Complex_I,
+   -0.029516366124 +  -0.034146314859*_Complex_I,
+    0.048669779301 +   0.046512207389*_Complex_I,
+    0.038022220135 +   0.079673200846*_Complex_I,
+    0.175985097885 +   0.215863847733*_Complex_I,
+    0.106522679329 +   0.155265355110*_Complex_I,
+    0.033522182703 +   0.035048177838*_Complex_I,
+    0.014340730011 +  -0.097587269545*_Complex_I,
+   -0.051011729240 +   0.074478018284*_Complex_I,
+    0.048653256893 +   0.180682408810*_Complex_I,
+   -0.143753635883 +  -0.177901697159*_Complex_I,
+    0.077370518446 +  -0.033802193403*_Complex_I,
+    0.080779671669 +   0.048105204105*_Complex_I,
+    0.062754058838 +   0.065103608370*_Complex_I,
+    0.195216703415 +  -0.269327521324*_Complex_I,
+    0.035685497522 +   0.128597819805*_Complex_I,
+   -0.152576267719 +   0.002838765644*_Complex_I,
+    0.037064149976 +   0.005790404975*_Complex_I,
+    0.028551298380 +  -0.260332655907*_Complex_I,
+   -0.112396037579 +  -0.135177958012*_Complex_I,
+   -0.015914693475 +   0.063825702667*_Complex_I,
+    0.158389151096 +   0.117301619053*_Complex_I,
+   -0.063320201635 +   0.147249364853*_Complex_I,
+    0.257527685165 +  -0.037165626884*_Complex_I,
+    0.047737368941 +  -0.188134491444*_Complex_I,
+    0.014590293169 +  -0.009723882377*_Complex_I,
+    0.026365819573 +  -0.025334075093*_Complex_I,
+   -0.102777397633 +   0.233691811562*_Complex_I,
+    0.072789901495 +  -0.054794228077*_Complex_I,
+   -0.129656755924 +  -0.084271728992*_Complex_I,
+   -0.022544041276 +  -0.037263685465*_Complex_I,
+    0.004222166538 +  -0.032020455599*_Complex_I,
+   -0.040505915880 +   0.015989097953*_Complex_I,
+    0.076600146294 +   0.028494662046*_Complex_I,
+    0.165050017834 +   0.060545289516*_Complex_I,
+   -0.201356935501 +  -0.007056738436*_Complex_I,
+   -0.144541025162 +  -0.020850527287*_Complex_I,
+   -0.060554254055 +  -0.035383135080*_Complex_I,
+   -0.083671200275 +   0.002356898040*_Complex_I,
+    0.009340242296 +  -0.031346979737*_Complex_I,
+    0.099454361200 +  -0.127831709385*_Complex_I,
+   -0.167506957054 +   0.087425726652*_Complex_I,
+   -0.039634212852 +  -0.083207929134*_Complex_I,
+   -0.225357127190 +   0.072750198841*_Complex_I,
+   -0.006095945090 +  -0.048631900549*_Complex_I,
+   -0.011678160727 +  -0.136811399460*_Complex_I,
+    0.182721781731 +  -0.034160619974*_Complex_I,
+   -0.137553799152 +   0.036647570133*_Complex_I,
+   -0.020456752181 +  -0.038014954329*_Complex_I,
+    0.128560674191 +  -0.011797984689*_Complex_I,
+    0.093237119913 +   0.134914875031*_Complex_I,
+    0.109499740601 +   0.181737196445*_Complex_I,
+    0.177813911438 +   0.024316579103*_Complex_I,
+    0.149165189266 +  -0.090540468693*_Complex_I,
+    0.132123124599 +  -0.023848700523*_Complex_I,
+   -0.173898208141 +   0.013859193027*_Complex_I,
+   -0.238600111008 +   0.217159080505*_Complex_I,
+   -0.000013622420 +  -0.006076892465*_Complex_I,
+   -0.008374422789 +  -0.236851501465*_Complex_I,
+   -0.283055114746 +   0.141181695461*_Complex_I,
+   -0.089649707079 +   0.020758484304*_Complex_I,
+    0.095366454124 +   0.158369851112*_Complex_I,
+    0.015813191235 +  -0.056471478939*_Complex_I,
+    0.127879250050 +   0.081945985556*_Complex_I,
+    0.035356411338 +  -0.067337626219*_Complex_I,
+   -0.245302104950 +  -0.055383253098*_Complex_I,
+   -0.136766076088 +  -0.014186948538*_Complex_I,
+   -0.141382408142 +  -0.000181592884*_Complex_I,
+   -0.047739619017 +  -0.041759270430*_Complex_I,
+   -0.023178872466 +   0.034544199705*_Complex_I,
+    0.061150830984 +   0.075226747990*_Complex_I,
+   -0.058196645975 +   0.033970293403*_Complex_I,
+   -0.145030462742 +   0.039564606547*_Complex_I,
+   -0.199735391140 +   0.212692308426*_Complex_I,
+   -0.083191102743 +   0.161805856228*_Complex_I,
+    0.098720330000 +  -0.211091256142*_Complex_I,
+    0.051044720411 +  -0.086010062695*_Complex_I,
+   -0.118900668621 +  -0.002717586420*_Complex_I,
+   -0.069598269463 +  -0.110835444927*_Complex_I};
+
+float complex fftfilt_crcf_data_h4x256_y[] = {
+    0.006059868184 +   0.010311000972*_Complex_I,
+    0.023178078930 +   0.016597675907*_Complex_I,
+    0.001208400606 +  -0.016916628103*_Complex_I,
+   -0.056896873822 +  -0.028897872629*_Complex_I,
+   -0.036461808619 +  -0.010528658122*_Complex_I,
+    0.037930900295 +  -0.007482611703*_Complex_I,
+    0.029162273827 +   0.043800977293*_Complex_I,
+   -0.042757112462 +   0.027781259560*_Complex_I,
+   -0.020487164712 +  -0.061602550704*_Complex_I,
+   -0.016651889059 +   0.011713200441*_Complex_I,
+    0.014333029188 +   0.031224344773*_Complex_I,
+   -0.028467695798 +  -0.024208640112*_Complex_I,
+    0.025832696718 +   0.019920182397*_Complex_I,
+   -0.018263455064 +   0.020029328938*_Complex_I,
+   -0.008683265862 +  -0.027376146840*_Complex_I,
+   -0.006227647384 +   0.018128890559*_Complex_I,
+   -0.005092271511 +  -0.000209362331*_Complex_I,
+    0.018081477268 +  -0.025761751836*_Complex_I,
+   -0.017992967322 +  -0.024982308404*_Complex_I,
+   -0.001734990008 +  -0.016554996144*_Complex_I,
+    0.026068014213 +  -0.041963253019*_Complex_I,
+    0.027586922248 +   0.001220122229*_Complex_I,
+    0.012814232728 +   0.026257959070*_Complex_I,
+   -0.048501690424 +  -0.002622820479*_Complex_I,
+    0.045376716710 +   0.014386270946*_Complex_I,
+    0.047800317199 +  -0.052577099490*_Complex_I,
+   -0.078296298763 +   0.024698851676*_Complex_I,
+    0.006293167971 +   0.038911765139*_Complex_I,
+    0.016953644799 +  -0.044169610042*_Complex_I,
+   -0.022820292108 +  -0.019446419914*_Complex_I,
+   -0.013440891814 +  -0.013844717016*_Complex_I,
+    0.012635287358 +   0.045953219949*_Complex_I,
+   -0.004889948433 +  -0.036010114541*_Complex_I,
+   -0.016426625652 +  -0.007240686790*_Complex_I,
+    0.053888265216 +   0.051988318810*_Complex_I,
+   -0.022978930110 +  -0.019347527890*_Complex_I,
+   -0.014632670495 +   0.009187820283*_Complex_I,
+    0.028981046972 +   0.028827768992*_Complex_I,
+   -0.016453648195 +  -0.009132783918*_Complex_I,
+   -0.010139552985 +   0.041406876217*_Complex_I,
+   -0.006352754502 +   0.001483701581*_Complex_I,
+    0.023445201384 +   0.014547612472*_Complex_I,
+   -0.009250442382 +  -0.029488557351*_Complex_I,
+    0.024527507249 +   0.038284467295*_Complex_I,
+    0.009919837691 +  -0.013523965732*_Complex_I,
+    0.023008727191 +   0.014672234378*_Complex_I,
+   -0.041817346662 +   0.028108049584*_Complex_I,
+    0.038656077865 +   0.001879063803*_Complex_I,
+   -0.030624607394 +  -0.015270823004*_Complex_I,
+   -0.011163069470 +   0.026250535961*_Complex_I,
+    0.030927857953 +   0.017782771893*_Complex_I,
+   -0.009187521194 +  -0.002284106361*_Complex_I,
+    0.023021457646 +   0.002319743817*_Complex_I,
+    0.002183457008 +  -0.048848851834*_Complex_I,
+    0.024149575692 +   0.023619625148*_Complex_I,
+   -0.038849588696 +   0.001002466517*_Complex_I,
+    0.014961889285 +   0.029401021704*_Complex_I,
+   -0.005688927643 +   0.018501108984*_Complex_I,
+    0.002270910497 +  -0.023424239102*_Complex_I,
+   -0.012218835419 +   0.007569433759*_Complex_I,
+   -0.081049834844 +   0.062554661642*_Complex_I,
+    0.063226186616 +   0.018432005055*_Complex_I,
+   -0.018370298666 +   0.036631089037*_Complex_I,
+    0.010275373508 +   0.019400535292*_Complex_I,
+   -0.008744823687 +   0.008298191463*_Complex_I,
+   -0.007436928001 +   0.012839303011*_Complex_I,
+    0.052061411111 +  -0.007282750818*_Complex_I,
+   -0.045668210091 +  -0.005240621957*_Complex_I,
+    0.006463364693 +  -0.046969833961*_Complex_I,
+    0.047231835108 +   0.017925737087*_Complex_I,
+    0.018296593037 +  -0.031722900784*_Complex_I,
+    0.003320288626 +   0.029589882902*_Complex_I,
+   -0.016756503569 +   0.000753544241*_Complex_I,
+   -0.000538421251 +  -0.046863924905*_Complex_I,
+   -0.042842141445 +  -0.023675331266*_Complex_I,
+    0.020793064630 +  -0.011272959088*_Complex_I,
+    0.048633718156 +   0.000888764564*_Complex_I,
+   -0.058367383027 +  -0.024603242648*_Complex_I,
+    0.023764054076 +   0.025940916726*_Complex_I,
+    0.019423147902 +  -0.045621226585*_Complex_I,
+   -0.048053653552 +  -0.014004397126*_Complex_I,
+    0.051461188295 +  -0.004580127799*_Complex_I,
+    0.000072882267 +  -0.021075898269*_Complex_I,
+    0.001594299020 +  -0.022143698521*_Complex_I,
+    0.033154983906 +   0.030058007048*_Complex_I,
+   -0.012594575098 +  -0.028965724511*_Complex_I,
+   -0.010583515338 +   0.028968867756*_Complex_I,
+   -0.054194842002 +   0.008176900584*_Complex_I,
+    0.029128076149 +  -0.024688556963*_Complex_I,
+    0.003010233851 +   0.012087666948*_Complex_I,
+    0.012788000724 +  -0.005917484080*_Complex_I,
+    0.005823040482 +   0.035833156760*_Complex_I,
+    0.009314948304 +   0.024460365458*_Complex_I,
+    0.022001107298 +  -0.006273716153*_Complex_I,
+   -0.021575905979 +  -0.001442477527*_Complex_I,
+    0.041753275685 +  -0.008456774704*_Complex_I,
+    0.025289151882 +   0.013847773473*_Complex_I,
+   -0.017151178833 +  -0.001123303791*_Complex_I,
+   -0.024939022931 +  -0.030114627959*_Complex_I,
+    0.013792691837 +   0.007716695455*_Complex_I,
+    0.026453740944 +  -0.011989457656*_Complex_I,
+   -0.035945169446 +   0.021435504561*_Complex_I,
+    0.032501406199 +  -0.028539713517*_Complex_I,
+   -0.015295973874 +   0.026437256451*_Complex_I,
+   -0.004134027099 +  -0.032386805528*_Complex_I,
+    0.047527752256 +  -0.011152387256*_Complex_I,
+   -0.044529397603 +  -0.024589491243*_Complex_I,
+    0.042085917317 +   0.067737759803*_Complex_I,
+   -0.021267753978 +   0.003431390108*_Complex_I,
+   -0.053880077452 +  -0.011182616288*_Complex_I,
+    0.019902772754 +  -0.029591806606*_Complex_I,
+    0.005358033182 +   0.016609146898*_Complex_I,
+   -0.036261790964 +  -0.018892239095*_Complex_I,
+    0.011134802022 +  -0.001028496419*_Complex_I,
+   -0.050869309200 +  -0.004439202137*_Complex_I,
+   -0.028307773042 +  -0.006096679489*_Complex_I,
+    0.065257591616 +  -0.033605740720*_Complex_I,
+    0.004628677817 +   0.046654745363*_Complex_I,
+    0.027227886379 +  -0.037405943501*_Complex_I,
+   -0.032050718154 +  -0.008470442952*_Complex_I,
+   -0.008872103935 +   0.012325554139*_Complex_I,
+    0.004047059830 +  -0.037017014389*_Complex_I,
+   -0.035453440267 +   0.005344037694*_Complex_I,
+    0.010704785090 +  -0.016337463075*_Complex_I,
+   -0.030159066222 +   0.024707243754*_Complex_I,
+    0.020346072551 +   0.006553876216*_Complex_I,
+   -0.011897480047 +  -0.021565463012*_Complex_I,
+   -0.004465715862 +   0.050343859162*_Complex_I,
+   -0.015523348770 +   0.002953074058*_Complex_I,
+   -0.021671519813 +  -0.054336385799*_Complex_I,
+    0.025180402141 +   0.035625240780*_Complex_I,
+   -0.015627538530 +   0.051389004055*_Complex_I,
+   -0.028673608441 +  -0.023927002620*_Complex_I,
+    0.040599172161 +  -0.035405429827*_Complex_I,
+   -0.012379694629 +   0.027233362371*_Complex_I,
+   -0.043152122115 +   0.047727396912*_Complex_I,
+    0.048160488676 +   0.011347729858*_Complex_I,
+   -0.046736949340 +  -0.023803373100*_Complex_I,
+    0.009245777592 +   0.008847967472*_Complex_I,
+    0.012152552309 +   0.007542499150*_Complex_I,
+   -0.044355838252 +   0.056937859612*_Complex_I,
+    0.008769479148 +  -0.041449112921*_Complex_I,
+   -0.039154058148 +   0.008519281894*_Complex_I,
+    0.029670337728 +  -0.018567039979*_Complex_I,
+   -0.004360041436 +   0.009962611552*_Complex_I,
+    0.009455849568 +   0.060794526263*_Complex_I,
+   -0.007406255544 +  -0.023779500426*_Complex_I,
+    0.024140392564 +  -0.000990198094*_Complex_I,
+   -0.002405235069 +   0.045967822672*_Complex_I,
+    0.055053080500 +  -0.032543394298*_Complex_I,
+   -0.033337576595 +   0.030424287480*_Complex_I,
+   -0.025251956775 +  -0.000824714706*_Complex_I,
+   -0.014809843352 +  -0.020503870649*_Complex_I,
+   -0.022962265375 +   0.005498838976*_Complex_I,
+    0.007866492401 +  -0.022921694981*_Complex_I,
+   -0.025557031391 +   0.017436967625*_Complex_I,
+    0.027361670922 +   0.039540920716*_Complex_I,
+   -0.037103850469 +  -0.033116749204*_Complex_I,
+    0.017144745074 +   0.058045180011*_Complex_I,
+    0.008105425889 +  -0.015309983982*_Complex_I,
+   -0.006650118444 +   0.003482516084*_Complex_I,
+   -0.011513157356 +   0.049425701442*_Complex_I,
+    0.038412271512 +  -0.019560238313*_Complex_I,
+    0.026334973579 +   0.012959388879*_Complex_I,
+   -0.003371545788 +   0.000377270846*_Complex_I,
+   -0.029595884162 +   0.009716579081*_Complex_I,
+    0.017727723120 +  -0.026138227085*_Complex_I,
+    0.012322519379 +  -0.049558984658*_Complex_I,
+   -0.026796988722 +   0.036486852957*_Complex_I,
+   -0.017291674519 +  -0.008043280034*_Complex_I,
+    0.028640846159 +  -0.012189476909*_Complex_I,
+    0.010234405721 +   0.003017545570*_Complex_I,
+    0.006584481357 +  -0.009021558338*_Complex_I,
+   -0.022148247256 +   0.004345047986*_Complex_I,
+    0.037711072166 +   0.013661393387*_Complex_I,
+   -0.025677561015 +   0.008959528481*_Complex_I,
+    0.020117937550 +   0.001416082797*_Complex_I,
+    0.036273540108 +  -0.037988612000*_Complex_I,
+   -0.009832458294 +   0.022778259426*_Complex_I,
+   -0.007452052832 +  -0.003232619018*_Complex_I,
+    0.027806123164 +   0.021703539285*_Complex_I,
+    0.021030743738 +   0.032118581494*_Complex_I,
+    0.035420951705 +   0.040521245842*_Complex_I,
+   -0.008374298228 +  -0.005319284142*_Complex_I,
+    0.004142265794 +  -0.017707830367*_Complex_I,
+   -0.002272597722 +   0.008133912171*_Complex_I,
+    0.002135690771 +   0.056547995055*_Complex_I,
+   -0.006717788148 +  -0.027626980558*_Complex_I,
+   -0.017795801727 +  -0.050060314493*_Complex_I,
+    0.050348648898 +   0.049756863364*_Complex_I,
+   -0.006668519461 +   0.005471354270*_Complex_I,
+    0.031191975857 +  -0.045353001209*_Complex_I,
+    0.025407683165 +  -0.018531409391*_Complex_I,
+   -0.046738508420 +   0.066929291225*_Complex_I,
+   -0.000707147993 +  -0.044733357800*_Complex_I,
+    0.037759803940 +  -0.028700053973*_Complex_I,
+   -0.034121953222 +  -0.052608653720*_Complex_I,
+   -0.017261681450 +   0.038291536957*_Complex_I,
+    0.044274419912 +   0.025096236007*_Complex_I,
+    0.001453954093 +   0.013865033987*_Complex_I,
+    0.004454502807 +  -0.002333726953*_Complex_I,
+    0.062910894997 +  -0.047902277474*_Complex_I,
+   -0.041170383580 +  -0.004258984080*_Complex_I,
+    0.021032411606 +   0.023244356831*_Complex_I,
+   -0.011019598807 +   0.018024590663*_Complex_I,
+   -0.003977275574 +   0.022558085902*_Complex_I,
+    0.008142338448 +  -0.061263169770*_Complex_I,
+   -0.040412311920 +   0.014535332123*_Complex_I,
+    0.026396971660 +  -0.000147948023*_Complex_I,
+   -0.013707137421 +  -0.002634559956*_Complex_I,
+    0.004374543380 +   0.008360519390*_Complex_I,
+    0.041968579789 +   0.007215835772*_Complex_I,
+   -0.028335666347 +   0.002719860858*_Complex_I,
+   -0.067248700384 +  -0.011709090114*_Complex_I,
+    0.022263469171 +  -0.001324450231*_Complex_I,
+   -0.014020557910 +  -0.000950863534*_Complex_I,
+   -0.011306257165 +  -0.000520086651*_Complex_I,
+    0.025214827331 +  -0.027199113964*_Complex_I,
+   -0.023406560837 +   0.003949552479*_Complex_I,
+   -0.041930892788 +   0.016117503991*_Complex_I,
+   -0.002573727540 +  -0.024804241879*_Complex_I,
+   -0.036096841921 +   0.022961064703*_Complex_I,
+    0.031847322765 +  -0.046819879524*_Complex_I,
+    0.007740249190 +  -0.006771536473*_Complex_I,
+    0.001665062016 +   0.020313331769*_Complex_I,
+   -0.051320971549 +  -0.008165457704*_Complex_I,
+    0.057246239219 +  -0.015598020100*_Complex_I,
+    0.020777735323 +   0.029309410804*_Complex_I,
+    0.004586628235 +   0.042828336116*_Complex_I,
+    0.036544916785 +   0.001529339266*_Complex_I,
+    0.034254870037 +  -0.029648210991*_Complex_I,
+    0.018166856966 +  -0.002136023066*_Complex_I,
+   -0.020485570645 +   0.016777734021*_Complex_I,
+   -0.066536421479 +   0.031283983440*_Complex_I,
+    0.012940550380 +   0.020303008580*_Complex_I,
+    0.023195801416 +  -0.072912377311*_Complex_I,
+   -0.066574302932 +   0.014524209319*_Complex_I,
+   -0.046087408784 +   0.059314287035*_Complex_I,
+    0.051145158090 +  -0.018053503400*_Complex_I,
+    0.003319584852 +   0.019225038270*_Complex_I,
+   -0.002381437087 +  -0.018745607253*_Complex_I,
+    0.026600447010 +   0.023038482211*_Complex_I,
+   -0.054100537802 +  -0.035500673450*_Complex_I,
+   -0.044770266493 +   0.009968159227*_Complex_I,
+    0.006304211224 +   0.001543141693*_Complex_I,
+   -0.023283661455 +  -0.009128698496*_Complex_I,
+    0.002209473868 +  -0.000822823133*_Complex_I,
+    0.002052052541 +   0.022819476191*_Complex_I,
+   -0.002213516155 +   0.004613633607*_Complex_I,
+   -0.041860771422 +   0.000627854778*_Complex_I,
+   -0.032948617085 +   0.038855531028*_Complex_I,
+   -0.017437619533 +   0.046788113461*_Complex_I,
+    0.026062969909 +  -0.045935009629*_Complex_I,
+    0.015631583001 +  -0.046464537818*_Complex_I,
+   -0.036593182213 +   0.039547933437*_Complex_I,
+   -0.024611804187 +  -0.022132462723*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_crcf_data_h7x256.c b/src/filter/tests/data/fftfilt_crcf_data_h7x256.c
new file mode 100644
index 0000000..722d9e4
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_crcf_data_h7x256.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_crcf_data_h7x256.c: autotest fftfilt data
+//
+
+#include <complex.h>
+
+float fftfilt_crcf_data_h7x256_h[] = {
+   -0.040934386849,
+    0.135112583637,
+    0.002939097211,
+    0.060173767805,
+   -0.080519974232,
+    0.155969536304,
+    0.169374001026};
+
+float complex fftfilt_crcf_data_h7x256_x[] = {
+    0.040651902556 +   0.099964648485*_Complex_I,
+   -0.099666911364 +   0.065967345238*_Complex_I,
+    0.160125207901 +   0.220931744576*_Complex_I,
+    0.040764364600 +  -0.044899615645*_Complex_I,
+   -0.134997522831 +   0.246626877785*_Complex_I,
+    0.081977373362 +  -0.136002039909*_Complex_I,
+    0.110743105412 +   0.054145228863*_Complex_I,
+    0.044197705388 +  -0.067522579432*_Complex_I,
+   -0.109018945694 +  -0.106943678856*_Complex_I,
+    0.006750953943 +  -0.091825586557*_Complex_I,
+    0.074130672216 +   0.058626526594*_Complex_I,
+   -0.109099924564 +  -0.185111522675*_Complex_I,
+    0.050862681866 +  -0.099265706539*_Complex_I,
+    0.139591431618 +  -0.009831842780*_Complex_I,
+    0.098799163103 +   0.068593806028*_Complex_I,
+   -0.134761643410 +  -0.071746927500*_Complex_I,
+    0.040908864141 +  -0.077851450443*_Complex_I,
+    0.009703576565 +   0.085823422670*_Complex_I,
+   -0.088899284601 +  -0.024389025569*_Complex_I,
+    0.131887495518 +   0.003364508972*_Complex_I,
+   -0.016711652279 +  -0.064561820030*_Complex_I,
+   -0.028739383817 +   0.119105470181*_Complex_I,
+   -0.037841024995 +   0.130922996998*_Complex_I,
+   -0.036886861920 +   0.049854472280*_Complex_I,
+    0.195188224316 +   0.196104526520*_Complex_I,
+    0.090881633759 +  -0.049584397674*_Complex_I,
+    0.095823466778 +  -0.010633364320*_Complex_I,
+   -0.026297450066 +   0.112273478508*_Complex_I,
+    0.064357328415 +  -0.151258838177*_Complex_I,
+    0.083770233393 +  -0.010217200220*_Complex_I,
+   -0.085294854641 +  -0.001434229873*_Complex_I,
+   -0.024482454360 +  -0.028102940321*_Complex_I,
+    0.007926462591 +   0.049879977107*_Complex_I,
+   -0.019109925628 +  -0.122103679180*_Complex_I,
+    0.065177565813 +  -0.099032187462*_Complex_I,
+   -0.029712364078 +   0.058807843924*_Complex_I,
+    0.014222274721 +  -0.111795830727*_Complex_I,
+   -0.013231332600 +  -0.097048926353*_Complex_I,
+   -0.042737719417 +  -0.082051867247*_Complex_I,
+   -0.000470168283 +   0.065859609842*_Complex_I,
+   -0.012786905468 +   0.140206325054*_Complex_I,
+    0.101196074486 +   0.024098466337*_Complex_I,
+    0.029521119595 +   0.073089057207*_Complex_I,
+    0.036042726040 +  -0.055842936039*_Complex_I,
+    0.006287913024 +   0.163206160069*_Complex_I,
+    0.162955164909 +  -0.168543994427*_Complex_I,
+    0.025857266784 +   0.046382865310*_Complex_I,
+    0.105994081497 +  -0.114217007160*_Complex_I,
+    0.166455543041 +  -0.042309248447*_Complex_I,
+   -0.055975890160 +  -0.082556062937*_Complex_I,
+    0.100109672546 +   0.042784979939*_Complex_I,
+   -0.133630573750 +   0.026788604259*_Complex_I,
+   -0.124175155163 +   0.100820934772*_Complex_I,
+   -0.018217980862 +   0.020558734238*_Complex_I,
+    0.020028033853 +  -0.023399496078*_Complex_I,
+    0.041591683030 +   0.234618091583*_Complex_I,
+    0.114061284065 +   0.076200354099*_Complex_I,
+   -0.044339376688 +   0.155996751785*_Complex_I,
+   -0.137206864357 +   0.152004182339*_Complex_I,
+   -0.125251030922 +  -0.189576375484*_Complex_I,
+   -0.096886903048 +   0.100963902473*_Complex_I,
+    0.045814022422 +  -0.153462719917*_Complex_I,
+    0.018112409115 +   0.033487904072*_Complex_I,
+   -0.094020819664 +  -0.066115939617*_Complex_I,
+   -0.183385014534 +  -0.106691968441*_Complex_I,
+    0.008264037967 +  -0.041415163875*_Complex_I,
+   -0.061662602425 +  -0.034961605072*_Complex_I,
+    0.125767064095 +   0.063457810879*_Complex_I,
+    0.017231588066 +  -0.019841212034*_Complex_I,
+    0.129437744617 +  -0.025927716494*_Complex_I,
+   -0.060927426815 +   0.096969240904*_Complex_I,
+   -0.015513072908 +   0.036051341891*_Complex_I,
+   -0.111995267868 +   0.039143866301*_Complex_I,
+   -0.073930400610 +   0.029889217019*_Complex_I,
+   -0.175347507000 +   0.054301995039*_Complex_I,
+    0.052788543701 +   0.158147716522*_Complex_I,
+    0.015504409373 +   0.139973330498*_Complex_I,
+   -0.031992411613 +  -0.023225200176*_Complex_I,
+    0.025022339821 +  -0.131516921520*_Complex_I,
+    0.018602204323 +   0.044248020649*_Complex_I,
+   -0.173263669014 +  -0.001720846072*_Complex_I,
+   -0.067457306385 +  -0.031605756283*_Complex_I,
+   -0.120310330391 +   0.084929233789*_Complex_I,
+   -0.217696404457 +   0.025325486064*_Complex_I,
+   -0.051374822855 +   0.090975987911*_Complex_I,
+    0.090311527252 +  -0.099616253376*_Complex_I,
+   -0.032548421621 +   0.022745725513*_Complex_I,
+    0.136037707329 +   0.054802763462*_Complex_I,
+    0.137908041477 +  -0.013432055712*_Complex_I,
+   -0.055867260695 +   0.124781918526*_Complex_I,
+   -0.042160874605 +  -0.145593822002*_Complex_I,
+   -0.119642519951 +   0.119596099854*_Complex_I,
+    0.139787364006 +   0.183254098892*_Complex_I,
+   -0.134095728397 +  -0.062589937449*_Complex_I,
+   -0.132995867729 +  -0.087235867977*_Complex_I,
+   -0.066653913260 +  -0.209919691086*_Complex_I,
+    0.103792250156 +  -0.098301386833*_Complex_I,
+   -0.047595480084 +  -0.060045230389*_Complex_I,
+   -0.112747573853 +  -0.090008395910*_Complex_I,
+   -0.209859752655 +  -0.037542074919*_Complex_I,
+    0.126466608047 +  -0.080237525702*_Complex_I,
+    0.016278307140 +   0.020775717497*_Complex_I,
+   -0.172989428043 +  -0.065381830931*_Complex_I,
+   -0.146700656414 +  -0.139556598663*_Complex_I,
+   -0.016435039043 +   0.061182838678*_Complex_I,
+    0.033882060647 +  -0.038332518935*_Complex_I,
+   -0.062427771091 +  -0.101914501190*_Complex_I,
+    0.005463292450 +  -0.123674154282*_Complex_I,
+   -0.021189038455 +   0.067074733973*_Complex_I,
+   -0.026172134280 +  -0.118123090267*_Complex_I,
+   -0.056077581644 +  -0.012562614679*_Complex_I,
+   -0.044715997577 +  -0.070106768608*_Complex_I,
+   -0.131478667259 +  -0.141248393059*_Complex_I,
+    0.029736247659 +  -0.026428756118*_Complex_I,
+   -0.037444081903 +  -0.068290340900*_Complex_I,
+    0.001371991076 +   0.079103118181*_Complex_I,
+    0.096291542053 +  -0.006254652143*_Complex_I,
+    0.011948133260 +  -0.061023879051*_Complex_I,
+    0.012181475013 +  -0.016896595061*_Complex_I,
+    0.018844342232 +  -0.121562480927*_Complex_I,
+   -0.075810509920 +  -0.044127961993*_Complex_I,
+   -0.065980046988 +  -0.120858967304*_Complex_I,
+    0.017340828478 +   0.001790644042*_Complex_I,
+   -0.158844494820 +  -0.130223393440*_Complex_I,
+    0.112532448769 +  -0.015026393533*_Complex_I,
+    0.207700538635 +   0.072319573164*_Complex_I,
+   -0.005225989595 +  -0.052219772339*_Complex_I,
+   -0.040421813726 +  -0.137831771374*_Complex_I,
+   -0.173290038109 +   0.160280382633*_Complex_I,
+    0.051121586561 +  -0.104382145405*_Complex_I,
+   -0.088693290949 +   0.112509071827*_Complex_I,
+    0.035253915191 +  -0.128079938889*_Complex_I,
+    0.069757878780 +  -0.118046510220*_Complex_I,
+    0.072834354639 +   0.093183624744*_Complex_I,
+    0.098304384947 +  -0.038691642880*_Complex_I,
+    0.043434667587 +  -0.021110518277*_Complex_I,
+    0.053629237413 +   0.059115654230*_Complex_I,
+    0.016377405822 +  -0.074930238724*_Complex_I,
+   -0.082616806030 +  -0.107039499283*_Complex_I,
+    0.063279908895 +   0.085305947065*_Complex_I,
+   -0.034898114204 +   0.129442250729*_Complex_I,
+   -0.014594067633 +  -0.068186104298*_Complex_I,
+   -0.013007178903 +   0.240023064613*_Complex_I,
+   -0.017352801561 +  -0.107890737057*_Complex_I,
+   -0.104129159451 +   0.294831180573*_Complex_I,
+    0.034248217940 +  -0.020147347450*_Complex_I,
+   -0.068929183483 +  -0.145515120029*_Complex_I,
+    0.021941699088 +  -0.150268518925*_Complex_I,
+    0.106213212013 +   0.050292271376*_Complex_I,
+   -0.139671075344 +  -0.032991030812*_Complex_I,
+   -0.077905637026 +  -0.102883970737*_Complex_I,
+    0.060987144709 +   0.119357335567*_Complex_I,
+    0.035836818814 +  -0.036872345209*_Complex_I,
+    0.104147458076 +   0.079617452621*_Complex_I,
+   -0.002261339687 +  -0.082611733675*_Complex_I,
+   -0.012895359099 +   0.065233492851*_Complex_I,
+    0.006274250150 +   0.018203525245*_Complex_I,
+    0.133517765999 +   0.130283260345*_Complex_I,
+   -0.197759151459 +  -0.014819876850*_Complex_I,
+   -0.113921284676 +  -0.029470649362*_Complex_I,
+    0.057060551643 +   0.010812921077*_Complex_I,
+   -0.017510490119 +  -0.139960432053*_Complex_I,
+   -0.051596522331 +  -0.019726826251*_Complex_I,
+    0.128962993622 +   0.032193422318*_Complex_I,
+   -0.085369610786 +   0.007678684592*_Complex_I,
+   -0.040599119663 +   0.058400410414*_Complex_I,
+    0.034683728218 +   0.006657131016*_Complex_I,
+   -0.062493687868 +   0.258592700958*_Complex_I,
+    0.030317050219 +  -0.182111239433*_Complex_I,
+   -0.029376697540 +   0.052327275276*_Complex_I,
+   -0.109441542625 +  -0.079162907600*_Complex_I,
+   -0.090063333511 +  -0.016401563585*_Complex_I,
+   -0.209231019020 +   0.104617154598*_Complex_I,
+    0.033998718858 +   0.114239072800*_Complex_I,
+    0.016250103712 +  -0.094514828920*_Complex_I,
+   -0.065307158232 +  -0.109144735336*_Complex_I,
+    0.044777354598 +   0.034570571780*_Complex_I,
+    0.047505182028 +   0.106286907196*_Complex_I,
+    0.032323399186 +  -0.060076057911*_Complex_I,
+    0.009413731098 +   0.113497722149*_Complex_I,
+   -0.001863899827 +   0.103868281841*_Complex_I,
+   -0.136217641830 +   0.035371488333*_Complex_I,
+    0.035117888451 +  -0.181699144840*_Complex_I,
+    0.012568083405 +   0.029830288887*_Complex_I,
+    0.222174477577 +  -0.128343975544*_Complex_I,
+   -0.073185539246 +   0.019389149547*_Complex_I,
+    0.101314771175 +   0.003134848550*_Complex_I,
+    0.029810345173 +   0.106935989857*_Complex_I,
+    0.062490916252 +  -0.069559586048*_Complex_I,
+    0.149825906754 +   0.072701114416*_Complex_I,
+    0.123934853077 +  -0.025215783715*_Complex_I,
+    0.009782531857 +  -0.153410112858*_Complex_I,
+    0.160168147087 +   0.089273881912*_Complex_I,
+    0.168133974075 +   0.006127674505*_Complex_I,
+    0.045770967007 +  -0.068010586500*_Complex_I,
+    0.031379061937 +  -0.011051291972*_Complex_I,
+    0.158589434624 +  -0.111993229389*_Complex_I,
+   -0.076348328590 +  -0.078152877092*_Complex_I,
+   -0.134444558620 +  -0.117534625530*_Complex_I,
+   -0.079543095827 +  -0.066662031412*_Complex_I,
+   -0.071512258053 +  -0.002769588307*_Complex_I,
+    0.044172352552 +  -0.001636494324*_Complex_I,
+    0.106910932064 +   0.067301118374*_Complex_I,
+   -0.189863562584 +  -0.210064339638*_Complex_I,
+    0.160713946819 +  -0.175641644001*_Complex_I,
+    0.107809066772 +  -0.015213376284*_Complex_I,
+    0.122092103958 +  -0.030190780759*_Complex_I,
+   -0.003812288493 +  -0.100728774071*_Complex_I,
+   -0.106180906296 +   0.040024718642*_Complex_I,
+   -0.023936416209 +   0.098257493973*_Complex_I,
+    0.036565282941 +   0.003215969726*_Complex_I,
+   -0.103855776787 +  -0.073435127735*_Complex_I,
+   -0.050348949432 +   0.062807798386*_Complex_I,
+   -0.112694954872 +   0.127760851383*_Complex_I,
+   -0.090150475502 +   0.123642957211*_Complex_I,
+    0.045266637206 +  -0.160168957710*_Complex_I,
+    0.026377058029 +   0.006977221370*_Complex_I,
+    0.102039659023 +  -0.043179959059*_Complex_I,
+    0.013855941594 +   0.044194161892*_Complex_I,
+   -0.218561863899 +  -0.144262731075*_Complex_I,
+   -0.137404084206 +   0.063279211521*_Complex_I,
+    0.238168430328 +   0.085272878408*_Complex_I,
+   -0.048531836271 +   0.178845489025*_Complex_I,
+   -0.144287097454 +   0.058967697620*_Complex_I,
+   -0.049209004641 +  -0.025680020452*_Complex_I,
+    0.024836380780 +  -0.005075773597*_Complex_I,
+    0.102902829647 +   0.135188984871*_Complex_I,
+    0.011638478935 +  -0.053264147043*_Complex_I,
+    0.022318521142 +  -0.005398513377*_Complex_I,
+   -0.073730462790 +  -0.112155592442*_Complex_I,
+   -0.006353335828 +  -0.047271516919*_Complex_I,
+   -0.009832777083 +  -0.083933675289*_Complex_I,
+   -0.189570760727 +  -0.024030640721*_Complex_I,
+    0.170936667919 +  -0.021397763491*_Complex_I,
+    0.027291259170 +   0.065315496922*_Complex_I,
+    0.021211113036 +   0.016976553202*_Complex_I,
+    0.009872701019 +  -0.091594105959*_Complex_I,
+    0.057232087851 +  -0.185351312160*_Complex_I,
+    0.200562620163 +  -0.098748236895*_Complex_I,
+    0.055897176266 +  -0.010039170831*_Complex_I,
+   -0.127369284630 +  -0.044656923413*_Complex_I,
+    0.137353527546 +   0.299679923058*_Complex_I,
+    0.045141702890 +  -0.044110915065*_Complex_I,
+   -0.004061393067 +  -0.012276126444*_Complex_I,
+   -0.177904891968 +   0.037945824862*_Complex_I,
+    0.154672491550 +  -0.095904517174*_Complex_I,
+   -0.175321102142 +   0.007050059736*_Complex_I,
+   -0.146184301376 +  -0.079787492752*_Complex_I,
+    0.189255583286 +   0.028883567452*_Complex_I,
+   -0.122478711605 +  -0.069346314669*_Complex_I,
+   -0.046293541789 +   0.053173589706*_Complex_I,
+   -0.007427679747 +   0.097887659073*_Complex_I,
+    0.015306715667 +   0.002264038287*_Complex_I,
+   -0.000068226655 +   0.105025970936*_Complex_I,
+    0.003785865381 +  -0.108235859871*_Complex_I,
+   -0.007453963161 +   0.090025615692*_Complex_I};
+
+float complex fftfilt_crcf_data_h7x256_y[] = {
+   -0.001664060705 +  -0.004091991592*_Complex_I,
+    0.009572387490 +   0.010806149100*_Complex_I,
+   -0.019901401209 +   0.000163118771*_Complex_I,
+    0.022119513679 +   0.037897731040*_Complex_I,
+    0.002233819268 +  -0.019592330428*_Complex_I,
+    0.002525225428 +   0.062331636862*_Complex_I,
+   -0.012953756015 +  -0.013138017071*_Complex_I,
+    0.010082536290 +   0.073767533264*_Complex_I,
+    0.060041705276 +  -0.002211361863*_Complex_I,
+   -0.028964339812 +   0.034181387304*_Complex_I,
+   -0.018779359256 +  -0.002983805531*_Complex_I,
+    0.035540274574 +  -0.000359767320*_Complex_I,
+    0.018230003371 +  -0.019050266810*_Complex_I,
+   -0.004763113580 +  -0.030748598959*_Complex_I,
+   -0.014980219026 +  -0.052722939755*_Complex_I,
+    0.043826553194 +   0.014698974734*_Complex_I,
+   -0.019748368274 +  -0.017846203362*_Complex_I,
+   -0.011106427606 +  -0.056159008955*_Complex_I,
+    0.019392753020 +  -0.015821591090*_Complex_I,
+    0.034983837452 +   0.006944986235*_Complex_I,
+    0.011247676464 +   0.014886292150*_Complex_I,
+   -0.023269214094 +  -0.046261333224*_Complex_I,
+    0.021153525557 +   0.012909728412*_Complex_I,
+   -0.027534552856 +   0.022575118010*_Complex_I,
+   -0.007955539149 +   0.007852778474*_Complex_I,
+    0.042312648793 +   0.017460367142*_Complex_I,
+    0.002444803765 +  -0.005588089285*_Complex_I,
+    0.018236124634 +   0.042201155890*_Complex_I,
+   -0.028316282736 +   0.032506714358*_Complex_I,
+    0.027833124697 +   0.022694313405*_Complex_I,
+    0.052935514738 +   0.031327141782*_Complex_I,
+    0.026052595484 +  -0.027272295746*_Complex_I,
+    0.008104080597 +   0.021431694747*_Complex_I,
+   -0.004512749861 +   0.007815877639*_Complex_I,
+    0.024134102168 +  -0.041085781838*_Complex_I,
+    0.013299793171 +  -0.012836542385*_Complex_I,
+   -0.024458533812 +  -0.003758988445*_Complex_I,
+    0.004926203119 +  -0.004067048560*_Complex_I,
+   -0.008670506918 +  -0.009165691319*_Complex_I,
+    0.004383190990 +  -0.061656974229*_Complex_I,
+    0.004798093293 +  -0.001521204143*_Complex_I,
+   -0.010192016400 +   0.013551533652*_Complex_I,
+    0.016184999222 +  -0.022825917758*_Complex_I,
+   -0.006827691414 +  -0.013869452441*_Complex_I,
+    0.004506159339 +  -0.027475686016*_Complex_I,
+   -0.014160870823 +   0.064266780404*_Complex_I,
+    0.034386845087 +  -0.005930865931*_Complex_I,
+    0.018854367860 +   0.040245466115*_Complex_I,
+    0.027504376699 +  -0.033177621818*_Complex_I,
+    0.020613311216 +   0.029686212518*_Complex_I,
+    0.019605341108 +  -0.022282553995*_Complex_I,
+    0.051946574188 +  -0.010220239313*_Complex_I,
+   -0.008537785955 +  -0.011901108333*_Complex_I,
+    0.038021151548 +  -0.003863047727*_Complex_I,
+   -0.000285452285 +  -0.017843481523*_Complex_I,
+    0.010371007070 +  -0.016105029388*_Complex_I,
+    0.006025408264 +   0.033055735207*_Complex_I,
+   -0.021980650718 +   0.021798423647*_Complex_I,
+   -0.023022513053 +   0.051363849161*_Complex_I,
+   -0.009988970700 +   0.014282686356*_Complex_I,
+   -0.015333264002 +   0.006580976190*_Complex_I,
+    0.004814453158 +   0.067575161384*_Complex_I,
+    0.021078361301 +  -0.008218606426*_Complex_I,
+   -0.018224318008 +   0.078249889963*_Complex_I,
+   -0.037359896117 +  -0.025653924650*_Complex_I,
+   -0.064316968357 +  -0.014904552890*_Complex_I,
+   -0.013278818311 +  -0.017987895509*_Complex_I,
+   -0.006335027346 +  -0.029308988553*_Complex_I,
+    0.019772938592 +   0.010742017022*_Complex_I,
+   -0.051503658033 +  -0.028040995216*_Complex_I,
+    0.002794574067 +  -0.025427598795*_Complex_I,
+   -0.024524259323 +  -0.007221318106*_Complex_I,
+    0.017882433375 +   0.007567005700*_Complex_I,
+   -0.002250551860 +   0.019747472870*_Complex_I,
+    0.023938973082 +  -0.011112496998*_Complex_I,
+   -0.019139308840 +   0.011136397615*_Complex_I,
+   -0.002187533851 +   0.036491286044*_Complex_I,
+   -0.021134226221 +   0.033399966516*_Complex_I,
+   -0.018505797162 +   0.019092652268*_Complex_I,
+   -0.040662996211 +  -0.010428531884*_Complex_I,
+   -0.014960045707 +   0.026857740562*_Complex_I,
+   -0.005153165788 +   0.043765214305*_Complex_I,
+   -0.007957971301 +   0.025585809340*_Complex_I,
+   -0.020982171142 +  -0.017767345049*_Complex_I,
+   -0.010632593672 +  -0.017190118051*_Complex_I,
+   -0.036959052593 +   0.031325636500*_Complex_I,
+   -0.029896295979 +  -0.024658675955*_Complex_I,
+   -0.025453655228 +   0.011865474671*_Complex_I,
+   -0.032120802983 +   0.013036366771*_Complex_I,
+   -0.032795629447 +   0.021107115286*_Complex_I,
+    0.010773771971 +   0.024117992872*_Complex_I,
+    0.006601385448 +  -0.042746141925*_Complex_I,
+   -0.020772448711 +   0.029219872585*_Complex_I,
+    0.070536673110 +   0.016052326150*_Complex_I,
+   -0.001423170033 +   0.031759755912*_Complex_I,
+   -0.013628227138 +  -0.003553860969*_Complex_I,
+   -0.058771606883 +  -0.049123627122*_Complex_I,
+    0.020108788723 +   0.037188142016*_Complex_I,
+    0.007949063332 +   0.010951582458*_Complex_I,
+   -0.038626115166 +  -0.024020666588*_Complex_I,
+   -0.078006273376 +  -0.045266963734*_Complex_I,
+    0.017751019078 +  -0.063270144075*_Complex_I,
+    0.016258961397 +  -0.015778900175*_Complex_I,
+   -0.018458853139 +  -0.029074158472*_Complex_I,
+   -0.080688576156 +  -0.034942123609*_Complex_I,
+   -0.031578691749 +  -0.015054873307*_Complex_I,
+    0.036145679578 +  -0.014310450501*_Complex_I,
+   -0.021959415316 +  -0.000580080691*_Complex_I,
+   -0.047396546182 +  -0.059828747217*_Complex_I,
+   -0.035670847715 +  -0.003606200086*_Complex_I,
+    0.006553370474 +  -0.010100243677*_Complex_I,
+   -0.011536306226 +  -0.007568475396*_Complex_I,
+   -0.010414782074 +  -0.052787147602*_Complex_I,
+   -0.022759606756 +  -0.019938883855*_Complex_I,
+   -0.000682229297 +  -0.011460511608*_Complex_I,
+   -0.022518239866 +  -0.037363417124*_Complex_I,
+   -0.007962730587 +   0.007463843703*_Complex_I,
+   -0.020202765636 +  -0.034000570007*_Complex_I,
+   -0.013134852636 +  -0.025359042386*_Complex_I,
+    0.005789769049 +  -0.019359516099*_Complex_I,
+   -0.007477326398 +  -0.017065274446*_Complex_I,
+    0.007535152908 +   0.014947146624*_Complex_I,
+    0.008478526310 +  -0.033064163294*_Complex_I,
+    0.006495736538 +  -0.000621013504*_Complex_I,
+   -0.018881009743 +  -0.042515704819*_Complex_I,
+    0.003959382518 +  -0.023006182258*_Complex_I,
+   -0.005478138646 +  -0.022439956712*_Complex_I,
+    0.012649989781 +  -0.011810626415*_Complex_I,
+   -0.016784136514 +  -0.039783192634*_Complex_I,
+   -0.052016102486 +  -0.007841932680*_Complex_I,
+    0.059471950363 +  -0.013592303981*_Complex_I,
+    0.013914799172 +   0.044984722860*_Complex_I,
+    0.011486842937 +  -0.061671443344*_Complex_I,
+   -0.036780337271 +  -0.003311742125*_Complex_I,
+   -0.006092641235 +   0.007927763608*_Complex_I,
+    0.007902421446 +  -0.001011649739*_Complex_I,
+   -0.006795769455 +   0.008805976503*_Complex_I,
+    0.023605170020 +  -0.038943989870*_Complex_I,
+    0.023625556066 +  -0.009183727558*_Complex_I,
+    0.013693690762 +  -0.003169361400*_Complex_I,
+    0.029827583427 +  -0.013202113602*_Complex_I,
+    0.005499368566 +   0.025768234920*_Complex_I,
+    0.020555898301 +  -0.006579736075*_Complex_I,
+   -0.018397046470 +   0.008180231852*_Complex_I,
+   -0.000311933642 +  -0.045290959470*_Complex_I,
+   -0.009854739488 +   0.094914113403*_Complex_I,
+   -0.001041015584 +  -0.010428696751*_Complex_I,
+   -0.019479887241 +   0.038746806007*_Complex_I,
+    0.003949972577 +  -0.023915676515*_Complex_I,
+   -0.005952904345 +   0.028280800121*_Complex_I,
+   -0.020794732314 +   0.049370817714*_Complex_I,
+   -0.013758601114 +  -0.029866172207*_Complex_I,
+   -0.018665194668 +  -0.036784877494*_Complex_I,
+    0.027598854453 +  -0.029032203867*_Complex_I,
+    0.020417636582 +   0.032869611698*_Complex_I,
+   -0.038033374173 +  -0.047062159994*_Complex_I,
+   -0.002307491697 +   0.016775992609*_Complex_I,
+    0.002741426303 +   0.000401399904*_Complex_I,
+    0.047873297365 +   0.035012972663*_Complex_I,
+   -0.002960979438 +  -0.003970085476*_Complex_I,
+   -0.013174401910 +  -0.001911987889*_Complex_I,
+   -0.015764753015 +   0.009609419552*_Complex_I,
+    0.030869778703 +   0.004752229880*_Complex_I,
+   -0.007925303167 +   0.018384260100*_Complex_I,
+   -0.036144266243 +  -0.012421792859*_Complex_I,
+   -0.021583982780 +   0.005519018931*_Complex_I,
+    0.011692092106 +  -0.008831839761*_Complex_I,
+   -0.019409391330 +  -0.038426827460*_Complex_I,
+    0.006223391109 +   0.046989172728*_Complex_I,
+    0.018999067344 +  -0.023638921178*_Complex_I,
+   -0.026744966281 +   0.035209035395*_Complex_I,
+   -0.005797132267 +  -0.030721098320*_Complex_I,
+   -0.012007029920 +   0.052541286005*_Complex_I,
+   -0.040002533893 +   0.015828735411*_Complex_I,
+    0.007259346684 +   0.002315325843*_Complex_I,
+   -0.022414689402 +  -0.003834870337*_Complex_I,
+   -0.024299572844 +  -0.033955598422*_Complex_I,
+   -0.045734353675 +  -0.001347407015*_Complex_I,
+   -0.030146751771 +   0.053501404395*_Complex_I,
+    0.020367548034 +   0.003025664117*_Complex_I,
+   -0.006737275010 +  -0.018512929694*_Complex_I,
+   -0.000605709571 +  -0.012347963683*_Complex_I,
+   -0.006890463622 +   0.046621939143*_Complex_I,
+    0.016047502033 +  -0.019923449202*_Complex_I,
+   -0.008396903768 +   0.010041927047*_Complex_I,
+    0.047436458062 +   0.003595322623*_Complex_I,
+   -0.037015474086 +   0.041649052012*_Complex_I,
+    0.007016220323 +  -0.036370210637*_Complex_I,
+   -0.012617538063 +   0.002683467470*_Complex_I,
+    0.051168439092 +  -0.028397908806*_Complex_I,
+    0.035205638612 +  -0.001881106820*_Complex_I,
+    0.021551345104 +  -0.005936731955*_Complex_I,
+    0.020923031070 +   0.002729234686*_Complex_I,
+    0.024976448430 +   0.011252084504*_Complex_I,
+    0.045876193259 +  -0.003769034340*_Complex_I,
+    0.058950819572 +   0.017386610345*_Complex_I,
+    0.017620108715 +  -0.032126491912*_Complex_I,
+    0.040499252854 +  -0.028610567370*_Complex_I,
+    0.047208712591 +   0.014810229612*_Complex_I,
+    0.027499183980 +  -0.028800252172*_Complex_I,
+   -0.012932302613 +  -0.018166904753*_Complex_I,
+    0.016403288642 +  -0.020622110742*_Complex_I,
+    0.022373704021 +  -0.028689811011*_Complex_I,
+   -0.009452243729 +  -0.008680644929*_Complex_I,
+   -0.058678992249 +  -0.051174770891*_Complex_I,
+   -0.005006438141 +  -0.031267299630*_Complex_I,
+   -0.015215097299 +  -0.020119691971*_Complex_I,
+    0.066084164099 +   0.016564502968*_Complex_I,
+   -0.013768201897 +  -0.023474209345*_Complex_I,
+   -0.021803271593 +  -0.062476214596*_Complex_I,
+    0.028932477041 +  -0.022490403547*_Complex_I,
+    0.040341712936 +   0.006962852165*_Complex_I,
+    0.015330216728 +  -0.030617975708*_Complex_I,
+   -0.015573978799 +  -0.015495893724*_Complex_I,
+   -0.042595498240 +   0.029811952687*_Complex_I,
+   -0.007383005008 +   0.050473873697*_Complex_I,
+   -0.007960910417 +  -0.029841422966*_Complex_I,
+   -0.022273885029 +  -0.003249632656*_Complex_I,
+   -0.002824822559 +   0.003348401440*_Complex_I,
+   -0.024087272328 +   0.065990131062*_Complex_I,
+   -0.028057881818 +  -0.029151797322*_Complex_I,
+   -0.024558131583 +  -0.015268822223*_Complex_I,
+    0.039877624209 +  -0.013405850916*_Complex_I,
+    0.028823486535 +   0.037004255305*_Complex_I,
+   -0.023970193031 +  -0.005435181200*_Complex_I,
+   -0.088636699560 +  -0.013757698161*_Complex_I,
+    0.008098637338 +   0.006870343753*_Complex_I,
+    0.054927003829 +   0.056475265541*_Complex_I,
+   -0.024306265176 +   0.034672928541*_Complex_I,
+   -0.021853531429 +   0.018230890552*_Complex_I,
+   -0.021682656382 +  -0.032466179919*_Complex_I,
+    0.019989585378 +   0.020908798620*_Complex_I,
+    0.019423343509 +  -0.002219973236*_Complex_I,
+   -0.021632746497 +  -0.006294932050*_Complex_I,
+    0.013621743308 +  -0.025286913038*_Complex_I,
+   -0.020772874190 +  -0.012989704190*_Complex_I,
+    0.025482399527 +  -0.014215236725*_Complex_I,
+   -0.044300771406 +  -0.017049381631*_Complex_I,
+   -0.006816700080 +  -0.032915524093*_Complex_I,
+    0.057073619573 +  -0.013791458776*_Complex_I,
+    0.023935306468 +   0.010113795207*_Complex_I,
+   -0.010074654341 +  -0.020758506394*_Complex_I,
+    0.014148879115 +   0.005089223239*_Complex_I,
+    0.035479315734 +  -0.053250867185*_Complex_I,
+    0.068075559099 +  -0.000004138871*_Complex_I,
+   -0.049122106052 +  -0.026433363499*_Complex_I,
+    0.023522704576 +   0.028855355307*_Complex_I,
+    0.002677137744 +   0.051086576447*_Complex_I,
+    0.002630815479 +  -0.030154181511*_Complex_I,
+   -0.021284842311 +   0.018492273061*_Complex_I,
+   -0.014785053010 +  -0.025361228004*_Complex_I,
+    0.015700956081 +  -0.004007983078*_Complex_I,
+   -0.076870199719 +  -0.004459394713*_Complex_I,
+    0.013883703469 +  -0.003931123862*_Complex_I,
+    0.016113428385 +   0.014312499105*_Complex_I,
+   -0.025629511396 +  -0.029198167224*_Complex_I};
+
diff --git a/src/filter/tests/data/fftfilt_rrrf_data_h13x256.c b/src/filter/tests/data/fftfilt_rrrf_data_h13x256.c
new file mode 100644
index 0000000..14b1134
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_rrrf_data_h13x256.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_rrrf_data_h13x256.c: autotest fftfilt data
+//
+
+float fftfilt_rrrf_data_h13x256_h[] = {
+   -0.039212104678,
+   -0.045289030671,
+   -0.184304034710,
+    0.077858728170,
+    0.040869677067,
+    0.142814588547,
+   -0.108285641670,
+    0.089570593834,
+   -0.172709572315,
+    0.001841047592,
+    0.074960124493,
+   -0.114357507229,
+    0.043473127484};
+
+float fftfilt_rrrf_data_h13x256_x[] = {
+    0.122770094872,
+    0.089148938656,
+    0.017954966426,
+   -0.028631305695,
+   -0.024217808247,
+   -0.019191932678,
+    0.124998247623,
+   -0.010905320942,
+   -0.182813262939,
+    0.022872561216,
+   -0.167512893677,
+    0.004144698754,
+   -0.091696554422,
+    0.145599734783,
+    0.149157440662,
+    0.111943149567,
+   -0.083463251591,
+    0.074205029011,
+    0.057325953245,
+    0.098762345314,
+   -0.056830596924,
+    0.129721879959,
+    0.070393782854,
+    0.044613954425,
+   -0.041217750311,
+   -0.047065210342,
+    0.042085236311,
+    0.065604847670,
+    0.011089600623,
+    0.055663853884,
+    0.010694834590,
+   -0.107917737961,
+    0.091816771030,
+   -0.071010309458,
+    0.225902843475,
+   -0.193409955502,
+    0.134397053719,
+    0.006444688141,
+   -0.100715303421,
+    0.013986042142,
+    0.037019279599,
+   -0.018146976829,
+    0.038268235326,
+    0.124087500572,
+   -0.065513682365,
+    0.243372726440,
+    0.070982813835,
+   -0.114888060093,
+    0.083260691166,
+    0.078598594666,
+    0.119385373592,
+    0.124578750134,
+    0.229874610901,
+    0.068851232529,
+   -0.152908086777,
+    0.022878190875,
+   -0.038320010900,
+   -0.019077518582,
+   -0.187935698032,
+    0.000150304066,
+    0.107031500340,
+   -0.054503691196,
+   -0.189286994934,
+    0.106510078907,
+    0.016531120241,
+   -0.062976771593,
+   -0.034572181106,
+    0.116243016720,
+    0.014939060807,
+   -0.104378926754,
+    0.035551485419,
+   -0.211227297783,
+    0.056110930443,
+   -0.069913518429,
+    0.050586497784,
+    0.100069129467,
+    0.307702898979,
+    0.186338675022,
+    0.197983598709,
+    0.070847284794,
+   -0.168327832222,
+   -0.115514957905,
+   -0.069601941109,
+    0.079878026247,
+    0.015964616835,
+    0.128264808655,
+    0.015298053622,
+    0.210790205002,
+    0.040280216932,
+    0.091706073284,
+    0.090860366821,
+   -0.034307426214,
+   -0.043785154819,
+    0.100965428352,
+    0.062400549650,
+    0.122274744511,
+   -0.063038861752,
+   -0.173100697994,
+   -0.167581903934,
+    0.088559448719,
+   -0.130531704426,
+   -0.021028609574,
+    0.060926514864,
+    0.078577047586,
+    0.197759902477,
+    0.019897483289,
+   -0.015052948892,
+   -0.156514930725,
+    0.121016633511,
+    0.080825585127,
+    0.026895985007,
+   -0.032256072760,
+   -0.033008736372,
+    0.101098561287,
+   -0.004690715298,
+   -0.097240817547,
+    0.124587404728,
+   -0.080275380611,
+   -0.170625138283,
+    0.064333653450,
+   -0.082882106304,
+    0.055449581146,
+    0.036299997568,
+   -0.036278706789,
+    0.075224477053,
+    0.005092585459,
+    0.031370615959,
+    0.020764264464,
+    0.049643722177,
+    0.021591423452,
+   -0.040642136335,
+    0.027282950282,
+    0.134740507603,
+   -0.029146528244,
+    0.034743618965,
+   -0.014017164707,
+   -0.070363909006,
+    0.020523373783,
+    0.000458177784,
+   -0.068270605803,
+    0.180056059361,
+   -0.119201040268,
+   -0.046510973573,
+   -0.058247959614,
+    0.155885064602,
+   -0.083602911234,
+   -0.119938647747,
+   -0.114219510555,
+    0.161393058300,
+    0.232402944565,
+   -0.151511478424,
+    0.066477537155,
+   -0.115181660652,
+   -0.164413774014,
+    0.031050026417,
+   -0.038917222619,
+    0.093678462505,
+   -0.040763008595,
+   -0.004956462234,
+    0.044149956107,
+    0.113123583794,
+   -0.092754375935,
+   -0.027752390504,
+    0.137882554531,
+    0.035738137364,
+   -0.051311886311,
+    0.134906625748,
+    0.123595929146,
+    0.159623205662,
+    0.014216412604,
+   -0.078501766920,
+   -0.046537932754,
+    0.066755509377,
+   -0.061657583714,
+    0.008335828036,
+    0.009980520606,
+    0.106155145168,
+   -0.010150838643,
+   -0.012183431536,
+    0.101287877560,
+    0.024197725952,
+   -0.060843515396,
+   -0.240097737312,
+    0.065998744965,
+   -0.050199592113,
+   -0.055537813902,
+    0.165046787262,
+    0.133701193333,
+   -0.018093936145,
+   -0.113999474049,
+   -0.069255769253,
+   -0.081180727482,
+    0.070946794748,
+    0.136880230904,
+    0.025476306677,
+   -0.102032244205,
+   -0.024109381437,
+   -0.016868881881,
+    0.167234003544,
+    0.171832489967,
+   -0.030091801286,
+    0.011118320376,
+   -0.008921170235,
+    0.101217842102,
+   -0.048454919457,
+    0.151966249943,
+    0.198045432568,
+    0.040109497309,
+    0.016406393051,
+    0.089656168222,
+    0.035927999020,
+   -0.033026877046,
+   -0.054043507576,
+    0.211845135689,
+   -0.036055120826,
+    0.160977423191,
+   -0.197179889679,
+    0.040342581272,
+    0.150020170212,
+   -0.073872137070,
+   -0.164493560791,
+    0.124404942989,
+    0.031155037880,
+   -0.061415308714,
+    0.000370543310,
+   -0.019733832777,
+    0.089461517334,
+    0.229397058487,
+    0.149904847145,
+   -0.119740092754,
+   -0.233863234520,
+   -0.029367747903,
+    0.070431739092,
+    0.001778364740,
+    0.166927957535,
+   -0.018398888409,
+   -0.102967965603,
+    0.006021655723,
+    0.067407786846,
+   -0.140168070793,
+    0.011420756578,
+    0.095064306259,
+    0.020388415456,
+   -0.112541508675,
+   -0.085077953339,
+    0.125045490265,
+    0.210480499268,
+   -0.117026185989,
+   -0.089133924246,
+    0.147170877457,
+    0.107982301712,
+    0.096577769518,
+   -0.058198565245,
+    0.027206465602,
+    0.002091913670,
+   -0.088938659430};
+
+float fftfilt_rrrf_data_h13x256_y[] = {
+   -0.004814073811,
+   -0.009055856107,
+   -0.027368544867,
+   -0.006562254910,
+    0.010895739671,
+    0.029701022061,
+   -0.001626673446,
+   -0.000844726806,
+   -0.037111011029,
+    0.004419375864,
+    0.047068466895,
+    0.003910634431,
+    0.012396721813,
+   -0.025213063245,
+    0.000635157075,
+   -0.085524325904,
+    0.041146455184,
+   -0.050979327116,
+    0.077630920989,
+   -0.003623999480,
+   -0.004841852073,
+   -0.028754487596,
+    0.001410758665,
+   -0.034036822601,
+    0.014107997579,
+   -0.022709798205,
+    0.025617017445,
+   -0.003252183639,
+   -0.003963481395,
+   -0.036270671993,
+   -0.021399438537,
+    0.017388718079,
+   -0.000128062767,
+    0.028859143315,
+   -0.030391114442,
+    0.001543784470,
+   -0.046555566322,
+    0.058215460338,
+   -0.063274785430,
+    0.077974731120,
+   -0.057110585904,
+    0.054791309007,
+   -0.060943087255,
+    0.010461367165,
+    0.008954983539,
+   -0.076741190112,
+    0.063566034944,
+   -0.058742851449,
+    0.008267455413,
+    0.026477649501,
+    0.013490128892,
+   -0.071421597975,
+   -0.011335301312,
+   -0.030890897000,
+   -0.062240856218,
+    0.083426871968,
+    0.015357553814,
+   -0.000362231866,
+    0.008918381045,
+   -0.028650443960,
+    0.017482443006,
+   -0.052161386060,
+    0.020679845153,
+   -0.025369370715,
+    0.045526628268,
+   -0.011020145844,
+    0.002644272584,
+    0.006285259524,
+   -0.004936878581,
+   -0.019116352540,
+    0.039400594243,
+    0.000124031921,
+    0.002039447415,
+    0.062249843178,
+   -0.043579179266,
+   -0.001996355335,
+   -0.069473606074,
+    0.023450651628,
+   -0.118043698351,
+    0.033847857380,
+    0.001708993883,
+    0.047506531454,
+    0.075888786942,
+    0.008727973204,
+   -0.040651907871,
+   -0.077531568445,
+   -0.017930168448,
+   -0.069340649806,
+    0.041402166708,
+   -0.040029272550,
+    0.025336666274,
+   -0.018448259990,
+    0.033311907821,
+   -0.013731300546,
+    0.016338663490,
+   -0.049502280186,
+   -0.034415215410,
+    0.005555120775,
+    0.018242670176,
+    0.047690282789,
+    0.041262648570,
+   -0.072855941506,
+    0.009723921419,
+   -0.029262747157,
+   -0.011884300184,
+   -0.027377242282,
+    0.003541591160,
+    0.004992461263,
+    0.044689949294,
+    0.066697897821,
+   -0.089113718326,
+    0.005326909592,
+   -0.042616477087,
+    0.036692093307,
+   -0.004180374377,
+   -0.003271218876,
+   -0.009369454837,
+   -0.002500828958,
+    0.016544265383,
+    0.002187635659,
+    0.029976595593,
+   -0.018441944452,
+   -0.020909152277,
+    0.007956140844,
+   -0.021637777770,
+   -0.014177920945,
+    0.055566577829,
+   -0.042960866797,
+    0.006896087167,
+    0.024767376727,
+   -0.043457653016,
+    0.034935882911,
+   -0.015575911379,
+   -0.009819491182,
+   -0.018887628248,
+   -0.001068307187,
+    0.006963055931,
+    0.013680239410,
+    0.005809808354,
+    0.005066455307,
+   -0.040283759818,
+    0.017470897286,
+   -0.022805491441,
+    0.015206465884,
+    0.019136018024,
+    0.021643961761,
+   -0.071515530177,
+    0.078236064221,
+   -0.029742524131,
+    0.032918640371,
+   -0.053023950617,
+   -0.047312921105,
+    0.034250312751,
+    0.032284357688,
+    0.068808863735,
+   -0.015296681918,
+    0.000837419600,
+   -0.075314366704,
+    0.024501519621,
+    0.005869076899,
+   -0.039416548615,
+    0.069295643723,
+   -0.060264925779,
+    0.046170239698,
+   -0.002067325538,
+   -0.026442474538,
+   -0.007890011691,
+    0.000037015562,
+   -0.035959976850,
+   -0.016139843757,
+   -0.002858263222,
+    0.006428619035,
+    0.027561864469,
+    0.042341971465,
+   -0.058543104834,
+   -0.007599628510,
+   -0.014902259564,
+   -0.011133330016,
+   -0.020432058943,
+    0.017048024915,
+   -0.016928345566,
+    0.012963663673,
+    0.008408004748,
+    0.018403366116,
+    0.046619907266,
+   -0.038433448340,
+    0.007648025051,
+   -0.059208853553,
+   -0.011295552139,
+   -0.025495843506,
+    0.062840766336,
+    0.041709165483,
+   -0.000905313483,
+    0.033310202711,
+   -0.082126428402,
+   -0.037682258830,
+    0.020034347469,
+    0.049244781989,
+   -0.001378192350,
+   -0.008849598752,
+   -0.050136739148,
+   -0.035155204482,
+    0.024111310154,
+    0.038903294513,
+   -0.003167046710,
+   -0.032326890373,
+    0.004090324360,
+   -0.075671618163,
+    0.006238782693,
+   -0.017653047474,
+    0.025665426828,
+   -0.012204527876,
+    0.007845325954,
+    0.005183653849,
+   -0.034222431570,
+   -0.037618568932,
+    0.013395877564,
+   -0.045882750561,
+    0.072052386700,
+   -0.040677327938,
+    0.025524603777,
+   -0.058679392496,
+    0.073277528966,
+   -0.045606738121,
+   -0.018120414293,
+    0.035599866018,
+   -0.037821283601,
+    0.009959320327,
+   -0.008944663864,
+   -0.071448196749,
+   -0.002389984851,
+    0.105394267739,
+    0.040560858684,
+   -0.015716980196,
+   -0.051319091715,
+   -0.053534618918,
+   -0.028961761656,
+    0.040242569747,
+    0.040510770959,
+    0.011016182224,
+   -0.040345662543,
+    0.048481523965,
+   -0.035361728505,
+   -0.024831493953,
+    0.018886824467,
+    0.026390411027,
+   -0.016368948643,
+   -0.009813816544,
+   -0.035846470146,
+    0.008843767444,
+    0.038479236659,
+   -0.019424751909,
+   -0.036842320539,
+   -0.011401190478,
+    0.012789717510,
+    0.024104726420};
+
diff --git a/src/filter/tests/data/fftfilt_rrrf_data_h23x256.c b/src/filter/tests/data/fftfilt_rrrf_data_h23x256.c
new file mode 100644
index 0000000..8d5c556
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_rrrf_data_h23x256.c
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_rrrf_data_h23x256.c: autotest fftfilt data
+//
+
+float fftfilt_rrrf_data_h23x256_h[] = {
+   -0.118176388741,
+   -0.025418123603,
+   -0.059258782864,
+    0.142066907883,
+    0.004454790801,
+   -0.016711865366,
+    0.005629793927,
+   -0.123983550072,
+    0.173112368584,
+   -0.160880231857,
+    0.054240733385,
+   -0.031276765466,
+    0.154612803459,
+   -0.006389370561,
+    0.079528987408,
+   -0.002910712734,
+   -0.159243178368,
+    0.022829471529,
+   -0.113626921177,
+   -0.095083606243,
+   -0.122057342529,
+    0.104624044895,
+    0.011057048291};
+
+float fftfilt_rrrf_data_h23x256_x[] = {
+    0.059350812435,
+   -0.000424881605,
+   -0.049009537697,
+   -0.034536927938,
+    0.103308689594,
+   -0.055876135826,
+   -0.148367643356,
+   -0.111977064610,
+    0.067770421505,
+    0.043956550956,
+    0.143173515797,
+    0.004022727162,
+    0.111428678036,
+    0.028413870931,
+    0.166605424881,
+    0.038992109895,
+   -0.010133068264,
+    0.076038408279,
+    0.105890285969,
+    0.147610998154,
+   -0.047342804074,
+    0.055655419827,
+   -0.013745918870,
+   -0.016829429567,
+   -0.062561410666,
+    0.095859551430,
+    0.019820876420,
+    0.037723916769,
+    0.054752278328,
+    0.057050412893,
+    0.207521724701,
+   -0.130268049240,
+    0.165039086342,
+   -0.194032716751,
+    0.189072954655,
+   -0.137876522541,
+    0.047114580870,
+   -0.072617882490,
+   -0.100785088539,
+    0.064732289314,
+   -0.073995077610,
+    0.019561932981,
+    0.046311601996,
+   -0.104536223412,
+   -0.033436897397,
+    0.151995897293,
+    0.031935700774,
+   -0.028941801190,
+    0.078559994698,
+   -0.047812539339,
+    0.130064833164,
+   -0.056572306156,
+   -0.042208436131,
+    0.085643953085,
+   -0.080961161852,
+    0.057486045361,
+    0.020754659176,
+    0.070619314909,
+    0.158251059055,
+    0.088375788927,
+   -0.043805596232,
+    0.127501475811,
+    0.115190374851,
+   -0.044433131814,
+    0.034547865391,
+   -0.044843772054,
+   -0.062454062700,
+    0.030952209234,
+    0.040272334218,
+   -0.046460473537,
+    0.054621320963,
+   -0.026183435321,
+   -0.025449073315,
+    0.053822481632,
+    0.173386669159,
+    0.039320838451,
+    0.080104076862,
+   -0.057773065567,
+   -0.145702457428,
+   -0.104137754440,
+    0.001670002751,
+   -0.015087755024,
+    0.030506807566,
+    0.050601142645,
+   -0.168711090088,
+    0.072758728266,
+   -0.085856407881,
+    0.036477252841,
+    0.033204284310,
+   -0.074460619688,
+   -0.132449066639,
+   -0.005033437908,
+    0.051431566477,
+   -0.000122980447,
+    0.104783654213,
+    0.086071228981,
+    0.087956553698,
+    0.018256981671,
+    0.273954725266,
+   -0.047239318490,
+    0.061503499746,
+    0.006188166887,
+   -0.009299278259,
+    0.085814112425,
+    0.131504595280,
+   -0.038905382156,
+   -0.043368801475,
+    0.201532816887,
+    0.094288158417,
+    0.092639130354,
+    0.049885711074,
+   -0.063641804457,
+   -0.134385573864,
+   -0.129504549503,
+    0.116876983643,
+    0.034869593382,
+    0.110995888710,
+    0.209363675117,
+    0.089506244659,
+    0.032240226865,
+   -0.035387760401,
+   -0.011358998716,
+   -0.217154717445,
+   -0.075535118580,
+    0.154909908772,
+    0.035121211410,
+   -0.009927383065,
+   -0.017504146695,
+   -0.041967305541,
+    0.001003832649,
+    0.014955335855,
+    0.081758105755,
+    0.026359021664,
+   -0.056244474649,
+   -0.145741295815,
+    0.020014302433,
+    0.017129588127,
+   -0.074675554037,
+   -0.048099777102,
+    0.079832047224,
+    0.177468204498,
+   -0.064336484671,
+    0.007799343765,
+    0.058800733089,
+   -0.015487244725,
+    0.040828344226,
+   -0.009191709757,
+    0.097432601452,
+   -0.064271640778,
+    0.010111794621,
+   -0.088703465462,
+    0.031145793200,
+    0.060152173042,
+   -0.041082245111,
+    0.033332934976,
+   -0.040413409472,
+    0.004355887696,
+    0.058080500364,
+    0.014798428118,
+   -0.067486256361,
+   -0.059195333719,
+   -0.058406758308,
+   -0.032816353440,
+   -0.179800736904,
+   -0.068667584658,
+   -0.086956882477,
+    0.275450372696,
+    0.033261230588,
+   -0.055281239748,
+    0.144350075722,
+   -0.202899336815,
+   -0.041189083457,
+    0.045112118125,
+   -0.064310503006,
+    0.123022067547,
+    0.007392913103,
+    0.059860253334,
+   -0.088910865784,
+    0.246307659149,
+    0.136940467358,
+    0.148684620857,
+   -0.067826855183,
+    0.044850471616,
+    0.021945165098,
+   -0.050006192923,
+   -0.015575663745,
+   -0.011492988467,
+   -0.215618276596,
+    0.015057675540,
+   -0.014546294510,
+   -0.091270244122,
+   -0.014081606269,
+   -0.005267200246,
+    0.008527950943,
+    0.084022140503,
+    0.050226074457,
+   -0.086916053295,
+    0.009611618519,
+    0.078965556622,
+   -0.269776344299,
+    0.251430296898,
+   -0.083879828453,
+    0.058131092787,
+    0.093344569206,
+   -0.009404690564,
+   -0.180945825577,
+    0.026505178213,
+    0.178118884563,
+   -0.036366626620,
+    0.119481074810,
+   -0.046089172363,
+    0.012725484371,
+    0.105508100986,
+    0.139382028580,
+   -0.145310318470,
+    0.066300112009,
+   -0.155467641354,
+   -0.186421251297,
+    0.136932229996,
+    0.091225856543,
+   -0.057187086344,
+    0.073608285189,
+    0.008684530854,
+   -0.131529307365,
+   -0.020294201374,
+    0.129804944992,
+    0.146946787834,
+   -0.069120013714,
+   -0.056884109974,
+   -0.069945359230,
+   -0.075904464722,
+    0.096382999420,
+   -0.052887690067,
+   -0.023378646374,
+   -0.020363801718,
+    0.052989178896,
+    0.097394061089,
+   -0.095514106750,
+    0.114465987682,
+    0.149921727180,
+   -0.145940053463,
+    0.116530537605,
+   -0.002146193571,
+   -0.033402639627,
+    0.088303333521,
+    0.014771881700,
+    0.033233019710,
+   -0.034135624766,
+   -0.053486305475,
+    0.064908540249,
+    0.001977766119,
+    0.061249178648,
+   -0.045406821370,
+    0.009912858903,
+    0.121469497681,
+    0.103848683834};
+
+float fftfilt_rrrf_data_h23x256_y[] = {
+   -0.007013864682,
+   -0.001458375313,
+    0.002285512965,
+    0.013784144279,
+   -0.008222504569,
+   -0.001932445802,
+    0.008048223230,
+    0.028296439689,
+    0.006779838852,
+   -0.027075112618,
+   -0.038021355416,
+   -0.008207405447,
+    0.022727377225,
+    0.003661135507,
+   -0.025140469834,
+   -0.012236287908,
+    0.016448242015,
+   -0.022041432338,
+    0.003765875876,
+   -0.086743626880,
+    0.008804356573,
+   -0.012282203678,
+    0.096545010469,
+   -0.016051310432,
+    0.025673500424,
+    0.031954762710,
+    0.010291226924,
+   -0.004617541860,
+   -0.078915357449,
+    0.016347541321,
+   -0.078243227224,
+    0.053416005824,
+   -0.073942376885,
+    0.075168320113,
+   -0.105443090203,
+    0.032685403487,
+   -0.054354778278,
+   -0.017947992570,
+    0.028325854650,
+   -0.066017207222,
+    0.127152882603,
+   -0.129786378633,
+    0.153392128443,
+   -0.101101386787,
+    0.089515971822,
+   -0.098153948006,
+   -0.018623281322,
+   -0.007210919422,
+   -0.052842605245,
+    0.032639231668,
+   -0.084346314422,
+    0.075150470719,
+   -0.077797755022,
+    0.090483672403,
+   -0.028648123276,
+   -0.018159951111,
+    0.048359402647,
+   -0.037666024522,
+    0.028960325781,
+   -0.033371992052,
+    0.026921999056,
+   -0.009095421681,
+   -0.003055504078,
+    0.008517976100,
+   -0.047205023338,
+    0.015219099504,
+   -0.029899324803,
+    0.037376834246,
+   -0.058818185973,
+    0.016202491134,
+    0.027713179518,
+    0.010360717592,
+    0.018872350014,
+   -0.007238271158,
+   -0.026777182477,
+   -0.042586692131,
+   -0.005396798853,
+   -0.053278897967,
+    0.003600267191,
+    0.021901335336,
+   -0.013091061698,
+   -0.047316523688,
+    0.018443222297,
+   -0.025150770998,
+    0.036826636675,
+    0.013809067598,
+    0.036530318086,
+   -0.025825991720,
+    0.047029879857,
+   -0.018694842890,
+   -0.024991556586,
+    0.021396439314,
+   -0.100068124600,
+    0.002032355704,
+   -0.058647750333,
+    0.054049505689,
+   -0.025341189676,
+    0.067615154602,
+   -0.044899284658,
+    0.021135756051,
+   -0.004151297683,
+   -0.010694122753,
+    0.009371387806,
+   -0.029794165824,
+    0.013390817044,
+   -0.059682815079,
+    0.111820810826,
+   -0.059861745262,
+    0.045667041385,
+   -0.016011228532,
+    0.043233291738,
+   -0.024093786781,
+    0.036936965515,
+   -0.007311481950,
+   -0.104248864495,
+    0.022282329467,
+   -0.092586037340,
+   -0.017084241199,
+   -0.042839250540,
+    0.036605764530,
+    0.013232172340,
+    0.027382131751,
+    0.061010646729,
+   -0.075041791947,
+   -0.032923246322,
+   -0.064505588738,
+   -0.062494604045,
+   -0.014118814560,
+    0.041292311729,
+    0.082239901043,
+   -0.005253566244,
+    0.046726579388,
+    0.010585548464,
+   -0.055116829572,
+   -0.057026861265,
+   -0.032618339372,
+   -0.034378961584,
+   -0.034963708479,
+    0.058675519175,
+    0.029871506017,
+   -0.031900560694,
+    0.023355935441,
+   -0.001213445200,
+    0.026491853482,
+   -0.027623272659,
+   -0.003191991347,
+   -0.004121011628,
+   -0.029001573940,
+    0.026187127755,
+   -0.056189942809,
+    0.041920449009,
+   -0.012905850771,
+    0.030518693114,
+    0.033991468359,
+    0.003919996873,
+    0.023741085525,
+   -0.055324780487,
+    0.059796004556,
+   -0.070732526849,
+    0.002634444280,
+    0.006125322170,
+    0.020783019394,
+   -0.008248981557,
+   -0.019686458368,
+    0.030194556929,
+   -0.000581311209,
+   -0.020507462528,
+   -0.044154988493,
+    0.008251496795,
+    0.032183839802,
+    0.053024468712,
+   -0.042724151114,
+    0.042587433931,
+   -0.075197608106,
+    0.019796200862,
+   -0.044058022099,
+   -0.054716564872,
+    0.056900772003,
+   -0.025187522741,
+    0.065664264024,
+   -0.010713761704,
+    0.060761984311,
+   -0.016047173875,
+    0.028406749588,
+   -0.011879920533,
+   -0.118533899059,
+    0.071371773260,
+   -0.012801056424,
+    0.063748099223,
+   -0.029818945809,
+    0.059673780884,
+    0.002981467618,
+    0.014900750827,
+    0.005863349123,
+   -0.039271388965,
+   -0.047804844624,
+   -0.005476117492,
+   -0.002941660427,
+   -0.084269578445,
+   -0.007723144927,
+   -0.019565778540,
+    0.026971812744,
+   -0.069210853637,
+    0.071362335146,
+   -0.034155620576,
+    0.049871029844,
+    0.102419559566,
+   -0.067458257439,
+    0.053173633928,
+   -0.083831072696,
+    0.070644801531,
+   -0.073425276208,
+    0.065129431849,
+   -0.109383680801,
+    0.028592225091,
+    0.086994019490,
+   -0.034044742467,
+    0.053289338054,
+   -0.050965593897,
+    0.008752040612,
+   -0.097246591796,
+    0.115066726542,
+   -0.074721917181,
+    0.057622211485,
+    0.035793970560,
+   -0.018555134605,
+   -0.047793260188,
+   -0.023063158221,
+   -0.035765778569,
+    0.000988835923,
+    0.043336509870,
+   -0.077241994534,
+    0.019293408578,
+    0.033162609969,
+    0.051320440605,
+   -0.043008262694,
+    0.032451189832,
+   -0.017267827022,
+   -0.043257260696,
+    0.050857662915,
+   -0.017938260516,
+   -0.027199631897,
+    0.002448747593,
+    0.004648939335,
+   -0.022984164695,
+   -0.052488307553,
+    0.014620583398,
+    0.060946098284,
+   -0.005975541627,
+    0.036790785823,
+   -0.003976400632,
+   -0.031985970920,
+    0.020296749161,
+    0.022385014193,
+   -0.041326326661,
+   -0.052361612870};
+
diff --git a/src/filter/tests/data/fftfilt_rrrf_data_h4x256.c b/src/filter/tests/data/fftfilt_rrrf_data_h4x256.c
new file mode 100644
index 0000000..20f65be
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_rrrf_data_h4x256.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_rrrf_data_h4x256.c: autotest fftfilt data
+//
+
+float fftfilt_rrrf_data_h4x256_h[] = {
+   -0.103940248489,
+   -0.125938880444,
+    0.019687046111,
+    0.038741669059};
+
+float fftfilt_rrrf_data_h4x256_x[] = {
+   -0.059766334295,
+   -0.066150742769,
+   -0.115301787853,
+    0.034305834770,
+   -0.116833734512,
+    0.037061965466,
+   -0.058715832233,
+    0.103076207638,
+    0.134518480301,
+    0.079880267382,
+    0.012277089804,
+    0.043612530828,
+    0.034223002195,
+    0.113585817814,
+    0.069119685888,
+   -0.130585312843,
+   -0.169095098972,
+    0.012008042634,
+   -0.133639252186,
+    0.173219811916,
+   -0.083090245724,
+    0.129079174995,
+    0.023044827580,
+    0.088738435507,
+    0.102692329884,
+    0.009038489312,
+    0.159331941605,
+    0.072815608978,
+    0.078159433603,
+    0.087989467382,
+   -0.150272154808,
+    0.144541347027,
+    0.013840167224,
+    0.008892998844,
+    0.118263459206,
+    0.026517456770,
+   -0.071624934673,
+   -0.036617729068,
+   -0.005045709386,
+   -0.113883030415,
+   -0.208834385872,
+    0.086749321222,
+   -0.013728235662,
+    0.043468075991,
+   -0.096142125130,
+    0.020740854740,
+   -0.034397336841,
+    0.085756975412,
+    0.007817015052,
+    0.026385569572,
+    0.146892285347,
+   -0.156527042389,
+    0.141406416893,
+   -0.023495438695,
+    0.049500951171,
+   -0.035764679313,
+    0.071550452709,
+   -0.003587320819,
+   -0.186498534679,
+   -0.080298507214,
+    0.134777379036,
+   -0.081320983171,
+    0.001440288033,
+   -0.026431754231,
+    0.076391905546,
+   -0.008682205528,
+   -0.218401527405,
+   -0.006006127596,
+   -0.150505733490,
+    0.031596601009,
+   -0.030527853966,
+    0.192572534084,
+   -0.008412856609,
+    0.041643416882,
+   -0.028573197126,
+    0.172620558739,
+    0.092417323589,
+    0.038117933273,
+   -0.115529227257,
+   -0.017371174693,
+   -0.128486943245,
+    0.049849176407,
+    0.064552265406,
+    0.094295257330,
+    0.016759039462,
+    0.093557018042,
+    0.020290662348,
+    0.005136798322,
+    0.054379999638,
+   -0.015200743079,
+    0.015589100122,
+    0.080561774969,
+   -0.027493861318,
+   -0.097876155376,
+   -0.090297585726,
+    0.048021733761,
+    0.026696670055,
+   -0.154493343830,
+    0.041202291846,
+   -0.082294452190,
+   -0.036641538143,
+   -0.046914353967,
+    0.022663275898,
+   -0.076259374619,
+   -0.156308090687,
+   -0.068274164200,
+   -0.126401603222,
+   -0.200392508507,
+    0.018104647100,
+   -0.017743445933,
+   -0.141112804413,
+   -0.159551227093,
+    0.075381636620,
+   -0.220814013481,
+    0.038408300281,
+   -0.004995152354,
+    0.010078751296,
+    0.040657266974,
+    0.056239426136,
+    0.085060149431,
+    0.068754714727,
+    0.072872400284,
+    0.106947481632,
+   -0.308546304703,
+   -0.069674599171,
+   -0.112623000145,
+   -0.063631707430,
+    0.078959625959,
+   -0.228999781609,
+   -0.113809764385,
+    0.038127812743,
+   -0.046605563164,
+   -0.048587369919,
+   -0.235435414314,
+   -0.217472290993,
+    0.053750389814,
+   -0.035415196419,
+   -0.067825865746,
+   -0.150451087952,
+    0.047033441067,
+    0.091101139784,
+   -0.194262254238,
+   -0.111800551414,
+   -0.285789370537,
+   -0.007608364522,
+   -0.149913394451,
+   -0.096925151348,
+   -0.305913662910,
+   -0.080984199047,
+   -0.036364766955,
+    0.092257493734,
+    0.099389094114,
+   -0.225461554527,
+   -0.044632428885,
+   -0.025108575821,
+   -0.014164578915,
+    0.001609388553,
+    0.104212951660,
+    0.016141586006,
+    0.131583070755,
+   -0.175425446033,
+   -0.006701639295,
+   -0.114346027374,
+   -0.035700270534,
+    0.021623027325,
+   -0.127881789207,
+    0.100705778599,
+    0.045099687576,
+   -0.037036603689,
+   -0.068620997667,
+    0.104958283901,
+   -0.153554320335,
+    0.094572162628,
+   -0.042882648110,
+   -0.064524829388,
+    0.009822543710,
+    0.113274049759,
+    0.060883563757,
+   -0.039188215137,
+    0.073277068138,
+   -0.110787749290,
+    0.098354798555,
+    0.109893596172,
+   -0.042616480589,
+   -0.014896343648,
+   -0.040605995059,
+    0.127049469948,
+    0.003995794803,
+   -0.061846542358,
+    0.016790096462,
+    0.079767954350,
+    0.026320463419,
+    0.049973765016,
+   -0.133794248104,
+   -0.014496140182,
+   -0.245779371262,
+    0.112647521496,
+   -0.046020454168,
+   -0.190287458897,
+    0.154702842236,
+   -0.087878227234,
+   -0.094459164143,
+   -0.189534711838,
+   -0.055773961544,
+    0.065852761269,
+   -0.046472328901,
+    0.110529434681,
+    0.073642146587,
+    0.123039674759,
+    0.047418674827,
+   -0.039671051502,
+   -0.026384806633,
+    0.050780695677,
+    0.001590615511,
+   -0.054047483206,
+   -0.081048285961,
+   -0.063029623032,
+   -0.083545958996,
+    0.075329506397,
+   -0.047838664055,
+    0.040128618479,
+   -0.104642879963,
+    0.093437254429,
+   -0.038486540318,
+   -0.082574462891,
+   -0.065856921673,
+   -0.071426254511,
+    0.019827936590,
+    0.133355295658,
+    0.094185638428,
+    0.077937924862,
+    0.060769236088,
+    0.125227236748,
+    0.128954041004,
+    0.119129931927,
+   -0.007088107616,
+    0.067930990458,
+   -0.097152686119,
+   -0.086086273193,
+    0.029765573144,
+   -0.162787938118,
+   -0.091519236565,
+   -0.078853517771,
+    0.116651356220,
+   -0.025571110845,
+   -0.151243126392,
+   -0.166459095478,
+    0.048026204109,
+   -0.046259659529,
+   -0.216182160378,
+   -0.018041458726,
+    0.014725986123,
+    0.130805134773,
+   -0.088644373417,
+   -0.039490658045,
+    0.067798143625};
+
+float fftfilt_rrrf_data_h4x256_y[] = {
+    0.006212127638,
+    0.014402629871,
+    0.019138824386,
+    0.007337460817,
+    0.002990547175,
+    0.007070076667,
+    0.000464349934,
+   -0.007115853714,
+   -0.026683285355,
+   -0.025489364937,
+   -0.004694499360,
+    0.000704816717,
+   -0.005713266142,
+   -0.014781907855,
+   -0.019125815998,
+    0.008430239467,
+    0.039782821314,
+    0.020154501439,
+    0.003990141650,
+   -0.007488755965,
+   -0.015344438902,
+   -0.004719450259,
+   -0.013576348764,
+   -0.012803601954,
+   -0.016395058243,
+   -0.011232627125,
+   -0.012239715093,
+   -0.023478145421,
+   -0.013807285781,
+   -0.011382649065,
+    0.008897756697,
+    0.008661723023,
+   -0.019191481858,
+   -0.005643557602,
+   -0.007540062613,
+   -0.016938930639,
+    0.006777922483,
+    0.017930194135,
+    0.004753295233,
+    0.008976717035,
+    0.034530632258,
+    0.014846123076,
+   -0.018021547028,
+   -0.009171918769,
+    0.007609260636,
+    0.010276125260,
+    0.000754459077,
+   -0.007898019473,
+   -0.011486286587,
+   -0.003371297493,
+   -0.015114727435,
+   -0.001407692146,
+    0.008929118567,
+   -0.012757146890,
+   -0.005466396146,
+    0.002499060105,
+   -0.002868533164,
+   -0.007424468314,
+    0.019859520897,
+    0.034535023664,
+   -0.007706674216,
+   -0.017327293951,
+    0.009634239964,
+    0.006186465510,
+   -0.007733563710,
+   -0.009182844461,
+    0.024274056965,
+    0.030918145186,
+    0.011763964249,
+    0.007090882408,
+   -0.004001868553,
+   -0.021380192871,
+   -0.022754833149,
+   -0.000660437013,
+    0.005020336999,
+   -0.013849839551,
+   -0.030294705862,
+   -0.013309506170,
+    0.015714639418,
+    0.020686016629,
+    0.014744994233,
+    0.006182383795,
+   -0.016190044652,
+   -0.021927128113,
+   -0.010415294145,
+   -0.007477696788,
+   -0.009908390960,
+   -0.000598168905,
+   -0.002275175069,
+   -0.004381364749,
+    0.001563619326,
+   -0.008529370506,
+   -0.007570139794,
+    0.015825789161,
+    0.024291791638,
+    0.003388535453,
+   -0.014392240260,
+    0.010143054713,
+    0.017560162996,
+    0.001357491276,
+    0.008998423176,
+    0.009466994781,
+   -0.000356873396,
+    0.002729071359,
+    0.024479354632,
+    0.026158390848,
+    0.015704925808,
+    0.029347970586,
+    0.018221877386,
+   -0.009277966443,
+    0.009494776448,
+    0.034707471003,
+    0.008793011874,
+    0.004849946576,
+    0.019119662287,
+   -0.005744666206,
+   -0.008217061474,
+   -0.004105571223,
+   -0.010960970310,
+   -0.014733014296,
+   -0.015176443566,
+   -0.012379874872,
+   -0.015644666925,
+    0.022699848187,
+    0.051028649748,
+    0.018549762248,
+    0.007472224160,
+   -0.005109901608,
+    0.008242283953,
+    0.039758674555,
+    0.008920758417,
+   -0.011069992067,
+    0.007261079530,
+    0.031149863671,
+    0.049492477364,
+    0.015284004318,
+   -0.016490747527,
+    0.004142934403,
+    0.025564996273,
+    0.011351639261,
+   -0.020982038768,
+    0.003815714690,
+    0.039701414255,
+    0.043490014719,
+    0.027055742092,
+    0.006582535902,
+    0.017732605940,
+    0.040757274537,
+    0.039227876953,
+    0.004201243751,
+   -0.018455474336,
+   -0.025802710495,
+    0.011325024528,
+    0.038564368478,
+    0.007642577107,
+   -0.004979021836,
+   -0.000606857515,
+   -0.012286211537,
+   -0.015319298242,
+   -0.013595634758,
+    0.006017503669,
+    0.026005288293,
+    0.014373290385,
+    0.011183095605,
+   -0.000262218936,
+    0.005436096272,
+    0.004680511180,
+   -0.019050348257,
+   -0.004801965020,
+    0.016586221529,
+   -0.001249242642,
+   -0.000043659144,
+    0.008916431699,
+   -0.006409851067,
+    0.008020215359,
+    0.009924877896,
+   -0.015942421690,
+   -0.022900282471,
+   -0.000983801859,
+    0.002905926546,
+    0.003874104947,
+    0.003653855068,
+   -0.023151268515,
+   -0.011766095752,
+    0.012889310865,
+    0.009515094775,
+   -0.010035978512,
+   -0.017792313224,
+    0.006853203808,
+    0.011044491421,
+   -0.011468398919,
+   -0.014847133247,
+   -0.006288183493,
+    0.011221483243,
+    0.020360164728,
+    0.026674050122,
+    0.013775768815,
+   -0.014803599781,
+    0.018270084125,
+    0.011342883867,
+   -0.015878125160,
+    0.016558982776,
+    0.035859750030,
+    0.024402777719,
+   -0.007211526384,
+   -0.011903993248,
+   -0.006500113845,
+   -0.019937993274,
+   -0.021687601373,
+   -0.014692296619,
+    0.003426871553,
+    0.013438867168,
+   -0.000899212332,
+   -0.008616954586,
+    0.005394918949,
+    0.017229501936,
+    0.015756032882,
+    0.012932176847,
+   -0.001688896039,
+   -0.008601197014,
+    0.000100074788,
+    0.007799442483,
+    0.002403379977,
+   -0.008272542196,
+    0.011215225703,
+    0.020106749108,
+    0.012601329561,
+    0.002438821104,
+   -0.020315669734,
+   -0.028961114111,
+   -0.016568981922,
+   -0.009111140762,
+   -0.015486083309,
+   -0.024958681018,
+   -0.023803066262,
+   -0.006876114273,
+    0.001173115553,
+    0.006018669947,
+    0.022245884571,
+    0.008466878338,
+    0.007712934138,
+    0.027264733171,
+    0.017670238763,
+   -0.010302467061,
+   -0.017131074405,
+    0.018182248950,
+    0.040365038360,
+    0.012003618618,
+   -0.010376624909,
+    0.022792508105,
+    0.030050872208,
+   -0.005306666123,
+   -0.024180933161,
+   -0.007668779085,
+    0.018414117957,
+    0.001248916742};
+
diff --git a/src/filter/tests/data/fftfilt_rrrf_data_h7x256.c b/src/filter/tests/data/fftfilt_rrrf_data_h7x256.c
new file mode 100644
index 0000000..de15517
--- /dev/null
+++ b/src/filter/tests/data/fftfilt_rrrf_data_h7x256.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_rrrf_data_h7x256.c: autotest fftfilt data
+//
+
+float fftfilt_rrrf_data_h7x256_h[] = {
+    0.140616559982,
+   -0.011992717534,
+   -0.003013938852,
+   -0.011477991939,
+   -0.112187731266,
+   -0.093354654312,
+    0.074616163969};
+
+float fftfilt_rrrf_data_h7x256_x[] = {
+   -0.198634433746,
+   -0.276704359055,
+   -0.007436756045,
+   -0.029181697965,
+   -0.130730390549,
+    0.145140337944,
+    0.093274253607,
+   -0.028276038170,
+   -0.026093566418,
+    0.183164799213,
+    0.053475326300,
+   -0.103025162220,
+    0.062650483847,
+   -0.188771390915,
+   -0.037113583088,
+    0.002068812773,
+   -0.008017702401,
+   -0.109960675240,
+   -0.077615725994,
+    0.026018035412,
+   -0.109660518169,
+   -0.170424306393,
+   -0.037413102388,
+    0.057296097279,
+   -0.017119997740,
+   -0.108221471310,
+   -0.035699757934,
+    0.091696447134,
+   -0.179859602451,
+    0.172208881378,
+    0.047351920605,
+    0.009508173168,
+    0.096329414845,
+    0.048371517658,
+   -0.020774090290,
+    0.017976391315,
+   -0.049352347851,
+   -0.049652296305,
+   -0.076197183132,
+   -0.059912049770,
+    0.049865084887,
+   -0.183295714855,
+    0.092628794909,
+   -0.026747608185,
+    0.013366179168,
+   -0.097838991880,
+    0.071832561493,
+   -0.057548421621,
+    0.150674271584,
+    0.204948568344,
+    0.068133813143,
+    0.134078955650,
+    0.147824907303,
+    0.023681402206,
+    0.074496239424,
+   -0.257152867317,
+   -0.046538749337,
+    0.077637636662,
+    0.244811058044,
+    0.205812954903,
+    0.076498079300,
+    0.050237774849,
+    0.058088797331,
+    0.058162277937,
+   -0.003535797447,
+    0.002892631851,
+   -0.204548168182,
+    0.005657804012,
+    0.103203463554,
+    0.142777621746,
+   -0.033477127552,
+    0.199837732315,
+   -0.027476757765,
+   -0.044394394755,
+   -0.060066252947,
+   -0.104752016068,
+   -0.097709661722,
+   -0.130706298351,
+    0.032775890827,
+   -0.018839289248,
+    0.050399136543,
+    0.028471961617,
+   -0.059291934967,
+    0.089571076632,
+   -0.172548770905,
+   -0.012040051073,
+    0.020502640307,
+   -0.123626136780,
+   -0.026196390390,
+   -0.020364558697,
+   -0.019754604995,
+   -0.057270252705,
+   -0.317011761665,
+   -0.067777991295,
+   -0.081966227293,
+    0.051007336378,
+    0.005001089722,
+   -0.059572064877,
+   -0.246018052101,
+    0.084007954597,
+    0.111875116825,
+   -0.105674767494,
+   -0.093867790699,
+    0.124696004391,
+   -0.114728307724,
+    0.145655667782,
+    0.087596571445,
+    0.031920900941,
+   -0.048701521754,
+   -0.036635088921,
+    0.078258597851,
+   -0.174296259880,
+    0.001777398773,
+   -0.011556741595,
+    0.016063742340,
+   -0.122294688225,
+   -0.062106817961,
+   -0.083234494925,
+   -0.093048858643,
+   -0.132832098007,
+   -0.130951321125,
+   -0.075790464878,
+    0.107826757431,
+    0.065403455496,
+   -0.021866463125,
+    0.076470321417,
+   -0.115176773071,
+   -0.177813613415,
+    0.007404306531,
+    0.034460625052,
+    0.091559529305,
+    0.025584965944,
+   -0.233118081093,
+    0.016783872247,
+    0.118527340889,
+    0.002443835139,
+   -0.066102498770,
+    0.099924069643,
+    0.110413837433,
+    0.012530249357,
+    0.039682874084,
+   -0.102561068535,
+   -0.070982843637,
+   -0.224049115181,
+   -0.046074494720,
+   -0.053951752186,
+   -0.024024085701,
+   -0.020918877423,
+   -0.049386295676,
+   -0.124434947968,
+    0.201082754135,
+   -0.082364642620,
+   -0.069723898172,
+   -0.077210056782,
+   -0.078793448210,
+    0.021326577663,
+   -0.029980614781,
+    0.095146137476,
+    0.002052852511,
+    0.121688973904,
+    0.231916475296,
+    0.069326949120,
+   -0.106731951237,
+    0.110438179970,
+    0.032615479827,
+   -0.039900502563,
+    0.014169178903,
+    0.135481488705,
+    0.150449824333,
+   -0.034293845296,
+   -0.125741934776,
+    0.137323367596,
+   -0.073314142227,
+   -0.256729173660,
+   -0.066909456253,
+   -0.035936996341,
+   -0.142958796024,
+   -0.140863418579,
+   -0.085215097666,
+   -0.066673463583,
+    0.035038682818,
+   -0.087599039078,
+   -0.024130514264,
+    0.142523336411,
+    0.051653099060,
+    0.186094677448,
+   -0.169976019859,
+    0.064971995354,
+    0.007105557621,
+   -0.083290928602,
+   -0.028115510941,
+   -0.012801435590,
+   -0.001878900640,
+   -0.234911966324,
+   -0.035020393133,
+    0.021636278927,
+    0.075562286377,
+    0.018644069135,
+   -0.081580567360,
+   -0.153186178207,
+    0.046364918351,
+   -0.053498959541,
+   -0.063731038570,
+   -0.168667137623,
+   -0.226871848106,
+    0.215677857399,
+    0.166028416157,
+   -0.072063165903,
+   -0.145152175426,
+    0.010212332010,
+   -0.216380715370,
+   -0.124420678616,
+    0.041640162468,
+   -0.019694516063,
+   -0.128491973877,
+   -0.063744425774,
+    0.036548280716,
+   -0.048697099090,
+    0.177138936520,
+    0.101957058907,
+    0.200418853760,
+   -0.044115161896,
+   -0.081440508366,
+    0.134256255627,
+    0.034881916642,
+   -0.016943645477,
+    0.056465440989,
+   -0.041392689943,
+    0.020602276921,
+    0.080228483677,
+    0.113952696323,
+    0.142240214348,
+    0.065034645796,
+    0.036655876040,
+   -0.010240229964,
+    0.075093430281,
+    0.116919255257,
+   -0.045885655284,
+   -0.016456852853,
+   -0.016743476689,
+    0.059479594231,
+   -0.071723133326,
+    0.004458283633,
+    0.177911329269,
+   -0.114906835556,
+   -0.055673146248,
+   -0.121907567978,
+    0.113892269135,
+   -0.031124478579,
+    0.026398393512,
+    0.121518325806,
+   -0.129203355312,
+    0.013991312683,
+   -0.047266671062,
+   -0.165797197819,
+    0.099366217852};
+
+float fftfilt_rrrf_data_h7x256_y[] = {
+   -0.027931290767,
+   -0.036527048446,
+    0.002871378203,
+   -0.000900348620,
+    0.007449880861,
+    0.071736541733,
+    0.023948851395,
+   -0.020710146276,
+    0.011558567506,
+    0.018827456694,
+   -0.028042283949,
+   -0.010086450022,
+    0.020308556524,
+   -0.047821747894,
+   -0.027006769038,
+    0.020818889038,
+    0.007705728630,
+   -0.007304606298,
+    0.016866205381,
+   -0.005839934759,
+   -0.016298973644,
+   -0.008597800781,
+    0.015189446945,
+    0.006399836981,
+    0.003056670822,
+    0.016542526688,
+    0.007596592949,
+   -0.001806787749,
+   -0.031260991935,
+    0.044520387756,
+    0.016913430809,
+   -0.012715020434,
+    0.020266125790,
+    0.009387467377,
+   -0.038709991484,
+    0.008887802636,
+   -0.015809331270,
+   -0.019915827876,
+   -0.005174084562,
+   -0.003262775658,
+    0.010838392511,
+   -0.013798289642,
+    0.025261915119,
+    0.005237958158,
+   -0.001661751700,
+   -0.003462670878,
+    0.021981362166,
+   -0.028135663622,
+    0.030693053673,
+    0.034093858112,
+    0.009401595589,
+    0.008139424059,
+    0.010449388245,
+   -0.040981872086,
+   -0.007327036366,
+   -0.044931617778,
+   -0.027973650948,
+    0.004942790683,
+    0.037047060381,
+    0.049966777634,
+    0.041445787750,
+   -0.020836545762,
+   -0.033212295434,
+   -0.033698438557,
+   -0.011475301474,
+    0.002186574921,
+   -0.034953267018,
+   -0.004918868576,
+    0.014328874613,
+    0.025515369387,
+    0.015618207275,
+    0.045563709091,
+   -0.035167131438,
+   -0.031361372075,
+   -0.011997409010,
+   -0.022200908255,
+   -0.029863902791,
+    0.006254205122,
+    0.016506100233,
+    0.012520079323,
+    0.024973348930,
+    0.019048886905,
+   -0.007380278502,
+    0.001942907036,
+   -0.026935332102,
+   -0.008518025534,
+    0.010273786799,
+   -0.018002092298,
+    0.004447210446,
+    0.021730271212,
+   -0.015086741356,
+    0.003602761453,
+   -0.027587176784,
+   -0.009823822714,
+   -0.006937488555,
+    0.018748089942,
+    0.040553778014,
+    0.025275375143,
+   -0.042611521568,
+    0.011757676443,
+    0.004710422831,
+   -0.003608358999,
+    0.020301233199,
+    0.026791678092,
+   -0.054882770430,
+    0.030238791621,
+    0.038228983560,
+   -0.008795472799,
+   -0.014940802385,
+   -0.001995096866,
+   -0.020761276696,
+   -0.025668476061,
+    0.011544716840,
+    0.009019018042,
+   -0.004600880195,
+   -0.007860348130,
+    0.014728952731,
+   -0.022649831750,
+   -0.011085775543,
+   -0.005240739986,
+    0.003997918140,
+   -0.001607881215,
+    0.031565581702,
+    0.027013196139,
+    0.016834464421,
+    0.020396729202,
+   -0.032590176622,
+   -0.046660538625,
+    0.007036097494,
+    0.004957313420,
+    0.018631105443,
+    0.038717489292,
+   -0.026583582426,
+   -0.013797276939,
+    0.003938127652,
+   -0.007299187180,
+    0.020722040319,
+    0.035264671435,
+   -0.017759711601,
+   -0.009191541583,
+    0.019981890768,
+   -0.021059713833,
+   -0.035662571010,
+   -0.035057540677,
+    0.000216222158,
+    0.003192531187,
+    0.020478284609,
+    0.022147450700,
+    0.014786561310,
+   -0.022930234264,
+    0.034450785115,
+   -0.012487537726,
+   -0.002293520691,
+    0.004929010876,
+   -0.023625633332,
+   -0.013839752803,
+    0.027167472950,
+    0.023604122988,
+    0.009838224295,
+    0.016346254731,
+    0.025546898715,
+    0.000292790611,
+   -0.029285103586,
+    0.007194347038,
+   -0.034437467230,
+   -0.025457756182,
+    0.023911766892,
+    0.021373937942,
+   -0.001986685444,
+    0.002474461915,
+   -0.014709692341,
+   -0.000304917342,
+   -0.039652662083,
+   -0.034280490433,
+    0.020849251951,
+   -0.008861922819,
+   -0.030500204522,
+    0.028675656081,
+    0.016552898220,
+   -0.015166145728,
+    0.022000859239,
+    0.014908498909,
+    0.010360380914,
+    0.025116858815,
+    0.002567172058,
+    0.026977540816,
+   -0.014425407319,
+   -0.010252117168,
+   -0.022304314803,
+   -0.025107189270,
+    0.001828790258,
+    0.021171226565,
+   -0.018615482629,
+   -0.019119842628,
+    0.009505383997,
+    0.002038013339,
+    0.012475689592,
+    0.027626671729,
+    0.013547534561,
+   -0.038171874263,
+   -0.004721419960,
+   -0.014212116960,
+    0.006348483281,
+    0.002868666481,
+   -0.026061180698,
+    0.024531926129,
+    0.038983308893,
+    0.010709672428,
+    0.013920220038,
+   -0.014113848161,
+   -0.084973755410,
+   -0.004587206797,
+    0.043282508352,
+    0.006617714415,
+   -0.004038115641,
+    0.027079484275,
+   -0.002684682004,
+   -0.016580593774,
+    0.045475009133,
+    0.029616879939,
+    0.019247518497,
+   -0.013652480208,
+   -0.025296757973,
+   -0.013920765792,
+   -0.014738586605,
+   -0.008423961018,
+    0.034706519850,
+   -0.017597737374,
+   -0.019105819963,
+    0.019173187919,
+    0.013324282832,
+    0.016264621044,
+    0.011940905269,
+   -0.011374704130,
+   -0.022444725917,
+   -0.010784037503,
+    0.003078174417,
+   -0.007533459622,
+   -0.000398654282,
+   -0.008094217007,
+   -0.011750423046,
+   -0.010723392199,
+    0.016353940211,
+    0.024488199896,
+   -0.023819390187,
+   -0.005793482451,
+   -0.007536630407,
+   -0.006763537688,
+   -0.008121110474,
+    0.035389300910,
+    0.025857426716,
+   -0.024898575974,
+   -0.013389203604,
+    0.000622597391,
+   -0.039725817044,
+    0.021063238241};
+
diff --git a/src/filter/tests/data/firdecim_cccf_data_M2h4x20.c b/src/filter/tests/data/firdecim_cccf_data_M2h4x20.c
new file mode 100644
index 0000000..e221ffd
--- /dev/null
+++ b/src/filter/tests/data/firdecim_cccf_data_M2h4x20.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_cccf_data_M2h4x20.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float complex firdecim_cccf_data_M2h4x20_h[] = {
+   -0.010020822840 +   0.161896349165*_Complex_I,
+   -0.160666600378 +  -0.002813281324*_Complex_I,
+    0.074990149666 +  -0.043630565869*_Complex_I,
+   -0.147489652317 +  -0.117556939153*_Complex_I};
+
+float complex firdecim_cccf_data_M2h4x20_x[] = {
+   -0.062219283216 +   0.083707729807*_Complex_I,
+    0.142772365864 +  -0.155087351763*_Complex_I,
+   -0.035707773590 +  -0.015572103950*_Complex_I,
+    0.031591519635 +  -0.047093660211*_Complex_I,
+   -0.037688362501 +  -0.218679837029*_Complex_I,
+   -0.138226220905 +   0.033491576346*_Complex_I,
+    0.021866054096 +  -0.113640931250*_Complex_I,
+    0.067925963478 +   0.054442249893*_Complex_I,
+    0.057780733571 +  -0.143139771263*_Complex_I,
+   -0.066200531759 +  -0.072426438814*_Complex_I,
+   -0.024130609496 +  -0.023984088453*_Complex_I,
+    0.021066337898 +   0.123433455812*_Complex_I,
+    0.036017867444 +   0.011208051823*_Complex_I,
+    0.151336512188 +  -0.046531046794*_Complex_I,
+   -0.073914188474 +  -0.026770749988*_Complex_I,
+   -0.035715003742 +  -0.020256147810*_Complex_I,
+   -0.126178961558 +   0.049437469177*_Complex_I,
+   -0.105599807940 +  -0.032488566671*_Complex_I,
+    0.008377427496 +  -0.015424316799*_Complex_I,
+   -0.115074264949 +   0.025508983239*_Complex_I};
+
+float complex firdecim_cccf_data_M2h4x20_y[] = {
+   -0.012928487438 +  -0.010911895131*_Complex_I,
+   -0.021509784691 +   0.027882703574*_Complex_I,
+   -0.012073246090 +   0.010047339289*_Complex_I,
+    0.017918504479 +  -0.011835742405*_Complex_I,
+    0.032840148626 +   0.003684533788*_Complex_I,
+    0.009026607516 +  -0.021113482694*_Complex_I,
+   -0.006819209614 +   0.003546692017*_Complex_I,
+   -0.004777468860 +  -0.026060577544*_Complex_I,
+   -0.035559582789 +  -0.027278788500*_Complex_I,
+    0.014869302818 +   0.023426450224*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_cccf_data_M3h7x30.c b/src/filter/tests/data/firdecim_cccf_data_M3h7x30.c
new file mode 100644
index 0000000..24f6042
--- /dev/null
+++ b/src/filter/tests/data/firdecim_cccf_data_M3h7x30.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_cccf_data_M3h7x30.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float complex firdecim_cccf_data_M3h7x30_h[] = {
+   -0.006640626991 +   0.118904206653*_Complex_I,
+   -0.083146000558 +  -0.275130943103*_Complex_I,
+    0.079550052655 +   0.061532677877*_Complex_I,
+    0.065868508847 +   0.151901113121*_Complex_I,
+    0.044201174511 +   0.012455933659*_Complex_I,
+    0.055782888955 +   0.071073047549*_Complex_I,
+   -0.102933394297 +  -0.076862023914*_Complex_I};
+
+float complex firdecim_cccf_data_M3h7x30_x[] = {
+    0.069053975334 +  -0.000060226964*_Complex_I,
+    0.052934817246 +   0.026246887839*_Complex_I,
+   -0.008314557685 +   0.123860251104*_Complex_I,
+    0.013641467876 +  -0.040765094693*_Complex_I,
+    0.054181459433 +   0.105277320749*_Complex_I,
+    0.068589348571 +  -0.094171049027*_Complex_I,
+   -0.186652190115 +  -0.131382418642*_Complex_I,
+   -0.166555782200 +   0.069458887805*_Complex_I,
+    0.041855583632 +  -0.063553427322*_Complex_I,
+   -0.100690278440 +  -0.092501422890*_Complex_I,
+    0.083259362864 +  -0.058212988803*_Complex_I,
+    0.105969639897 +  -0.053978459405*_Complex_I,
+   -0.056835014832 +  -0.111112267168*_Complex_I,
+    0.030289623782 +  -0.152387642602*_Complex_I,
+    0.065445845678 +  -0.096149080571*_Complex_I,
+    0.111690055005 +  -0.006676373032*_Complex_I,
+   -0.003193339244 +  -0.073652552575*_Complex_I,
+   -0.250738405778 +   0.063945369804*_Complex_I,
+    0.082690418566 +   0.008257821532*_Complex_I,
+   -0.012580995920 +  -0.092904421381*_Complex_I,
+   -0.042140374030 +  -0.035352352256*_Complex_I,
+   -0.102518977358 +  -0.014894177307*_Complex_I,
+   -0.080456903607 +  -0.102303385723*_Complex_I,
+    0.067967690554 +  -0.008961243903*_Complex_I,
+   -0.008051789902 +  -0.288191500473*_Complex_I,
+    0.052113077407 +   0.045119892331*_Complex_I,
+    0.026292838395 +  -0.234398783970*_Complex_I,
+    0.043314256874 +   0.060501473229*_Complex_I,
+   -0.063513872529 +  -0.087941912765*_Complex_I,
+   -0.065933510466 +   0.101592986516*_Complex_I};
+
+float complex firdecim_cccf_data_M3h7x30_y[] = {
+   -0.000451400453 +   0.008211208098*_Complex_I,
+    0.046679220394 +   0.009712412319*_Complex_I,
+   -0.017763427623 +  -0.015970470090*_Complex_I,
+   -0.023951776164 +  -0.049756621013*_Complex_I,
+    0.005079568893 +  -0.033963764074*_Complex_I,
+    0.010470512110 +  -0.004032766261*_Complex_I,
+    0.063479867902 +   0.089940072424*_Complex_I,
+   -0.013695440245 +  -0.005147235363*_Complex_I,
+    0.018210209849 +  -0.062011233592*_Complex_I,
+   -0.014235918784 +   0.002032286514*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_cccf_data_M4h13x40.c b/src/filter/tests/data/firdecim_cccf_data_M4h13x40.c
new file mode 100644
index 0000000..781fc3a
--- /dev/null
+++ b/src/filter/tests/data/firdecim_cccf_data_M4h13x40.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_cccf_data_M4h13x40.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float complex firdecim_cccf_data_M4h13x40_h[] = {
+   -0.002562542003 +  -0.154124323834*_Complex_I,
+   -0.023583498668 +   0.104733275459*_Complex_I,
+    0.022367426058 +  -0.120923475320*_Complex_I,
+   -0.132621145254 +  -0.178735883634*_Complex_I,
+   -0.168112709779 +   0.034712075125*_Complex_I,
+    0.057724042805 +   0.008285579385*_Complex_I,
+   -0.047739385482 +   0.142477282131*_Complex_I,
+    0.067869682601 +   0.097364083820*_Complex_I,
+    0.059426213111 +   0.468175487351*_Complex_I,
+    0.006482342651 +  -0.003418897658*_Complex_I,
+   -0.004209444510 +  -0.083952075513*_Complex_I,
+    0.011361818419 +  -0.070937481458*_Complex_I,
+    0.094086356591 +  -0.103332276920*_Complex_I};
+
+float complex firdecim_cccf_data_M4h13x40_x[] = {
+   -0.024194231006 +  -0.138925187631*_Complex_I,
+    0.203856156849 +  -0.080078393335*_Complex_I,
+   -0.068961728054 +   0.201298529466*_Complex_I,
+   -0.051809449253 +   0.046347258011*_Complex_I,
+   -0.149903686678 +  -0.212870505681*_Complex_I,
+    0.029836494947 +  -0.048639942573*_Complex_I,
+    0.037533241642 +  -0.206079736569*_Complex_I,
+    0.051371569347 +  -0.039092683420*_Complex_I,
+   -0.090350663461 +   0.047672789069*_Complex_I,
+   -0.156001075276 +   0.148239674685*_Complex_I,
+    0.151211606918 +  -0.081137753153*_Complex_I,
+    0.014410288934 +  -0.091766222951*_Complex_I,
+    0.128650669104 +  -0.109361279278*_Complex_I,
+   -0.014454025380 +  -0.017884354391*_Complex_I,
+   -0.027777545721 +   0.014889779800*_Complex_I,
+   -0.102482421038 +   0.153280094094*_Complex_I,
+    0.054231085410 +  -0.036048916993*_Complex_I,
+   -0.097774825326 +   0.071640661283*_Complex_I,
+    0.016069399348 +  -0.072264453423*_Complex_I,
+   -0.020335452514 +  -0.102705025246*_Complex_I,
+   -0.001428129588 +   0.058319600411*_Complex_I,
+    0.050490262274 +   0.068382581850*_Complex_I,
+    0.037146918761 +  -0.014078089282*_Complex_I,
+    0.131825401964 +  -0.052212284043*_Complex_I,
+   -0.021358931115 +   0.034076612121*_Complex_I,
+    0.007883745543 +  -0.045225145346*_Complex_I,
+    0.066077716475 +  -0.011748246722*_Complex_I,
+    0.020987892322 +   0.148992296687*_Complex_I,
+    0.046814707826 +  -0.080934756420*_Complex_I,
+    0.092525039263 +   0.149989890332*_Complex_I,
+    0.063112963675 +  -0.084774798237*_Complex_I,
+   -0.019463083228 +   0.059819778564*_Complex_I,
+   -0.026299503842 +  -0.005553793151*_Complex_I,
+    0.135276140520 +  -0.011249885171*_Complex_I,
+   -0.055971818305 +  -0.061539002690*_Complex_I,
+    0.041589677363 +   0.218745686754*_Complex_I,
+   -0.154541640002 +   0.176344746584*_Complex_I,
+   -0.093756310243 +   0.038311802363*_Complex_I,
+    0.075234897328 +   0.138257000400*_Complex_I,
+   -0.097930194448 +  -0.076486145877*_Complex_I};
+
+float complex firdecim_cccf_data_M4h13x40_y[] = {
+   -0.021349751874 +   0.004084921123*_Complex_I,
+   -0.045716199011 +   0.026670647628*_Complex_I,
+    0.062793796326 +   0.020298966406*_Complex_I,
+    0.171764657602 +  -0.129296598427*_Complex_I,
+   -0.140510571307 +  -0.025391053246*_Complex_I,
+    0.078880319262 +   0.080737389437*_Complex_I,
+    0.026829099733 +  -0.015427110029*_Complex_I,
+   -0.058971359279 +  -0.004947680884*_Complex_I,
+   -0.009091338962 +  -0.022653029705*_Complex_I,
+    0.017223757651 +   0.057653177650*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_cccf_data_M5h23x50.c b/src/filter/tests/data/firdecim_cccf_data_M5h23x50.c
new file mode 100644
index 0000000..37f1724
--- /dev/null
+++ b/src/filter/tests/data/firdecim_cccf_data_M5h23x50.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_cccf_data_M5h23x50.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float complex firdecim_cccf_data_M5h23x50_h[] = {
+    0.105720400924 +   0.057437069192*_Complex_I,
+   -0.028209083196 +  -0.004313504575*_Complex_I,
+   -0.072874560021 +   0.042888303750*_Complex_I,
+   -0.085512857926 +   0.020535437583*_Complex_I,
+   -0.100456191359 +   0.103253822583*_Complex_I,
+    0.021202177209 +   0.035197979854*_Complex_I,
+   -0.181412547496 +  -0.065280832354*_Complex_I,
+   -0.089735489990 +  -0.035169413725*_Complex_I,
+    0.091350151539 +   0.024388575583*_Complex_I,
+    0.115625359788 +  -0.072145623875*_Complex_I,
+   -0.051852629863 +  -0.004567932396*_Complex_I,
+    0.009526328844 +  -0.150554350114*_Complex_I,
+    0.111472545344 +   0.100620569627*_Complex_I,
+    0.072732704374 +  -0.028216475627*_Complex_I,
+   -0.041864189108 +   0.010588416120*_Complex_I,
+    0.050598746124 +  -0.003896637950*_Complex_I,
+   -0.118777133157 +   0.070610240227*_Complex_I,
+   -0.016104655770 +   0.004328632751*_Complex_I,
+    0.057125599610 +   0.100422001833*_Complex_I,
+    0.154824278727 +  -0.040505357012*_Complex_I,
+    0.019585396789 +   0.108879628006*_Complex_I,
+    0.052374918456 +  -0.044109539660*_Complex_I,
+    0.013011637257 +  -0.076892387668*_Complex_I};
+
+float complex firdecim_cccf_data_M5h23x50_x[] = {
+   -0.045706059285 +  -0.026925548272*_Complex_I,
+    0.074640818692 +   0.020008220501*_Complex_I,
+   -0.041286610033 +  -0.136882935267*_Complex_I,
+    0.009816935182 +  -0.078646240225*_Complex_I,
+    0.089010380616 +  -0.149190270600*_Complex_I,
+    0.083497556154 +  -0.043557829025*_Complex_I,
+    0.182551809767 +  -0.029983057525*_Complex_I,
+    0.156019880201 +   0.005770028753*_Complex_I,
+    0.061786541086 +  -0.064921440736*_Complex_I,
+    0.243601293648 +   0.048234370911*_Complex_I,
+   -0.038392905410 +   0.026874189965*_Complex_I,
+    0.098986088062 +  -0.051036508330*_Complex_I,
+    0.145073787044 +   0.133408806994*_Complex_I,
+   -0.012274691331 +  -0.001003240627*_Complex_I,
+   -0.047977493556 +   0.090394581449*_Complex_I,
+    0.067234934373 +  -0.080081570574*_Complex_I,
+   -0.066259448979 +   0.124203939183*_Complex_I,
+   -0.107414957417 +  -0.017911178063*_Complex_I,
+   -0.035170912051 +  -0.011180346308*_Complex_I,
+    0.087106081663 +   0.108450957223*_Complex_I,
+   -0.071732901242 +  -0.054445545216*_Complex_I,
+   -0.027106150089 +   0.035717288781*_Complex_I,
+    0.008967266976 +  -0.124029666988*_Complex_I,
+   -0.027842462142 +   0.016454828477*_Complex_I,
+    0.035515455355 +   0.042131771080*_Complex_I,
+    0.012844114428 +   0.106011213637*_Complex_I,
+    0.081631987459 +   0.062426819327*_Complex_I,
+    0.130657890629 +  -0.002602075127*_Complex_I,
+   -0.094246564430 +  -0.033523978166*_Complex_I,
+   -0.053733329944 +   0.108359828647*_Complex_I,
+   -0.005664508011 +   0.021469330759*_Complex_I,
+   -0.014929153350 +   0.017941698073*_Complex_I,
+    0.020461861960 +  -0.100937164479*_Complex_I,
+    0.001605545133 +   0.153966424471*_Complex_I,
+   -0.042286310689 +  -0.074770813879*_Complex_I,
+    0.171684109561 +   0.076554260668*_Complex_I,
+    0.175238329863 +   0.075856527238*_Complex_I,
+    0.060276983038 +  -0.075910005194*_Complex_I,
+    0.009291016375 +  -0.012009065640*_Complex_I,
+    0.165631852407 +  -0.056358816657*_Complex_I,
+    0.022403278379 +  -0.053838057764*_Complex_I,
+    0.012578774881 +  -0.028070849755*_Complex_I,
+    0.111896323878 +  -0.067413483086*_Complex_I,
+    0.193577629326 +   0.022715731850*_Complex_I,
+   -0.024575467522 +  -0.001589364951*_Complex_I,
+    0.049219675943 +  -0.168094546969*_Complex_I,
+   -0.052368096464 +   0.158302243383*_Complex_I,
+    0.029964399158 +  -0.012022357247*_Complex_I,
+    0.126368745124 +   0.002307518720*_Complex_I,
+    0.014850257877 +   0.006988676981*_Complex_I};
+
+float complex firdecim_cccf_data_M5h23x50_y[] = {
+   -0.003285538333 +  -0.005471801848*_Complex_I,
+    0.007588480326 +   0.024542591101*_Complex_I,
+   -0.057029837651 +   0.045181309977*_Complex_I,
+   -0.052759259646 +  -0.065315303001*_Complex_I,
+    0.096950584899 +  -0.077249583961*_Complex_I,
+    0.000722131494 +   0.033051673725*_Complex_I,
+    0.010653757445 +  -0.053755642413*_Complex_I,
+    0.037166575400 +   0.001442464438*_Complex_I,
+    0.015685676896 +   0.008079773457*_Complex_I,
+   -0.025642858413 +   0.014281592109*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_crcf_data_M2h4x20.c b/src/filter/tests/data/firdecim_crcf_data_M2h4x20.c
new file mode 100644
index 0000000..d4a64d2
--- /dev/null
+++ b/src/filter/tests/data/firdecim_crcf_data_M2h4x20.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_crcf_data_M2h4x20.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float firdecim_crcf_data_M2h4x20_h[] = {
+    0.031235809409,
+   -0.216021656042,
+   -0.123125426658,
+    0.014724190886};
+
+float complex firdecim_crcf_data_M2h4x20_x[] = {
+   -0.121360059018 +  -0.040946328225*_Complex_I,
+   -0.048183917318 +  -0.090893384286*_Complex_I,
+    0.050431539967 +  -0.039477767743*_Complex_I,
+   -0.092873851447 +  -0.016290157417*_Complex_I,
+    0.067927836995 +   0.034766499357*_Complex_I,
+   -0.062738323035 +  -0.070124552362*_Complex_I,
+   -0.089685845706 +   0.085063483262*_Complex_I,
+    0.036320846361 +   0.192249836854*_Complex_I,
+   -0.097540647206 +  -0.078675689987*_Complex_I,
+    0.062450869382 +   0.091837474249*_Complex_I,
+   -0.062450045345 +  -0.029363766863*_Complex_I,
+   -0.030191356564 +   0.094898259922*_Complex_I,
+    0.130002759179 +  -0.034950407712*_Complex_I,
+    0.030652983878 +   0.015398314893*_Complex_I,
+   -0.028490871597 +   0.130293581637*_Complex_I,
+    0.114396819866 +  -0.058213046230*_Complex_I,
+   -0.090569184709 +  -0.080687388201*_Complex_I,
+   -0.002440656567 +   0.093805498501*_Complex_I,
+    0.181551012573 +   0.015635958339*_Complex_I,
+    0.081568978667 +   0.051967040191*_Complex_I};
+
+float complex firdecim_crcf_data_M2h4x20_y[] = {
+   -0.003790779673 +  -0.001278991704*_Complex_I,
+    0.026926548630 +   0.023443353500*_Complex_I,
+    0.015265670091 +   0.008127371986*_Complex_I,
+    0.001020290227 +   0.013284949227*_Complex_I,
+   -0.000774013472 +  -0.055493631953*_Complex_I,
+   -0.002896889061 +  -0.008238383106*_Complex_I,
+    0.019191455251 +  -0.016624124713*_Complex_I,
+   -0.023962832263 +   0.006444039955*_Complex_I,
+   -0.023581901158 +  -0.005760782332*_Complex_I,
+    0.019033897622 +  -0.010698088224*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_crcf_data_M3h7x30.c b/src/filter/tests/data/firdecim_crcf_data_M3h7x30.c
new file mode 100644
index 0000000..39ef6d6
--- /dev/null
+++ b/src/filter/tests/data/firdecim_crcf_data_M3h7x30.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_crcf_data_M3h7x30.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float firdecim_crcf_data_M3h7x30_h[] = {
+   -0.067137698321,
+    0.054412252965,
+   -0.182856503708,
+   -0.054617127674,
+   -0.009870765494,
+   -0.026617010237,
+    0.011725565067};
+
+float complex firdecim_crcf_data_M3h7x30_x[] = {
+   -0.088297122268 +   0.055665944654*_Complex_I,
+   -0.016432697221 +   0.148455554729*_Complex_I,
+    0.140757522457 +   0.073543902343*_Complex_I,
+    0.063460474341 +   0.032803595179*_Complex_I,
+   -0.010344707877 +  -0.062988102121*_Complex_I,
+    0.039529652200 +  -0.054529222034*_Complex_I,
+    0.119571808389 +  -0.098769254813*_Complex_I,
+    0.014024766895 +  -0.018877394409*_Complex_I,
+    0.075151006910 +  -0.136112320307*_Complex_I,
+    0.167466666363 +   0.037009456983*_Complex_I,
+   -0.006066715214 +   0.052827861682*_Complex_I,
+    0.048336909246 +  -0.157938756841*_Complex_I,
+    0.022542224668 +   0.123894877722*_Complex_I,
+   -0.109162126495 +  -0.080322251267*_Complex_I,
+    0.060401040885 +  -0.013549823453*_Complex_I,
+   -0.065450090580 +   0.134084060551*_Complex_I,
+    0.080552956850 +  -0.129409082705*_Complex_I,
+   -0.054479718117 +  -0.047011198746*_Complex_I,
+    0.186369060662 +  -0.125658475188*_Complex_I,
+   -0.191665411586 +  -0.030921221765*_Complex_I,
+    0.063129882080 +   0.100182552563*_Complex_I,
+    0.065520569371 +  -0.107290345371*_Complex_I,
+    0.146258184364 +  -0.041133067761*_Complex_I,
+    0.048045169203 +   0.040835459595*_Complex_I,
+    0.006679291241 +   0.024648912594*_Complex_I,
+    0.038593404874 +  -0.254488576034*_Complex_I,
+   -0.176346189965 +  -0.001937288661*_Complex_I,
+   -0.171141933206 +  -0.024023949503*_Complex_I,
+    0.216869453975 +   0.054039885226*_Complex_I,
+    0.165978694869 +  -0.091956658785*_Complex_I};
+
+float complex firdecim_crcf_data_M3h7x30_y[] = {
+    0.005928065557 +  -0.003737283399*_Complex_I,
+    0.011225704498 +  -0.028387046159*_Complex_I,
+   -0.009438639157 +   0.009365565615*_Complex_I,
+   -0.015620112302 +   0.001554880391*_Complex_I,
+   -0.006633571221 +  -0.027905221566*_Complex_I,
+    0.028058535196 +  -0.001231894454*_Complex_I,
+   -0.024058004824 +   0.025942864482*_Complex_I,
+    0.021530701506 +   0.030652367492*_Complex_I,
+   -0.021493289366 +   0.012309152915*_Complex_I,
+   -0.009126109290 +   0.046129860989*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_crcf_data_M4h13x40.c b/src/filter/tests/data/firdecim_crcf_data_M4h13x40.c
new file mode 100644
index 0000000..3e4f867
--- /dev/null
+++ b/src/filter/tests/data/firdecim_crcf_data_M4h13x40.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_crcf_data_M4h13x40.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float firdecim_crcf_data_M4h13x40_h[] = {
+   -0.017634479811,
+    0.078644323992,
+    0.010062975901,
+    0.002767448419,
+   -0.012666798459,
+    0.072474187801,
+    0.138641311321,
+    0.096895207192,
+    0.005424669952,
+   -0.095669120702,
+    0.115011527536,
+    0.045576979285,
+   -0.032175124851};
+
+float complex firdecim_crcf_data_M4h13x40_x[] = {
+   -0.112962750920 +  -0.174396579717*_Complex_I,
+    0.036553706748 +  -0.051072169083*_Complex_I,
+    0.022797265387 +   0.042572931849*_Complex_I,
+    0.008765340086 +   0.005707935306*_Complex_I,
+    0.106501635473 +  -0.087740150292*_Complex_I,
+   -0.021475975887 +   0.041386858553*_Complex_I,
+   -0.140639585019 +   0.075707242551*_Complex_I,
+   -0.170100822245 +   0.024811268189*_Complex_I,
+    0.077084240435 +   0.022103264096*_Complex_I,
+   -0.034031018357 +   0.008016611966*_Complex_I,
+    0.042615847754 +  -0.119291859183*_Complex_I,
+    0.004089922446 +   0.056510429034*_Complex_I,
+    0.145879823424 +   0.102239060582*_Complex_I,
+    0.131816984427 +  -0.085645947698*_Complex_I,
+   -0.118561232326 +   0.150671338454*_Complex_I,
+   -0.032855598424 +   0.084885603624*_Complex_I,
+   -0.029028910328 +   0.095508389514*_Complex_I,
+    0.162474927440 +  -0.002144805045*_Complex_I,
+   -0.066178053685 +  -0.010931400887*_Complex_I,
+   -0.164098083762 +  -0.014709199385*_Complex_I,
+   -0.042421547313 +  -0.165166102873*_Complex_I,
+   -0.143613555453 +  -0.020483960094*_Complex_I,
+    0.074273402726 +   0.026166548871*_Complex_I,
+    0.039546848666 +   0.060817356400*_Complex_I,
+   -0.283154531123 +  -0.068141024882*_Complex_I,
+   -0.171382954008 +   0.157655929741*_Complex_I,
+    0.100887524169 +  -0.227019774244*_Complex_I,
+   -0.134270894830 +  -0.095892207602*_Complex_I,
+    0.061905242843 +  -0.098697402618*_Complex_I,
+    0.070002196277 +   0.099716632447*_Complex_I,
+   -0.111631217405 +  -0.033672309864*_Complex_I,
+   -0.042616607537 +  -0.111931082366*_Complex_I,
+   -0.004433766647 +  -0.118350103892*_Complex_I,
+   -0.029922536821 +   0.288101612082*_Complex_I,
+    0.112347437422 +   0.012061775097*_Complex_I,
+    0.121008925435 +   0.081465180909*_Complex_I,
+   -0.022480348602 +   0.032724942501*_Complex_I,
+   -0.057932648610 +   0.146711091659*_Complex_I,
+    0.091043924052 +  -0.118961124120*_Complex_I,
+   -0.060921349688 +  -0.036527277081*_Complex_I};
+
+float complex firdecim_crcf_data_M4h13x40_y[] = {
+    0.001992039351 +   0.003075392964*_Complex_I,
+    0.000572688534 +   0.004492265743*_Complex_I,
+   -0.010835528800 +   0.003970601587*_Complex_I,
+   -0.029138197244 +   0.024645444782*_Complex_I,
+   -0.005729955161 +   0.004471983412*_Complex_I,
+   -0.016781980003 +   0.000255273787*_Complex_I,
+   -0.005670110185 +   0.008026992933*_Complex_I,
+    0.007921191245 +  -0.004652046754*_Complex_I,
+   -0.019294993452 +  -0.027474217637*_Complex_I,
+    0.025318608692 +  -0.002964377998*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_crcf_data_M5h23x50.c b/src/filter/tests/data/firdecim_crcf_data_M5h23x50.c
new file mode 100644
index 0000000..6d00281
--- /dev/null
+++ b/src/filter/tests/data/firdecim_crcf_data_M5h23x50.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_crcf_data_M5h23x50.c: autotest firdecim data
+//
+
+#include <complex.h>
+
+float firdecim_crcf_data_M5h23x50_h[] = {
+    0.002280218687,
+    0.030259168999,
+    0.042315352513,
+    0.023335125905,
+    0.187877503860,
+   -0.125992843938,
+    0.011263591538,
+    0.085275043313,
+   -0.000421564647,
+   -0.061084018425,
+   -0.061455696212,
+   -0.019398732964,
+    0.054084065833,
+   -0.139019814895,
+   -0.006954271771,
+    0.066739670458,
+    0.060380686235,
+   -0.030379569378,
+    0.062093917175,
+   -0.108597003256,
+   -0.026039280890,
+   -0.061515114912,
+    0.038253839794};
+
+float complex firdecim_crcf_data_M5h23x50_x[] = {
+   -0.182183709645 +  -0.105810548671*_Complex_I,
+   -0.119835664561 +   0.052187599214*_Complex_I,
+   -0.080413311444 +  -0.007654126449*_Complex_I,
+    0.097423911392 +   0.139994199967*_Complex_I,
+    0.227363312459 +   0.148762801146*_Complex_I,
+   -0.035703226649 +   0.016200983468*_Complex_I,
+    0.112245547440 +  -0.012233367286*_Complex_I,
+    0.048367738935 +  -0.084918284786*_Complex_I,
+    0.014605478600 +  -0.020568647750*_Complex_I,
+   -0.097889245398 +   0.168068267634*_Complex_I,
+    0.049395920002 +   0.049056756925*_Complex_I,
+    0.077571659414 +  -0.016603490981*_Complex_I,
+   -0.184216448023 +   0.038356590636*_Complex_I,
+   -0.001919304196 +   0.044652884985*_Complex_I,
+   -0.137958101403 +   0.039437297572*_Complex_I,
+   -0.014249713446 +  -0.012873748415*_Complex_I,
+    0.080615138558 +   0.010722054400*_Complex_I,
+    0.163287071747 +   0.157849778887*_Complex_I,
+    0.014760203683 +   0.017904920717*_Complex_I,
+   -0.023896962586 +  -0.167898683088*_Complex_I,
+   -0.103145190697 +  -0.052205104745*_Complex_I,
+    0.060639622041 +   0.000262344853*_Complex_I,
+   -0.103152379379 +   0.094308639066*_Complex_I,
+    0.087724817642 +   0.138912843443*_Complex_I,
+    0.118286611822 +  -0.059245080063*_Complex_I,
+    0.072397607400 +   0.055570352604*_Complex_I,
+   -0.120828136006 +  -0.053193491502*_Complex_I,
+    0.017568748356 +   0.062747031152*_Complex_I,
+   -0.173562312809 +   0.024099395003*_Complex_I,
+    0.029150371763 +   0.142074561483*_Complex_I,
+    0.025859464873 +   0.055295550529*_Complex_I,
+    0.033521057962 +   0.015990392806*_Complex_I,
+   -0.153314777647 +   0.029921210372*_Complex_I,
+   -0.083426782876 +   0.085727794977*_Complex_I,
+   -0.082913880724 +   0.118554053112*_Complex_I,
+   -0.000295266978 +   0.046816730776*_Complex_I,
+   -0.091635622603 +  -0.074886539618*_Complex_I,
+    0.187307078036 +   0.079033670911*_Complex_I,
+    0.016483267210 +   0.214994027303*_Complex_I,
+    0.182841584926 +  -0.060579741754*_Complex_I,
+    0.098946994244 +   0.014088074701*_Complex_I,
+    0.103226498009 +  -0.107310190377*_Complex_I,
+   -0.102346312686 +  -0.043967864658*_Complex_I,
+    0.003356469485 +   0.080001448898*_Complex_I,
+    0.015380876414 +  -0.033386312073*_Complex_I,
+    0.113435756454 +   0.076011600452*_Complex_I,
+    0.222681717597 +  -0.048179807062*_Complex_I,
+    0.213685265876 +   0.154819154890*_Complex_I,
+   -0.145263809029 +   0.080441672618*_Complex_I,
+   -0.065703128284 +   0.056608305388*_Complex_I};
+
+float complex firdecim_crcf_data_M5h23x50_y[] = {
+   -0.000415418699 +  -0.000241271190*_Complex_I,
+    0.009483904304 +   0.033419922261*_Complex_I,
+    0.053902979681 +   0.014937609575*_Complex_I,
+   -0.004064163171 +  -0.007100167361*_Complex_I,
+    0.027338357030 +   0.015054094814*_Complex_I,
+    0.032378938500 +   0.015062971531*_Complex_I,
+   -0.072114751319 +  -0.021601257083*_Complex_I,
+   -0.000363929634 +  -0.002841410024*_Complex_I,
+   -0.028488318939 +  -0.002263371809*_Complex_I,
+    0.049749343516 +   0.025688357259*_Complex_I};
+
diff --git a/src/filter/tests/data/firdecim_rrrf_data_M2h4x20.c b/src/filter/tests/data/firdecim_rrrf_data_M2h4x20.c
new file mode 100644
index 0000000..c14c9c7
--- /dev/null
+++ b/src/filter/tests/data/firdecim_rrrf_data_M2h4x20.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_rrrf_data_M2h4x20.c: autotest firdecim data
+//
+
+float firdecim_rrrf_data_M2h4x20_h[] = {
+    0.070782309250,
+   -0.114245586699,
+    0.008215220382,
+   -0.116718567037};
+
+float firdecim_rrrf_data_M2h4x20_x[] = {
+    0.098311328839,
+   -0.051405684088,
+   -0.097846553674,
+    0.070744555579,
+   -0.092569362020,
+    0.125743342560,
+   -0.015936561579,
+    0.092440100110,
+   -0.007046555447,
+   -0.011409188742,
+    0.065796277764,
+    0.121624056526,
+    0.001291608019,
+   -0.103251042400,
+   -0.067174551288,
+   -0.069262060525,
+    0.038107684731,
+   -0.091432498375,
+    0.094339814736,
+    0.004969612518};
+
+float firdecim_rrrf_data_M2h4x20_y[] = {
+    0.006958702881,
+   -0.000245283250,
+   -0.009438359685,
+   -0.024511329437,
+   -0.025867150062,
+   -0.004886703086,
+   -0.011931393613,
+   -0.007143968697,
+    0.022109694637,
+    0.025520580844};
+
diff --git a/src/filter/tests/data/firdecim_rrrf_data_M3h7x30.c b/src/filter/tests/data/firdecim_rrrf_data_M3h7x30.c
new file mode 100644
index 0000000..28a52cf
--- /dev/null
+++ b/src/filter/tests/data/firdecim_rrrf_data_M3h7x30.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_rrrf_data_M3h7x30.c: autotest firdecim data
+//
+
+float firdecim_rrrf_data_M3h7x30_h[] = {
+   -0.074705344704,
+    0.020538592337,
+   -0.020061495576,
+    0.096703320953,
+    0.100160540368,
+    0.097166994081,
+    0.083888510566};
+
+float firdecim_rrrf_data_M3h7x30_x[] = {
+   -0.077089593357,
+    0.032375219181,
+    0.141488397385,
+    0.034405467968,
+   -0.138389144841,
+    0.154646756715,
+    0.086331166522,
+    0.041332691838,
+    0.091791117954,
+   -0.065105465675,
+    0.075745848821,
+    0.012475523017,
+   -0.107768670583,
+    0.233864631918,
+   -0.110944430251,
+   -0.060308141718,
+   -0.251948448147,
+    0.153274083486,
+   -0.114102376762,
+   -0.183548035449,
+   -0.152968693185,
+    0.049660839590,
+   -0.056844064619,
+   -0.153304122180,
+    0.018994945270,
+    0.000201893929,
+   -0.055497296237,
+    0.101173781919,
+   -0.050453255942,
+   -0.019320758294};
+
+float firdecim_rrrf_data_M3h7x30_y[] = {
+    0.005759004645,
+   -0.007768614835,
+    0.013680669269,
+    0.019197170461,
+    0.020943849985,
+   -0.009738613503,
+    0.013465672357,
+   -0.028391740957,
+   -0.041353046024,
+   -0.023577651344};
+
diff --git a/src/filter/tests/data/firdecim_rrrf_data_M4h13x40.c b/src/filter/tests/data/firdecim_rrrf_data_M4h13x40.c
new file mode 100644
index 0000000..d3dde49
--- /dev/null
+++ b/src/filter/tests/data/firdecim_rrrf_data_M4h13x40.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_rrrf_data_M4h13x40.c: autotest firdecim data
+//
+
+float firdecim_rrrf_data_M4h13x40_h[] = {
+   -0.050178519704,
+    0.099634819705,
+    0.087767256514,
+    0.031474342328,
+   -0.161835107291,
+    0.031896126939,
+   -0.240761793751,
+   -0.044460468897,
+    0.019656280155,
+    0.150308997913,
+   -0.145260671043,
+    0.044691633752,
+   -0.228340355042};
+
+float firdecim_rrrf_data_M4h13x40_x[] = {
+   -0.006034368030,
+   -0.171592529075,
+   -0.063780807965,
+    0.013676314399,
+    0.116628260211,
+    0.119863895134,
+    0.111602959230,
+    0.130154286756,
+    0.038035316610,
+    0.059453633879,
+   -0.109240225614,
+    0.077742393809,
+   -0.021666946122,
+    0.067622802572,
+   -0.058137116614,
+   -0.069171140610,
+    0.067330056503,
+    0.087258333681,
+    0.042012179512,
+   -0.025556605587,
+   -0.111286144010,
+   -0.040760608600,
+    0.127330790275,
+    0.033399307914,
+   -0.005634951089,
+    0.043243872564,
+    0.038979737383,
+   -0.045742865035,
+   -0.193432344744,
+   -0.020574800494,
+   -0.065306698326,
+   -0.101479446471,
+   -0.016230721248,
+    0.000392179194,
+   -0.065936759641,
+    0.032965659365,
+    0.057788436776,
+   -0.268236198963,
+   -0.077821704788,
+   -0.125842807499};
+
+float firdecim_rrrf_data_M4h13x40_y[] = {
+    0.000302795655,
+   -0.014511652270,
+    0.029055193091,
+   -0.025764218622,
+   -0.000775391175,
+    0.028459415706,
+    0.024044484393,
+   -0.040542156850,
+    0.012864586577,
+   -0.002482554609};
+
diff --git a/src/filter/tests/data/firdecim_rrrf_data_M5h23x50.c b/src/filter/tests/data/firdecim_rrrf_data_M5h23x50.c
new file mode 100644
index 0000000..11341d7
--- /dev/null
+++ b/src/filter/tests/data/firdecim_rrrf_data_M5h23x50.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_rrrf_data_M5h23x50.c: autotest firdecim data
+//
+
+float firdecim_rrrf_data_M5h23x50_h[] = {
+   -0.067636988306,
+   -0.129830871935,
+   -0.096852198413,
+   -0.101084148415,
+   -0.099274002866,
+    0.068432593779,
+   -0.123467175510,
+    0.053608743344,
+    0.062001100783,
+   -0.186969419240,
+    0.089108369240,
+   -0.007475824870,
+    0.163590779858,
+    0.097601917788,
+    0.092009543577,
+   -0.041838135753,
+   -0.056136168702,
+    0.003032887982,
+    0.009882398914,
+    0.074984250657,
+   -0.019855593061,
+   -0.060394654408,
+    0.023157699589};
+
+float firdecim_rrrf_data_M5h23x50_x[] = {
+   -0.053013073944,
+   -0.096057779578,
+    0.089544324704,
+   -0.000543553496,
+    0.162822533187,
+   -0.074585191755,
+    0.104404924404,
+   -0.029558379775,
+   -0.007349400311,
+   -0.194774278732,
+   -0.170758029656,
+    0.045377876421,
+    0.080652365875,
+    0.072939196850,
+   -0.089654082233,
+   -0.103982998990,
+    0.163030711963,
+   -0.024228077817,
+    0.131281994357,
+    0.016327074888,
+   -0.055411828614,
+    0.043034256150,
+    0.018076404143,
+    0.097676360852,
+    0.137184094723,
+    0.082482387627,
+    0.056634950389,
+    0.027836125209,
+   -0.014908882821,
+    0.162327107907,
+   -0.153015761693,
+   -0.020845168829,
+   -0.001125342955,
+   -0.044310410921,
+   -0.053090705118,
+   -0.024619825348,
+    0.039550843685,
+   -0.206275854122,
+    0.034696692856,
+   -0.153816675012,
+   -0.028473257841,
+    0.054242064784,
+    0.197473069156,
+    0.002233912029,
+    0.021458220407,
+    0.122982229244,
+    0.127458372904,
+   -0.040153514587,
+   -0.055390576800,
+    0.126488480454};
+
+float firdecim_rrrf_data_M5h23x50_y[] = {
+    0.003585644662,
+   -0.019185323038,
+    0.023723637517,
+   -0.016266117610,
+   -0.039970055370,
+   -0.037092249176,
+    0.027140256398,
+    0.025837751181,
+    0.028127341349,
+   -0.054916331965};
+
diff --git a/src/filter/tests/data/firfilt_cccf_data_h13x32.c b/src/filter/tests/data/firfilt_cccf_data_h13x32.c
new file mode 100644
index 0000000..b7d011f
--- /dev/null
+++ b/src/filter/tests/data/firfilt_cccf_data_h13x32.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_cccf_data_h13x32.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float complex firfilt_cccf_data_h13x32_h[] = {
+   -0.016900612660 +  -0.094711890552*_Complex_I,
+    0.021604417271 +   0.142776429157*_Complex_I,
+   -0.115236149764 +  -0.169153952067*_Complex_I,
+   -0.104650665737 +  -0.070706448696*_Complex_I,
+   -0.324134992109 +   0.000276600538*_Complex_I,
+    0.084579923351 +   0.050862911890*_Complex_I,
+    0.037182962083 +  -0.060208798591*_Complex_I,
+   -0.025070759196 +  -0.102300651911*_Complex_I,
+   -0.172106098520 +   0.036477087826*_Complex_I,
+   -0.104230620945 +  -0.026070285900*_Complex_I,
+   -0.098231380216 +   0.164187739422*_Complex_I,
+    0.073260028098 +   0.102435049392*_Complex_I,
+   -0.064585416482 +   0.016491998619*_Complex_I};
+
+float complex firfilt_cccf_data_h13x32_x[] = {
+   -0.230394652772 +  -0.084902186709*_Complex_I,
+    0.116521954181 +  -0.075921100646*_Complex_I,
+   -0.037505572140 +   0.050295775262*_Complex_I,
+    0.011196490896 +  -0.111587364031*_Complex_I,
+   -0.136496843877 +   0.079379261628*_Complex_I,
+    0.119267886716 +  -0.106802817114*_Complex_I,
+   -0.019101973377 +  -0.012951763335*_Complex_I,
+    0.013025947353 +  -0.027697311008*_Complex_I,
+   -0.019147300547 +   0.101327659435*_Complex_I,
+    0.099708276687 +  -0.005787000260*_Complex_I,
+    0.068401822498 +   0.064073386055*_Complex_I,
+   -0.037396159261 +  -0.075336873450*_Complex_I,
+   -0.100404642212 +  -0.010670539381*_Complex_I,
+    0.158140877685 +  -0.091346399746*_Complex_I,
+   -0.014860736865 +  -0.063687261938*_Complex_I,
+    0.080260238653 +  -0.095670107832*_Complex_I,
+   -0.039374218095 +  -0.044034420137*_Complex_I,
+   -0.064080642555 +  -0.070686889846*_Complex_I,
+    0.001854749241 +   0.115365376524*_Complex_I,
+    0.010746506918 +  -0.241675580817*_Complex_I,
+    0.000549090314 +  -0.105646197984*_Complex_I,
+    0.076651038231 +   0.139227636964*_Complex_I,
+    0.142025555338 +  -0.030479656574*_Complex_I,
+    0.052085787214 +   0.049670506263*_Complex_I,
+    0.072171078854 +   0.026786779778*_Complex_I,
+   -0.025753470644 +   0.135280754343*_Complex_I,
+   -0.021573256842 +  -0.151434524956*_Complex_I,
+    0.075816838810 +  -0.117216580293*_Complex_I,
+    0.072760555750 +  -0.032730991216*_Complex_I,
+   -0.066907752810 +  -0.020500064860*_Complex_I,
+   -0.166213598571 +   0.030726287248*_Complex_I,
+   -0.276903984324 +  -0.003789664941*_Complex_I};
+
+float complex firfilt_cccf_data_h13x32_y[] = {
+   -0.004147435830 +   0.023256012109*_Complex_I,
+   -0.002015434559 +  -0.044482089545*_Complex_I,
+    0.030942859967 +   0.066454518780*_Complex_I,
+   -0.026911288885 +   0.010771317579*_Complex_I,
+    0.095968947613 +   0.038484837745*_Complex_I,
+   -0.092014317271 +  -0.013169924277*_Complex_I,
+    0.049198135608 +   0.035484075721*_Complex_I,
+   -0.025796455461 +   0.044082591823*_Complex_I,
+    0.078672781715 +  -0.025486364457*_Complex_I,
+   -0.072129355236 +   0.058152098733*_Complex_I,
+    0.060708464475 +  -0.020499489129*_Complex_I,
+   -0.012897457718 +   0.010369404477*_Complex_I,
+    0.040569721031 +  -0.076402320248*_Complex_I,
+   -0.061438066081 +   0.022914256728*_Complex_I,
+    0.010560216826 +   0.008397165450*_Complex_I,
+   -0.007466249710 +   0.045405657448*_Complex_I,
+    0.045052264644 +  -0.014977016558*_Complex_I,
+   -0.103883227654 +   0.025733650083*_Complex_I,
+   -0.020931229354 +   0.011790200727*_Complex_I,
+   -0.082554523156 +   0.082257052415*_Complex_I,
+    0.062290900829 +   0.006126149167*_Complex_I,
+   -0.001939383449 +   0.054104380393*_Complex_I,
+   -0.080894136635 +  -0.026061525625*_Complex_I,
+   -0.014425095108 +   0.144945090491*_Complex_I,
+    0.027136649299 +   0.013953073572*_Complex_I,
+   -0.004380661218 +  -0.036741472069*_Complex_I,
+   -0.088435984052 +  -0.001543185349*_Complex_I,
+    0.051743211037 +  -0.012397485596*_Complex_I,
+   -0.019549545189 +   0.010425517862*_Complex_I,
+   -0.009114204900 +   0.008932656001*_Complex_I,
+   -0.003773949758 +   0.033245440352*_Complex_I,
+   -0.062498938791 +   0.037655501652*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_cccf_data_h23x64.c b/src/filter/tests/data/firfilt_cccf_data_h23x64.c
new file mode 100644
index 0000000..252be57
--- /dev/null
+++ b/src/filter/tests/data/firfilt_cccf_data_h23x64.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_cccf_data_h23x64.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float complex firfilt_cccf_data_h23x64_h[] = {
+    0.007386483162 +  -0.009424072453*_Complex_I,
+    0.050423187035 +   0.150124405632*_Complex_I,
+    0.243542351374 +   0.080326589069*_Complex_I,
+   -0.024338280301 +  -0.114090910866*_Complex_I,
+    0.196460953569 +  -0.124970764904*_Complex_I,
+   -0.105561981596 +   0.041404720111*_Complex_I,
+   -0.101639566392 +  -0.046297689433*_Complex_I,
+   -0.163657198362 +   0.005526168199*_Complex_I,
+   -0.112922732770 +   0.063835893419*_Complex_I,
+    0.101174309677 +  -0.014946487353*_Complex_I,
+    0.022730956655 +  -0.120281555357*_Complex_I,
+    0.098219187560 +  -0.193182817593*_Complex_I,
+   -0.104212145985 +   0.050487269587*_Complex_I,
+   -0.111765574417 +   0.198335922799*_Complex_I,
+   -0.176342404298 +   0.020342954856*_Complex_I,
+   -0.008161867550 +   0.131912314088*_Complex_I,
+    0.002095296558 +   0.017356532465*_Complex_I,
+   -0.048192202598 +  -0.033184854044*_Complex_I,
+   -0.116973286896 +   0.160940600018*_Complex_I,
+   -0.117776822786 +   0.154916853064*_Complex_I,
+   -0.054689494037 +  -0.147596255159*_Complex_I,
+   -0.061473756719 +  -0.101802249138*_Complex_I,
+   -0.069996222196 +   0.068670445429*_Complex_I};
+
+float complex firfilt_cccf_data_h23x64_x[] = {
+   -0.078781393877 +   0.110995541416*_Complex_I,
+   -0.047869391092 +  -0.021406341819*_Complex_I,
+    0.037075730398 +   0.142733143372*_Complex_I,
+    0.030132814646 +  -0.067132734894*_Complex_I,
+    0.061282796666 +   0.010485822100*_Complex_I,
+    0.102675878253 +  -0.033081022766*_Complex_I,
+    0.033541370777 +   0.079278562425*_Complex_I,
+   -0.054593695346 +  -0.027854211664*_Complex_I,
+    0.145123800751 +  -0.056723330167*_Complex_I,
+    0.011046150157 +   0.040345229995*_Complex_I,
+   -0.035136035623 +   0.087660836105*_Complex_I,
+    0.016994675402 +   0.012775882535*_Complex_I,
+    0.044321746934 +  -0.049948987747*_Complex_I,
+   -0.028623515736 +   0.009984465451*_Complex_I,
+   -0.047556759717 +   0.018286483908*_Complex_I,
+   -0.006226807241 +   0.024241509652*_Complex_I,
+    0.064579293390 +  -0.055779520425*_Complex_I,
+    0.006724710050 +  -0.045321225291*_Complex_I,
+   -0.014197864403 +  -0.074364241862*_Complex_I,
+   -0.189692736681 +   0.036236471869*_Complex_I,
+   -0.043006387932 +   0.015254723104*_Complex_I,
+    0.183456865086 +  -0.046633999430*_Complex_I,
+   -0.096745178650 +  -0.157866894203*_Complex_I,
+    0.049851534577 +  -0.011841563370*_Complex_I,
+   -0.128605225270 +  -0.121500740667*_Complex_I,
+    0.208521246393 +   0.074928493344*_Complex_I,
+    0.015993704716 +  -0.142778722944*_Complex_I,
+    0.099274527811 +   0.252094407113*_Complex_I,
+    0.095595074594 +  -0.110420978049*_Complex_I,
+   -0.160033363989 +   0.021797330986*_Complex_I,
+   -0.033832678704 +   0.074483735094*_Complex_I,
+    0.063099121768 +   0.029430379768*_Complex_I,
+    0.073790479995 +   0.116859113625*_Complex_I,
+    0.029697732194 +  -0.064419571602*_Complex_I,
+   -0.121376933005 +  -0.068190026772*_Complex_I,
+    0.157696201880 +   0.013694270490*_Complex_I,
+    0.192086239328 +   0.177711736014*_Complex_I,
+    0.050443921971 +   0.194189638728*_Complex_I,
+    0.104425685379 +   0.145809752108*_Complex_I,
+    0.052724394018 +  -0.077432998652*_Complex_I,
+   -0.138821609433 +   0.064732623753*_Complex_I,
+   -0.091470669189 +   0.063059053083*_Complex_I,
+    0.126212251998 +   0.047314249385*_Complex_I,
+   -0.096047066646 +   0.177236809567*_Complex_I,
+    0.052301840404 +  -0.008778675600*_Complex_I,
+   -0.121968378742 +  -0.054938313986*_Complex_I,
+   -0.052829966951 +  -0.030310428105*_Complex_I,
+    0.018028043678 +  -0.025287719529*_Complex_I,
+   -0.027374857808 +   0.073286356627*_Complex_I,
+    0.097021248414 +   0.117164045551*_Complex_I,
+    0.041807049929 +  -0.087406811774*_Complex_I,
+   -0.016480216738 +   0.009889058359*_Complex_I,
+    0.025179492882 +   0.203064743411*_Complex_I,
+    0.157077633956 +  -0.037868143998*_Complex_I,
+    0.186492422521 +   0.007792322512*_Complex_I,
+   -0.058684153831 +   0.005148456049*_Complex_I,
+    0.043091823396 +   0.043031309641*_Complex_I,
+    0.070177096613 +  -0.053637529243*_Complex_I,
+   -0.026547396369 +  -0.080476576072*_Complex_I,
+   -0.001571308583 +  -0.035758437403*_Complex_I,
+   -0.015191595806 +  -0.121248560074*_Complex_I,
+   -0.150343511708 +  -0.218457418741*_Complex_I,
+    0.214202412315 +  -0.062090364159*_Complex_I,
+    0.051648310107 +   0.008540965007*_Complex_I};
+
+float complex firfilt_cccf_data_h23x64_y[] = {
+    0.000464112585 +   0.001562308262*_Complex_I,
+   -0.021190870009 +  -0.005937253959*_Complex_I,
+   -0.025683625328 +   0.013143026224*_Complex_I,
+   -0.015326079628 +   0.009211447989*_Complex_I,
+    0.006829901541 +   0.076012393531*_Complex_I,
+    0.021716854352 +  -0.026318353103*_Complex_I,
+    0.061035502538 +   0.035739634892*_Complex_I,
+    0.020352690891 +  -0.042409807844*_Complex_I,
+    0.023045428223 +  -0.028106571621*_Complex_I,
+    0.010032616072 +  -0.021662309111*_Complex_I,
+    0.022940764323 +   0.032595296512*_Complex_I,
+   -0.051515604616 +   0.041000933031*_Complex_I,
+    0.005746042283 +  -0.027665280631*_Complex_I,
+    0.029717785937 +   0.005867034288*_Complex_I,
+    0.009507729239 +  -0.065470886864*_Complex_I,
+   -0.055592736641 +  -0.039847345306*_Complex_I,
+   -0.019477634830 +  -0.054757658494*_Complex_I,
+   -0.001079490064 +   0.040551530555*_Complex_I,
+   -0.023105026049 +  -0.021489136993*_Complex_I,
+   -0.013772576463 +  -0.059289523063*_Complex_I,
+    0.010368064793 +  -0.051492201632*_Complex_I,
+   -0.052573934313 +   0.036913855369*_Complex_I,
+   -0.029463371581 +   0.017335195435*_Complex_I,
+   -0.002016229441 +   0.029445890489*_Complex_I,
+   -0.064174313079 +  -0.021020171587*_Complex_I,
+    0.046007178326 +  -0.038216963612*_Complex_I,
+   -0.047237084003 +  -0.018439493080*_Complex_I,
+    0.077901945535 +   0.026803231226*_Complex_I,
+   -0.160590610173 +  -0.048895121067*_Complex_I,
+    0.033009384953 +   0.122366292068*_Complex_I,
+    0.047500812021 +   0.030446168988*_Complex_I,
+    0.027755264333 +   0.023740335853*_Complex_I,
+   -0.029958186540 +  -0.099197879754*_Complex_I,
+   -0.093565182221 +   0.037118606810*_Complex_I,
+    0.037892348897 +   0.081665210582*_Complex_I,
+    0.017203419707 +  -0.049660120581*_Complex_I,
+    0.123469958964 +   0.075552201157*_Complex_I,
+    0.075598725500 +  -0.068066054148*_Complex_I,
+   -0.019888756884 +   0.076027214270*_Complex_I,
+   -0.016956893179 +   0.064628354823*_Complex_I,
+    0.054092135963 +   0.231143717249*_Complex_I,
+    0.107916203047 +  -0.024623113347*_Complex_I,
+   -0.067826976867 +  -0.080607084998*_Complex_I,
+   -0.084406829425 +   0.006461650420*_Complex_I,
+   -0.097403134020 +   0.026083051061*_Complex_I,
+   -0.174546809897 +   0.011876261770*_Complex_I,
+    0.073748943090 +  -0.055147402729*_Complex_I,
+    0.097549181238 +  -0.004423842043*_Complex_I,
+    0.077466051940 +  -0.102440912522*_Complex_I,
+   -0.159166842272 +  -0.050231066222*_Complex_I,
+   -0.169079556837 +   0.020999784115*_Complex_I,
+   -0.011606027825 +   0.070186046595*_Complex_I,
+    0.059157126631 +  -0.011799023310*_Complex_I,
+    0.026862671786 +  -0.020855448914*_Complex_I,
+   -0.068295073554 +   0.079282428595*_Complex_I,
+   -0.048389220558 +  -0.036671891516*_Complex_I,
+   -0.104950395650 +  -0.102233724778*_Complex_I,
+   -0.028973062537 +  -0.073361406548*_Complex_I,
+    0.093580906591 +  -0.063967765018*_Complex_I,
+    0.024718362523 +  -0.132740996962*_Complex_I,
+   -0.024359580020 +  -0.079781595584*_Complex_I,
+   -0.073363097948 +  -0.031874352056*_Complex_I,
+    0.007512286472 +  -0.110602957303*_Complex_I,
+    0.074317818667 +  -0.076288705193*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_cccf_data_h4x8.c b/src/filter/tests/data/firfilt_cccf_data_h4x8.c
new file mode 100644
index 0000000..d8b0980
--- /dev/null
+++ b/src/filter/tests/data/firfilt_cccf_data_h4x8.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_cccf_data_h4x8.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float complex firfilt_cccf_data_h4x8_h[] = {
+   -0.031084031859 +  -0.013523088141*_Complex_I,
+   -0.129175459526 +   0.067705781544*_Complex_I,
+    0.221460930668 +  -0.057515301525*_Complex_I,
+   -0.023734659272 +   0.094741117144*_Complex_I};
+
+float complex firfilt_cccf_data_h4x8_x[] = {
+    0.035529355081 +  -0.025645176804*_Complex_I,
+    0.010053327033 +   0.058182925297*_Complex_I,
+   -0.051911580279 +  -0.038455783791*_Complex_I,
+    0.087876912441 +  -0.008273223193*_Complex_I,
+   -0.195891631367 +   0.043381908184*_Complex_I,
+   -0.018068686759 +   0.096890714727*_Complex_I,
+    0.072440149482 +   0.119581756000*_Complex_I,
+   -0.070807340362 +   0.027693599784*_Complex_I};
+
+float complex firfilt_cccf_data_h4x8_y[] = {
+   -0.001451197592 +   0.000316688892*_Complex_I,
+   -0.002378879142 +   0.003773758321*_Complex_I,
+    0.002248990658 +  -0.012660658356*_Complex_I,
+    0.013625136077 +   0.016803422304*_Complex_I,
+   -0.023574746940 +   0.002359815677*_Complex_I,
+    0.048100008285 +  -0.032526172551*_Complex_I,
+   -0.047049781567 +   0.010960128197*_Complex_I,
+   -0.012767824065 +  -0.007537619160*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_cccf_data_h7x16.c b/src/filter/tests/data/firfilt_cccf_data_h7x16.c
new file mode 100644
index 0000000..913454a
--- /dev/null
+++ b/src/filter/tests/data/firfilt_cccf_data_h7x16.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_cccf_data_h7x16.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float complex firfilt_cccf_data_h7x16_h[] = {
+    0.059672454354 +  -0.020066091352*_Complex_I,
+   -0.074492656514 +  -0.047414131969*_Complex_I,
+    0.035667494507 +  -0.071294810193*_Complex_I,
+    0.039143057258 +   0.079656472813*_Complex_I,
+   -0.014290705994 +  -0.119186857470*_Complex_I,
+    0.072724115403 +   0.012655052298*_Complex_I,
+   -0.195972668164 +  -0.009791166658*_Complex_I};
+
+float complex firfilt_cccf_data_h7x16_x[] = {
+   -0.078420841238 +   0.087748743839*_Complex_I,
+   -0.108100101013 +   0.135812982907*_Complex_I,
+   -0.084609799993 +  -0.122999836505*_Complex_I,
+    0.004784051237 +  -0.110951409765*_Complex_I,
+   -0.051782714777 +  -0.055641082544*_Complex_I,
+   -0.152740278646 +   0.078038723137*_Complex_I,
+   -0.069927868237 +  -0.029365176615*_Complex_I,
+   -0.014589069109 +  -0.028292391657*_Complex_I,
+   -0.001773824861 +   0.092262042610*_Complex_I,
+    0.013547386638 +   0.007206123368*_Complex_I,
+    0.024099272780 +  -0.029680768546*_Complex_I,
+   -0.084253318229 +  -0.003754881467*_Complex_I,
+   -0.074747526125 +   0.016818234473*_Complex_I,
+   -0.018741410126 +   0.035601500906*_Complex_I,
+   -0.022799538279 +   0.052284827296*_Complex_I,
+   -0.019334058586 +   0.032106298627*_Complex_I};
+
+float complex firfilt_cccf_data_h7x16_y[] = {
+   -0.002918789759 +   0.006809782675*_Complex_I,
+    0.006276944689 +   0.007455059607*_Complex_I,
+    0.010434073117 +  -0.001912734754*_Complex_I,
+   -0.005702292584 +   0.016196656152*_Complex_I,
+   -0.025081177186 +   0.012200218722*_Complex_I,
+    0.003335423652 +   0.014801280201*_Complex_I,
+    0.006724005065 +   0.002747460191*_Complex_I,
+    0.009525148495 +  -0.023079851588*_Complex_I,
+   -0.004054704111 +   0.027058137376*_Complex_I,
+    0.008922503980 +   0.020765026017*_Complex_I,
+    0.003379220673 +   0.021621450005*_Complex_I,
+    0.008092396331 +  -0.009379092265*_Complex_I,
+    0.024410342880 +   0.008452609790*_Complex_I,
+    0.007947112966 +   0.022079470097*_Complex_I,
+   -0.003430027671 +  -0.018931432282*_Complex_I,
+    0.001572545942 +   0.003497349824*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_crcf_data_h13x32.c b/src/filter/tests/data/firfilt_crcf_data_h13x32.c
new file mode 100644
index 0000000..c0ac405
--- /dev/null
+++ b/src/filter/tests/data/firfilt_crcf_data_h13x32.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_crcf_data_h13x32.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float firfilt_crcf_data_h13x32_h[] = {
+    0.005113901796,
+    0.005286268339,
+   -0.119811540901,
+   -0.004501609093,
+   -0.065007372397,
+    0.048866593324,
+    0.013172470077,
+   -0.002661385800,
+    0.103490232103,
+   -0.080549511664,
+    0.044381828692,
+    0.059428974785,
+    0.036011050317};
+
+float complex firfilt_crcf_data_h13x32_x[] = {
+   -0.150478051517 +   0.011369787230*_Complex_I,
+   -0.011682362019 +  -0.131898855698*_Complex_I,
+    0.002241423344 +   0.056583759593*_Complex_I,
+   -0.059449335305 +   0.187622651747*_Complex_I,
+   -0.180436000741 +   0.005303426369*_Complex_I,
+    0.011260126047 +   0.058406411239*_Complex_I,
+    0.104607282644 +   0.079267822272*_Complex_I,
+    0.085374933204 +   0.051946754389*_Complex_I,
+   -0.022910313299 +   0.030414137098*_Complex_I,
+    0.088237668857 +  -0.149226301695*_Complex_I,
+   -0.077336181799 +   0.011213633150*_Complex_I,
+   -0.041671694767 +  -0.010591371244*_Complex_I,
+   -0.072827057153 +   0.029626684791*_Complex_I,
+   -0.031258311706 +  -0.032641520688*_Complex_I,
+   -0.008154356734 +   0.089595819254*_Complex_I,
+    0.008874945980 +   0.055052307581*_Complex_I,
+    0.021540988384 +   0.002343360972*_Complex_I,
+    0.031406241630 +  -0.041565525378*_Complex_I,
+   -0.186621079183 +  -0.055706611874*_Complex_I,
+   -0.204887932486 +   0.107546397697*_Complex_I,
+   -0.001299342939 +  -0.091725815207*_Complex_I,
+    0.098883395599 +  -0.042781095000*_Complex_I,
+   -0.148848628287 +   0.075959712533*_Complex_I,
+    0.168232372928 +   0.044377154144*_Complex_I,
+    0.162761143249 +  -0.111406681457*_Complex_I,
+   -0.069728838323 +   0.030195226651*_Complex_I,
+   -0.015037533839 +   0.032270195219*_Complex_I,
+    0.047319395233 +  -0.049920188058*_Complex_I,
+   -0.275633692987 +  -0.077201329969*_Complex_I,
+    0.260550021883 +  -0.026872750426*_Complex_I,
+    0.154250187563 +  -0.051859524539*_Complex_I,
+    0.085702710731 +  -0.034956856551*_Complex_I};
+
+float complex firfilt_crcf_data_h13x32_y[] = {
+   -0.000769529978 +   0.000058143975*_Complex_I,
+   -0.000855209812 +  -0.000614414049*_Complex_I,
+    0.017978713542 +  -0.001770120683*_Complex_I,
+    0.001784905863 +   0.017010423559*_Complex_I,
+    0.008329226648 +  -0.005905805446*_Complex_I,
+   -0.000377533572 +  -0.013277356194*_Complex_I,
+    0.019781654463 +  -0.010739936790*_Complex_I,
+    0.004673510867 +  -0.017536447645*_Complex_I,
+   -0.018937503016 +   0.001766778181*_Complex_I,
+   -0.009796095237 +  -0.022965903194*_Complex_I,
+   -0.011542534189 +   0.009647205778*_Complex_I,
+   -0.026694559597 +   0.028680204257*_Complex_I,
+   -0.004613286077 +  -0.018608161470*_Complex_I,
+    0.011559988327 +   0.025425767569*_Complex_I,
+    0.015974924004 +   0.005946996608*_Complex_I,
+   -0.007726490650 +   0.012377335870*_Complex_I,
+   -0.007714581040 +  -0.006039979551*_Complex_I,
+    0.018766419009 +  -0.012572800123*_Complex_I,
+   -0.012542031545 +   0.012437887461*_Complex_I,
+    0.000458385988 +   0.000571447349*_Complex_I,
+    0.016943660061 +   0.007409564688*_Complex_I,
+    0.024399758745 +  -0.020976831445*_Complex_I,
+    0.007946403220 +   0.025133322345*_Complex_I,
+   -0.012852674380 +  -0.005689128468*_Complex_I,
+    0.003313037804 +   0.000263249514*_Complex_I,
+   -0.027377402722 +  -0.004127607511*_Complex_I,
+   -0.026320368186 +   0.009134835527*_Complex_I,
+   -0.012297987932 +   0.013200852928*_Complex_I,
+    0.007101262388 +  -0.017149417134*_Complex_I,
+    0.000657385901 +   0.001192381502*_Complex_I,
+   -0.008080139857 +   0.018480645475*_Complex_I,
+   -0.007551765865 +   0.003627711775*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_crcf_data_h23x64.c b/src/filter/tests/data/firfilt_crcf_data_h23x64.c
new file mode 100644
index 0000000..e3691fb
--- /dev/null
+++ b/src/filter/tests/data/firfilt_crcf_data_h23x64.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_crcf_data_h23x64.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float firfilt_crcf_data_h23x64_h[] = {
+    0.158654410967,
+   -0.020397216842,
+   -0.054283334982,
+    0.077922310650,
+   -0.072938526203,
+   -0.018649195029,
+    0.037699516688,
+    0.039617662775,
+   -0.201106990060,
+   -0.045133363773,
+   -0.083275831491,
+   -0.080588772189,
+   -0.009860694810,
+    0.086105167459,
+    0.145475786114,
+   -0.015729607185,
+   -0.064199255334,
+   -0.041339777246,
+    0.031333251672,
+   -0.178929283974,
+   -0.144469434532,
+   -0.088642880661,
+    0.061210119166};
+
+float complex firfilt_crcf_data_h23x64_x[] = {
+   -0.061754655621 +   0.035123551291*_Complex_I,
+   -0.042987945529 +   0.204710494551*_Complex_I,
+   -0.115448103025 +  -0.048546960311*_Complex_I,
+   -0.107929842580 +  -0.067280385493*_Complex_I,
+   -0.138103588190 +   0.010448310166*_Complex_I,
+   -0.001556297552 +   0.087792883061*_Complex_I,
+    0.046355639811 +   0.064514990229*_Complex_I,
+   -0.073718979418 +   0.103212389197*_Complex_I,
+   -0.167000830993 +  -0.051389222147*_Complex_I,
+   -0.014426416714 +   0.176317900074*_Complex_I,
+   -0.107373342455 +  -0.005804161965*_Complex_I,
+    0.080493073150 +  -0.061646586042*_Complex_I,
+    0.027796421084 +  -0.154117176222*_Complex_I,
+    0.146227243025 +  -0.085442723323*_Complex_I,
+   -0.069720789489 +   0.186876621048*_Complex_I,
+    0.062823376713 +   0.065977046975*_Complex_I,
+    0.090031816754 +   0.114581114467*_Complex_I,
+    0.086442323768 +   0.125045380084*_Complex_I,
+    0.019615700330 +  -0.022164049481*_Complex_I,
+    0.002010913947 +   0.016348332723*_Complex_I,
+    0.115492888651 +  -0.140112679132*_Complex_I,
+   -0.000370552599 +   0.115418598778*_Complex_I,
+    0.171462023973 +   0.045787506437*_Complex_I,
+   -0.044111687463 +  -0.011704116119*_Complex_I,
+   -0.267816339997 +   0.084734013406*_Complex_I,
+    0.013814245144 +   0.002740227971*_Complex_I,
+    0.090220778414 +   0.134830380675*_Complex_I,
+   -0.106145737111 +  -0.024658098491*_Complex_I,
+   -0.112807070004 +   0.044997920710*_Complex_I,
+   -0.192053613103 +  -0.062114377970*_Complex_I,
+   -0.079637314543 +   0.045259089396*_Complex_I,
+    0.012470128523 +  -0.117498759881*_Complex_I,
+   -0.029098880159 +   0.100511335166*_Complex_I,
+    0.076820185739 +  -0.000579442122*_Complex_I,
+   -0.146557365265 +   0.068919305920*_Complex_I,
+    0.046650052137 +   0.086754950098*_Complex_I,
+    0.049312254431 +   0.100155311839*_Complex_I,
+   -0.018181427657 +  -0.155695073922*_Complex_I,
+   -0.020818721382 +   0.050839229113*_Complex_I,
+    0.075624933038 +  -0.140809485613*_Complex_I,
+    0.024131064286 +  -0.061085135867*_Complex_I,
+   -0.031646019927 +  -0.020171616314*_Complex_I,
+    0.029466323016 +  -0.143095954720*_Complex_I,
+   -0.000140873686 +   0.104732973661*_Complex_I,
+    0.124462359562 +  -0.105079943629*_Complex_I,
+   -0.058374142846 +   0.033466529094*_Complex_I,
+    0.026595988216 +   0.220281782376*_Complex_I,
+   -0.007221829817 +   0.141135026450*_Complex_I,
+   -0.021218003577 +  -0.050066565886*_Complex_I,
+    0.035694484054 +   0.231602026762*_Complex_I,
+    0.102466686268 +  -0.099286054817*_Complex_I,
+   -0.054792214672 +   0.060392934122*_Complex_I,
+   -0.001852582407 +   0.025794064903*_Complex_I,
+    0.071274514129 +   0.142237231718*_Complex_I,
+    0.029476880291 +   0.023165981190*_Complex_I,
+   -0.027797892310 +  -0.051616914281*_Complex_I,
+    0.061786844168 +   0.001190554171*_Complex_I,
+    0.041304624689 +  -0.028506531399*_Complex_I,
+   -0.075701492591 +  -0.019189579127*_Complex_I,
+   -0.043195431393 +  -0.100084431779*_Complex_I,
+    0.039964361882 +   0.004559897250*_Complex_I,
+   -0.252729884604 +  -0.091698894534*_Complex_I,
+   -0.056671384848 +   0.100393269844*_Complex_I,
+    0.047875493425 +  -0.053453060952*_Complex_I};
+
+float complex firfilt_crcf_data_h23x64_y[] = {
+   -0.009797648512 +   0.005572506341*_Complex_I,
+   -0.005560604075 +   0.031761800240*_Complex_I,
+   -0.014087267678 +  -0.013784337240*_Complex_I,
+   -0.017247262021 +  -0.018059567129*_Complex_I,
+   -0.012287793463 +   0.019054948700*_Complex_I,
+    0.003719976223 +  -0.002001383647*_Complex_I,
+    0.013367035883 +   0.003682443788*_Complex_I,
+   -0.017360179024 +   0.026029334165*_Complex_I,
+   -0.009179615321 +  -0.007210433367*_Complex_I,
+    0.014210107693 +  -0.025365792504*_Complex_I,
+    0.004045839585 +  -0.004700697501*_Complex_I,
+    0.037185762921 +  -0.032401586998*_Complex_I,
+    0.069046316115 +  -0.013271584282*_Complex_I,
+    0.033064747039 +  -0.020579633062*_Complex_I,
+   -0.010997016057 +   0.045070901497*_Complex_I,
+    0.004422576554 +   0.002390243257*_Complex_I,
+    0.033125238067 +  -0.006016697456*_Complex_I,
+   -0.016975087630 +  -0.035483520861*_Complex_I,
+    0.038314599881 +  -0.033008052971*_Complex_I,
+    0.036960796853 +   0.013280585218*_Complex_I,
+    0.048652304550 +  -0.021208466846*_Complex_I,
+   -0.029006820125 +   0.022587827802*_Complex_I,
+    0.028022156827 +   0.005836099363*_Complex_I,
+    0.022094506847 +   0.031041275846*_Complex_I,
+   -0.052928165220 +  -0.021052052066*_Complex_I,
+    0.018511503181 +  -0.110568047514*_Complex_I,
+    0.013151749885 +  -0.084455750067*_Complex_I,
+   -0.002103151576 +  -0.014246866235*_Complex_I,
+   -0.004981810769 +   0.036099080474*_Complex_I,
+    0.027562144328 +  -0.004142628424*_Complex_I,
+   -0.062965485082 +  -0.000721660223*_Complex_I,
+    0.001355528130 +   0.039494603193*_Complex_I,
+   -0.013676340973 +   0.019821560124*_Complex_I,
+    0.017190322837 +  -0.023603201848*_Complex_I,
+   -0.013639010839 +  -0.090413596530*_Complex_I,
+    0.035568646535 +  -0.005785803158*_Complex_I,
+    0.009541669638 +  -0.037656127703*_Complex_I,
+   -0.037436737172 +  -0.045380997215*_Complex_I,
+   -0.001373976193 +  -0.005853270514*_Complex_I,
+    0.037863401295 +   0.058080181959*_Complex_I,
+    0.036122757372 +  -0.031465040781*_Complex_I,
+   -0.080256218002 +   0.010144726857*_Complex_I,
+   -0.012130289685 +  -0.064577476905*_Complex_I,
+    0.001740193468 +  -0.024376309816*_Complex_I,
+    0.062877940058 +  -0.049615721082*_Complex_I,
+    0.020116976889 +  -0.036592663265*_Complex_I,
+   -0.000380054511 +   0.039223753313*_Complex_I,
+    0.017068821785 +   0.021526831643*_Complex_I,
+    0.021954695055 +   0.037691348334*_Complex_I,
+    0.067990755649 +   0.068783656132*_Complex_I,
+    0.037145711035 +   0.043650952232*_Complex_I,
+   -0.014003385917 +  -0.065891325372*_Complex_I,
+   -0.048189055475 +   0.056129109261*_Complex_I,
+    0.057414520047 +  -0.036184434048*_Complex_I,
+   -0.016064913162 +  -0.054756587631*_Complex_I,
+   -0.016229690128 +  -0.092689300019*_Complex_I,
+    0.009080345865 +   0.006957646952*_Complex_I,
+    0.017213929524 +  -0.068928651567*_Complex_I,
+   -0.036862824002 +   0.024446597685*_Complex_I,
+   -0.018313177259 +   0.012846228600*_Complex_I,
+   -0.002556177228 +   0.060786016298*_Complex_I,
+   -0.069716561264 +   0.004791194532*_Complex_I,
+   -0.004221111697 +  -0.015828369005*_Complex_I,
+    0.018252044788 +   0.020184229477*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_crcf_data_h4x8.c b/src/filter/tests/data/firfilt_crcf_data_h4x8.c
new file mode 100644
index 0000000..35eb624
--- /dev/null
+++ b/src/filter/tests/data/firfilt_crcf_data_h4x8.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_crcf_data_h4x8.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float firfilt_crcf_data_h4x8_h[] = {
+    0.081125556518,
+   -0.048097526791,
+    0.083945750272,
+    0.016820374297};
+
+float complex firfilt_crcf_data_h4x8_x[] = {
+    0.212556497097 +  -0.062593316778*_Complex_I,
+    0.074632428892 +  -0.155168218555*_Complex_I,
+   -0.139928836211 +  -0.151937330426*_Complex_I,
+    0.057785165005 +   0.027214057011*_Complex_I,
+    0.047104310251 +  -0.016114794312*_Complex_I,
+    0.140236563713 +   0.296500956218*_Complex_I,
+   -0.134588116658 +  -0.011633439603*_Complex_I,
+    0.035369414992 +  -0.047878728319*_Complex_I};
+
+float complex firfilt_crcf_data_h4x8_y[] = {
+    0.017243764119 +  -0.005077917658*_Complex_I,
+   -0.004168844485 +  -0.009577524353*_Complex_I,
+    0.002901774665 +  -0.010117235877*_Complex_I,
+    0.021258439696 +  -0.004562990201*_Complex_I,
+   -0.009449045890 +  -0.017980731205*_Complex_I,
+    0.011608332082 +   0.024557748499*_Complex_I,
+   -0.012737392976 +  -0.016099749821*_Complex_I,
+    0.021907294708 +   0.021294289547*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_crcf_data_h7x16.c b/src/filter/tests/data/firfilt_crcf_data_h7x16.c
new file mode 100644
index 0000000..f399bf6
--- /dev/null
+++ b/src/filter/tests/data/firfilt_crcf_data_h7x16.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_crcf_data_h7x16.c: autotest firfilt data
+//
+
+#include <complex.h>
+
+float firfilt_crcf_data_h7x16_h[] = {
+    0.027835212534,
+   -0.040645664069,
+   -0.095885554580,
+    0.200974194416,
+    0.142773900141,
+   -0.084839081860,
+    0.026675441534};
+
+float complex firfilt_crcf_data_h7x16_x[] = {
+   -0.050554619430 +  -0.051932459243*_Complex_I,
+    0.033654160787 +   0.041969310283*_Complex_I,
+    0.083110727700 +   0.078162488859*_Complex_I,
+   -0.077891200631 +  -0.098321707426*_Complex_I,
+   -0.187861386260 +  -0.107086777579*_Complex_I,
+   -0.151977015997 +   0.027468572824*_Complex_I,
+    0.081513596724 +   0.062785557274*_Complex_I,
+    0.031782625481 +  -0.013462505188*_Complex_I,
+   -0.007610286404 +   0.087544934055*_Complex_I,
+    0.155881117469 +   0.110195135092*_Complex_I,
+   -0.104429397916 +   0.137400420010*_Complex_I,
+   -0.116176549274 +   0.071085593565*_Complex_I,
+   -0.164136953851 +   0.202903767828*_Complex_I,
+    0.084667802512 +   0.059492856539*_Complex_I,
+    0.026416620904 +   0.138990393077*_Complex_I,
+   -0.116423582608 +  -0.028759261678*_Complex_I};
+
+float complex firfilt_crcf_data_h7x16_y[] = {
+   -0.001407198576 +  -0.001445551040*_Complex_I,
+    0.002991596797 +   0.003279053964*_Complex_I,
+    0.005792966776 +   0.005449371658*_Complex_I,
+   -0.018933330628 +  -0.020375106640*_Complex_I,
+   -0.010486602589 +  -0.005458937140*_Complex_I,
+    0.036671134061 +   0.040651505702*_Complex_I,
+    0.018467514775 +  -0.002647275592*_Complex_I,
+   -0.042885517171 +  -0.046631668104*_Complex_I,
+   -0.057859564144 +  -0.002378383977*_Complex_I,
+    0.010144797998 +   0.023802283800*_Complex_I,
+    0.017394817258 +  -0.007977151925*_Complex_I,
+   -0.021897278604 +  -0.003093924849*_Complex_I,
+    0.039886090889 +   0.027046322091*_Complex_I,
+    0.022929460881 +   0.022153334200*_Complex_I,
+   -0.038653803420 +   0.008885169596*_Complex_I,
+   -0.048989194709 +   0.030055784593*_Complex_I};
+
diff --git a/src/filter/tests/data/firfilt_rrrf_data_h13x32.c b/src/filter/tests/data/firfilt_rrrf_data_h13x32.c
new file mode 100644
index 0000000..3eaae96
--- /dev/null
+++ b/src/filter/tests/data/firfilt_rrrf_data_h13x32.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_rrrf_data_h13x32.c: autotest firfilt data
+//
+
+float firfilt_rrrf_data_h13x32_h[] = {
+    0.061701400663,
+   -0.003256845257,
+    0.085737504490,
+   -0.119088464421,
+   -0.094195167190,
+   -0.035614627393,
+   -0.075640421976,
+   -0.178180567927,
+    0.062656377018,
+    0.024575001277,
+    0.158593583928,
+    0.002228379520,
+    0.112351904789};
+
+float firfilt_rrrf_data_h13x32_x[] = {
+   -0.020470493148,
+    0.102018118476,
+   -0.054985718021,
+    0.006308577753,
+   -0.136776277914,
+   -0.097553343057,
+    0.015106021667,
+   -0.115276028482,
+   -0.139555063727,
+   -0.008140535272,
+    0.149942052537,
+   -0.011892278070,
+    0.065230575705,
+    0.142075094351,
+   -0.164907789585,
+   -0.032484693281,
+   -0.096276951150,
+    0.147981941286,
+   -0.058167278365,
+    0.122586631059,
+   -0.085249376452,
+   -0.044902927900,
+    0.078432553797,
+    0.057861317911,
+   -0.063852807160,
+   -0.049949958624,
+   -0.041909155283,
+   -0.022591033341,
+    0.048035976026,
+    0.022495836486,
+   -0.073843897589,
+    0.112675367305};
+
+float firfilt_rrrf_data_h13x32_y[] = {
+   -0.001263058100,
+    0.006361330031,
+   -0.005480042042,
+    0.011752906544,
+   -0.023395131780,
+   -0.007365237036,
+   -0.008133908376,
+   -0.001942584790,
+    0.002035200118,
+    0.017539310799,
+    0.018129651215,
+    0.071947442682,
+    0.032171241395,
+    0.004309016586,
+   -0.015673178031,
+    0.002288611018,
+   -0.077270639492,
+   -0.048373764758,
+   -0.012295934305,
+    0.006856074159,
+   -0.019007594657,
+    0.044313034038,
+    0.016681139459,
+    0.021356291565,
+   -0.035698344648,
+    0.018453059195,
+   -0.069848287101,
+    0.038262898124,
+   -0.010626132241,
+    0.023942333214,
+   -0.013545270316,
+    0.034191460826};
+
diff --git a/src/filter/tests/data/firfilt_rrrf_data_h23x64.c b/src/filter/tests/data/firfilt_rrrf_data_h23x64.c
new file mode 100644
index 0000000..07515c8
--- /dev/null
+++ b/src/filter/tests/data/firfilt_rrrf_data_h23x64.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_rrrf_data_h23x64.c: autotest firfilt data
+//
+
+float firfilt_rrrf_data_h23x64_h[] = {
+    0.023507854506,
+    0.027103933172,
+    0.106752895472,
+    0.074063587060,
+    0.012575431714,
+   -0.014208904360,
+   -0.189995720602,
+    0.034113160679,
+    0.175609922460,
+    0.105200934542,
+   -0.005136522642,
+   -0.005531746670,
+    0.139731146359,
+   -0.079842377920,
+   -0.233214917226,
+    0.035638281722,
+   -0.163205439104,
+   -0.032478500610,
+    0.199430113992,
+    0.220129425730,
+   -0.036005167889,
+    0.056409106456,
+   -0.004421322176};
+
+float firfilt_rrrf_data_h23x64_x[] = {
+   -0.013049061997,
+    0.119281727724,
+   -0.119365267096,
+    0.039835879910,
+   -0.009513587246,
+    0.082243762515,
+    0.027956820983,
+   -0.069085027701,
+    0.052848019057,
+    0.146020985374,
+    0.037417929487,
+   -0.084232021180,
+   -0.009683296201,
+   -0.090624445719,
+   -0.006075254980,
+    0.165633370941,
+   -0.073874907410,
+    0.017988275377,
+    0.070181770610,
+   -0.244164921762,
+   -0.038716233769,
+    0.076498331411,
+   -0.013031514838,
+   -0.213155932917,
+   -0.027341233086,
+   -0.146873071789,
+   -0.026995500788,
+   -0.072655809839,
+    0.156894258163,
+    0.124930378658,
+    0.056420922814,
+    0.134062816355,
+   -0.086478418152,
+    0.065523216564,
+   -0.081200039055,
+    0.011086704550,
+    0.085758668011,
+    0.132499850065,
+    0.054287271241,
+    0.057554900510,
+   -0.022403923256,
+    0.244428874813,
+    0.084740945011,
+    0.112048571173,
+   -0.245511705590,
+    0.030473857673,
+   -0.089496518267,
+    0.112146468923,
+   -0.038155919073,
+   -0.079948318739,
+    0.081965047586,
+    0.060785574587,
+   -0.015596830096,
+    0.062834032397,
+   -0.138572554778,
+    0.183714729747,
+    0.046778584717,
+   -0.046081790098,
+   -0.074463177805,
+   -0.206183023427,
+    0.125672790474,
+    0.071969502765,
+    0.088478692161,
+   -0.118547492713};
+
+float firfilt_rrrf_data_h23x64_y[] = {
+   -0.000306755451,
+    0.002450376596,
+   -0.000966042507,
+    0.009468397319,
+   -0.003216187841,
+   -0.001227073462,
+    0.004104453009,
+   -0.013702311097,
+    0.032216367463,
+    0.008663721625,
+   -0.000634352930,
+   -0.004782446546,
+    0.012088802412,
+    0.038158275560,
+   -0.033642624259,
+   -0.056941408268,
+    0.025461989933,
+    0.042996863950,
+    0.042722153417,
+   -0.013343258900,
+   -0.002992404220,
+   -0.070410666038,
+   -0.020716833687,
+   -0.012052257283,
+    0.015366009966,
+   -0.010804920764,
+   -0.018064798938,
+    0.027733552912,
+   -0.024577324228,
+    0.020395693321,
+    0.044679445466,
+   -0.071992656961,
+   -0.028274328337,
+    0.109882255190,
+   -0.054423216252,
+   -0.062518613459,
+    0.073109299563,
+   -0.001030697779,
+    0.000264209985,
+    0.117080616804,
+    0.074883914872,
+    0.008816712343,
+   -0.093804636161,
+   -0.024674797173,
+   -0.069634193023,
+   -0.015132328421,
+    0.010801011890,
+   -0.019904446976,
+    0.054767171785,
+    0.070007069212,
+    0.098407344602,
+   -0.023208041689,
+   -0.034398540605,
+   -0.056178855164,
+    0.015638332591,
+    0.011048505195,
+   -0.032658159097,
+   -0.031311550317,
+    0.061445928366,
+    0.033547250971,
+    0.134751463913,
+   -0.058463369920,
+    0.012179813382,
+   -0.001597271199};
+
diff --git a/src/filter/tests/data/firfilt_rrrf_data_h4x8.c b/src/filter/tests/data/firfilt_rrrf_data_h4x8.c
new file mode 100644
index 0000000..f364125
--- /dev/null
+++ b/src/filter/tests/data/firfilt_rrrf_data_h4x8.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_rrrf_data_h4x8.c: autotest firfilt data
+//
+
+float firfilt_rrrf_data_h4x8_h[] = {
+   -0.121887159208,
+   -0.231303112477,
+   -0.011081038093,
+    0.002940945390};
+
+float firfilt_rrrf_data_h4x8_x[] = {
+    0.087523073427,
+    0.091626543082,
+    0.069905988906,
+   -0.025530671018,
+   -0.085926435885,
+    0.016121796124,
+    0.067241716218,
+    0.036151454364};
+
+float firfilt_rrrf_data_h4x8_y[] = {
+   -0.010667938785,
+   -0.031412458342,
+   -0.030683993510,
+   -0.013815528486,
+    0.015873490575,
+    0.018398508167,
+   -0.011047853592,
+   -0.020390967516};
+
diff --git a/src/filter/tests/data/firfilt_rrrf_data_h7x16.c b/src/filter/tests/data/firfilt_rrrf_data_h7x16.c
new file mode 100644
index 0000000..3782c96
--- /dev/null
+++ b/src/filter/tests/data/firfilt_rrrf_data_h7x16.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_rrrf_data_h7x16.c: autotest firfilt data
+//
+
+float firfilt_rrrf_data_h7x16_h[] = {
+    0.167653994833,
+    0.011522192990,
+    0.015882599202,
+   -0.093561396312,
+    0.074301353348,
+   -0.124295601121,
+    0.032095823166};
+
+float firfilt_rrrf_data_h7x16_x[] = {
+    0.096678620741,
+    0.015023630036,
+    0.051530016668,
+   -0.094237043292,
+    0.007423744308,
+    0.024664429619,
+   -0.199548815687,
+    0.003258035222,
+    0.082829872734,
+    0.142277057992,
+   -0.149671886106,
+   -0.058687993370,
+    0.018936199920,
+   -0.026660942281,
+    0.016612364911,
+    0.178689105006};
+
+float firfilt_rrrf_data_h7x16_y[] = {
+    0.016208556982,
+    0.003632721318,
+    0.010347826097,
+   -0.024012250428,
+    0.006754954149,
+   -0.012997772766,
+   -0.019171751321,
+   -0.014980556455,
+    0.022366048926,
+    0.041414757082,
+   -0.040097174540,
+    0.008782967641,
+   -0.013845574916,
+    0.009200324306,
+   -0.017877119698,
+    0.046763669812};
+
diff --git a/src/filter/tests/data/gen_firfilt_data.m b/src/filter/tests/data/gen_firfilt_data.m
new file mode 100644
index 0000000..3071124
--- /dev/null
+++ b/src/filter/tests/data/gen_firfilt_data.m
@@ -0,0 +1,159 @@
+% 
+% generate firfilt data for autotests
+%
+
+clear all;
+close all;
+
+% 
+% function to generate data
+%
+function gen_firfilt_datafile(type,h_len,x_len);
+
+    % determine type
+    x_complex = 0;
+    h_complex = 0;
+    y_complex = 0;
+    if strcmp(type,'rrr'),
+        x_complex = 0;
+        h_complex = 0;
+        y_complex = 0;
+    elseif strcmp(type,'crc'),
+        x_complex = 1;
+        h_complex = 0;
+        y_complex = 1;
+    elseif strcmp(type,'ccc'),
+        x_complex = 1;
+        h_complex = 1;
+        y_complex = 1;
+    else,
+        error(['invalid/unsupported type (' type ')']);
+    end;
+
+    % generate filter coefficients
+    if h_complex,
+        h = 0.1*[randn(1,h_len) + 1i*randn(1,h_len)];
+    else,
+        h = 0.1*[randn(1,h_len)];
+    end;
+
+    % generate input data
+    if x_complex,
+        x = 0.1*[randn(1,x_len) + 1i*randn(1,x_len)];
+    else,
+        x = 0.1*[randn(1,x_len)];
+    end;
+
+    % filter input
+    y = filter(h,1,x);
+    y_len = length(y);
+
+    % print results
+    % filename example: firfilt_crcf_data_h12x44.c
+    filename = ['firfilt_' type 'f_data_h' num2str(h_len) 'x' num2str(x_len) '.c'];
+    fid = fopen(filename,'w');
+
+    fprintf(fid,'/* Copyright (c) 2007 - 2015 Joseph Gaeddert\n');
+    fprintf(fid,' *\n');
+    fprintf(fid,' * Permission is hereby granted, free of charge, to any person obtaining a copy\n');
+    fprintf(fid,' * of this software and associated documentation files (the "Software"), to deal\n');
+    fprintf(fid,' * in the Software without restriction, including without limitation the rights\n');
+    fprintf(fid,' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n');
+    fprintf(fid,' * copies of the Software, and to permit persons to whom the Software is\n');
+    fprintf(fid,' * furnished to do so, subject to the following conditions:\n');
+    fprintf(fid,' * \n');
+    fprintf(fid,' * The above copyright notice and this permission notice shall be included in\n');
+    fprintf(fid,' * all copies or substantial portions of the Software.\n');
+    fprintf(fid,' *\n');
+    fprintf(fid,' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n');
+    fprintf(fid,' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n');
+    fprintf(fid,' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n');
+    fprintf(fid,' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n');
+    fprintf(fid,' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n');
+    fprintf(fid,' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n');
+    fprintf(fid,' * THE SOFTWARE.\n');
+    fprintf(fid,' */\n');
+    fprintf(fid,'\n');
+    fprintf(fid,'//\n');
+    fprintf(fid,'// %s: autotest firfilt data\n', filename);
+    fprintf(fid,'//\n');
+    fprintf(fid,'\n');
+    if h_complex || x_complex || y_complex,
+        fprintf(fid,'#include <complex.h>\n\n');
+    end;
+
+    % construct base name, e.g. 'firfilt_crcf_test_h12x44'
+    basename = ['firfilt_' type 'f_data_h' num2str(h_len) 'x' num2str(x_len)];
+
+    % save coefficients array
+    if h_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_h[] = {\n', basename);
+    for i=1:h_len,
+        if h_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(h(i)), imag(h(i)));
+        else,         fprintf(fid,'  %16.12f', h(i));
+        end;
+
+        if i==h_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    % save input array
+    if x_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_x[] = {\n', basename);
+    for i=1:x_len,
+        if x_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(x(i)), imag(x(i)));
+        else,         fprintf(fid,'  %16.12f', x(i));
+        end;
+
+        if i==x_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    % save output array
+    if y_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_y[] = {\n', basename);
+    for i=1:y_len,
+        if y_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(y(i)), imag(y(i)));
+        else,         fprintf(fid,'  %16.12f', y(i));
+        end;
+
+        if i==y_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    fclose(fid);
+    printf('results written to %s\n', filename);
+
+endfunction;
+
+%
+% generate firfilt data for autotests
+%
+
+% rrrf data
+gen_firfilt_datafile('rrr',  4,   8);
+gen_firfilt_datafile('rrr',  7,  16);
+gen_firfilt_datafile('rrr', 13,  32);
+gen_firfilt_datafile('rrr', 23,  64);
+
+% crcf data
+gen_firfilt_datafile('crc',  4,   8);
+gen_firfilt_datafile('crc',  7,  16);
+gen_firfilt_datafile('crc', 13,  32);
+gen_firfilt_datafile('crc', 23,  64);
+
+% cccf data
+gen_firfilt_datafile('ccc',  4,   8);
+gen_firfilt_datafile('ccc',  7,  16);
+gen_firfilt_datafile('ccc', 13,  32);
+gen_firfilt_datafile('ccc', 23,  64);
+
diff --git a/src/filter/tests/data/gen_iirfilt_data.m b/src/filter/tests/data/gen_iirfilt_data.m
new file mode 100644
index 0000000..261ef44
--- /dev/null
+++ b/src/filter/tests/data/gen_iirfilt_data.m
@@ -0,0 +1,222 @@
+% 
+% generate iirfilt data for autotests
+%
+
+clear all;
+close all;
+
+% 
+% function to generate data
+%
+function gen_iirfilt_datafile(type,h_len,x_len);
+
+    % determine type
+    x_complex = 0;
+    h_complex = 0;
+    y_complex = 0;
+    if strcmp(type,'rrr'),
+        x_complex = 0;
+        h_complex = 0;
+        y_complex = 0;
+    elseif strcmp(type,'crc'),
+        x_complex = 1;
+        h_complex = 0;
+        y_complex = 1;
+    elseif strcmp(type,'ccc'),
+        x_complex = 1;
+        h_complex = 1;
+        y_complex = 1;
+    else,
+        error(['invalid/unsupported type (' type ')']);
+    end;
+
+    % 
+    % generate low-pass butterworth filter coefficients
+    %
+
+    % generate complex roots
+    n = h_len - 1;      % filter order
+    r = mod(n, 2);      % odd/even order
+    L = (n-r)/2;        % filter semi-length
+    pa = zeros(1,n);    % analog poles (roots of a)
+    for i=0:(L-1),
+        theta = (2*(i+1) + n - 1)*pi / (2*n);
+        pa(2*i+1) = exp( j*theta );
+        pa(2*i+2) = exp(-j*theta );
+    end;
+    if r,
+        pa(end) = -1;
+    end;
+
+    % convert analog poles/zeros/gain to digital
+    fc = 0.1;
+    m  = abs(tan(pi*fc));
+    p = zeros(1,n);
+    z = zeros(1,n);
+    k = 1;
+    for i=1:n,
+        % compute digital zeros...
+        z(i) = -1;
+
+        % compute digital pols
+        pm = pa(i)*m;
+        p(i) = (1 + pm)/(1 - pm);
+
+        % compute digital gain
+        k = k*(1 - p(i)) / (1 - z(i));
+    end;
+
+    % expand roots
+    b = [1];
+    a = [1];
+    k = 1;  % gain
+    for i=1:n,
+        b = conv(b, [1, -z(i)]);
+        a = conv(a, [1, -p(i)]);
+        k = k*(1 - p(i))/(1 - z(i));
+    end;
+    k = real(k);
+    a = real(a);
+    b = real(b) * k;
+
+    % modulate coefficients for complex type
+    if h_complex,
+        for i=1:h_len,
+            a(i) = a(i) * exp(j*2*pi*0.1*i);
+            b(i) = b(i) * exp(j*2*pi*0.1*i);
+        end;
+    end;
+
+    % generate input data
+    if x_complex,
+        x = 0.1*[randn(1,x_len) + 1i*randn(1,x_len)];
+    else,
+        x = 0.1*[randn(1,x_len)];
+    end;
+
+    % filter input
+    y = filter(b,a,x);
+    y_len = length(y);
+
+    % print results
+    % filename example: iirfilt_crcf_data_h12x44.c
+    filename = ['iirfilt_' type 'f_data_h' num2str(h_len) 'x' num2str(x_len) '.c'];
+    fid = fopen(filename,'w');
+
+    fprintf(fid,'/* Copyright (c) 2007 - 2015 Joseph Gaeddert\n');
+    fprintf(fid,' *\n');
+    fprintf(fid,' * Permission is hereby granted, free of charge, to any person obtaining a copy\n');
+    fprintf(fid,' * of this software and associated documentation files (the "Software"), to deal\n');
+    fprintf(fid,' * in the Software without restriction, including without limitation the rights\n');
+    fprintf(fid,' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n');
+    fprintf(fid,' * copies of the Software, and to permit persons to whom the Software is\n');
+    fprintf(fid,' * furnished to do so, subject to the following conditions:\n');
+    fprintf(fid,' * \n');
+    fprintf(fid,' * The above copyright notice and this permission notice shall be included in\n');
+    fprintf(fid,' * all copies or substantial portions of the Software.\n');
+    fprintf(fid,' *\n');
+    fprintf(fid,' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n');
+    fprintf(fid,' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n');
+    fprintf(fid,' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n');
+    fprintf(fid,' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n');
+    fprintf(fid,' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n');
+    fprintf(fid,' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n');
+    fprintf(fid,' * THE SOFTWARE.\n');
+    fprintf(fid,' */\n');
+    fprintf(fid,'\n');
+    fprintf(fid,'//\n');
+    fprintf(fid,'// %s: autotest iirfilt data\n', filename);
+    fprintf(fid,'//\n');
+    fprintf(fid,'\n');
+    if h_complex || x_complex || y_complex,
+        fprintf(fid,'#include <complex.h>\n\n');
+    end;
+
+    % construct base name, e.g. 'iirfilt_crcf_test_h12x44'
+    basename = ['iirfilt_' type 'f_data_h' num2str(h_len) 'x' num2str(x_len)];
+
+    % save coefficients arrays
+    if h_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_b[] = {\n', basename);
+    for i=1:h_len,
+        if h_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(b(i)), imag(b(i)));
+        else,         fprintf(fid,'  %16.12f', b(i));
+        end;
+
+        if i==h_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    % save coefficients arrays
+    if h_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_a[] = {\n', basename);
+    for i=1:h_len,
+        if h_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(a(i)), imag(a(i)));
+        else,         fprintf(fid,'  %16.12f', a(i));
+        end;
+
+        if i==h_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    % save input array
+    if x_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_x[] = {\n', basename);
+    for i=1:x_len,
+        if x_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(x(i)), imag(x(i)));
+        else,         fprintf(fid,'  %16.12f', x(i));
+        end;
+
+        if i==x_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    % save output array
+    if y_complex, fprintf(fid,'float complex ');
+    else,         fprintf(fid,'float ');
+    end;
+    fprintf(fid,'%s_y[] = {\n', basename);
+    for i=1:y_len,
+        if y_complex, fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(y(i)), imag(y(i)));
+        else,         fprintf(fid,'  %16.12f', y(i));
+        end;
+
+        if i==y_len,  fprintf(fid,'};\n\n');
+        else,         fprintf(fid,',\n');
+        end;
+    end;
+
+    fclose(fid);
+    printf('results written to %s\n', filename);
+
+endfunction;
+
+% 
+% generate test data
+%
+
+% rrrf data
+gen_iirfilt_datafile('rrr', 3, 64);
+gen_iirfilt_datafile('rrr', 5, 64);
+gen_iirfilt_datafile('rrr', 7, 64);
+
+% crcf data
+gen_iirfilt_datafile('crc', 3, 64);
+gen_iirfilt_datafile('crc', 5, 64);
+gen_iirfilt_datafile('crc', 7, 64);
+
+% cccf data
+gen_iirfilt_datafile('ccc', 3, 64);
+gen_iirfilt_datafile('ccc', 5, 64);
+gen_iirfilt_datafile('ccc', 7, 64);
+
+
diff --git a/src/filter/tests/data/iirfilt_cccf_data_h3x64.c b/src/filter/tests/data/iirfilt_cccf_data_h3x64.c
new file mode 100644
index 0000000..8816825
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_cccf_data_h3x64.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_cccf_data_h3x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float complex iirfilt_cccf_data_h3x64_b[] = {
+    0.054572462936 +   0.039649215181*_Complex_I,
+    0.041689651984 +   0.128307555581*_Complex_I,
+   -0.020844825992 +   0.064153777791*_Complex_I};
+
+float complex iirfilt_cccf_data_h3x64_a[] = {
+    0.809016994375 +   0.587785252292*_Complex_I,
+   -0.353200399524 +  -1.087039054939*_Complex_I,
+   -0.127562709117 +   0.392597649806*_Complex_I};
+
+float complex iirfilt_cccf_data_h3x64_x[] = {
+   -0.024833213118 +  -0.034917156419*_Complex_I,
+   -0.203303753453 +  -0.146194321825*_Complex_I,
+    0.046955837790 +  -0.018522626941*_Complex_I,
+   -0.009310945228 +  -0.145839543251*_Complex_I,
+    0.050014713286 +   0.074118954232*_Complex_I,
+    0.053029015111 +   0.038309174053*_Complex_I,
+    0.024855918446 +  -0.013616668881*_Complex_I,
+   -0.075445314910 +  -0.100119258919*_Complex_I,
+    0.160017817197 +   0.023949186601*_Complex_I,
+    0.146076212120 +  -0.000269193014*_Complex_I,
+    0.015195370146 +   0.275825448022*_Complex_I,
+    0.071299735570 +   0.054576601750*_Complex_I,
+   -0.143098150747 +   0.029176300561*_Complex_I,
+   -0.029456081088 +  -0.072086611744*_Complex_I,
+    0.068475191123 +  -0.216689722106*_Complex_I,
+    0.076757256920 +   0.097275143829*_Complex_I,
+   -0.043846185419 +   0.306249972124*_Complex_I,
+    0.070413973865 +   0.090585247522*_Complex_I,
+    0.132485779791 +  -0.159268673808*_Complex_I,
+    0.026917161537 +  -0.100640356329*_Complex_I,
+    0.007473659993 +   0.080379816333*_Complex_I,
+    0.190359743048 +   0.112433411463*_Complex_I,
+   -0.120301668631 +   0.050439886600*_Complex_I,
+   -0.046297800762 +  -0.003443247054*_Complex_I,
+   -0.124614723514 +   0.115573697342*_Complex_I,
+    0.145848137742 +  -0.028451577663*_Complex_I,
+    0.150217608922 +   0.041411226796*_Complex_I,
+    0.065634804159 +  -0.047306957256*_Complex_I,
+   -0.082921695322 +  -0.079112928494*_Complex_I,
+   -0.145226550689 +  -0.188234399475*_Complex_I,
+   -0.034483517482 +   0.115199412906*_Complex_I,
+    0.011933371178 +  -0.013196555096*_Complex_I,
+   -0.135183164034 +  -0.016707765823*_Complex_I,
+   -0.023217606041 +   0.034147466698*_Complex_I,
+    0.048294051851 +  -0.023691893478*_Complex_I,
+    0.032010168738 +  -0.060275616633*_Complex_I,
+   -0.145436488960 +  -0.187315942943*_Complex_I,
+   -0.087977813311 +   0.136955551173*_Complex_I,
+   -0.124764798898 +   0.030020090717*_Complex_I,
+   -0.085914119584 +   0.066553144151*_Complex_I,
+    0.080940796771 +   0.007884486399*_Complex_I,
+   -0.073277212995 +   0.029723354716*_Complex_I,
+    0.218025609607 +  -0.011507652078*_Complex_I,
+   -0.050453154704 +   0.168205457912*_Complex_I,
+    0.065308138197 +   0.010673521587*_Complex_I,
+    0.164945191560 +   0.069669644982*_Complex_I,
+   -0.019014062556 +  -0.020454796529*_Complex_I,
+    0.064918556283 +  -0.050548033336*_Complex_I,
+    0.028164657959 +  -0.173995204773*_Complex_I,
+    0.122134013651 +   0.005095487421*_Complex_I,
+    0.234213744496 +  -0.143695630868*_Complex_I,
+    0.084679105788 +  -0.021311700717*_Complex_I,
+    0.054268742828 +   0.007308179887*_Complex_I,
+   -0.029620954263 +   0.054909993996*_Complex_I,
+    0.019992503058 +  -0.115385511675*_Complex_I,
+   -0.064206101272 +  -0.057135496497*_Complex_I,
+    0.102760385313 +   0.007278409932*_Complex_I,
+    0.177210934528 +   0.054290451135*_Complex_I,
+    0.128563865148 +   0.003219961582*_Complex_I,
+   -0.083833350402 +   0.040801324808*_Complex_I,
+   -0.080842077428 +   0.120550027178*_Complex_I,
+   -0.019021541271 +   0.041759686472*_Complex_I,
+   -0.115073955933 +  -0.091807014377*_Complex_I,
+    0.153344859386 +  -0.062742865142*_Complex_I};
+
+float complex iirfilt_cccf_data_h3x64_y[] = {
+   -0.001675131192 +  -0.002355346350*_Complex_I,
+   -0.013622046565 +  -0.018945208542*_Complex_I,
+   -0.006286044544 +  -0.061360482408*_Complex_I,
+    0.040817629094 +  -0.077423785655*_Complex_I,
+    0.082560403156 +  -0.042905448043*_Complex_I,
+    0.081885166095 +   0.020646150389*_Complex_I,
+    0.035185788192 +   0.059385572466*_Complex_I,
+   -0.012349534229 +   0.041702279147*_Complex_I,
+   -0.008719427172 +  -0.005108227398*_Complex_I,
+    0.043586624569 +  -0.002694870087*_Complex_I,
+    0.060010371171 +   0.071790678150*_Complex_I,
+   -0.011700201664 +   0.134289646346*_Complex_I,
+   -0.104085781588 +   0.103901309521*_Complex_I,
+   -0.133770658357 +   0.006298194510*_Complex_I,
+   -0.071593140378 +  -0.089829654416*_Complex_I,
+    0.047530468946 +  -0.094499459711*_Complex_I,
+    0.094339040540 +   0.021353778886*_Complex_I,
+    0.000763487486 +   0.119530519765*_Complex_I,
+   -0.094370685059 +   0.079578408854*_Complex_I,
+   -0.069334619720 +  -0.012623371571*_Complex_I,
+    0.012050086613 +  -0.029603434811*_Complex_I,
+    0.049219627613 +   0.026131367418*_Complex_I,
+    0.013542931769 +   0.089200137012*_Complex_I,
+   -0.066921684818 +   0.079213353516*_Complex_I,
+   -0.100736964307 +   0.008675016473*_Complex_I,
+   -0.073014776026 +  -0.045716119068*_Complex_I,
+   -0.002251024625 +  -0.047215888024*_Complex_I,
+    0.043409846071 +   0.011329094890*_Complex_I,
+    0.020075927067 +   0.051751627220*_Complex_I,
+   -0.025464006128 +   0.018170901171*_Complex_I,
+   -0.017900643236 +  -0.046047380472*_Complex_I,
+    0.021720291461 +  -0.051218319397*_Complex_I,
+    0.023820600962 +  -0.021299144521*_Complex_I,
+   -0.000443310781 +  -0.015435045664*_Complex_I,
+   -0.005171097244 +  -0.029938427283*_Complex_I,
+    0.015962947694 +  -0.032614702790*_Complex_I,
+    0.026567768778 +  -0.027656459980*_Complex_I,
+    0.025886386590 +  -0.031773276552*_Complex_I,
+    0.011142820861 +  -0.022130140814*_Complex_I,
+   -0.023018410554 +  -0.024004380806*_Complex_I,
+   -0.028989134806 +  -0.045608118967*_Complex_I,
+   -0.005447370179 +  -0.044390449128*_Complex_I,
+    0.016111369022 +  -0.025494020513*_Complex_I,
+    0.033164181168 +   0.018349162452*_Complex_I,
+   -0.002881749460 +   0.064999887863*_Complex_I,
+   -0.037794707920 +   0.054120734400*_Complex_I,
+   -0.033549552744 +   0.041209685622*_Complex_I,
+   -0.029745345484 +   0.028385093658*_Complex_I,
+   -0.012206946909 +   0.000426141907*_Complex_I,
+    0.033070597342 +  -0.013052292197*_Complex_I,
+    0.081548142195 +   0.013614572710*_Complex_I,
+    0.101806391304 +   0.065450185198*_Complex_I,
+    0.073804512902 +   0.112077362053*_Complex_I,
+    0.012136514124 +   0.118696097982*_Complex_I,
+   -0.039510405075 +   0.074133214086*_Complex_I,
+   -0.038427157781 +   0.006482000625*_Complex_I,
+    0.006530434509 +  -0.025726623154*_Complex_I,
+    0.055688677705 +   0.002152977720*_Complex_I,
+    0.064499041530 +   0.067061388496*_Complex_I,
+    0.016662206642 +   0.109004403882*_Complex_I,
+   -0.055089624558 +   0.092364887087*_Complex_I,
+   -0.096355781376 +   0.032987760948*_Complex_I,
+   -0.090540548264 +  -0.030202075604*_Complex_I,
+   -0.036200313340 +  -0.078861745571*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_cccf_data_h5x64.c b/src/filter/tests/data/iirfilt_cccf_data_h5x64.c
new file mode 100644
index 0000000..71b8e36
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_cccf_data_h5x64.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_cccf_data_h5x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float complex iirfilt_cccf_data_h5x64_b[] = {
+    0.003902975763 +   0.002835677878*_Complex_I,
+    0.005963216337 +   0.018352892749*_Complex_I,
+   -0.008944824505 +   0.027529339123*_Complex_I,
+   -0.015611903052 +   0.011342711511*_Complex_I,
+   -0.004824343358 +   0.000000000000*_Complex_I};
+
+float complex iirfilt_cccf_data_h5x64_a[] = {
+    0.809016994375 +   0.587785252292*_Complex_I,
+   -0.732219787612 +  -2.253540785927*_Complex_I,
+   -0.715061744841 +   2.200733760162*_Complex_I,
+    0.853242236735 +  -0.619916771678*_Complex_I,
+   -0.187379492368 +   0.000000000000*_Complex_I};
+
+float complex iirfilt_cccf_data_h5x64_x[] = {
+   -0.139691779780 +  -0.008483106386*_Complex_I,
+   -0.016364784445 +  -0.044182994598*_Complex_I,
+   -0.027693468539 +  -0.032794063650*_Complex_I,
+   -0.104737698912 +   0.045751273952*_Complex_I,
+    0.142328368973 +  -0.016207638981*_Complex_I,
+   -0.036733974222 +  -0.069688017846*_Complex_I,
+    0.060203955630 +   0.133175392895*_Complex_I,
+   -0.001765141276 +   0.015191859044*_Complex_I,
+   -0.001821699218 +  -0.185914660662*_Complex_I,
+   -0.117231114471 +  -0.018976758319*_Complex_I,
+    0.056506370766 +   0.169868105876*_Complex_I,
+    0.053136860955 +  -0.057576775991*_Complex_I,
+   -0.033555900108 +   0.043991132807*_Complex_I,
+    0.174322859539 +  -0.016411750716*_Complex_I,
+    0.048756047942 +  -0.098065494186*_Complex_I,
+    0.097489588055 +  -0.128464359980*_Complex_I,
+    0.013198059974 +   0.124985844357*_Complex_I,
+    0.041433063500 +  -0.091281097108*_Complex_I,
+   -0.065717968668 +  -0.070462413308*_Complex_I,
+   -0.229581678727 +   0.016392994937*_Complex_I,
+    0.136389898546 +  -0.044643438493*_Complex_I,
+    0.170063648104 +  -0.138204335760*_Complex_I,
+   -0.261885373085 +  -0.036055969744*_Complex_I,
+    0.049405811588 +  -0.051424162851*_Complex_I,
+    0.083569523085 +  -0.051487491473*_Complex_I,
+    0.071788757486 +  -0.084369347265*_Complex_I,
+    0.003003764878 +   0.132223891359*_Complex_I,
+   -0.024192470852 +   0.070159890098*_Complex_I,
+    0.038349724977 +   0.034974242507*_Complex_I,
+   -0.116330185087 +   0.073501409708*_Complex_I,
+    0.024045897003 +   0.055819960513*_Complex_I,
+    0.017889237527 +  -0.004469182117*_Complex_I,
+   -0.024106461932 +  -0.117012466455*_Complex_I,
+    0.071738300668 +   0.045036393627*_Complex_I,
+    0.028843052996 +  -0.078769113102*_Complex_I,
+    0.063621483394 +  -0.058568970562*_Complex_I,
+   -0.068750316567 +   0.039728781772*_Complex_I,
+    0.100842588600 +   0.110482009478*_Complex_I,
+   -0.125927976752 +   0.054480687277*_Complex_I,
+   -0.020084532983 +  -0.082864071477*_Complex_I,
+    0.031724080774 +  -0.219039642975*_Complex_I,
+   -0.048991766271 +  -0.080798818403*_Complex_I,
+   -0.051508440449 +   0.079624596487*_Complex_I,
+    0.079700844111 +   0.120292888480*_Complex_I,
+   -0.042922860897 +  -0.142995288222*_Complex_I,
+   -0.066949342662 +   0.135970774477*_Complex_I,
+    0.019060475689 +   0.089390846898*_Complex_I,
+   -0.050687616475 +   0.060447837252*_Complex_I,
+    0.032012337624 +  -0.039188802723*_Complex_I,
+   -0.076316420307 +  -0.055316484097*_Complex_I,
+    0.066550049652 +   0.021107378195*_Complex_I,
+   -0.136321433622 +   0.150816889613*_Complex_I,
+   -0.111846218504 +  -0.006277547874*_Complex_I,
+   -0.162278498147 +   0.091756441800*_Complex_I,
+   -0.169150340863 +   0.165920997781*_Complex_I,
+    0.031684008666 +  -0.124705259988*_Complex_I,
+    0.142337877021 +  -0.055397634380*_Complex_I,
+    0.008471585352 +   0.093647831898*_Complex_I,
+    0.056572323420 +   0.087500434149*_Complex_I,
+    0.028450731692 +  -0.096199543245*_Complex_I,
+    0.239170762991 +   0.094782640689*_Complex_I,
+    0.004312922000 +  -0.031035272406*_Complex_I,
+    0.020341098638 +  -0.131272589643*_Complex_I,
+   -0.037882757496 +   0.067034671078*_Complex_I};
+
+float complex iirfilt_cccf_data_h5x64_y[] = {
+   -0.000673921110 +  -0.000040925418*_Complex_I,
+   -0.003398473742 +  -0.002947141586*_Complex_I,
+   -0.002922206681 +  -0.013825603768*_Complex_I,
+    0.011351930937 +  -0.025613672702*_Complex_I,
+    0.033720704702 +  -0.021360914845*_Complex_I,
+    0.044875076203 +  -0.000783873348*_Complex_I,
+    0.038233094284 +   0.020787564602*_Complex_I,
+    0.020600577348 +   0.033987306418*_Complex_I,
+   -0.002864320304 +   0.035531578366*_Complex_I,
+   -0.022888054681 +   0.018549029265*_Complex_I,
+   -0.019307396550 +  -0.009888096688*_Complex_I,
+    0.008180046532 +  -0.019679894883*_Complex_I,
+    0.026199927267 +   0.001599932018*_Complex_I,
+    0.012556168826 +   0.025757319931*_Complex_I,
+   -0.012290310862 +   0.024840701409*_Complex_I,
+   -0.017818483865 +   0.008876391685*_Complex_I,
+   -0.003397754549 +   0.006239299921*_Complex_I,
+    0.005174089802 +   0.027426334643*_Complex_I,
+   -0.014436018353 +   0.050414195091*_Complex_I,
+   -0.048944405480 +   0.042237569566*_Complex_I,
+   -0.061143399567 +   0.001943733440*_Complex_I,
+   -0.031290087686 +  -0.037546290383*_Complex_I,
+    0.024191792032 +  -0.037281132537*_Complex_I,
+    0.055994057347 +   0.009484372596*_Complex_I,
+    0.035113320604 +   0.053950125645*_Complex_I,
+   -0.000319184066 +   0.053553543859*_Complex_I,
+   -0.003748044735 +   0.033707721402*_Complex_I,
+    0.009722926629 +   0.040553199336*_Complex_I,
+   -0.006989849521 +   0.066477615272*_Complex_I,
+   -0.055424307952 +   0.061810233374*_Complex_I,
+   -0.086421338855 +   0.011297407459*_Complex_I,
+   -0.068414816046 +  -0.045801400009*_Complex_I,
+   -0.020384923008 +  -0.067735377613*_Complex_I,
+    0.019283433521 +  -0.051755804853*_Complex_I,
+    0.034417002461 +  -0.021893955948*_Complex_I,
+    0.031236415488 +   0.004272156915*_Complex_I,
+    0.019138535253 +   0.021319083228*_Complex_I,
+    0.004038232283 +   0.030041100664*_Complex_I,
+   -0.013714706258 +   0.032179049891*_Complex_I,
+   -0.035521139667 +   0.023369946120*_Complex_I,
+   -0.050537187534 +  -0.006832451226*_Complex_I,
+   -0.030529075574 +  -0.049682114778*_Complex_I,
+    0.029567916254 +  -0.061475046317*_Complex_I,
+    0.079914850669 +  -0.013639694885*_Complex_I,
+    0.066420597833 +   0.056057061117*_Complex_I,
+    0.001313278909 +   0.079337579523*_Complex_I,
+   -0.048412140007 +   0.039309365767*_Complex_I,
+   -0.044229246054 +  -0.013111052841*_Complex_I,
+   -0.014299116501 +  -0.031757251195*_Complex_I,
+    0.001458230817 +  -0.025843774220*_Complex_I,
+    0.003668689756 +  -0.024816091795*_Complex_I,
+    0.013579943623 +  -0.028642603348*_Complex_I,
+    0.025561989594 +  -0.017704337809*_Complex_I,
+    0.013826559239 +  -0.002285027652*_Complex_I,
+   -0.012918803263 +  -0.015966285682*_Complex_I,
+   -0.016572881084 +  -0.058389357048*_Complex_I,
+    0.019617042366 +  -0.094896571757*_Complex_I,
+    0.080139425197 +  -0.089945804958*_Complex_I,
+    0.120711490363 +  -0.029113736905*_Complex_I,
+    0.094470811623 +   0.049975768139*_Complex_I,
+    0.016348380400 +   0.077528800177*_Complex_I,
+   -0.038765003010 +   0.039996085676*_Complex_I,
+   -0.035736567857 +  -0.000694897676*_Complex_I,
+   -0.015553151187 +  -0.003464831303*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_cccf_data_h7x64.c b/src/filter/tests/data/iirfilt_cccf_data_h7x64.c
new file mode 100644
index 0000000..064ca43
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_cccf_data_h7x64.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_cccf_data_h7x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float complex iirfilt_cccf_data_h7x64_b[] = {
+    0.000275500748 +   0.000200163010*_Complex_I,
+    0.000631391531 +   0.001943223322*_Complex_I,
+   -0.001578478829 +   0.004858058305*_Complex_I,
+   -0.005510014966 +   0.004003260202*_Complex_I,
+   -0.005108064791 +   0.000000000000*_Complex_I,
+   -0.001653004490 +  -0.001200978061*_Complex_I,
+   -0.000105231922 +  -0.000323870554*_Complex_I};
+
+float complex iirfilt_cccf_data_h7x64_a[] = {
+    0.809016994375 +   0.587785252292*_Complex_I,
+   -1.106106182941 +  -3.404244789607*_Complex_I,
+   -1.748624319785 +   5.381712281707*_Complex_I,
+    4.017105304210 +  -2.918597842985*_Complex_I,
+   -2.529494905841 +   0.000000000000*_Complex_I,
+    0.570578744331 +   0.414549723333*_Complex_I,
+   -0.025882175591 +  -0.079657145723*_Complex_I};
+
+float complex iirfilt_cccf_data_h7x64_x[] = {
+   -0.098550426609 +  -0.003621212634*_Complex_I,
+   -0.016756220318 +   0.048451159934*_Complex_I,
+    0.128559901552 +  -0.175906398240*_Complex_I,
+    0.042354962510 +   0.175294582513*_Complex_I,
+    0.044042909613 +  -0.033981151417*_Complex_I,
+    0.096167707817 +   0.043304738718*_Complex_I,
+    0.008843481666 +   0.157764101243*_Complex_I,
+    0.047946611970 +   0.072647112324*_Complex_I,
+   -0.005116789209 +   0.027119998834*_Complex_I,
+    0.056600964002 +   0.277180066672*_Complex_I,
+   -0.038103339311 +  -0.158209538469*_Complex_I,
+   -0.190286655252 +  -0.175199843166*_Complex_I,
+    0.046655418918 +   0.117071622909*_Complex_I,
+   -0.040116383719 +   0.003741131243*_Complex_I,
+    0.106707347270 +  -0.131096478967*_Complex_I,
+    0.156218986693 +  -0.053370165924*_Complex_I,
+    0.049386533650 +   0.102352649704*_Complex_I,
+   -0.035443738919 +  -0.092876336588*_Complex_I,
+   -0.061253027039 +  -0.061242620775*_Complex_I,
+   -0.225771811818 +  -0.034350311320*_Complex_I,
+   -0.010532720169 +   0.007358131605*_Complex_I,
+    0.030330062576 +   0.124193458018*_Complex_I,
+    0.141406666457 +  -0.078752149635*_Complex_I,
+   -0.145791273156 +   0.023246728519*_Complex_I,
+   -0.011541061424 +  -0.152743782535*_Complex_I,
+   -0.068198003148 +  -0.062679366340*_Complex_I,
+    0.073619746128 +   0.013331230908*_Complex_I,
+    0.092211502182 +   0.096482943911*_Complex_I,
+   -0.038021973216 +  -0.031446557366*_Complex_I,
+   -0.008559170319 +  -0.023697838529*_Complex_I,
+    0.004944119278 +  -0.073417141786*_Complex_I,
+    0.110759757286 +   0.145622092402*_Complex_I,
+    0.087582530761 +  -0.051117071597*_Complex_I,
+   -0.104346081702 +  -0.129040232033*_Complex_I,
+    0.013296100000 +   0.043301334280*_Complex_I,
+    0.106655262054 +  -0.084832308563*_Complex_I,
+    0.036672357213 +  -0.108439057934*_Complex_I,
+   -0.072605878689 +  -0.074457714998*_Complex_I,
+   -0.070446103747 +  -0.090753818960*_Complex_I,
+   -0.043844178551 +  -0.001965788736*_Complex_I,
+    0.074808520578 +  -0.029341850412*_Complex_I,
+   -0.029417618007 +  -0.105379562333*_Complex_I,
+   -0.084718140076 +   0.065078236549*_Complex_I,
+    0.006736811174 +   0.057115610089*_Complex_I,
+    0.128294037265 +   0.016598548472*_Complex_I,
+    0.056536504992 +   0.095386541532*_Complex_I,
+   -0.180675222360 +   0.073501707373*_Complex_I,
+    0.075621856422 +  -0.019260447414*_Complex_I,
+   -0.123616785095 +   0.040307567607*_Complex_I,
+    0.231558123335 +  -0.162832384201*_Complex_I,
+   -0.215960867581 +  -0.007986024628*_Complex_I,
+    0.128200674463 +  -0.048905384561*_Complex_I,
+   -0.024701418483 +  -0.022333678059*_Complex_I,
+    0.168095436407 +   0.021880796886*_Complex_I,
+   -0.053661700721 +   0.138981768476*_Complex_I,
+   -0.085052752996 +   0.061710282625*_Complex_I,
+   -0.070265421254 +   0.003218384541*_Complex_I,
+    0.078160292681 +  -0.023479825626*_Complex_I,
+   -0.039515617329 +   0.098596493423*_Complex_I,
+    0.024455660290 +   0.059965817173*_Complex_I,
+   -0.054708570486 +  -0.041259730433*_Complex_I,
+    0.014136360877 +  -0.049081577540*_Complex_I,
+    0.076788203575 +   0.031140443013*_Complex_I,
+    0.140726220651 +  -0.116220629853*_Complex_I};
+
+float complex iirfilt_cccf_data_h7x64_y[] = {
+   -0.000033560131 +  -0.000001233159*_Complex_I,
+   -0.000258851151 +  -0.000182022816*_Complex_I,
+   -0.000494649917 +  -0.001373362038*_Complex_I,
+    0.001394642558 +  -0.004175303248*_Complex_I,
+    0.008554931816 +  -0.004804359573*_Complex_I,
+    0.016796138686 +   0.005120290566*_Complex_I,
+    0.010932000560 +   0.024911804007*_Complex_I,
+   -0.017061529751 +   0.034832808964*_Complex_I,
+   -0.049761088897 +   0.015264433783*_Complex_I,
+   -0.059020207327 +  -0.027171699378*_Complex_I,
+   -0.036205994556 +  -0.064624947209*_Complex_I,
+    0.000601917126 +  -0.076588962383*_Complex_I,
+    0.028262998034 +  -0.068260421645*_Complex_I,
+    0.044537214577 +  -0.058361858676*_Complex_I,
+    0.064596876978 +  -0.048925217761*_Complex_I,
+    0.088128996753 +  -0.021843906515*_Complex_I,
+    0.089856452956 +   0.026332726109*_Complex_I,
+    0.054471127041 +   0.068223976540*_Complex_I,
+   -0.000290875094 +   0.076123105710*_Complex_I,
+   -0.044282578145 +   0.048889124487*_Complex_I,
+   -0.059353580762 +   0.004067442508*_Complex_I,
+   -0.041761212642 +  -0.036859569919*_Complex_I,
+   -0.000561785939 +  -0.053116743086*_Complex_I,
+    0.040104448371 +  -0.032897900323*_Complex_I,
+    0.050619601896 +   0.011940322017*_Complex_I,
+    0.021768243925 +   0.046287205351*_Complex_I,
+   -0.019213577363 +   0.040918226497*_Complex_I,
+   -0.030599821708 +   0.006207948813*_Complex_I,
+   -0.002845413304 +  -0.013157003063*_Complex_I,
+    0.024556915659 +   0.011232088898*_Complex_I,
+    0.007350619475 +   0.051864253016*_Complex_I,
+   -0.044765635147 +   0.053713175283*_Complex_I,
+   -0.073600260113 +   0.004656138635*_Complex_I,
+   -0.044922245023 +  -0.043553047795*_Complex_I,
+    0.005045527096 +  -0.039432366427*_Complex_I,
+    0.016567161506 +   0.001409186000*_Complex_I,
+   -0.014892968732 +   0.020249556494*_Complex_I,
+   -0.038020558990 +  -0.003690764330*_Complex_I,
+   -0.020379281032 +  -0.029896511388*_Complex_I,
+    0.009911763020 +  -0.018485013558*_Complex_I,
+    0.009213285286 +   0.015408996809*_Complex_I,
+   -0.020607967078 +   0.028064347058*_Complex_I,
+   -0.040072285887 +   0.009103845685*_Complex_I,
+   -0.029633824328 +  -0.009491108934*_Complex_I,
+   -0.012674711398 +  -0.002412590925*_Complex_I,
+   -0.017757155057 +   0.015724298695*_Complex_I,
+   -0.039259428540 +   0.015648709858*_Complex_I,
+   -0.050519151856 +  -0.005459876230*_Complex_I,
+   -0.042568847598 +  -0.027243436178*_Complex_I,
+   -0.027047977959 +  -0.039658949321*_Complex_I,
+   -0.008598155325 +  -0.047768931278*_Complex_I,
+    0.018620409918 +  -0.047826408941*_Complex_I,
+    0.046696907915 +  -0.025513963561*_Complex_I,
+    0.051035872527 +   0.016271017736*_Complex_I,
+    0.021627412575 +   0.048969234788*_Complex_I,
+   -0.020027138727 +   0.049506949727*_Complex_I,
+   -0.046847717023 +   0.024846966173*_Complex_I,
+   -0.053679844164 +  -0.005914224145*_Complex_I,
+   -0.045675563357 +  -0.036479785730*_Complex_I,
+   -0.018483743745 +  -0.062516165506*_Complex_I,
+    0.025991231714 +  -0.064521736503*_Complex_I,
+    0.058934016585 +  -0.030613923443*_Complex_I,
+    0.051235943856 +   0.012914473925*_Complex_I,
+    0.016853513686 +   0.024980258409*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_crcf_data_h3x64.c b/src/filter/tests/data/iirfilt_crcf_data_h3x64.c
new file mode 100644
index 0000000..387305f
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_crcf_data_h3x64.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_crcf_data_h3x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float iirfilt_crcf_data_h3x64_b[] = {
+    0.067455273889,
+    0.134910547778,
+    0.067455273889};
+
+float iirfilt_crcf_data_h3x64_a[] = {
+    1.000000000000,
+   -1.142980502540,
+    0.412801598096};
+
+float complex iirfilt_crcf_data_h3x64_x[] = {
+    0.157714921372 +   0.096933651372*_Complex_I,
+    0.112931825396 +   0.063629523666*_Complex_I,
+    0.036350589338 +   0.126527528681*_Complex_I,
+   -0.131639758916 +   0.002850582881*_Complex_I,
+    0.124813993717 +  -0.161564813627*_Complex_I,
+    0.055810697081 +  -0.079984015750*_Complex_I,
+   -0.113597866433 +   0.059629208365*_Complex_I,
+   -0.081276130300 +  -0.032884892886*_Complex_I,
+    0.189206432747 +   0.151230084280*_Complex_I,
+    0.046653973791 +   0.039106242827*_Complex_I,
+    0.062707370238 +   0.114770667926*_Complex_I,
+   -0.005370634420 +  -0.115131667770*_Complex_I,
+   -0.042638922558 +  -0.133249195976*_Complex_I,
+   -0.036786574989 +  -0.119823837652*_Complex_I,
+    0.086588872188 +   0.012335566540*_Complex_I,
+    0.162897246678 +   0.076457627729*_Complex_I,
+   -0.046976140791 +   0.024454556465*_Complex_I,
+    0.037224871206 +   0.005544202178*_Complex_I,
+   -0.106866213474 +  -0.004556655371*_Complex_I,
+    0.112442348190 +  -0.043675611175*_Complex_I,
+   -0.139836308260 +  -0.165558641382*_Complex_I,
+   -0.124475577909 +   0.044150412760*_Complex_I,
+   -0.112036331740 +   0.092938383030*_Complex_I,
+    0.004665562144 +   0.077748113050*_Complex_I,
+   -0.016082143750 +  -0.159087937039*_Complex_I,
+   -0.122649745886 +   0.169674782967*_Complex_I,
+   -0.059903644318 +  -0.028174007533*_Complex_I,
+   -0.135926561418 +   0.161939672399*_Complex_I,
+    0.058515792150 +  -0.092207684338*_Complex_I,
+   -0.001328847093 +  -0.044399214544*_Complex_I,
+    0.148659720963 +  -0.189463417484*_Complex_I,
+   -0.026581782415 +   0.095051513873*_Complex_I,
+    0.031890698003 +  -0.042421129272*_Complex_I,
+   -0.122306862234 +   0.005962301978*_Complex_I,
+   -0.061265177784 +   0.106033789898*_Complex_I,
+    0.261292757319 +  -0.214947283935*_Complex_I,
+    0.103799428426 +   0.080318756697*_Complex_I,
+   -0.049679368664 +   0.150833170036*_Complex_I,
+    0.042691894941 +   0.069350178325*_Complex_I,
+   -0.046630189152 +  -0.062689469185*_Complex_I,
+   -0.012129146913 +   0.031853481774*_Complex_I,
+    0.020440626886 +   0.011527708526*_Complex_I,
+    0.048482096955 +   0.129990980549*_Complex_I,
+    0.042635428860 +  -0.223911417035*_Complex_I,
+   -0.010964205149 +  -0.143631009428*_Complex_I,
+    0.065181991532 +  -0.058004538996*_Complex_I,
+   -0.000090077953 +  -0.081470015783*_Complex_I,
+    0.011047855650 +   0.146709286197*_Complex_I,
+   -0.114534249385 +   0.131496385737*_Complex_I,
+    0.071234677078 +   0.077433138944*_Complex_I,
+    0.200723551597 +   0.001379408486*_Complex_I,
+   -0.026760201597 +   0.140751935283*_Complex_I,
+    0.047450662078 +  -0.024838330640*_Complex_I,
+    0.013847228689 +  -0.209058524164*_Complex_I,
+    0.093422613814 +   0.012081621105*_Complex_I,
+    0.136809870406 +  -0.145382142602*_Complex_I,
+    0.009187149051 +   0.116724633427*_Complex_I,
+    0.136993412455 +   0.031913868005*_Complex_I,
+    0.000837449944 +  -0.080964662247*_Complex_I,
+   -0.060279791907 +  -0.040256932738*_Complex_I,
+    0.037791227194 +  -0.030926237571*_Complex_I,
+   -0.041416454010 +  -0.072873679453*_Complex_I,
+   -0.022768793380 +  -0.030601437534*_Complex_I,
+    0.108180951121 +   0.069467116791*_Complex_I};
+
+float complex iirfilt_crcf_data_h3x64_y[] = {
+    0.010638703218 +   0.006538686002*_Complex_I,
+    0.041055083998 +   0.024843109564*_Complex_I,
+    0.070859923453 +   0.049353938820*_Complex_I,
+    0.067686035774 +   0.067709646463*_Complex_I,
+    0.041224538648 +   0.055038544970*_Complex_I,
+    0.030901594293 +   0.007957479174*_Complex_I,
+    0.026588402992 +  -0.031291537326*_Complex_I,
+    0.000590470547 +  -0.038619471159*_Complex_I,
+   -0.016165649635 +  -0.021437053514*_Complex_I,
+    0.004469726906 +   0.012262241589*_Complex_I,
+    0.035069044446 +   0.046083751239*_Complex_I,
+    0.049482788267 +   0.057966414042*_Complex_I,
+    0.042710472013 +   0.030452084359*_Complex_I,
+    0.019794496184 +  -0.022948199659*_Complex_I,
+    0.002995531001 +  -0.063121775432*_Complex_I,
+    0.015441215948 +  -0.063934986641*_Complex_I,
+    0.041061093206 +  -0.034223045125*_Complex_I,
+    0.047719585714 +  -0.003893175152*_Complex_I,
+    0.032237020788 +   0.011767694149*_Complex_I,
+    0.012826028856 +   0.011870448387*_Complex_I,
+   -0.000119320177 +  -0.008657506452*_Complex_I,
+   -0.025108083598 +  -0.037099080266*_Complex_I,
+   -0.062432000766 +  -0.037771955955*_Complex_I,
+   -0.084190602890 +  -0.007096982968*_Complex_I,
+   -0.078469021221 +   0.013507514859*_Complex_I,
+   -0.065062852144 +   0.013595810519*_Complex_I,
+   -0.063645820627 +   0.020222934773*_Complex_I,
+   -0.071411851802 +   0.036070220963*_Complex_I,
+   -0.073780802600 +   0.046606489405*_Complex_I,
+   -0.056215295888 +   0.033869398399*_Complex_I,
+   -0.020000348431 +  -0.005517294981*_Complex_I,
+    0.018518800909 +  -0.042431351088*_Complex_I,
+    0.038015719300 +  -0.049039042613*_Complex_I,
+    0.030065702572 +  -0.037444083204*_Complex_I,
+    0.000189612796 +  -0.017459075006*_Complex_I,
+   -0.011084433416 +  -0.004290467457*_Complex_I,
+    0.025372744806 +  -0.014124980335*_Complex_I,
+    0.061854301275 +  -0.007862452949*_Complex_I,
+    0.063403692253 +   0.027289128630*_Complex_I,
+    0.046198628950 +   0.049738403126*_Complex_I,
+    0.022401700824 +   0.043954279075*_Complex_I,
+    0.003130865455 +   0.030553031785*_Complex_I,
+   -0.000459085357 +   0.029249594984*_Complex_I,
+    0.008978406999 +   0.024030129591*_Complex_I,
+    0.018734404042 +  -0.015736413767*_Complex_I,
+    0.023500424489 +  -0.066300146201*_Complex_I,
+    0.027175003553 +  -0.092293433110*_Complex_I,
+    0.026489439187 +  -0.083128349690*_Complex_I,
+    0.012817484482 +  -0.033748033571*_Complex_I,
+   -0.006186235518 +   0.028602009338*_Complex_I,
+    0.003062407634 +   0.066032501481*_Complex_I,
+    0.036133722347 +   0.078570737258*_Complex_I,
+    0.053166399461 +   0.079952991289*_Complex_I,
+    0.051382646333 +   0.050991991806*_Complex_I,
+    0.048152970882 +  -0.003786577505*_Complex_I,
+    0.056593380224 +  -0.047656514100*_Complex_I,
+    0.070186171051 +  -0.063832284508*_Complex_I,
+    0.076568506010 +  -0.045193020750*_Complex_I,
+    0.077701414291 +  -0.018586956449*_Complex_I,
+    0.062491319025 +  -0.014074548285*_Complex_I,
+    0.033824419480 +  -0.021392925087*_Complex_I,
+    0.011102622655 +  -0.030445232324*_Complex_I,
+   -0.005846867304 +  -0.039949067467*_Complex_I,
+   -0.009834168653 +  -0.037451411970*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_crcf_data_h5x64.c b/src/filter/tests/data/iirfilt_crcf_data_h5x64.c
new file mode 100644
index 0000000..13b5ae1
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_crcf_data_h5x64.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_crcf_data_h5x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float iirfilt_crcf_data_h5x64_b[] = {
+    0.004824343358,
+    0.019297373431,
+    0.028946060146,
+    0.019297373431,
+    0.004824343358};
+
+float iirfilt_crcf_data_h5x64_a[] = {
+    1.000000000000,
+   -2.369513007182,
+    2.313988414416,
+   -1.054665405879,
+    0.187379492368};
+
+float complex iirfilt_crcf_data_h5x64_x[] = {
+    0.034816486087 +  -0.057435933225*_Complex_I,
+   -0.138922686034 +   0.002487157060*_Complex_I,
+    0.002168200782 +   0.075752631688*_Complex_I,
+    0.110645778286 +   0.097973148283*_Complex_I,
+   -0.062999498912 +   0.034473236147*_Complex_I,
+    0.030513585484 +   0.108486942526*_Complex_I,
+    0.130905798224 +  -0.206952317034*_Complex_I,
+    0.104557822758 +  -0.049326363722*_Complex_I,
+    0.175995475228 +  -0.040903480637*_Complex_I,
+    0.263382017202 +   0.007386489593*_Complex_I,
+    0.075401623113 +   0.034346157974*_Complex_I,
+   -0.004787311774 +   0.050323428781*_Complex_I,
+    0.039595621673 +  -0.013034423002*_Complex_I,
+    0.289435339135 +   0.004887226549*_Complex_I,
+    0.000109192613 +  -0.098143317718*_Complex_I,
+    0.218628727322 +   0.045783638022*_Complex_I,
+    0.011357280346 +   0.060469524704*_Complex_I,
+   -0.005462153874 +   0.011440185961*_Complex_I,
+    0.086903581383 +   0.054667638583*_Complex_I,
+    0.009197661173 +  -0.006158688411*_Complex_I,
+    0.082244960567 +  -0.138538495472*_Complex_I,
+    0.078074527675 +  -0.110054617600*_Complex_I,
+   -0.071517687777 +  -0.053281282254*_Complex_I,
+    0.050695873651 +   0.049035186418*_Complex_I,
+   -0.064011859850 +   0.057026886508*_Complex_I,
+    0.002187309591 +   0.247199024244*_Complex_I,
+    0.177910643270 +   0.036260952778*_Complex_I,
+   -0.133404966159 +   0.007272659425*_Complex_I,
+    0.104545832232 +   0.100374198245*_Complex_I,
+    0.152937259249 +  -0.006345268825*_Complex_I,
+   -0.089893987831 +  -0.066730853350*_Complex_I,
+   -0.031614665144 +  -0.108199665873*_Complex_I,
+   -0.019945023665 +  -0.036637064501*_Complex_I,
+    0.035423077805 +  -0.084251698103*_Complex_I,
+   -0.045559909198 +  -0.065683835690*_Complex_I,
+    0.082043588708 +  -0.073113783286*_Complex_I,
+    0.146363247845 +  -0.313133192986*_Complex_I,
+    0.163657926540 +   0.035668446628*_Complex_I,
+   -0.133126328934 +  -0.033221937410*_Complex_I,
+   -0.041582597083 +   0.060780541288*_Complex_I,
+   -0.102136662865 +   0.064174466409*_Complex_I,
+    0.129371455827 +  -0.122309646349*_Complex_I,
+    0.081072163867 +   0.121012321373*_Complex_I,
+    0.035429837296 +   0.040975877524*_Complex_I,
+    0.047403447509 +   0.014166128441*_Complex_I,
+    0.081667476778 +   0.117576764934*_Complex_I,
+    0.142529344063 +  -0.015788307193*_Complex_I,
+    0.107269994778 +   0.039678416397*_Complex_I,
+   -0.189638285144 +  -0.025105560583*_Complex_I,
+    0.127449393546 +  -0.026105377201*_Complex_I,
+    0.051057729361 +  -0.029706326528*_Complex_I,
+    0.095342091161 +  -0.221870514334*_Complex_I,
+    0.033077914989 +  -0.078421433242*_Complex_I,
+    0.139326073519 +   0.176520418207*_Complex_I,
+    0.180222947875 +  -0.140485235948*_Complex_I,
+   -0.092080231794 +   0.044423535254*_Complex_I,
+   -0.116184788790 +  -0.081712392283*_Complex_I,
+   -0.015159814021 +  -0.253655296232*_Complex_I,
+   -0.165572835614 +   0.032968825997*_Complex_I,
+   -0.018186027637 +   0.146030996176*_Complex_I,
+   -0.081743534837 +   0.046764387177*_Complex_I,
+   -0.163907916201 +   0.000894565796*_Complex_I,
+    0.066507006426 +   0.153027239112*_Complex_I,
+   -0.093381553000 +   0.004333390574*_Complex_I};
+
+float complex iirfilt_crcf_data_h5x64_y[] = {
+    0.000167966683 +  -0.000277090663*_Complex_I,
+    0.000399655237 +  -0.001752933682*_Complex_I,
+   -0.001104267382 +  -0.004761506250*_Complex_I,
+   -0.006137988749 +  -0.006620307024*_Complex_I,
+   -0.012217626919 +  -0.002445090386*_Complex_I,
+   -0.014480238497 +   0.010330691717*_Complex_I,
+   -0.010763861323 +   0.028395668560*_Complex_I,
+   -0.000501503170 +   0.042087127701*_Complex_I,
+    0.017677520131 +   0.040492499282*_Complex_I,
+    0.044775392784 +   0.020918790830*_Complex_I,
+    0.079868253591 +  -0.007890790986*_Complex_I,
+    0.117333468546 +  -0.032191317082*_Complex_I,
+    0.145332483454 +  -0.041696063730*_Complex_I,
+    0.153450768495 +  -0.034623789141*_Complex_I,
+    0.143091173002 +  -0.017649460381*_Complex_I,
+    0.125440207813 +  -0.001185301267*_Complex_I,
+    0.110781054574 +   0.007694330137*_Complex_I,
+    0.102309442381 +   0.009525121208*_Complex_I,
+    0.096424656974 +   0.009467240886*_Complex_I,
+    0.087905819414 +   0.011472932774*_Complex_I,
+    0.075351746332 +   0.015189934128*_Complex_I,
+    0.061539619627 +   0.015371965161*_Complex_I,
+    0.050238103007 +   0.005354811389*_Complex_I,
+    0.042392909138 +  -0.015692162843*_Complex_I,
+    0.035487235197 +  -0.039320795693*_Complex_I,
+    0.026683183579 +  -0.051939223046*_Complex_I,
+    0.016086735480 +  -0.042351028087*_Complex_I,
+    0.007718993110 +  -0.009467148500*_Complex_I,
+    0.005371191435 +   0.034876269506*_Complex_I,
+    0.009169183447 +   0.073622002790*_Complex_I,
+    0.018251989832 +   0.094472386300*_Complex_I,
+    0.030162933688 +   0.092028482193*_Complex_I,
+    0.038047814129 +   0.066731638129*_Complex_I,
+    0.035763727219 +   0.025446392854*_Complex_I,
+    0.023934856252 +  -0.020176728351*_Complex_I,
+    0.008437472733 +  -0.058844376262*_Complex_I,
+   -0.003244989715 +  -0.085035950076*_Complex_I,
+   -0.003390870214 +  -0.101037326348*_Complex_I,
+    0.012003956420 +  -0.111181202859*_Complex_I,
+    0.036474465180 +  -0.114015132267*_Complex_I,
+    0.054397129333 +  -0.103816397690*_Complex_I,
+    0.053458816959 +  -0.078552582252*_Complex_I,
+    0.035502609681 +  -0.044222383504*_Complex_I,
+    0.014265637465 +  -0.010619069148*_Complex_I,
+    0.003101144454 +   0.016085677224*_Complex_I,
+    0.006286890891 +   0.034538795527*_Complex_I,
+    0.020824414839 +   0.045682197348*_Complex_I,
+    0.042111099700 +   0.051038175528*_Complex_I,
+    0.064730289486 +   0.051165119231*_Complex_I,
+    0.079924238054 +   0.045642918872*_Complex_I,
+    0.080081550492 +   0.034340023196*_Complex_I,
+    0.067180942535 +   0.017458189468*_Complex_I,
+    0.051064446907 +  -0.005688909571*_Complex_I,
+    0.040695417558 +  -0.033996511064*_Complex_I,
+    0.040715290376 +  -0.059379363759*_Complex_I,
+    0.051739083738 +  -0.071274663107*_Complex_I,
+    0.067460869459 +  -0.066847437344*_Complex_I,
+    0.074610717895 +  -0.053096308826*_Complex_I,
+    0.062263741557 +  -0.042094216632*_Complex_I,
+    0.029932249437 +  -0.041388946019*_Complex_I,
+   -0.013495205238 +  -0.045430267433*_Complex_I,
+   -0.055715901366 +  -0.041773963667*_Complex_I,
+   -0.087248260391 +  -0.024536503294*_Complex_I,
+   -0.103228307102 +   0.002973369704*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_crcf_data_h7x64.c b/src/filter/tests/data/iirfilt_crcf_data_h7x64.c
new file mode 100644
index 0000000..6e6ac9c
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_crcf_data_h7x64.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_crcf_data_h7x64.c: autotest iirfilt data
+//
+
+#include <complex.h>
+
+float iirfilt_crcf_data_h7x64_b[] = {
+    0.000340537653,
+    0.002043225916,
+    0.005108064791,
+    0.006810753054,
+    0.005108064791,
+    0.002043225916,
+    0.000340537653};
+
+float iirfilt_crcf_data_h7x64_a[] = {
+    1.000000000000,
+   -3.579434798331,
+    5.658667165934,
+   -4.965415228779,
+    2.529494905841,
+   -0.705274114510,
+    0.083756479619};
+
+float complex iirfilt_crcf_data_h7x64_x[] = {
+    0.004233635522 +   0.032166672422*_Complex_I,
+   -0.157552590267 +   0.250368602921*_Complex_I,
+    0.054267060665 +  -0.005044027264*_Complex_I,
+    0.051025281222 +   0.067245838231*_Complex_I,
+   -0.027078112717 +  -0.126549310903*_Complex_I,
+    0.035527067912 +   0.084082095013*_Complex_I,
+   -0.045166037588 +   0.117512061237*_Complex_I,
+   -0.170543036494 +  -0.210138171552*_Complex_I,
+    0.122017191616 +   0.083315021977*_Complex_I,
+    0.087665380573 +  -0.113419352152*_Complex_I,
+   -0.020592340496 +   0.051461184327*_Complex_I,
+    0.005438907415 +  -0.034203108407*_Complex_I,
+   -0.004238610906 +  -0.010875374856*_Complex_I,
+    0.014448167389 +   0.140297619530*_Complex_I,
+    0.023037852846 +   0.147634578106*_Complex_I,
+    0.039831512515 +   0.117367316573*_Complex_I,
+   -0.049079756276 +  -0.134214287084*_Complex_I,
+   -0.081369646592 +  -0.123999271543*_Complex_I,
+   -0.060778482772 +  -0.005943225227*_Complex_I,
+    0.060096536726 +   0.110619062414*_Complex_I,
+   -0.029590602066 +   0.069542558264*_Complex_I,
+    0.276694153680 +   0.033847635389*_Complex_I,
+   -0.069801585631 +  -0.024433292968*_Complex_I,
+    0.103527771239 +   0.043047059964*_Complex_I,
+    0.052392266919 +   0.106611921459*_Complex_I,
+    0.047921013690 +   0.144591771640*_Complex_I,
+   -0.100885260679 +   0.085422512163*_Complex_I,
+    0.023817071089 +   0.007631681829*_Complex_I,
+   -0.103520290629 +   0.065084699549*_Complex_I,
+    0.141218652175 +   0.181817618913*_Complex_I,
+    0.091918916526 +   0.090132723783*_Complex_I,
+    0.073412476081 +  -0.185654057154*_Complex_I,
+    0.064690355167 +  -0.154617720920*_Complex_I,
+   -0.141562424619 +  -0.078498890176*_Complex_I,
+   -0.089061791163 +  -0.089972135569*_Complex_I,
+    0.061285035005 +  -0.042181313189*_Complex_I,
+    0.011648008082 +   0.030075451872*_Complex_I,
+   -0.050030705016 +   0.044231269056*_Complex_I,
+    0.055904020426 +  -0.100086776348*_Complex_I,
+    0.067035512200 +   0.110427319881*_Complex_I,
+    0.112195729535 +  -0.159140605014*_Complex_I,
+   -0.008321783937 +  -0.074224475191*_Complex_I,
+    0.124554695470 +  -0.000035276105*_Complex_I,
+    0.178233609860 +  -0.241786527172*_Complex_I,
+    0.013428861272 +   0.019929110617*_Complex_I,
+    0.106814998943 +   0.061446999514*_Complex_I,
+   -0.087662510082 +  -0.142756477357*_Complex_I,
+    0.005116389200 +   0.004875226134*_Complex_I,
+   -0.082163710392 +  -0.003854447463*_Complex_I,
+    0.055960629067 +   0.147529880141*_Complex_I,
+    0.104198321794 +   0.047973142904*_Complex_I,
+   -0.092852806707 +  -0.088517198490*_Complex_I,
+    0.008453380604 +   0.016712253059*_Complex_I,
+   -0.168237622832 +   0.182661482388*_Complex_I,
+   -0.003676740087 +   0.067558336540*_Complex_I,
+    0.083397702670 +   0.084971378309*_Complex_I,
+    0.105858021404 +   0.040735314366*_Complex_I,
+   -0.117228290082 +  -0.024601874801*_Complex_I,
+   -0.158931397897 +  -0.101660731802*_Complex_I,
+   -0.079872460607 +  -0.030609694665*_Complex_I,
+   -0.070191072161 +   0.183132157615*_Complex_I,
+   -0.187191287439 +  -0.013144361324*_Complex_I,
+   -0.097352654114 +  -0.073775329625*_Complex_I,
+   -0.160782253378 +   0.108482062513*_Complex_I};
+
+float complex iirfilt_crcf_data_h7x64_y[] = {
+    0.000001441712 +   0.000010953963*_Complex_I,
+   -0.000039841800 +   0.000190192712*_Complex_I,
+   -0.000432579170 +   0.001292948964*_Complex_I,
+   -0.001963477446 +   0.005116752194*_Complex_I,
+   -0.005460987213 +   0.013853437396*_Complex_I,
+   -0.010691848048 +   0.028003970363*_Complex_I,
+   -0.015829219307 +   0.044636043744*_Complex_I,
+   -0.018444702482 +   0.058202478955*_Complex_I,
+   -0.017385042715 +   0.063522217409*_Complex_I,
+   -0.013852978602 +   0.058220061227*_Complex_I,
+   -0.010372120290 +   0.043135943082*_Complex_I,
+   -0.008386259332 +   0.021557712213*_Complex_I,
+   -0.007057894617 +  -0.001756678543*_Complex_I,
+   -0.004462600311 +  -0.021708921739*_Complex_I,
+    0.000413860784 +  -0.033985730231*_Complex_I,
+    0.006867599715 +  -0.035636537512*_Complex_I,
+    0.013151792937 +  -0.025207996188*_Complex_I,
+    0.017581663360 +  -0.003488920494*_Complex_I,
+    0.018987594562 +   0.024903700370*_Complex_I,
+    0.016476110032 +   0.051134067764*_Complex_I,
+    0.009459018773 +   0.065241189212*_Complex_I,
+   -0.001441469538 +   0.061675106678*_Complex_I,
+   -0.013298737003 +   0.042982674038*_Complex_I,
+   -0.020975009992 +   0.018439112600*_Complex_I,
+   -0.019171489571 +  -0.001220893708*_Complex_I,
+   -0.005478667970 +  -0.008887635676*_Complex_I,
+    0.017790116314 +  -0.002906881750*_Complex_I,
+    0.044167796652 +   0.014155489246*_Complex_I,
+    0.065318293362 +   0.037562815973*_Complex_I,
+    0.073959143834 +   0.061781091547*_Complex_I,
+    0.066724360261 +   0.081615483562*_Complex_I,
+    0.046102292983 +   0.093697367755*_Complex_I,
+    0.020214021152 +   0.097173425139*_Complex_I,
+   -0.000316969804 +   0.092297203234*_Complex_I,
+   -0.007456572181 +   0.078335403968*_Complex_I,
+   -0.000109513959 +   0.053648935265*_Complex_I,
+    0.015245116502 +   0.018291043433*_Complex_I,
+    0.028507759029 +  -0.023453077073*_Complex_I,
+    0.031847159216 +  -0.063005523552*_Complex_I,
+    0.023465571027 +  -0.090337797990*_Complex_I,
+    0.007593101686 +  -0.098277994324*_Complex_I,
+   -0.008080860379 +  -0.085900545160*_Complex_I,
+   -0.015861382531 +  -0.059241419647*_Complex_I,
+   -0.011151330660 +  -0.029092859925*_Complex_I,
+    0.006066947770 +  -0.006896796239*_Complex_I,
+    0.031921995818 +  -0.000519982404*_Complex_I,
+    0.060475303064 +  -0.011467885217*_Complex_I,
+    0.085275600399 +  -0.034448894253*_Complex_I,
+    0.100389600600 +  -0.059802028892*_Complex_I,
+    0.101457128362 +  -0.077810326455*_Complex_I,
+    0.087090251053 +  -0.082216982388*_Complex_I,
+    0.060162530535 +  -0.071286280151*_Complex_I,
+    0.027739822472 +  -0.047336780803*_Complex_I,
+   -0.001368012589 +  -0.016047415915*_Complex_I,
+   -0.020598333225 +   0.014978961236*_Complex_I,
+   -0.028528780470 +   0.039136270897*_Complex_I,
+   -0.028450797573 +   0.053688158765*_Complex_I,
+   -0.024993006862 +   0.060174099568*_Complex_I,
+   -0.020853885213 +   0.062097322569*_Complex_I,
+   -0.016606097058 +   0.061833377745*_Complex_I,
+   -0.013052589087 +   0.058990818175*_Complex_I,
+   -0.012808887831 +   0.051482609352*_Complex_I,
+   -0.019272140076 +   0.038484586328*_Complex_I,
+   -0.034383553960 +   0.022386350903*_Complex_I};
+
diff --git a/src/filter/tests/data/iirfilt_rrrf_data_h3x64.c b/src/filter/tests/data/iirfilt_rrrf_data_h3x64.c
new file mode 100644
index 0000000..bfc2add
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_rrrf_data_h3x64.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_rrrf_data_h3x64.c: autotest iirfilt data
+//
+
+float iirfilt_rrrf_data_h3x64_b[] = {
+    0.067455273889,
+    0.134910547778,
+    0.067455273889};
+
+float iirfilt_rrrf_data_h3x64_a[] = {
+    1.000000000000,
+   -1.142980502540,
+    0.412801598096};
+
+float iirfilt_rrrf_data_h3x64_x[] = {
+   -0.028895214542,
+   -0.069021825021,
+    0.030604896335,
+   -0.028355924919,
+    0.116559552929,
+    0.143033796152,
+    0.034240066758,
+    0.245754661028,
+   -0.085048302237,
+    0.037276632441,
+   -0.027971098990,
+    0.028792388017,
+    0.061299546415,
+    0.019217231932,
+   -0.068210516011,
+   -0.030965552175,
+   -0.104543256906,
+   -0.219154025020,
+   -0.060076982615,
+    0.175962646540,
+    0.111681770625,
+    0.038846767797,
+   -0.074493060382,
+   -0.068437932045,
+   -0.210154533613,
+   -0.059543914700,
+   -0.003275225407,
+    0.027794772921,
+    0.001988917543,
+   -0.065169452924,
+    0.076196405886,
+   -0.110895781816,
+   -0.193987624818,
+   -0.048983036631,
+   -0.057777317818,
+   -0.273728284279,
+    0.139583588189,
+   -0.048520445612,
+    0.038028122617,
+    0.142317597668,
+    0.006158624121,
+    0.094921369690,
+    0.106674550704,
+   -0.007693480844,
+    0.009150091523,
+   -0.124420550406,
+   -0.048419736980,
+    0.074400545011,
+   -0.007592916601,
+   -0.023547687981,
+    0.086672380799,
+    0.052067890613,
+    0.134684497125,
+    0.051380348106,
+   -0.007288328205,
+   -0.053065747221,
+   -0.029240930355,
+   -0.010101089710,
+   -0.143148550970,
+   -0.025032159897,
+   -0.067586830823,
+    0.279800929422,
+    0.176230712305,
+   -0.021010061898};
+
+float iirfilt_rrrf_data_h3x64_y[] = {
+   -0.001949134611,
+   -0.010781978190,
+   -0.020715430137,
+   -0.021666234384,
+   -0.010111215930,
+    0.020847673860,
+    0.057471408279,
+    0.087927924056,
+    0.106503226141,
+    0.093052363747,
+    0.059797591762,
+    0.030618424605,
+    0.016444297289,
+    0.017664634020,
+    0.015528526008,
+    0.000462009068,
+   -0.021712865669,
+   -0.055983972836,
+   -0.095696176666,
+   -0.097287095541,
+   -0.044473486551,
+    0.018885017236,
+    0.047693346170,
+    0.034670819807,
+   -0.008493798786,
+   -0.061005530002,
+   -0.088651942626,
+   -0.078727771165,
+   -0.049725604069,
+   -0.026589239497,
+   -0.013382207683,
+   -0.005916351681,
+   -0.024144727506,
+   -0.062110319563,
+   -0.084615103632,
+   -0.100637524633,
+   -0.111508046878,
+   -0.088814270452,
+   -0.050047352820,
+   -0.009082968266,
+    0.032458745755,
+    0.057683060225,
+    0.072948677736,
+    0.079842760097,
+    0.067920438561,
+    0.036995173732,
+   -0.005187492618,
+   -0.031107316161,
+   -0.027154581221,
+   -0.015790078206,
+   -0.004680801567,
+    0.014784961041,
+    0.040787361040,
+    0.065664391433,
+    0.073741325675,
+    0.056081570347,
+    0.024036384207,
+   -0.003883289580,
+   -0.027352105314,
+   -0.051342067332,
+   -0.064984304437,
+   -0.045014384740,
+    0.020451760070,
+    0.083190163742};
+
diff --git a/src/filter/tests/data/iirfilt_rrrf_data_h5x64.c b/src/filter/tests/data/iirfilt_rrrf_data_h5x64.c
new file mode 100644
index 0000000..9d85c50
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_rrrf_data_h5x64.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_rrrf_data_h5x64.c: autotest iirfilt data
+//
+
+float iirfilt_rrrf_data_h5x64_b[] = {
+    0.004824343358,
+    0.019297373431,
+    0.028946060146,
+    0.019297373431,
+    0.004824343358};
+
+float iirfilt_rrrf_data_h5x64_a[] = {
+    1.000000000000,
+   -2.369513007182,
+    2.313988414416,
+   -1.054665405879,
+    0.187379492368};
+
+float iirfilt_rrrf_data_h5x64_x[] = {
+   -0.071435037288,
+   -0.013824638625,
+    0.000604766808,
+   -0.157245338765,
+   -0.148499909731,
+    0.036854467235,
+   -0.092170127241,
+   -0.030750688358,
+   -0.256687046040,
+   -0.011293794477,
+   -0.152735111898,
+   -0.039350864050,
+   -0.026481755383,
+    0.026344708916,
+   -0.165804900216,
+    0.075110855166,
+    0.169733039664,
+    0.112960857425,
+    0.102855045747,
+   -0.058065586616,
+   -0.019482689014,
+   -0.069631526814,
+    0.080391768879,
+   -0.148962611101,
+   -0.063224959929,
+    0.177207853044,
+    0.064119106525,
+    0.182006292285,
+    0.032851335390,
+    0.025859709218,
+    0.012249245529,
+    0.020538703403,
+   -0.010050632447,
+    0.157918073531,
+    0.155799289392,
+    0.161618591338,
+   -0.061931920946,
+   -0.002480403144,
+   -0.080860681406,
+   -0.119527652190,
+    0.041981586004,
+    0.049388453948,
+   -0.119534817516,
+   -0.004446996769,
+   -0.010415835934,
+    0.070065217532,
+    0.016956615378,
+    0.196554086088,
+   -0.089159910224,
+    0.005262040310,
+    0.025380561695,
+   -0.190719045717,
+   -0.137704432243,
+   -0.012169688901,
+   -0.072683558269,
+    0.115787622162,
+   -0.013755488460,
+    0.060139162368,
+    0.002047451288,
+    0.123978241358,
+    0.006962698789,
+   -0.019071948553,
+   -0.062140245356,
+   -0.111724700578};
+
+float iirfilt_rrrf_data_h5x64_y[] = {
+   -0.000344627148,
+   -0.002261801903,
+   -0.006893530299,
+   -0.013989605147,
+   -0.023862607597,
+   -0.038312044528,
+   -0.056089194368,
+   -0.071280615399,
+   -0.079550560246,
+   -0.083028835474,
+   -0.086748998983,
+   -0.092533693567,
+   -0.097949600919,
+   -0.098429082259,
+   -0.090466124573,
+   -0.075337575372,
+   -0.056782281433,
+   -0.034261954387,
+   -0.004056342067,
+    0.032512857031,
+    0.065692345386,
+    0.082703529450,
+    0.076861903089,
+    0.052102523397,
+    0.018224928933,
+   -0.015275405731,
+   -0.038407046800,
+   -0.040894489472,
+   -0.018910631091,
+    0.020295410235,
+    0.061246521124,
+    0.088475490616,
+    0.094228350367,
+    0.080859234115,
+    0.059352937798,
+    0.044593577702,
+    0.046274753880,
+    0.061145236873,
+    0.075354763624,
+    0.074888924042,
+    0.054105775368,
+    0.018945116326,
+   -0.016551695165,
+   -0.040692464489,
+   -0.050718972892,
+   -0.049083944974,
+   -0.038287527306,
+   -0.019928604327,
+    0.004267855964,
+    0.029703630925,
+    0.048181600553,
+    0.052618134862,
+    0.039776735477,
+    0.010021908144,
+   -0.029959006998,
+   -0.066860943714,
+   -0.086821708742,
+   -0.082569377253,
+   -0.056991021236,
+   -0.020344296462,
+    0.016435506907,
+    0.045346639362,
+    0.060576452509,
+    0.058193434336};
+
diff --git a/src/filter/tests/data/iirfilt_rrrf_data_h7x64.c b/src/filter/tests/data/iirfilt_rrrf_data_h7x64.c
new file mode 100644
index 0000000..4f4ebd1
--- /dev/null
+++ b/src/filter/tests/data/iirfilt_rrrf_data_h7x64.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_rrrf_data_h7x64.c: autotest iirfilt data
+//
+
+float iirfilt_rrrf_data_h7x64_b[] = {
+    0.000340537653,
+    0.002043225916,
+    0.005108064791,
+    0.006810753054,
+    0.005108064791,
+    0.002043225916,
+    0.000340537653};
+
+float iirfilt_rrrf_data_h7x64_a[] = {
+    1.000000000000,
+   -3.579434798331,
+    5.658667165934,
+   -4.965415228779,
+    2.529494905841,
+   -0.705274114510,
+    0.083756479619};
+
+float iirfilt_rrrf_data_h7x64_x[] = {
+    0.014320460019,
+   -0.048569878607,
+   -0.028557745674,
+   -0.034174525472,
+    0.041246019674,
+    0.176001687252,
+    0.069536678145,
+    0.085964825944,
+   -0.085126074719,
+   -0.107634084553,
+    0.014274481662,
+   -0.086938201253,
+    0.041217197713,
+   -0.012812412897,
+    0.112987870327,
+   -0.147787754865,
+    0.049941237222,
+    0.088160959037,
+    0.142541502659,
+   -0.024664403921,
+    0.087433714309,
+   -0.131271281366,
+   -0.101343790061,
+   -0.095899142873,
+    0.077515723928,
+   -0.128215021128,
+   -0.085076363355,
+    0.019632244375,
+   -0.024601764309,
+    0.135464981990,
+   -0.096554438720,
+   -0.055332193061,
+    0.107799859774,
+    0.016856032152,
+   -0.133717866605,
+    0.107909223614,
+   -0.000321585843,
+   -0.040614549926,
+   -0.065576753282,
+    0.080916136192,
+   -0.062112903973,
+    0.097792565904,
+    0.053573189262,
+    0.101970184507,
+    0.007522028461,
+   -0.110691703882,
+    0.182044058589,
+   -0.040926270512,
+   -0.092754193263,
+   -0.034827070696,
+   -0.046124677770,
+    0.127874701414,
+    0.085875818987,
+   -0.157133769105,
+   -0.116516897741,
+   -0.217687959211,
+    0.111806530552,
+   -0.053629641350,
+   -0.117298829882,
+    0.008546053824,
+   -0.015153030008,
+   -0.012695501670,
+   -0.154176199014,
+   -0.055346850979};
+
+float iirfilt_rrrf_data_h7x64_y[] = {
+    0.000004876656,
+    0.000030175734,
+    0.000044602316,
+   -0.000207441342,
+   -0.001316715725,
+   -0.003834371001,
+   -0.007275018208,
+   -0.009225570706,
+   -0.005882384450,
+    0.005486878229,
+    0.023590162522,
+    0.042219969073,
+    0.052802355644,
+    0.049012293557,
+    0.030431004460,
+    0.003094166085,
+   -0.023276941451,
+   -0.039861135430,
+   -0.042388322400,
+   -0.031669690474,
+   -0.011613377381,
+    0.012796864675,
+    0.036227386395,
+    0.053027192148,
+    0.057804353706,
+    0.047254362471,
+    0.022407095078,
+   -0.010858715211,
+   -0.043986796642,
+   -0.068957103048,
+   -0.080449888307,
+   -0.076669122221,
+   -0.059625824351,
+   -0.034801293799,
+   -0.009374291628,
+    0.010427870419,
+    0.021114523610,
+    0.022335161439,
+    0.016423814986,
+    0.007136897293,
+   -0.002051012043,
+   -0.008952563583,
+   -0.012527673359,
+   -0.012220519177,
+   -0.007483592886,
+    0.002024488272,
+    0.015481250523,
+    0.030132672907,
+    0.041993092794,
+    0.047610337377,
+    0.045176156453,
+    0.034600601261,
+    0.017674973347,
+   -0.001466118954,
+   -0.017059538441,
+   -0.024634672249,
+   -0.024401342427,
+   -0.021654066688,
+   -0.023062032857,
+   -0.031927019266,
+   -0.046389502559,
+   -0.061179016193,
+   -0.070675753063,
+   -0.071539050622};
+
diff --git a/src/filter/tests/fftfilt_autotest.h b/src/filter/tests/fftfilt_autotest.h
new file mode 100644
index 0000000..45a3b48
--- /dev/null
+++ b/src/filter/tests/fftfilt_autotest.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest fftfilt data definitions
+//
+
+#ifndef __LIQUID_FFTFILT_AUTOTEST_H__
+#define __LIQUID_FFTFILT_AUTOTEST_H__
+
+// autotest helper functions:
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void fftfilt_rrrf_test(float *      _h,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len);
+
+void fftfilt_crcf_test(float *         _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+void fftfilt_cccf_test(float complex * _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+// 
+// autotest datasets
+//
+
+// rrrf
+extern float         fftfilt_rrrf_data_h4x256_h[];
+extern float         fftfilt_rrrf_data_h4x256_x[];
+extern float         fftfilt_rrrf_data_h4x256_y[];
+
+extern float         fftfilt_rrrf_data_h7x256_h[];
+extern float         fftfilt_rrrf_data_h7x256_x[];
+extern float         fftfilt_rrrf_data_h7x256_y[];
+
+extern float         fftfilt_rrrf_data_h13x256_h[];
+extern float         fftfilt_rrrf_data_h13x256_x[];
+extern float         fftfilt_rrrf_data_h13x256_y[];
+
+extern float         fftfilt_rrrf_data_h23x256_h[];
+extern float         fftfilt_rrrf_data_h23x256_x[];
+extern float         fftfilt_rrrf_data_h23x256_y[];
+
+// crcf
+extern float         fftfilt_crcf_data_h4x256_h[];
+extern float complex fftfilt_crcf_data_h4x256_x[];
+extern float complex fftfilt_crcf_data_h4x256_y[];
+
+extern float         fftfilt_crcf_data_h7x256_h[];
+extern float complex fftfilt_crcf_data_h7x256_x[];
+extern float complex fftfilt_crcf_data_h7x256_y[];
+
+extern float         fftfilt_crcf_data_h13x256_h[];
+extern float complex fftfilt_crcf_data_h13x256_x[];
+extern float complex fftfilt_crcf_data_h13x256_y[];
+
+extern float         fftfilt_crcf_data_h23x256_h[];
+extern float complex fftfilt_crcf_data_h23x256_x[];
+extern float complex fftfilt_crcf_data_h23x256_y[];
+
+// cccf
+extern float complex fftfilt_cccf_data_h4x256_h[];
+extern float complex fftfilt_cccf_data_h4x256_x[];
+extern float complex fftfilt_cccf_data_h4x256_y[];
+
+extern float complex fftfilt_cccf_data_h7x256_h[];
+extern float complex fftfilt_cccf_data_h7x256_x[];
+extern float complex fftfilt_cccf_data_h7x256_y[];
+
+extern float complex fftfilt_cccf_data_h13x256_h[];
+extern float complex fftfilt_cccf_data_h13x256_x[];
+extern float complex fftfilt_cccf_data_h13x256_y[];
+
+extern float complex fftfilt_cccf_data_h23x256_h[];
+extern float complex fftfilt_cccf_data_h23x256_x[];
+extern float complex fftfilt_cccf_data_h23x256_y[];
+
+#endif // __LIQUID_FFTFILT_AUTOTEST_H__
+
diff --git a/src/filter/tests/fftfilt_runtest.c b/src/filter/tests/fftfilt_runtest.c
new file mode 100644
index 0000000..5a7377b
--- /dev/null
+++ b/src/filter/tests/fftfilt_runtest.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void fftfilt_rrrf_test(float *      _h,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len)
+{
+    float tol = 0.001f;
+
+    // determine appropriate block size
+    // NOTE: this number can be anything at least _h_len-1
+    unsigned int n = 1 << liquid_nextpow2(_h_len-1);
+
+    // determine number of blocks
+    div_t d = div(_x_len, n);
+    unsigned int num_blocks = d.quot + (d.rem ? 1 : 0);
+    if (liquid_autotest_verbose) {
+        printf("fftfilt_rrrf_test(), h_len: %3u, x_len: %3u (%3u blocks @ %3u samples, %3u remaining)\n",
+                _h_len, _x_len, n, d.quot, d.rem);
+    }
+
+    // load filter coefficients externally
+    fftfilt_rrrf q = fftfilt_rrrf_create(_h, _h_len, n);
+
+    // allocate memory for output
+    float y_test[n*num_blocks];
+
+    unsigned int i;
+
+    // compute output in blocks of size 'n'
+    for (i=0; i<num_blocks; i++)
+        fftfilt_rrrf_execute(q, &_x[i*n], &y_test[i*n]);
+
+    // compare results
+    for (i=0; i<_y_len; i++)
+        CONTEND_DELTA( y_test[i], _y[i], tol );
+    
+    // destroy filter object
+    fftfilt_rrrf_destroy(q);
+}
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void fftfilt_crcf_test(float *         _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // determine appropriate block size
+    // NOTE: this number can be anything at least _h_len-1
+    unsigned int n = 1 << liquid_nextpow2(_h_len-1);
+
+    // determine number of blocks
+    div_t d = div(_x_len, n);
+    unsigned int num_blocks = d.quot + (d.rem ? 1 : 0);
+    if (liquid_autotest_verbose) {
+        printf("fftfilt_crcf_test(), h_len: %3u, x_len: %3u (%3u blocks @ %3u samples, %3u remaining)\n",
+                _h_len, _x_len, n, d.quot, d.rem);
+    }
+
+    // load filter coefficients externally
+    fftfilt_crcf q = fftfilt_crcf_create(_h, _h_len, n);
+
+    // allocate memory for output
+    float complex y_test[n*num_blocks];
+
+    unsigned int i;
+
+    // compute output in blocks of size 'n'
+    for (i=0; i<num_blocks; i++)
+        fftfilt_crcf_execute(q, &_x[i*n], &y_test[i*n]);
+
+    // compare results
+    for (i=0; i<_y_len; i++) {
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    fftfilt_crcf_destroy(q);
+}
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void fftfilt_cccf_test(float complex * _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // determine appropriate block size
+    // NOTE: this number can be anything at least _h_len-1
+    unsigned int n = 1 << liquid_nextpow2(_h_len-1);
+
+    // determine number of blocks
+    div_t d = div(_x_len, n);
+    unsigned int num_blocks = d.quot + (d.rem ? 1 : 0);
+    if (liquid_autotest_verbose) {
+        printf("fftfilt_cccf_test(), h_len: %3u, x_len: %3u (%3u blocks @ %3u samples, %3u remaining)\n",
+                _h_len, _x_len, n, d.quot, d.rem);
+    }
+
+    // load filter coefficients externally
+    fftfilt_cccf q = fftfilt_cccf_create(_h, _h_len, n);
+
+    // allocate memory for output
+    float complex y_test[n*num_blocks];
+
+    unsigned int i;
+
+    // compute output in blocks of size 'n'
+    for (i=0; i<num_blocks; i++)
+        fftfilt_cccf_execute(q, &_x[i*n], &y_test[i*n]);
+
+    // compare results
+    for (i=0; i<_y_len; i++) {
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    fftfilt_cccf_destroy(q);
+}
+
diff --git a/src/filter/tests/fftfilt_xxxf_autotest.c b/src/filter/tests/fftfilt_xxxf_autotest.c
new file mode 100644
index 0000000..ad66d15
--- /dev/null
+++ b/src/filter/tests/fftfilt_xxxf_autotest.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// fftfilt_xxxf_autotest.c : test floating-point filters
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/filter/tests/fftfilt_autotest.h"
+
+// 
+// AUTOTEST: fftfilt_rrrf tests
+//
+void autotest_fftfilt_rrrf_data_h4x256()
+{
+    fftfilt_rrrf_test(fftfilt_rrrf_data_h4x256_h, 4,
+                      fftfilt_rrrf_data_h4x256_x, 256,
+                      fftfilt_rrrf_data_h4x256_y, 256);
+}
+void autotest_fftfilt_rrrf_data_h7x256()
+{
+    fftfilt_rrrf_test(fftfilt_rrrf_data_h7x256_h, 7,
+                      fftfilt_rrrf_data_h7x256_x, 256,
+                      fftfilt_rrrf_data_h7x256_y, 256);
+}
+void autotest_fftfilt_rrrf_data_h13x256()
+{
+    fftfilt_rrrf_test(fftfilt_rrrf_data_h13x256_h, 13,
+                      fftfilt_rrrf_data_h13x256_x, 256,
+                      fftfilt_rrrf_data_h13x256_y, 256);
+}
+void autotest_fftfilt_rrrf_data_h23x256()
+{
+    fftfilt_rrrf_test(fftfilt_rrrf_data_h23x256_h, 23,
+                      fftfilt_rrrf_data_h23x256_x, 256,
+                      fftfilt_rrrf_data_h23x256_y, 256);
+}
+
+
+// 
+// AUTOTEST: fftfilt_crcf tests
+//
+void autotest_fftfilt_crcf_data_h4x256()
+{
+    fftfilt_crcf_test(fftfilt_crcf_data_h4x256_h, 4,
+                      fftfilt_crcf_data_h4x256_x, 256,
+                      fftfilt_crcf_data_h4x256_y, 256);
+}
+void autotest_fftfilt_crcf_data_h7x256()
+{
+    fftfilt_crcf_test(fftfilt_crcf_data_h7x256_h, 7,
+                      fftfilt_crcf_data_h7x256_x, 256,
+                      fftfilt_crcf_data_h7x256_y, 256);
+}
+void autotest_fftfilt_crcf_data_h13x256()
+{
+    fftfilt_crcf_test(fftfilt_crcf_data_h13x256_h, 13,
+                      fftfilt_crcf_data_h13x256_x, 256,
+                      fftfilt_crcf_data_h13x256_y, 256);
+}
+void autotest_fftfilt_crcf_data_h23x256()
+{
+    fftfilt_crcf_test(fftfilt_crcf_data_h23x256_h, 23,
+                      fftfilt_crcf_data_h23x256_x, 256,
+                      fftfilt_crcf_data_h23x256_y, 256);
+}
+
+
+// 
+// AUTOTEST: fftfilt_cccf tests
+//
+void autotest_fftfilt_cccf_data_h4x256()
+{
+    fftfilt_cccf_test(fftfilt_cccf_data_h4x256_h, 4,
+                      fftfilt_cccf_data_h4x256_x, 256,
+                      fftfilt_cccf_data_h4x256_y, 256);
+}
+void autotest_fftfilt_cccf_data_h7x256()
+{
+    fftfilt_cccf_test(fftfilt_cccf_data_h7x256_h, 7,
+                      fftfilt_cccf_data_h7x256_x, 256,
+                      fftfilt_cccf_data_h7x256_y, 256);
+}
+void autotest_fftfilt_cccf_data_h13x256()
+{
+    fftfilt_cccf_test(fftfilt_cccf_data_h13x256_h, 13,
+                      fftfilt_cccf_data_h13x256_x, 256,
+                      fftfilt_cccf_data_h13x256_y, 256);
+}
+void autotest_fftfilt_cccf_data_h23x256()
+{
+    fftfilt_cccf_test(fftfilt_cccf_data_h23x256_h, 23,
+                      fftfilt_cccf_data_h23x256_x, 256,
+                      fftfilt_cccf_data_h23x256_y, 256);
+}
+
+
diff --git a/src/filter/tests/filter_crosscorr_autotest.c b/src/filter/tests/filter_crosscorr_autotest.c
new file mode 100644
index 0000000..22adf9c
--- /dev/null
+++ b/src/filter/tests/filter_crosscorr_autotest.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+void autotest_filter_crosscorr_rrrf()
+{
+    // options
+    float tol = 1e-3f;
+
+    // input vectors
+    int x_len = 16;
+    float x[16] = {
+          0.25887000,   0.11752000,   0.67812000,  -1.02480000, 
+          1.46750000,  -0.67462000,   0.93029000,   0.98751000, 
+          0.00969890,   1.05300000,   1.38100000,   1.47540000, 
+          1.14110000,  -0.39480000,  -0.30426000,   1.58190000 };
+
+    int y_len = 8;
+    float y[8] = {
+         -1.15920000,  -1.57390000,   0.65239000,  -0.54542000, 
+         -0.97277000,   0.99115000,  -0.76247000,  -1.08210000 };
+
+
+    // derived values
+    unsigned int rxy_len = x_len + y_len - 1;
+    float rxy[23];
+    float rxy_test[23] = {
+         -0.28013000,  -0.32455000,  -0.56685000,   0.45660000, 
+         -0.39008000,  -1.95950000,   1.25850000,  -3.35780000, 
+         -1.85760000,   1.07920000,  -5.31760000,  -2.18630000, 
+         -2.05850000,  -3.52450000,  -0.90010000,  -4.55350000, 
+         -4.17770000,  -1.09920000,  -5.13670000,  -1.76270000, 
+          1.96850000,  -2.13700000,  -1.83370000};
+
+
+    if (liquid_autotest_verbose)
+        printf("testing corr(x,y):\n");
+
+    // corr(x,y)
+    int i;
+    for (i=0; i<rxy_len; i++) {
+        int lag = i - y_len + 1;
+        rxy[i] = liquid_filter_crosscorr(x,x_len, y,y_len, lag);
+
+        // print results
+        if (liquid_autotest_verbose)
+            printf("  rxy(%3d) = %12.8f (expected %12.8f, e=%12.4e)\n", lag, rxy[i], rxy_test[i], rxy[i]-rxy_test[i]);
+    }
+    for (i=0; i<rxy_len; i++)
+        CONTEND_DELTA( rxy[i], rxy_test[i], tol );
+
+
+    // derived values
+    unsigned int ryx_len = x_len + y_len - 1;
+    float ryx[23];
+    float ryx_test[23] = {
+         -1.83370000,  -2.13700000,   1.96850000,  -1.76270000, 
+         -5.13670000,  -1.09920000,  -4.17770000,  -4.55350000, 
+         -0.90010000,  -3.52450000,  -2.05850000,  -2.18630000, 
+         -5.31760000,   1.07920000,  -1.85760000,  -3.35780000, 
+          1.25850000,  -1.95950000,  -0.39008000,   0.45660000, 
+         -0.56685000,  -0.32455000,  -0.28013000};
+        
+    if (liquid_autotest_verbose)
+        printf("testing corr(y,x):\n");
+
+    // corr(y,x)
+    for (i=0; i<ryx_len; i++) {
+        int lag = i - x_len + 1;
+        ryx[i] = liquid_filter_crosscorr(y,y_len, x,x_len, lag);
+
+        // print results
+        if (liquid_autotest_verbose)
+            printf("  ryx(%3d) = %12.8f (expected %12.8f, e=%12.4e)\n", lag, ryx[i], ryx_test[i], ryx[i]-ryx_test[i]);
+    }
+    for (i=0; i<ryx_len; i++)
+        CONTEND_DELTA( ryx[i], ryx_test[i], tol );
+}
+
diff --git a/src/filter/tests/firdecim_autotest.h b/src/filter/tests/firdecim_autotest.h
new file mode 100644
index 0000000..b599364
--- /dev/null
+++ b/src/filter/tests/firdecim_autotest.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest firdecim data definitions
+//
+
+#ifndef __LIQUID_FIRDECIM_H__
+#define __LIQUID_FIRDECIM_H__
+
+// autotest helper functions:
+//  _M      :   decimation factor
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firdecim_rrrf_test(unsigned int _M,
+                        float *      _h,
+                        unsigned int _h_len,
+                        float *      _x,
+                        unsigned int _x_len,
+                        float *      _y,
+                        unsigned int _y_len);
+
+void firdecim_crcf_test(unsigned int    _M,
+                        float *         _h,
+                        unsigned int    _h_len,
+                        float complex * _x,
+                        unsigned int    _x_len,
+                        float complex * _y,
+                        unsigned int    _y_len);
+
+void firdecim_cccf_test(unsigned int    _M,
+                        float complex * _h,
+                        unsigned int    _h_len,
+                        float complex * _x,
+                        unsigned int    _x_len,
+                        float complex * _y,
+                        unsigned int    _y_len);
+
+// 
+// autotest datasets
+//
+
+// rrrf
+extern float         firdecim_rrrf_data_M2h4x20_h[];
+extern float         firdecim_rrrf_data_M2h4x20_x[];
+extern float         firdecim_rrrf_data_M2h4x20_y[];
+
+extern float         firdecim_rrrf_data_M3h7x30_h[];
+extern float         firdecim_rrrf_data_M3h7x30_x[];
+extern float         firdecim_rrrf_data_M3h7x30_y[];
+
+extern float         firdecim_rrrf_data_M4h13x40_h[];
+extern float         firdecim_rrrf_data_M4h13x40_x[];
+extern float         firdecim_rrrf_data_M4h13x40_y[];
+
+extern float         firdecim_rrrf_data_M5h23x50_h[];
+extern float         firdecim_rrrf_data_M5h23x50_x[];
+extern float         firdecim_rrrf_data_M5h23x50_y[];
+
+// crcf
+extern float         firdecim_crcf_data_M2h4x20_h[];
+extern float complex firdecim_crcf_data_M2h4x20_x[];
+extern float complex firdecim_crcf_data_M2h4x20_y[];
+
+extern float         firdecim_crcf_data_M3h7x30_h[];
+extern float complex firdecim_crcf_data_M3h7x30_x[];
+extern float complex firdecim_crcf_data_M3h7x30_y[];
+
+extern float         firdecim_crcf_data_M4h13x40_h[];
+extern float complex firdecim_crcf_data_M4h13x40_x[];
+extern float complex firdecim_crcf_data_M4h13x40_y[];
+
+extern float         firdecim_crcf_data_M5h23x50_h[];
+extern float complex firdecim_crcf_data_M5h23x50_x[];
+extern float complex firdecim_crcf_data_M5h23x50_y[];
+
+// cccf
+extern float complex firdecim_cccf_data_M2h4x20_h[];
+extern float complex firdecim_cccf_data_M2h4x20_x[];
+extern float complex firdecim_cccf_data_M2h4x20_y[];
+
+extern float complex firdecim_cccf_data_M3h7x30_h[];
+extern float complex firdecim_cccf_data_M3h7x30_x[];
+extern float complex firdecim_cccf_data_M3h7x30_y[];
+
+extern float complex firdecim_cccf_data_M4h13x40_h[];
+extern float complex firdecim_cccf_data_M4h13x40_x[];
+extern float complex firdecim_cccf_data_M4h13x40_y[];
+
+extern float complex firdecim_cccf_data_M5h23x50_h[];
+extern float complex firdecim_cccf_data_M5h23x50_x[];
+extern float complex firdecim_cccf_data_M5h23x50_y[];
+
+#endif // __LIQUID_FIRDECIM_H__
+
diff --git a/src/filter/tests/firdecim_runtest.c b/src/filter/tests/firdecim_runtest.c
new file mode 100644
index 0000000..6c3a5b8
--- /dev/null
+++ b/src/filter/tests/firdecim_runtest.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _M      :   decimation rate
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firdecim_rrrf_test(unsigned int    _M,
+                        float *         _h,
+                        unsigned int    _h_len,
+                        float *         _x,
+                        unsigned int    _x_len,
+                        float *         _y,
+                        unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firdecim_rrrf q = firdecim_rrrf_create(_M, _h, _h_len);
+
+    // allocate memory for output
+    float y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_y_len; i++) {
+        firdecim_rrrf_execute(q, &_x[_M*i], &y_test[i]);
+        
+        CONTEND_DELTA( y_test[i], _y[i], tol );
+    }
+    
+    // destroy decimator object object
+    firdecim_rrrf_destroy(q);
+}
+
+// autotest helper function
+//  _M      :   decimation rate
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firdecim_crcf_test(unsigned int    _M,
+                        float *         _h,
+                        unsigned int    _h_len,
+                        float complex * _x,
+                        unsigned int    _x_len,
+                        float complex * _y,
+                        unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firdecim_crcf q = firdecim_crcf_create(_M, _h, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_y_len; i++) {
+        firdecim_crcf_execute(q, &_x[_M*i], &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy decimator object object
+    firdecim_crcf_destroy(q);
+}
+
+// autotest helper function
+//  _M      :   decimation rate
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firdecim_cccf_test(unsigned int    _M,
+                        float complex * _h,
+                        unsigned int    _h_len,
+                        float complex * _x,
+                        unsigned int    _x_len,
+                        float complex * _y,
+                        unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firdecim_cccf q = firdecim_cccf_create(_M, _h, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_y_len; i++) {
+        firdecim_cccf_execute(q, &_x[_M*i], &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy decimator object object
+    firdecim_cccf_destroy(q);
+}
+
diff --git a/src/filter/tests/firdecim_xxxf_autotest.c b/src/filter/tests/firdecim_xxxf_autotest.c
new file mode 100644
index 0000000..d058ef3
--- /dev/null
+++ b/src/filter/tests/firdecim_xxxf_autotest.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firdecim_xxxf_autotest.c : test floating-point filters
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/filter/tests/firdecim_autotest.h"
+
+// 
+// AUTOTEST: firdecim_rrrf tests
+//
+void autotest_firdecim_rrrf_data_M2h4x20()
+{
+    firdecim_rrrf_test(2,
+                       firdecim_rrrf_data_M2h4x20_h, 4,
+                       firdecim_rrrf_data_M2h4x20_x, 20,
+                       firdecim_rrrf_data_M2h4x20_y, 10);
+}
+void autotest_firdecim_rrrf_data_M3h7x30()
+{
+    firdecim_rrrf_test(3,
+                       firdecim_rrrf_data_M3h7x30_h, 7,
+                       firdecim_rrrf_data_M3h7x30_x, 30,
+                       firdecim_rrrf_data_M3h7x30_y, 10);
+}
+void autotest_firdecim_rrrf_data_M4h13x40()
+{
+    firdecim_rrrf_test(4,
+                       firdecim_rrrf_data_M4h13x40_h, 13,
+                       firdecim_rrrf_data_M4h13x40_x, 40,
+                       firdecim_rrrf_data_M4h13x40_y, 10);
+}
+void autotest_firdecim_rrrf_data_M5h23x50()
+{
+    firdecim_rrrf_test(5,
+                       firdecim_rrrf_data_M5h23x50_h, 23,
+                       firdecim_rrrf_data_M5h23x50_x, 50,
+                       firdecim_rrrf_data_M5h23x50_y, 10);
+}
+
+
+// 
+// AUTOTEST: firdecim_crcf tests
+//
+void autotest_firdecim_crcf_data_M2h4x20()
+{
+    firdecim_crcf_test(2,
+                       firdecim_crcf_data_M2h4x20_h, 4,
+                       firdecim_crcf_data_M2h4x20_x, 20,
+                       firdecim_crcf_data_M2h4x20_y, 8);
+}
+void autotest_firdecim_crcf_data_M3h7x30()
+{
+    firdecim_crcf_test(3,
+                       firdecim_crcf_data_M3h7x30_h, 7,
+                       firdecim_crcf_data_M3h7x30_x, 30,
+                       firdecim_crcf_data_M3h7x30_y, 10);
+}
+void autotest_firdecim_crcf_data_M4h13x40()
+{
+    firdecim_crcf_test(4,
+                       firdecim_crcf_data_M4h13x40_h, 13,
+                       firdecim_crcf_data_M4h13x40_x, 40,
+                       firdecim_crcf_data_M4h13x40_y, 10);
+}
+void autotest_firdecim_crcf_data_M5h23x50()
+{
+    firdecim_crcf_test(5,
+                       firdecim_crcf_data_M5h23x50_h, 23,
+                       firdecim_crcf_data_M5h23x50_x, 50,
+                       firdecim_crcf_data_M5h23x50_y, 10);
+}
+
+
+// 
+// AUTOTEST: firdecim_cccf tests
+//
+void autotest_firdecim_cccf_data_M2h4x20()
+{
+    firdecim_cccf_test(2,
+                       firdecim_cccf_data_M2h4x20_h, 4,
+                       firdecim_cccf_data_M2h4x20_x, 20,
+                       firdecim_cccf_data_M2h4x20_y, 8);
+}
+void autotest_firdecim_cccf_data_M3h7x30()
+{
+    firdecim_cccf_test(3,
+                       firdecim_cccf_data_M3h7x30_h, 7,
+                       firdecim_cccf_data_M3h7x30_x, 30,
+                       firdecim_cccf_data_M3h7x30_y, 10);
+}
+void autotest_firdecim_cccf_data_M4h13x40()
+{
+    firdecim_cccf_test(4,
+                       firdecim_cccf_data_M4h13x40_h, 13,
+                       firdecim_cccf_data_M4h13x40_x, 40,
+                       firdecim_cccf_data_M4h13x40_y, 10);
+}
+void autotest_firdecim_cccf_data_M5h23x50()
+{
+    firdecim_cccf_test(5,
+                       firdecim_cccf_data_M5h23x50_h, 23,
+                       firdecim_cccf_data_M5h23x50_x, 50,
+                       firdecim_cccf_data_M5h23x50_y, 10);
+}
+
+
diff --git a/src/filter/tests/firdes_autotest.c b/src/filter/tests/firdes_autotest.c
new file mode 100644
index 0000000..2cdf00a
--- /dev/null
+++ b/src/filter/tests/firdes_autotest.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+void autotest_liquid_firdes_rcos() {
+
+    // Initialize variables
+    unsigned int k=2, m=3;
+    float beta=0.3f;
+    float offset=0.0f;
+
+    // Initialize pre-determined coefficient array
+    float h0[13] = {
+       1.65502646542134e-17,
+       7.20253052925685e-02,
+      -1.26653717080575e-16,
+      -1.74718023726940e-01,
+       2.95450626814946e-16,
+       6.23332275392119e-01,
+       1.00000000000000e+00,
+       6.23332275392119e-01,
+      -2.23850244261176e-16,
+      -1.74718023726940e-01,
+      -2.73763990895627e-17,
+       7.20253052925685e-02
+    };
+
+    // Create filter
+    float h[13];
+    liquid_firdes_rcos(k,m,beta,offset,h);
+
+    // Ensure data are equal
+    unsigned int i;
+    for (i=0; i<13; i++)
+        CONTEND_DELTA( h[i], h0[i], 0.00001f );
+}
+
+void autotest_liquid_firdes_rrcos() {
+
+    // Initialize variables
+    unsigned int k=2, m=3;
+    float beta=0.3f;
+    float offset=0.0f;
+
+    // Initialize pre-determined coefficient array
+    float h0[13] = {
+       -3.311577E-02, 
+        4.501582E-02, 
+        5.659688E-02, 
+       -1.536039E-01, 
+       -7.500154E-02, 
+        6.153450E-01, 
+        1.081972E+00, 
+        6.153450E-01, 
+       -7.500154E-02, 
+       -1.536039E-01, 
+        5.659688E-02, 
+        4.501582E-02,
+       -3.311577E-02}; 
+
+    // Create filter
+    float h[13];
+    liquid_firdes_rrcos(k,m,beta,offset,h);
+
+    // Ensure data are equal
+    unsigned int i;
+    for (i=0; i<13; i++)
+        CONTEND_DELTA( h[i], h0[i], 0.00001f );
+}
+
+
+void autotest_liquid_firdes_rkaiser()
+{
+    // Initialize variables
+    unsigned int k=2, m=3;
+    float beta=0.3f;
+    float offset=0.0f;
+    float isi_test = -30.0f;
+
+    // Create filter
+    unsigned int h_len = 2*k*m+1;
+    float h[h_len];
+    liquid_firdes_rkaiser(k,m,beta,offset,h);
+
+    // compute filter ISI
+    float isi_max;
+    float isi_rms;
+    liquid_filter_isi(h,k,m,&isi_rms,&isi_max);
+
+    // convert to log scale
+    isi_max = 20*log10f(isi_max);
+    isi_rms = 20*log10f(isi_rms);
+
+    // ensure ISI is sufficiently small
+    CONTEND_LESS_THAN(isi_max, isi_test);
+    CONTEND_LESS_THAN(isi_rms, isi_test);
+}
+
+
diff --git a/src/filter/tests/firdespm_autotest.c b/src/filter/tests/firdespm_autotest.c
new file mode 100644
index 0000000..b3df189
--- /dev/null
+++ b/src/filter/tests/firdespm_autotest.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// References:
+//  [McClellan:1973] J. H. McClellan, T. W. Parks, L. R. Rabiner, "A
+//      Computer Program for Designing Optimum FIR Linear Phase
+//      Digital Filters," IEEE Transactions on Audio and
+//      Electroacoustics, vol. AU-21, No. 6, December 1973.
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+void autotest_firdespm_bandpass_n24()
+{
+    // [McClellan:1973], Figure 7.
+
+    // Initialize variables
+    unsigned int n=24;
+    unsigned int num_bands=2;
+    float bands[4]  = {0.0f,0.08f,0.16f,0.50f};
+    float des[2]    = {1.0f,0.0f};
+    float weights[2]= {1.0f,1.0f};
+    liquid_firdespm_btype btype = LIQUID_FIRDESPM_BANDPASS;
+    float tol = 1e-4f;
+
+    // Initialize pre-determined coefficient array
+    float h0[24] = {
+        0.33740917e-2f,
+        0.14938299e-1f,
+        0.10569360e-1f,
+        0.25415067e-2f,
+       -0.15929392e-1f,
+       -0.34085343e-1f,
+       -0.38112177e-1f,
+       -0.14629169e-1f,
+        0.40089541e-1f,
+        0.11540713e-0f,
+        0.18850752e-0f,
+        0.23354606e-0f,
+        // symmetry
+        0.23354606e-0f,
+        0.18850752e-0f,
+        0.11540713e-0f,
+        0.40089541e-1f,
+       -0.14629169e-1f,
+       -0.38112177e-1f,
+       -0.34085343e-1f,
+       -0.15929392e-1f,
+        0.25415067e-2f,
+        0.10569360e-1f,
+        0.14938299e-1f,
+        0.33740917e-2f
+    };
+
+    // Create filter
+    float h[n];
+    firdespm_run(n,num_bands,bands,des,weights,NULL,btype,h);
+
+    // Ensure data are equal
+    unsigned int i;
+    for (i=0; i<n; i++)
+        CONTEND_DELTA( h[i], h0[i], tol );
+}
+
+
+void autotest_firdespm_bandpass_n32()
+{
+    // [McClellan:1973], Figure 9.
+
+    // Initialize variables
+    unsigned int n=32;
+    unsigned int num_bands = 3;
+    float bands[6] = {  0.0f,   0.1f,
+                        0.2f,   0.35f,
+                        0.425f, 0.5f};
+    float des[3] = {0.0f, 1.0f, 0.0f};
+    float weights[3] = {10.0f, 1.0f, 10.0f};
+    liquid_firdespm_btype btype = LIQUID_FIRDESPM_BANDPASS;
+    float tol = 1e-4f;
+
+    // Initialize pre-determined coefficient array
+    float h0[32] = {
+       -0.57534121e-2f,
+        0.99027198e-3f,
+        0.75733545e-2f,
+       -0.65141192e-2f,
+        0.13960525e-1f,
+        0.22951469e-2f,
+       -0.19994067e-1f,
+        0.71369560e-2f,
+       -0.39657363e-1f,
+        0.11260114e-1f,
+        0.66233643e-1f,
+       -0.10497223e-1f,
+        0.85136133e-1f,
+       -0.12024993e+0f,
+       -0.29678577e+0f,
+        0.30410917e+0f,
+        // symmetry
+        0.30410917e+0f,
+       -0.29678577e+0f,
+       -0.12024993e+0f,
+        0.85136133e-1f,
+       -0.10497223e-1f,
+        0.66233643e-1f,
+        0.11260114e-1f,
+       -0.39657363e-1f,
+        0.71369560e-2f,
+       -0.19994067e-1f,
+        0.22951469e-2f,
+        0.13960525e-1f,
+       -0.65141192e-2f,
+        0.75733545e-2f,
+        0.99027198e-3f,
+       -0.57534121e-2f
+    };
+
+    // Create filter
+    float h[n];
+    firdespm_run(n,num_bands,bands,des,weights,NULL,btype,h);
+
+    // Ensure data are equal
+    unsigned int i;
+    for (i=0; i<n; i++)
+        CONTEND_DELTA( h[i], h0[i], tol );
+}
+
diff --git a/src/filter/tests/firfilt_autotest.h b/src/filter/tests/firfilt_autotest.h
new file mode 100644
index 0000000..d3d980b
--- /dev/null
+++ b/src/filter/tests/firfilt_autotest.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest firfilt data definitions
+//
+
+#ifndef __LIQUID_FIRFILT_H__
+#define __LIQUID_FIRFILT_H__
+
+// autotest helper functions:
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firfilt_rrrf_test(float *      _h,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len);
+
+void firfilt_crcf_test(float *         _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+void firfilt_cccf_test(float complex * _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+// 
+// autotest datasets
+//
+
+// rrrf
+extern float         firfilt_rrrf_data_h4x8_h[];
+extern float         firfilt_rrrf_data_h4x8_x[];
+extern float         firfilt_rrrf_data_h4x8_y[];
+
+extern float         firfilt_rrrf_data_h7x16_h[];
+extern float         firfilt_rrrf_data_h7x16_x[];
+extern float         firfilt_rrrf_data_h7x16_y[];
+
+extern float         firfilt_rrrf_data_h13x32_h[];
+extern float         firfilt_rrrf_data_h13x32_x[];
+extern float         firfilt_rrrf_data_h13x32_y[];
+
+extern float         firfilt_rrrf_data_h23x64_h[];
+extern float         firfilt_rrrf_data_h23x64_x[];
+extern float         firfilt_rrrf_data_h23x64_y[];
+
+// crcf
+extern float         firfilt_crcf_data_h4x8_h[];
+extern float complex firfilt_crcf_data_h4x8_x[];
+extern float complex firfilt_crcf_data_h4x8_y[];
+
+extern float         firfilt_crcf_data_h7x16_h[];
+extern float complex firfilt_crcf_data_h7x16_x[];
+extern float complex firfilt_crcf_data_h7x16_y[];
+
+extern float         firfilt_crcf_data_h13x32_h[];
+extern float complex firfilt_crcf_data_h13x32_x[];
+extern float complex firfilt_crcf_data_h13x32_y[];
+
+extern float         firfilt_crcf_data_h23x64_h[];
+extern float complex firfilt_crcf_data_h23x64_x[];
+extern float complex firfilt_crcf_data_h23x64_y[];
+
+// cccf
+extern float complex firfilt_cccf_data_h4x8_h[];
+extern float complex firfilt_cccf_data_h4x8_x[];
+extern float complex firfilt_cccf_data_h4x8_y[];
+
+extern float complex firfilt_cccf_data_h7x16_h[];
+extern float complex firfilt_cccf_data_h7x16_x[];
+extern float complex firfilt_cccf_data_h7x16_y[];
+
+extern float complex firfilt_cccf_data_h13x32_h[];
+extern float complex firfilt_cccf_data_h13x32_x[];
+extern float complex firfilt_cccf_data_h13x32_y[];
+
+extern float complex firfilt_cccf_data_h23x64_h[];
+extern float complex firfilt_cccf_data_h23x64_x[];
+extern float complex firfilt_cccf_data_h23x64_y[];
+
+#endif // __LIQUID_FIRFILT_H__
+
diff --git a/src/filter/tests/firfilt_runtest.c b/src/filter/tests/firfilt_runtest.c
new file mode 100644
index 0000000..4ab75d9
--- /dev/null
+++ b/src/filter/tests/firfilt_runtest.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firfilt_rrrf_test(float *      _h,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firfilt_rrrf q = firfilt_rrrf_create(_h, _h_len);
+
+    // allocate memory for output
+    float y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        firfilt_rrrf_push(q, _x[i]);
+        firfilt_rrrf_execute(q, &y_test[i]);
+        
+        CONTEND_DELTA( y_test[i], _y[i], tol );
+    }
+
+    // destroy filter object
+    firfilt_rrrf_destroy(q);
+}
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firfilt_crcf_test(float *         _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firfilt_crcf q = firfilt_crcf_create(_h, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        firfilt_crcf_push(q, _x[i]);
+        firfilt_crcf_execute(q, &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    firfilt_crcf_destroy(q);
+}
+
+// autotest helper function
+//  _h      :   filter coefficients
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void firfilt_cccf_test(float complex * _h,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    firfilt_cccf q = firfilt_cccf_create(_h, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        firfilt_cccf_push(q, _x[i]);
+        firfilt_cccf_execute(q, &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    firfilt_cccf_destroy(q);
+}
+
diff --git a/src/filter/tests/firfilt_xxxf_autotest.c b/src/filter/tests/firfilt_xxxf_autotest.c
new file mode 100644
index 0000000..365db3a
--- /dev/null
+++ b/src/filter/tests/firfilt_xxxf_autotest.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firfilt_xxxf_autotest.c : test floating-point filters
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/filter/tests/firfilt_autotest.h"
+
+// 
+// AUTOTEST: firfilt_rrrf tests
+//
+void autotest_firfilt_rrrf_data_h4x8()
+{
+    firfilt_rrrf_test(firfilt_rrrf_data_h4x8_h, 4,
+                      firfilt_rrrf_data_h4x8_x, 8,
+                      firfilt_rrrf_data_h4x8_y, 8);
+}
+void autotest_firfilt_rrrf_data_h7x16()
+{
+    firfilt_rrrf_test(firfilt_rrrf_data_h7x16_h, 7,
+                      firfilt_rrrf_data_h7x16_x, 16,
+                      firfilt_rrrf_data_h7x16_y, 16);
+}
+void autotest_firfilt_rrrf_data_h13x32()
+{
+    firfilt_rrrf_test(firfilt_rrrf_data_h13x32_h, 13,
+                      firfilt_rrrf_data_h13x32_x, 32,
+                      firfilt_rrrf_data_h13x32_y, 32);
+}
+void autotest_firfilt_rrrf_data_h23x64()
+{
+    firfilt_rrrf_test(firfilt_rrrf_data_h23x64_h, 23,
+                      firfilt_rrrf_data_h23x64_x, 64,
+                      firfilt_rrrf_data_h23x64_y, 64);
+}
+
+
+// 
+// AUTOTEST: firfilt_crcf tests
+//
+void autotest_firfilt_crcf_data_h4x8()
+{
+    firfilt_crcf_test(firfilt_crcf_data_h4x8_h, 4,
+                      firfilt_crcf_data_h4x8_x, 8,
+                      firfilt_crcf_data_h4x8_y, 8);
+}
+void autotest_firfilt_crcf_data_h7x16()
+{
+    firfilt_crcf_test(firfilt_crcf_data_h7x16_h, 7,
+                      firfilt_crcf_data_h7x16_x, 16,
+                      firfilt_crcf_data_h7x16_y, 16);
+}
+void autotest_firfilt_crcf_data_h13x32()
+{
+    firfilt_crcf_test(firfilt_crcf_data_h13x32_h, 13,
+                      firfilt_crcf_data_h13x32_x, 32,
+                      firfilt_crcf_data_h13x32_y, 32);
+}
+void autotest_firfilt_crcf_data_h23x64()
+{
+    firfilt_crcf_test(firfilt_crcf_data_h23x64_h, 23,
+                      firfilt_crcf_data_h23x64_x, 64,
+                      firfilt_crcf_data_h23x64_y, 64);
+}
+
+
+// 
+// AUTOTEST: firfilt_cccf tests
+//
+void autotest_firfilt_cccf_data_h4x8()
+{
+    firfilt_cccf_test(firfilt_cccf_data_h4x8_h, 4,
+                      firfilt_cccf_data_h4x8_x, 8,
+                      firfilt_cccf_data_h4x8_y, 8);
+}
+void autotest_firfilt_cccf_data_h7x16()
+{
+    firfilt_cccf_test(firfilt_cccf_data_h7x16_h, 7,
+                      firfilt_cccf_data_h7x16_x, 16,
+                      firfilt_cccf_data_h7x16_y, 16);
+}
+void autotest_firfilt_cccf_data_h13x32()
+{
+    firfilt_cccf_test(firfilt_cccf_data_h13x32_h, 13,
+                      firfilt_cccf_data_h13x32_x, 32,
+                      firfilt_cccf_data_h13x32_y, 32);
+}
+void autotest_firfilt_cccf_data_h23x64()
+{
+    firfilt_cccf_test(firfilt_cccf_data_h23x64_h, 23,
+                      firfilt_cccf_data_h23x64_x, 64,
+                      firfilt_cccf_data_h23x64_y, 64);
+}
+
+
diff --git a/src/filter/tests/firhilb_autotest.c b/src/filter/tests/firhilb_autotest.c
new file mode 100644
index 0000000..d0a63ff
--- /dev/null
+++ b/src/filter/tests/firhilb_autotest.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+#define J _Complex_I
+
+//
+// AUTOTEST: Hilbert transform, 2:1 decimator
+//
+void autotest_firhilbf_decim()
+{
+    float x[32] = {
+         1.0000,  0.7071,  0.0000, -0.7071, -1.0000, -0.7071, -0.0000,  0.7071,
+         1.0000,  0.7071,  0.0000, -0.7071, -1.0000, -0.7071, -0.0000,  0.7071,
+         1.0000,  0.7071,  0.0000, -0.7071, -1.0000, -0.7071, -0.0000,  0.7071,
+         1.0000,  0.7071, -0.0000, -0.7071, -1.0000, -0.7071, -0.0000,  0.7071
+    };
+
+    float complex test[16] = {
+         0.0000+J*-0.0055,  0.0000+J*-0.0231,  0.0000+J*-0.0605,  0.0000+J*-0.1459,
+         0.0000+J*-0.5604,  0.7071+J* 0.7669, -0.7071+J* 0.7294, -0.7071+J*-0.7008,
+         0.7071+J*-0.7064,  0.7071+J* 0.7064, -0.7071+J* 0.7064, -0.7071+J*-0.7064,
+         0.7071+J*-0.7064,  0.7071+J* 0.7064, -0.7071+J* 0.7064, -0.7071+J*-0.7064
+    };
+
+    float complex y[16];
+    unsigned int m=5;   // h_len = 4*m+1 = 21
+    firhilbf ht = firhilbf_create(m,60.0f);
+    float tol=0.005f;
+
+    // run decimator
+    unsigned int i;
+    for (i=0; i<16; i++)
+        firhilbf_decim_execute(ht, &x[2*i], &y[i]);
+
+    if (liquid_autotest_verbose) {
+        printf("hilbert transform decimator output:\n");
+        for (i=0; i<16; i++)
+            printf("  y(%3u) = %8.5f + j*%8.5f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    // run validation
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA(crealf(y[i]), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(y[i]), cimagf(test[i]), tol);
+    }
+
+    // clean up filter object
+    firhilbf_destroy(ht);
+}
+
+//
+// AUTOTEST: Hilbert transform, 1:2 interpolator
+//
+void autotest_firhilbf_interp()
+{
+    float complex x[16] = {
+         1.0000+J* 0.0000,  0.0000+J* 1.0000, -1.0000+J* 0.0000, -0.0000+J*-1.0000,
+         1.0000+J*-0.0000,  0.0000+J* 1.0000, -1.0000+J* 0.0000, -0.0000+J*-1.0000,
+         1.0000+J*-0.0000,  0.0000+J* 1.0000, -1.0000+J* 0.0000, -0.0000+J*-1.0000,
+         1.0000+J*-0.0000, -0.0000+J* 1.0000, -1.0000+J* 0.0000, -0.0000+J*-1.0000
+    };
+
+    float test[32] = {
+         0.0000, -0.0055, -0.0000, -0.0231, -0.0000, -0.0605, -0.0000, -0.1459,
+        -0.0000, -0.5604, -0.0000,  0.7669,  1.0000,  0.7294,  0.0000, -0.7008,
+        -1.0000, -0.7064, -0.0000,  0.7064,  1.0000,  0.7064,  0.0000, -0.7064,
+        -1.0000, -0.7064, -0.0000,  0.7064,  1.0000,  0.7064,  0.0000, -0.7064
+    };
+
+
+    float y[32];
+    unsigned int m=5;   // h_len = 4*m+1 = 21
+    firhilbf ht = firhilbf_create(m,60.0f);
+    float tol=0.005f;
+
+    // run interpolator
+    unsigned int i;
+    for (i=0; i<16; i++)
+        firhilbf_interp_execute(ht, x[i], &y[2*i]);
+
+    if (liquid_autotest_verbose) {
+        printf("hilbert transform interpolator output:\n");
+        for (i=0; i<32; i++)
+            printf("  y(%3u) = %8.5f;\n", i+1, y[i]);
+    }
+
+    // run validation
+    for (i=0; i<32; i++) {
+        CONTEND_DELTA(y[i], test[i], tol);
+    }
+
+    // clean up filter object
+    firhilbf_destroy(ht);
+}
+
diff --git a/src/filter/tests/firinterp_autotest.c b/src/filter/tests/firinterp_autotest.c
new file mode 100644
index 0000000..0509401
--- /dev/null
+++ b/src/filter/tests/firinterp_autotest.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: 
+//
+void autotest_firinterp_rrrf_generic()
+{
+    float h[9] = {
+      -0.2762293319046737,
+       1.4757679031218007,
+       0.1432569489572376,
+      -0.2142368750177835,
+       1.3471241294836864,
+       0.1166010284926269,
+       0.0536534505390281,
+       0.1412672462812405,
+      -0.0991854372394269};
+
+    unsigned int M = 4;     // firinterp factor
+    firinterp_rrrf q = firinterp_rrrf_create(M,h,9);
+
+    float x[] = {1.0, -1.0, 1.0, 1.0};
+    float y[16];
+    float test[16] = {
+      -0.2762293319046737,
+       1.4757679031218007,
+       0.1432569489572376,
+      -0.2142368750177835,
+       1.6233534613883602,
+      -1.3591668746291738,
+      -0.0896034984182095,
+       0.3555041212990241,
+      -1.7225388986277870,
+       1.3591668746291738,
+       0.0896034984182095,
+      -0.3555041212990241,
+       1.1700802348184398,
+       1.5923689316144276,
+       0.1969103994962658,
+      -0.0729696287365430};
+
+    float tol = 1e-6;
+
+    unsigned int i;
+    for (i=0; i<4; i++)
+        firinterp_rrrf_execute(q, x[i], &y[i*M]);
+
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA(y[i], test[i], tol);
+
+        if (liquid_autotest_verbose)
+            printf("  y(%u) = %8.4f;\n", i+1, y[i]);
+    }
+
+    if (liquid_autotest_verbose)
+        firinterp_rrrf_print(q);
+
+    // destroy interpolator object
+    firinterp_rrrf_destroy(q);
+}
+
+void autotest_firinterp_crcf_generic()
+{
+    // h = [0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0];
+    float h[9] = {
+      -0.7393353832652201,
+       0.1909821993029451,
+      -1.7013834621383086,
+      -0.6157406339062349,
+       0.5806218191269317,
+       0.0576963976148674,
+      -1.0958217797368455,
+      -0.6379821629743743,
+       0.7019489165905530};
+
+    unsigned int M = 4;     // firinterp factor
+    firinterp_crcf q = firinterp_crcf_create(M,h,9);
+
+    //  x = [1+j*0.2, -0.2+j*1.3, 0.5+j*0.3, 1.1-j*0.2]
+    float complex x[4] = {
+      1.0000e+00+  2.0000e-01*_Complex_I, 
+     -2.0000e-01+  1.3000e+00*_Complex_I, 
+      5.0000e-01+  3.0000e-01*_Complex_I, 
+      1.1000e+00+ -2.0000e-01*_Complex_I
+    };
+        
+    float complex y[16];
+
+    // z = [x(1) 0 0 0 x(2) 0 0 0 x(3) 0 0 0 x(4) 0 0 0];
+    // test = filter(h,1,z)
+    float complex test[16] = {
+      -0.7393353832652201 - 0.1478670766530440*_Complex_I,
+       0.1909821993029451 + 0.0381964398605890*_Complex_I,
+      -1.7013834621383086 - 0.3402766924276617*_Complex_I,
+      -0.6157406339062349 - 0.1231481267812470*_Complex_I,
+       0.7284888957799757 - 0.8450116344193997*_Complex_I,
+       0.0194999577542784 + 0.2598161386168021*_Complex_I,
+      -0.7555450873091838 - 2.4309628567271702*_Complex_I,
+      -0.5148340361931273 - 0.9280592566729803*_Complex_I,
+       0.2161568611325566 + 0.6733975332035558*_Complex_I,
+       0.0839518201284991 + 0.1322999766902112*_Complex_I,
+      -0.6315273751217851 - 1.9349833522993918*_Complex_I,
+      -0.1802738843582426 - 1.0140990020385570*_Complex_I,
+      -0.6633477953463869 + 1.2345872139588425*_Complex_I,
+       0.2389286180406733 - 0.0208875205761288*_Complex_I,
+      -2.4194326982205623 + 0.0115301585066081*_Complex_I,
+      -0.9963057787840456 - 0.0682465221110653*_Complex_I };
+
+    float tol = 1e-6;
+
+    unsigned int i;
+    for (i=0; i<4; i++)
+        firinterp_crcf_execute(q, x[i], &y[i*M]);
+
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(y[i]), crealf(test[i]), tol);
+        CONTEND_DELTA( cimagf(y[i]), cimagf(test[i]), tol);
+
+        if (liquid_autotest_verbose)
+            printf("  y(%u) = %8.4f + j%8.4f;\n", i+1, crealf(y[i]), cimagf(y[i]));
+    }
+
+    if (liquid_autotest_verbose)
+        firinterp_crcf_print(q);
+
+    // destroy interpolator object
+    firinterp_crcf_destroy(q);
+}
+
diff --git a/src/filter/tests/firpfb_autotest.c b/src/filter/tests/firpfb_autotest.c
new file mode 100644
index 0000000..985f4bd
--- /dev/null
+++ b/src/filter/tests/firpfb_autotest.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+void autotest_firpfb_impulse_response()
+{
+    // Initialize variables
+    float tol=1e-4f;
+
+    // k=2, m=3, beta=0.3, npfb=4;
+    // h=rrcos(k*npfb,m,beta);
+    float h[48] = {
+     -0.033116,  -0.024181,  -0.006284,   0.018261, 
+      0.045016,   0.068033,   0.080919,   0.078177, 
+      0.056597,   0.016403,  -0.038106,  -0.098610, 
+     -0.153600,  -0.189940,  -0.194900,  -0.158390, 
+     -0.075002,   0.054511,   0.222690,   0.415800, 
+      0.615340,   0.800390,   0.950380,   1.048100, 
+      1.082000,   1.048100,   0.950380,   0.800390, 
+      0.615340,   0.415800,   0.222690,   0.054511, 
+     -0.075002,  -0.158390,  -0.194900,  -0.189940, 
+     -0.153600,  -0.098610,  -0.038106,   0.016403, 
+      0.056597,   0.078177,   0.080919,   0.068033, 
+      0.045016,   0.018261,  -0.006284,  -0.024181
+    };
+
+    // filter input
+    float noise[12] = {
+      0.438310,   1.001900,   0.200600,   0.790040, 
+      1.134200,   1.592200,  -0.702980,  -0.937560, 
+     -0.511270,  -1.684700,   0.328940,  -0.387780
+    };
+
+    // expected filter outputs
+    float test[4] = {
+        2.05558467194397f,
+        1.56922189602661f,
+        0.998479744645138,
+        0.386125857849177
+    };
+
+    // Load filter coefficients externally
+    firpfb_rrrf f = firpfb_rrrf_create(4, h, 48);
+    
+    unsigned int i;
+    for (i=0; i<12; i++)
+        firpfb_rrrf_push(f,noise[i]);
+
+    float y;
+    for (i=0; i<4; i++) {
+        firpfb_rrrf_execute(f,i,&y);
+        CONTEND_DELTA(test[i],y,tol);
+    }
+    
+    firpfb_rrrf_destroy(f);
+}
+
diff --git a/src/filter/tests/groupdelay_autotest.c b/src/filter/tests/groupdelay_autotest.c
new file mode 100644
index 0000000..d9fa6d8
--- /dev/null
+++ b/src/filter/tests/groupdelay_autotest.c
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST : fir group delay, n=3
+//
+void autotest_fir_groupdelay_n3()
+{
+    // create coefficients array
+    float h[3] = {0.1, 0.2, 0.4};
+
+    float tol = 1e-3f;
+    unsigned int i;
+
+    // create testing vectors
+    float fc[4] = { 0.000,
+                    0.125,
+                    0.250,
+                    0.375};
+    
+    float g0[4] = { 1.42857142857143,
+                    1.54756605839643,
+                    2.15384615384615,
+                    2.56861651421767};
+
+
+    // run tests
+    float g;
+    for (i=0; i<4; i++) {
+        g = fir_group_delay(h, 3, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // create filter
+    firfilt_rrrf filter = firfilt_rrrf_create(h,3);
+
+    // run tests again
+    for (i=0; i<4; i++) {
+        g = firfilt_rrrf_groupdelay(filter, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // destroy filter
+    firfilt_rrrf_destroy(filter);
+}
+
+
+//
+// AUTOTEST : iir group delay, n=3
+//
+void autotest_iir_groupdelay_n3()
+{
+    // create coefficients array
+    float b[3] = {0.20657210,  0.41314420, 0.20657210};
+    float a[3] = {1.00000000, -0.36952737, 0.19581573};
+
+    float tol = 1e-3f;
+    unsigned int i;
+
+    // create testing vectors
+    float fc[4] = { 0.000,
+                    0.125,
+                    0.250,
+                    0.375};
+    
+    float g0[4] = { 0.973248939389634,
+                    1.366481121240365,
+                    1.227756735863196,
+                    0.651058521306726};
+
+    // run tests
+    float g;
+    for (i=0; i<4; i++) {
+        g = iir_group_delay(b, 3, a, 3, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // create filter
+    iirfilt_rrrf filter = iirfilt_rrrf_create(b,3,a,3);
+
+    // run tests again
+    for (i=0; i<4; i++) {
+        g = iirfilt_rrrf_groupdelay(filter, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(filter);
+}
+
+
+//
+// AUTOTEST : iir group delay, n=8
+//
+void autotest_iir_groupdelay_n8()
+{
+    // create coefficients arrays (7th-order Butterworth)
+    float b[8];
+    b[  0] =   0.00484212;
+    b[  1] =   0.03389481;
+    b[  2] =   0.10168444;
+    b[  3] =   0.16947407;
+    b[  4] =   0.16947407;
+    b[  5] =   0.10168444;
+    b[  6] =   0.03389481;
+    b[  7] =   0.00484212;
+
+    float a[8];
+    a[  0] =   1.00000000;
+    a[  1] =  -1.38928008;
+    a[  2] =   1.67502367;
+    a[  3] =  -1.05389738;
+    a[  4] =   0.50855154;
+    a[  5] =  -0.14482945;
+    a[  6] =   0.02625222;
+    a[  7] =  -0.00202968;
+
+    float tol = 1e-3f;
+    unsigned int i;
+
+    // create testing vectors
+    float fc[7] = { 0.00000,
+                    0.06250,
+                    0.12500,
+                    0.18750,
+                    0.25000,
+                    0.31250,
+                    0.37500};
+
+    float g0[7] = { 3.09280801068444,
+                    3.30599360247944,
+                    4.18341028373046,
+                    7.71934054380586,
+                    4.34330109915390,
+                    2.60203085226210,
+                    1.97868452107144};
+
+    // run tests
+    float g;
+    for (i=0; i<7; i++) {
+        g = iir_group_delay(b, 8, a, 8, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    //
+    // test with iir filter (tf)
+    //
+
+    // create filter
+    iirfilt_rrrf filter = iirfilt_rrrf_create(b,8,a,8);
+
+    // run tests again
+    for (i=0; i<7; i++) {
+        g = iirfilt_rrrf_groupdelay(filter, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(filter);
+}
+
+
+//
+// AUTOTEST : iir group delay (second-order sections), n=8
+//
+void autotest_iir_groupdelay_sos_n8()
+{
+    // create coefficients arrays (7th-order Butterworth)
+    float B[12] = {
+        0.00484212,   0.00968423,   0.00484212,
+        1.00000000,   2.00000000,   1.00000000,
+        1.00000000,   2.00000000,   1.00000000,
+        1.00000000,   1.00000000,   0.00000000};
+
+    float A[12] = {
+        1.00000000,  -0.33283597,   0.07707999,
+        1.00000000,  -0.38797498,   0.25551325,
+        1.00000000,  -0.51008475,   0.65066898,
+        1.00000000,  -0.15838444,   0.00000000};
+
+    float tol = 1e-3f;
+    unsigned int i;
+
+    // create testing vectors
+    float fc[7] = { 0.00000,
+                    0.06250,
+                    0.12500,
+                    0.18750,
+                    0.25000,
+                    0.31250,
+                    0.37500};
+
+    float g0[7] = { 3.09280801068444,
+                    3.30599360247944,
+                    4.18341028373046,
+                    7.71934054380586,
+                    4.34330109915390,
+                    2.60203085226210,
+                    1.97868452107144};
+
+    //
+    // test with iir filter (second-order sections)
+    //
+
+    // create filter
+    iirfilt_rrrf filter = iirfilt_rrrf_create_sos(B,A,4);
+
+    // run tests
+    float g;
+    for (i=0; i<7; i++) {
+        g = iirfilt_rrrf_groupdelay(filter, fc[i]);
+        CONTEND_DELTA( g, g0[i], tol );
+    }
+
+    // destroy filter
+    iirfilt_rrrf_destroy(filter);
+}
+
diff --git a/src/filter/tests/iirdes_autotest.c b/src/filter/tests/iirdes_autotest.c
new file mode 100644
index 0000000..138497f
--- /dev/null
+++ b/src/filter/tests/iirdes_autotest.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// References
+//  [Ziemer:1998] Ziemer, Tranter, Fannin, "Signals & Systems,
+//      Continuous and Discrete," 4th ed., Prentice Hall, Upper
+//      Saddle River, NJ, 1998
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST : design 2nd-order butterworth filter (design comes
+//            from [Ziemer:1998] Example 9-7, pp. 440--442)
+//
+void autotest_iirdes_butter_2()
+{
+    // initialize variables
+    unsigned int order = 2; // filter order
+    float fc = 0.25f;       // normalized cutoff frequency
+    float f0 = 0.0f;        // center frequency (ignored for low-pass filter)
+    float Ap = 1.0f;        // pass-band ripple (ignored for Butterworth)
+    float As = 40.0f;       // stop-band attenuation (ignored for Butterworth)
+    float tol = 1e-6f;      // error tolerance
+
+    // initialize pre-determined coefficient array
+    // for 2^nd-order low-pass Butterworth filter
+    // with cutoff frequency 0.25
+    float a_test[3] = {
+        1.0f,
+        0.0f,
+        0.171572875253810f};
+    float b_test[3] = {
+        0.292893218813452f,
+        0.585786437626905f,
+        0.292893218813452f};
+
+    // output coefficients
+    float a[3];
+    float b[3];
+
+    // design butterworth filter
+    liquid_iirdes(LIQUID_IIRDES_BUTTER,
+                  LIQUID_IIRDES_LOWPASS,
+                  LIQUID_IIRDES_TF,
+                  order,
+                  fc, f0,
+                  Ap, As,
+                  b, a);
+
+    // Ensure data are equal to within tolerance
+    unsigned int i;
+    for (i=0; i<3; i++) {
+        CONTEND_DELTA( b[i], b_test[i], tol );
+        CONTEND_DELTA( a[i], a_test[i], tol );
+    }
+}
+
+// 
+// AUTOTEST : complex pair, n=6
+//
+
+void autotest_iirdes_cplxpair_n6()
+{
+    float tol = 1e-8f;
+
+    //
+    float complex r[6] = {
+       0.980066577841242 + 0.198669330795061 * _Complex_I,
+       5.000000000000000 + 0.000000000000000 * _Complex_I,
+      -0.416146836547142 + 0.909297426825682 * _Complex_I,
+       0.980066577841242 - 0.198669330795061 * _Complex_I,
+       0.300000000000000 + 0.000000000000000 * _Complex_I,
+      -0.416146836547142 - 0.909297426825682 * _Complex_I
+    };
+
+    float complex p[6];
+
+    float complex ptest[6] = {
+      -0.416146836547142 - 0.909297426825682 * _Complex_I,
+      -0.416146836547142 + 0.909297426825682 * _Complex_I,
+       0.980066577841242 - 0.198669330795061 * _Complex_I,
+       0.980066577841242 + 0.198669330795061 * _Complex_I,
+       0.300000000000000 + 0.000000000000000 * _Complex_I,
+       5.000000000000000 + 0.000000000000000 * _Complex_I
+    };
+
+    // compute complex pairs
+    liquid_cplxpair(r,6,1e-6f,p);
+
+    unsigned int i;
+
+    if (liquid_autotest_verbose) {
+        printf("complex set:\n");
+        for (i=0; i<6; i++)
+            printf("  r[%3u] : %12.8f + j*%12.8f\n", i, crealf(r[i]), cimagf(r[i]));
+
+        printf("complex pairs:\n");
+        for (i=0; i<6; i++)
+            printf("  p[%3u] : %12.8f + j*%12.8f\n", i, crealf(p[i]), cimagf(p[i]));
+    }
+
+    // run test
+    for (i=0; i<6; i++) {
+        CONTEND_DELTA( crealf(p[i]), crealf(ptest[i]), tol );
+        CONTEND_DELTA( cimagf(p[i]), cimagf(ptest[i]), tol );
+    }
+}
+
+
+// 
+// AUTOTEST : complex pair, n=20
+//
+
+void autotest_iirdes_cplxpair_n20()
+{
+    float tol = 1e-8f;
+
+    //
+    float complex r[20] = {
+      -0.340396183901119 + 1.109902927794652 * _Complex_I,
+       1.148964416793990 + 0.000000000000000 * _Complex_I,
+       0.190037889511651 + 0.597517076404221 * _Complex_I,
+      -0.340396183901119 - 1.109902927794652 * _Complex_I,
+       0.890883293686046 + 0.000000000000000 * _Complex_I,
+      -0.248338528396292 - 0.199390430636670 * _Complex_I,
+       0.190037889511651 - 0.597517076404221 * _Complex_I,
+       0.003180396218998 + 0.000000000000000 * _Complex_I,
+       0.261949046540733 - 0.739400953405199 * _Complex_I,
+       0.261949046540733 + 0.739400953405199 * _Complex_I,
+       0.309342570837113 + 0.000000000000000 * _Complex_I,
+       0.035516103001236 + 0.000000000000000 * _Complex_I,
+      -0.184159864176452 - 0.240335024546875 * _Complex_I,
+      -0.485244526317243 + 0.452251520655749 * _Complex_I,
+      -0.485244526317243 - 0.452251520655749 * _Complex_I,
+      -0.581633365450190 + 0.000000000000000 * _Complex_I,
+      -0.248338528396292 + 0.199390430636670 * _Complex_I,
+      -0.184159864176452 + 0.240335024546875 * _Complex_I,
+       1.013685316242435 + 0.000000000000000 * _Complex_I,
+      -0.089598596934739 + 0.000000000000000 * _Complex_I
+    };
+
+    float complex p[20];
+
+    float complex ptest[20] = {
+      -0.485244526317243 - 0.452251520655749 * _Complex_I,
+      -0.485244526317243 + 0.452251520655749 * _Complex_I,
+      -0.340396183901119 - 1.109902927794652 * _Complex_I,
+      -0.340396183901119 + 1.109902927794652 * _Complex_I,
+      -0.248338528396292 - 0.199390430636670 * _Complex_I,
+      -0.248338528396292 + 0.199390430636670 * _Complex_I,
+      -0.184159864176452 - 0.240335024546875 * _Complex_I,
+      -0.184159864176452 + 0.240335024546875 * _Complex_I,
+       0.190037889511651 - 0.597517076404221 * _Complex_I,
+       0.190037889511651 + 0.597517076404221 * _Complex_I,
+       0.261949046540733 - 0.739400953405199 * _Complex_I,
+       0.261949046540733 + 0.739400953405199 * _Complex_I,
+      -0.581633365450190 + 0.000000000000000 * _Complex_I,
+      -0.089598596934739 + 0.000000000000000 * _Complex_I,
+       0.003180396218998 + 0.000000000000000 * _Complex_I,
+       0.035516103001236 + 0.000000000000000 * _Complex_I,
+       0.309342570837113 + 0.000000000000000 * _Complex_I,
+       0.890883293686046 + 0.000000000000000 * _Complex_I,
+       1.013685316242435 + 0.000000000000000 * _Complex_I,
+       1.148964416793990 + 0.000000000000000 * _Complex_I
+    };
+
+    // compute complex pairs
+    liquid_cplxpair(r,20,1e-6f,p);
+
+    unsigned int i;
+
+    if (liquid_autotest_verbose) {
+        printf("complex set:\n");
+        for (i=0; i<20; i++)
+            printf("  r[%3u] : %12.8f + j*%12.8f\n", i, crealf(r[i]), cimagf(r[i]));
+
+        printf("complex pairs:\n");
+        for (i=0; i<20; i++)
+            printf("  p[%3u] : %12.8f + j*%12.8f\n", i, crealf(p[i]), cimagf(p[i]));
+    }
+
+    // run test
+    for (i=0; i<20; i++) {
+        CONTEND_DELTA( crealf(p[i]), crealf(ptest[i]), tol );
+        CONTEND_DELTA( cimagf(p[i]), cimagf(ptest[i]), tol );
+    }
+}
+
+// 
+// AUTOTEST : 
+//
+void autotest_iirdes_dzpk2sosf()
+{
+    unsigned int n=4;
+    float fc = 0.25f;
+
+    unsigned int i;
+    unsigned int L = n % 2 ? (n+1)/2 : n/2;
+    float B[3*L];
+    float A[3*L];
+
+    float complex za[n];    // analog zeros
+    float complex pa[n];    // analog poles
+    float complex ka;       // analog gain
+    butter_azpkf(n,za,pa,&ka);
+
+    float complex zd[n];    // digital zeros
+    float complex pd[n];    // digital poles
+    float complex kd;       // digital gain
+    float m = 1 / tanf(M_PI * fc);
+    bilinear_zpkf(za,  0,
+                  pa,  n,
+                  ka,  m,
+                  zd, pd, &kd);
+
+    if (liquid_autotest_verbose) {
+        printf("poles (digital):\n");
+        for (i=0; i<n; i++)
+            printf("  pd[%3u] = %12.8f + j*%12.8f\n", i, crealf(pd[i]), cimagf(pd[i]));
+
+        printf("zeros (digital):\n");
+        for (i=0; i<n; i++)
+            printf("  zd[%3u] = %12.8f + j*%12.8f\n", i, crealf(zd[i]), cimagf(zd[i]));
+    }
+
+    iirdes_dzpk2sosf(zd,pd,n,kd,B,A);
+
+    if (liquid_autotest_verbose) {
+        printf("B:\n");
+        for (i=0; i<L; i++)
+            printf("  %12.8f %12.8f %12.8f\n", B[3*i+0], B[3*i+1], B[3*i+2]);
+
+        printf("A:\n");
+        for (i=0; i<L; i++)
+            printf("  %12.8f %12.8f %12.8f\n", A[3*i+0], A[3*i+1], A[3*i+2]);
+    }
+}
+
+// 
+// AUTOTEST : iirdes_isstable
+//
+void autotest_iirdes_isstable_n2_yes()
+{
+    // initialize pre-determined coefficient array
+    // for 2^nd-order low-pass Butterworth filter
+    // with cutoff frequency 0.25
+    float a[3] = {
+        1.0f,
+        0.0f,
+        0.171572875253810f};
+    float b[3] = {
+        0.292893218813452f,
+        0.585786437626905f,
+        0.292893218813452f};
+
+    int stable = iirdes_isstable(b,a,3);
+    CONTEND_EQUALITY( stable, 1 );
+}
+
+
+// 
+// AUTOTEST : iirdes_isstable
+//
+void autotest_iirdes_isstable_n2_no()
+{
+    // initialize unstable filter
+    float a[3] = {
+        1.0f,
+        0.0f,
+        1.171572875253810f};
+    float b[3] = {
+        0.292893218813452f,
+        0.585786437626905f,
+        0.292893218813452f};
+
+    int stable = iirdes_isstable(b,a,3);
+    CONTEND_EQUALITY( stable, 0 );
+}
+
diff --git a/src/filter/tests/iirfilt_autotest.h b/src/filter/tests/iirfilt_autotest.h
new file mode 100644
index 0000000..17b4cd2
--- /dev/null
+++ b/src/filter/tests/iirfilt_autotest.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest iirfilt data definitions
+//
+
+#ifndef __LIQUID_IIRFILT_H__
+#define __LIQUID_IIRFILT_H__
+
+// autotest helper functions:
+//  _b      :   filter coefficients (numerator)
+//  _a      :   filter coefficients (denominator)
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void iirfilt_rrrf_test(float *      _b,
+                       float *      _a,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len);
+
+void iirfilt_crcf_test(float *         _b,
+                       float *         _a,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+void iirfilt_cccf_test(float complex * _b,
+                       float complex * _a,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len);
+
+// 
+// autotest datasets
+//
+
+// rrrf
+extern float         iirfilt_rrrf_data_h3x64_b[];
+extern float         iirfilt_rrrf_data_h3x64_a[];
+extern float         iirfilt_rrrf_data_h3x64_x[];
+extern float         iirfilt_rrrf_data_h3x64_y[];
+
+extern float         iirfilt_rrrf_data_h5x64_b[];
+extern float         iirfilt_rrrf_data_h5x64_a[];
+extern float         iirfilt_rrrf_data_h5x64_x[];
+extern float         iirfilt_rrrf_data_h5x64_y[];
+
+extern float         iirfilt_rrrf_data_h7x64_b[];
+extern float         iirfilt_rrrf_data_h7x64_a[];
+extern float         iirfilt_rrrf_data_h7x64_x[];
+extern float         iirfilt_rrrf_data_h7x64_y[];
+
+// crcf
+extern float         iirfilt_crcf_data_h3x64_b[];
+extern float         iirfilt_crcf_data_h3x64_a[];
+extern float complex iirfilt_crcf_data_h3x64_x[];
+extern float complex iirfilt_crcf_data_h3x64_y[];
+
+extern float         iirfilt_crcf_data_h5x64_b[];
+extern float         iirfilt_crcf_data_h5x64_a[];
+extern float complex iirfilt_crcf_data_h5x64_x[];
+extern float complex iirfilt_crcf_data_h5x64_y[];
+
+extern float         iirfilt_crcf_data_h7x64_b[];
+extern float         iirfilt_crcf_data_h7x64_a[];
+extern float complex iirfilt_crcf_data_h7x64_x[];
+extern float complex iirfilt_crcf_data_h7x64_y[];
+
+// cccf
+extern float complex iirfilt_cccf_data_h3x64_b[];
+extern float complex iirfilt_cccf_data_h3x64_a[];
+extern float complex iirfilt_cccf_data_h3x64_x[];
+extern float complex iirfilt_cccf_data_h3x64_y[];
+
+extern float complex iirfilt_cccf_data_h5x64_b[];
+extern float complex iirfilt_cccf_data_h5x64_a[];
+extern float complex iirfilt_cccf_data_h5x64_x[];
+extern float complex iirfilt_cccf_data_h5x64_y[];
+
+extern float complex iirfilt_cccf_data_h7x64_b[];
+extern float complex iirfilt_cccf_data_h7x64_a[];
+extern float complex iirfilt_cccf_data_h7x64_x[];
+extern float complex iirfilt_cccf_data_h7x64_y[];
+
+#endif // __LIQUID_IIRFILT_H__
+
diff --git a/src/filter/tests/iirfilt_runtest.c b/src/filter/tests/iirfilt_runtest.c
new file mode 100644
index 0000000..9dd30e5
--- /dev/null
+++ b/src/filter/tests/iirfilt_runtest.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _b      :   filter coefficients (numerator)
+//  _a      :   filter coefficients (denominator)
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void iirfilt_rrrf_test(float *      _b,
+                       float *      _a,
+                       unsigned int _h_len,
+                       float *      _x,
+                       unsigned int _x_len,
+                       float *      _y,
+                       unsigned int _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    iirfilt_rrrf q = iirfilt_rrrf_create(_b, _h_len, _a, _h_len);
+
+    // allocate memory for output
+    float y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        iirfilt_rrrf_execute(q, _x[i], &y_test[i]);
+        
+        CONTEND_DELTA( y_test[i], _y[i], tol );
+    }
+
+    // destroy filter object
+    iirfilt_rrrf_destroy(q);
+}
+
+// autotest helper function
+//  _b      :   filter coefficients (numerator)
+//  _a      :   filter coefficients (denominator)
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void iirfilt_crcf_test(float *         _b,
+                       float *         _a,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    iirfilt_crcf q = iirfilt_crcf_create(_b, _h_len, _a, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        iirfilt_crcf_execute(q, _x[i], &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    iirfilt_crcf_destroy(q);
+}
+
+// autotest helper function
+//  _b      :   filter coefficients (numerator)
+//  _a      :   filter coefficients (denominator)
+//  _h_len  :   filter coefficients length
+//  _x      :   input array
+//  _x_len  :   input array length
+//  _y      :   output array
+//  _y_len  :   output array length
+void iirfilt_cccf_test(float complex * _b,
+                       float complex * _a,
+                       unsigned int    _h_len,
+                       float complex * _x,
+                       unsigned int    _x_len,
+                       float complex * _y,
+                       unsigned int    _y_len)
+{
+    float tol = 0.001f;
+
+    // load filter coefficients externally
+    iirfilt_cccf q = iirfilt_cccf_create(_b, _h_len, _a, _h_len);
+
+    // allocate memory for output
+    float complex y_test[_y_len];
+
+    unsigned int i;
+    // compute output
+    for (i=0; i<_x_len; i++) {
+        iirfilt_cccf_execute(q, _x[i], &y_test[i]);
+        
+        CONTEND_DELTA( crealf(y_test[i]), crealf(_y[i]), tol );
+        CONTEND_DELTA( cimagf(y_test[i]), cimagf(_y[i]), tol );
+    }
+    
+    // destroy filter object
+    iirfilt_cccf_destroy(q);
+}
+
diff --git a/src/filter/tests/iirfilt_xxxf_autotest.c b/src/filter/tests/iirfilt_xxxf_autotest.c
new file mode 100644
index 0000000..7c10647
--- /dev/null
+++ b/src/filter/tests/iirfilt_xxxf_autotest.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// iirfilt_xxxf_autotest.c : test floating-point filters
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/filter/tests/iirfilt_autotest.h"
+
+// 
+// AUTOTEST: iirfilt_rrrf tests
+//
+void autotest_iirfilt_rrrf_h3x64()
+{
+    iirfilt_rrrf_test(iirfilt_rrrf_data_h3x64_b,
+                      iirfilt_rrrf_data_h3x64_a, 3,
+                      iirfilt_rrrf_data_h3x64_x, 64,
+                      iirfilt_rrrf_data_h3x64_y, 64);
+}
+void autotest_iirfilt_rrrf_h5x64()
+{
+    iirfilt_rrrf_test(iirfilt_rrrf_data_h5x64_b,
+                      iirfilt_rrrf_data_h5x64_a, 5,
+                      iirfilt_rrrf_data_h5x64_x, 64,
+                      iirfilt_rrrf_data_h5x64_y, 64);
+}
+void autotest_iirfilt_rrrf_h7x64()
+{
+    iirfilt_rrrf_test(iirfilt_rrrf_data_h7x64_b,
+                      iirfilt_rrrf_data_h7x64_a, 7,
+                      iirfilt_rrrf_data_h7x64_x, 64,
+                      iirfilt_rrrf_data_h7x64_y, 64);
+}
+
+
+// 
+// AUTOTEST: iirfilt_crcf tests
+//
+void autotest_iirfilt_crcf_h3x64()
+{
+    iirfilt_crcf_test(iirfilt_crcf_data_h3x64_b,
+                      iirfilt_crcf_data_h3x64_a, 3,
+                      iirfilt_crcf_data_h3x64_x, 64,
+                      iirfilt_crcf_data_h3x64_y, 64);
+}
+void autotest_iirfilt_crcf_h5x64()
+{
+    iirfilt_crcf_test(iirfilt_crcf_data_h5x64_b,
+                      iirfilt_crcf_data_h5x64_a, 5,
+                      iirfilt_crcf_data_h5x64_x, 64,
+                      iirfilt_crcf_data_h5x64_y, 64);
+}
+void autotest_iirfilt_crcf_h7x64()
+{
+    iirfilt_crcf_test(iirfilt_crcf_data_h7x64_b,
+                      iirfilt_crcf_data_h7x64_a, 7,
+                      iirfilt_crcf_data_h7x64_x, 64,
+                      iirfilt_crcf_data_h7x64_y, 64);
+}
+
+
+// 
+// AUTOTEST: iirfilt_cccf tests
+//
+void autotest_iirfilt_cccf_h3x64()
+{
+    iirfilt_cccf_test(iirfilt_cccf_data_h3x64_b,
+                      iirfilt_cccf_data_h3x64_a, 3,
+                      iirfilt_cccf_data_h3x64_x, 64,
+                      iirfilt_cccf_data_h3x64_y, 64);
+}
+void autotest_iirfilt_cccf_h5x64()
+{
+    iirfilt_cccf_test(iirfilt_cccf_data_h5x64_b,
+                      iirfilt_cccf_data_h5x64_a, 5,
+                      iirfilt_cccf_data_h5x64_x, 64,
+                      iirfilt_cccf_data_h5x64_y, 64);
+}
+void autotest_iirfilt_cccf_h7x64()
+{
+    iirfilt_cccf_test(iirfilt_cccf_data_h7x64_b,
+                      iirfilt_cccf_data_h7x64_a, 7,
+                      iirfilt_cccf_data_h7x64_x, 64,
+                      iirfilt_cccf_data_h7x64_y, 64);
+}
+
+
diff --git a/src/filter/tests/iirfiltsos_rrrf_autotest.c b/src/filter/tests/iirfiltsos_rrrf_autotest.c
new file mode 100644
index 0000000..f2cb46f
--- /dev/null
+++ b/src/filter/tests/iirfiltsos_rrrf_autotest.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+void autotest_iirfiltsos_impulse_n2()
+{
+    // initialize filter with 2nd-order low-pass butterworth filter
+    float a[3] = {
+        1.000000000000000,
+       -0.942809041582063,
+        0.333333333333333};
+
+    float b[3] = {
+        0.0976310729378175,
+        0.1952621458756350,
+        0.0976310729378175};
+
+    iirfiltsos_rrrf f = iirfiltsos_rrrf_create(b,a);
+
+    // initialize oracle; expected output (generated with octave)
+    float test[15] = {
+       9.76310729378175e-02,
+       2.87309604180767e-01,
+       3.35965474513536e-01,
+       2.20981418970514e-01,
+       9.63547883225231e-02,
+       1.71836926400291e-02,
+      -1.59173219853878e-02,
+      -2.07348926322729e-02,
+      -1.42432702548109e-02,
+      -6.51705310050832e-03,
+      -1.39657983602602e-03,
+       8.55642936806248e-04,
+       1.27223450919543e-03,
+       9.14259886013424e-04,
+       4.37894317157432e-04};
+
+    unsigned int i;
+    float v, y;
+    float tol=1e-4f;
+
+    // hit filter with impulse, compare output
+    for (i=0; i<15; i++) {
+        v = (i==0) ? 1.0f : 0.0f;
+
+        iirfiltsos_rrrf_execute(f, v, &y);
+
+        CONTEND_DELTA(test[i], y, tol);
+    }
+
+    iirfiltsos_rrrf_destroy(f);
+}
+
+
+void autotest_iirfiltsos_step_n2()
+{
+    // initialize filter with 2nd-order low-pass butterworth filter
+    float a[3] = {
+        1.000000000000000,
+       -0.942809041582063,
+        0.333333333333333};
+
+    float b[3] = {
+        0.0976310729378175,
+        0.1952621458756350,
+        0.0976310729378175};
+
+    iirfiltsos_rrrf f = iirfiltsos_rrrf_create(b,a);
+
+    float test[15] = {
+       0.0976310729378175,
+       0.3849406771185847,
+       0.7209061516321208,
+       0.9418875706026352,
+       1.0382423589251584,
+       1.0554260515651877,
+       1.0395087295798000,
+       1.0187738369475272,
+       1.0045305666927162,
+       0.9980135135922078,
+       0.9966169337561817,
+       0.9974725766929878,
+       0.9987448112021832,
+       0.9996590710881966,
+       1.0000969654053542};
+
+    unsigned int i;
+    float y;
+    float tol=1e-4f;
+
+    // hit filter with step, compare output
+    for (i=0; i<15; i++) {
+        iirfiltsos_rrrf_execute(f, 1.0f, &y);
+
+        CONTEND_DELTA(test[i], y, tol );
+    }
+
+    iirfiltsos_rrrf_destroy(f);
+}
+
diff --git a/src/filter/tests/msresamp_crcf_autotest.c b/src/filter/tests/msresamp_crcf_autotest.c
new file mode 100644
index 0000000..897dc3e
--- /dev/null
+++ b/src/filter/tests/msresamp_crcf_autotest.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST : test multi-stage arbitrary resampler
+//
+void autotest_msresamp_crcf()
+{
+    // options
+    unsigned int m = 13;        // filter semi-length (filter delay)
+    float r=0.127115323f;       // resampling rate (output/input)
+    float As=60.0f;             // resampling filter stop-band attenuation [dB]
+    unsigned int n=1200;        // number of input samples
+    float fx=0.0254230646f;     // complex input sinusoid frequency (0.2*r)
+    //float bw=0.45f;             // resampling filter bandwidth
+    //unsigned int npfb=64;       // number of filters in bank (timing resolution)
+
+    unsigned int i;
+
+    // number of input samples (zero-padded)
+    unsigned int nx = n + m;
+
+    // output buffer with extra padding for good measure
+    unsigned int y_len = (unsigned int) ceilf(1.1 * nx * r) + 4;
+
+    // arrays
+    float complex x[nx];
+    float complex y[y_len];
+
+    // create resampler
+    msresamp_crcf q = msresamp_crcf_create(r,As);
+
+    // generate input signal
+    float wsum = 0.0f;
+    for (i=0; i<nx; i++) {
+        // compute window
+        float w = i < n ? kaiser(i, n, 10.0f, 0.0f) : 0.0f;
+
+        // apply window to complex sinusoid
+        x[i] = cexpf(_Complex_I*2*M_PI*fx*i) * w;
+
+        // accumulate window
+        wsum += w;
+    }
+
+    // resample
+    unsigned int ny=0;
+    unsigned int nw;
+    for (i=0; i<nx; i++) {
+        // execute resampler, storing in output buffer
+        msresamp_crcf_execute(q, &x[i], 1, &y[ny], &nw);
+
+        // increment output size
+        ny += nw;
+    }
+
+    // clean up allocated objects
+    msresamp_crcf_destroy(q);
+
+    // 
+    // analyze resulting signal
+    //
+
+    // check that the actual resampling rate is close to the target
+    float r_actual = (float)ny / (float)nx;
+    float fy = fx / r;      // expected output frequency
+
+    // run FFT and ensure that carrier has moved and that image
+    // frequencies and distortion have been adequately suppressed
+    unsigned int nfft = 1 << liquid_nextpow2(ny);
+    float complex yfft[nfft];   // fft input
+    float complex Yfft[nfft];   // fft output
+    for (i=0; i<nfft; i++)
+        yfft[i] = i < ny ? y[i] : 0.0f;
+    fft_run(nfft, yfft, Yfft, LIQUID_FFT_FORWARD, 0);
+    fft_shift(Yfft, nfft);  // run FFT shift
+
+    // find peak frequency
+    float Ypeak = 0.0f;
+    float fpeak = 0.0f;
+    float max_sidelobe = -1e9f;     // maximum side-lobe [dB]
+    float main_lobe_width = 0.07f;  // TODO: figure this out from Kaiser's equations
+    for (i=0; i<nfft; i++) {
+        // normalized output frequency
+        float f = (float)i/(float)nfft - 0.5f;
+
+        // scale FFT output appropriately
+        Yfft[i] /= (r * wsum);
+        float Ymag = 20*log10f( cabsf(Yfft[i]) );
+
+        // find frequency location of maximum magnitude
+        if (Ymag > Ypeak || i==0) {
+            Ypeak = Ymag;
+            fpeak = f;
+        }
+
+        // find peak side-lobe value, ignoring frequencies
+        // within a certain range of signal frequency
+        if ( fabsf(f-fy) > main_lobe_width )
+            max_sidelobe = Ymag > max_sidelobe ? Ymag : max_sidelobe;
+    }
+
+    if (liquid_autotest_verbose) {
+        // print results
+        printf("  desired resampling rate   :   %12.8f\n", r);
+        printf("  measured resampling rate  :   %12.8f    (%u/%u)\n", r_actual, ny, nx);
+        printf("  peak spectrum             :   %12.8f dB (expected 0.0 dB)\n", Ypeak);
+        printf("  peak frequency            :   %12.8f    (expected %-12.8f)\n", fpeak, fy);
+        printf("  max sidelobe              :   %12.8f dB (expected at least %.2f dB)\n", max_sidelobe, -As);
+    }
+    CONTEND_DELTA(     r_actual, r,    0.01f ); // check actual output sample rate
+    CONTEND_DELTA(     Ypeak,    0.0f, 0.25f ); // peak should be about 0 dB
+    CONTEND_DELTA(     fpeak,    fy,   0.01f ); // peak frequency should be nearly 0.2
+    CONTEND_LESS_THAN( max_sidelobe, -As );     // maximum side-lobe should be sufficiently low
+
+#if 0
+    // export results for debugging
+    char filename[] = "msresamp_crcf_autotest.m";
+    FILE*fid = fopen(filename,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",filename);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"r    = %12.8f;\n", r);
+    fprintf(fid,"nx   = %u;\n", nx);
+    fprintf(fid,"ny   = %u;\n", ny);
+    fprintf(fid,"nfft = %u;\n", nfft);
+
+    fprintf(fid,"Y = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"Y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(Yfft[i]), cimagf(Yfft[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,20*log10(abs(Y)),'Color',[0.25 0.5 0.0],'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n",filename);
+#endif
+}
diff --git a/src/filter/tests/resamp2_crcf_autotest.c b/src/filter/tests/resamp2_crcf_autotest.c
new file mode 100644
index 0000000..bfd5f6f
--- /dev/null
+++ b/src/filter/tests/resamp2_crcf_autotest.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST : test half-band filterbank (analyzer)
+//
+void autotest_resamp2_analysis()
+{
+    unsigned int m=5;       // filter semi-length (actual length: 4*m+1)
+    unsigned int n=37;      // number of input samples
+    float As=60.0f;         // stop-band attenuation [dB]
+    float f0 =  0.0739f;    // low frequency signal
+    float f1 = -0.1387f;    // high frequency signal (+pi)
+    float tol = 1e-3f;      // error tolerance
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x[2*n+2*m+1]; // input signal (with delay)
+    float complex y0[n];        // low-pass output
+    float complex y1[n];        // high-pass output
+
+    // generate the baseband signal
+    for (i=0; i<2*n+2*m+1; i++)
+        x[i] = i < 2*n ? cexpf(_Complex_I*f0*i) + cexpf(_Complex_I*(M_PI+f1)*i) : 0.0f;
+
+    // create/print the half-band resampler, with a specified
+    // stopband attenuation level
+    resamp2_crcf q = resamp2_crcf_create(m,0,As);
+
+    // run half-band decimation
+    float complex y_hat[2];
+    for (i=0; i<n; i++) {
+        resamp2_crcf_analyzer_execute(q, &x[2*i], y_hat);
+        y0[i] = y_hat[0];
+        y1[i] = y_hat[1];
+    }
+
+    // clean up allocated objects
+    resamp2_crcf_destroy(q);
+
+    // validate output
+    for (i=m; i<n-m; i++) {
+        CONTEND_DELTA( crealf(y0[i+m]), cosf(2*f0*(i+0.5f)), tol );
+        CONTEND_DELTA( cimagf(y0[i+m]), sinf(2*f0*(i+0.5f)), tol );
+
+        CONTEND_DELTA( crealf(y1[i+m]), cosf(2*f1*(i+0.5f)), tol );
+        CONTEND_DELTA( cimagf(y1[i+m]), sinf(2*f1*(i+0.5f)), tol );
+    }
+
+#if 0
+    // debugging
+    FILE * fid = fopen("resamp2_test.m", "w");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+    for (i=0; i<2*n; i++)
+        fprintf(fid,"x(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x[i]), cimagf(x[i]));
+
+    for (i=0; i<n; i++) {
+        fprintf(fid,"y0(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y0[i]), cimagf(y0[i]));
+        fprintf(fid,"y1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y1[i]), cimagf(y1[i]));
+    }
+
+    // save expected values
+    for (i=0; i<n; i++) {
+        fprintf(fid,"z0(%3u) = %12.4e + j*%12.4e;\n", i+1, cosf(i*2*f0), sinf(i*2*f0));
+        fprintf(fid,"z1(%3u) = %12.4e + j*%12.4e;\n", i+1, cosf(i*2*f1), sinf(i*2*f1));
+    }
+
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(length(y0)-1);\n");
+    //fprintf(fid,"plot(t,real(z0),t-m+0.5,real(y0));\n");
+    fprintf(fid,"plot(t,real(z1),t-m+0.5,real(y1));\n");
+    fclose(fid);
+    printf("results written to '%s'\n","resamp2_test.m");
+#endif
+}
+
+// 
+// AUTOTEST : test half-band filterbank (synthesizer)
+//
+void autotest_resamp2_synthesis()
+{
+    unsigned int m=5;       // filter semi-length (actual length: 4*m+1)
+    unsigned int n=37;      // number of input samples
+    float As=60.0f;         // stop-band attenuation [dB]
+    float f0 =  0.0739f;    // low frequency signal
+    float f1 = -0.1387f;    // high frequency signal (+pi)
+    float tol = 3e-3f;      // error tolerance
+
+    unsigned int i;
+
+    // allocate memory for data arrays
+    float complex x0[n+2*m+1];  // input signal (with delay)
+    float complex x1[n+2*m+1];  // input signal (with delay)
+    float complex y[2*n];       // synthesized output
+
+    // generate the baseband signals
+    for (i=0; i<n+2*m+1; i++) {
+        x0[i] = i < 2*n ? cexpf(_Complex_I*f0*i) : 0.0f;
+        x1[i] = i < 2*n ? cexpf(_Complex_I*f1*i) : 0.0f;
+    }
+
+    // create/print the half-band resampler, with a specified
+    // stopband attenuation level
+    resamp2_crcf q = resamp2_crcf_create(m,0,As);
+
+    // run synthesis
+    float complex x_hat[2];
+    for (i=0; i<n; i++) {
+        x_hat[0] = x0[i];
+        x_hat[1] = x1[i];
+        resamp2_crcf_synthesizer_execute(q, x_hat, &y[2*i]);
+    }
+
+    // clean up allocated objects
+    resamp2_crcf_destroy(q);
+
+    // validate output
+    for (i=m; i<n-2*m; i++) {
+        CONTEND_DELTA( crealf(y[i+2*m]), cosf(0.5f*f0*i) + cosf((M_PI+0.5f*f1)*i), tol );
+        CONTEND_DELTA( cimagf(y[i+2*m]), sinf(0.5f*f0*i) + sinf((M_PI+0.5f*f1)*i), tol );
+    }
+
+#if 0
+    // debugging
+    FILE * fid = fopen("resamp2_test.m", "w");
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+    for (i=0; i<n+2*m+1; i++) {
+        fprintf(fid,"x0(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x0[i]), cimagf(x0[i]));
+        fprintf(fid,"x1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(x1[i]), cimagf(x1[i]));
+    }
+
+    for (i=0; i<2*n; i++)
+        fprintf(fid,"y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(y[i]), cimagf(y[i]));
+
+    // save expected values
+    for (i=0; i<2*n; i++) {
+        fprintf(fid,"z(%3u) = %12.4e + j*%12.4e;\n", i+1, cosf(i*0.5f*f0) + cosf(i*(M_PI+0.5f*f1)),
+                                                          sinf(i*0.5f*f0) + sinf(i*(M_PI+0.5f*f1)));
+    }
+
+    fprintf(fid,"m = %u;\n", m);
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(length(y)-1);\n");
+    fprintf(fid,"plot(t,real(z),t-2*m,real(y));\n");
+    fclose(fid);
+    printf("results written to '%s'\n","resamp2_test.m");
+#endif
+}
diff --git a/src/filter/tests/resamp_crcf_autotest.c b/src/filter/tests/resamp_crcf_autotest.c
new file mode 100644
index 0000000..08cbfd1
--- /dev/null
+++ b/src/filter/tests/resamp_crcf_autotest.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST : test arbitrary resampler
+//
+void autotest_resamp_crcf()
+{
+    // options
+    unsigned int m = 13;        // filter semi-length (filter delay)
+    float r=1.27115323f;        // resampling rate (output/input)
+    float bw=0.45f;             // resampling filter bandwidth
+    float As=60.0f;             // resampling filter stop-band attenuation [dB]
+    unsigned int npfb=64;       // number of filters in bank (timing resolution)
+    unsigned int n=400;         // number of input samples
+    float fx=0.254230646f;      // complex input sinusoid frequency (0.2*r)
+
+    unsigned int i;
+
+    // number of input samples (zero-padded)
+    unsigned int nx = n + m;
+
+    // output buffer with extra padding for good measure
+    unsigned int y_len = (unsigned int) ceilf(1.1 * nx * r) + 4;
+
+    // arrays
+    float complex x[nx];
+    float complex y[y_len];
+
+    // create resampler
+    resamp_crcf q = resamp_crcf_create(r,m,bw,As,npfb);
+
+    // generate input signal
+    float wsum = 0.0f;
+    for (i=0; i<nx; i++) {
+        // compute window
+        float w = i < n ? kaiser(i, n, 10.0f, 0.0f) : 0.0f;
+
+        // apply window to complex sinusoid
+        x[i] = cexpf(_Complex_I*2*M_PI*fx*i) * w;
+
+        // accumulate window
+        wsum += w;
+    }
+
+    // resample
+    unsigned int ny=0;
+    unsigned int nw;
+    for (i=0; i<nx; i++) {
+        // execute resampler, storing in output buffer
+        resamp_crcf_execute(q, x[i], &y[ny], &nw);
+
+        // increment output size
+        ny += nw;
+    }
+
+    // clean up allocated objects
+    resamp_crcf_destroy(q);
+
+    // 
+    // analyze resulting signal
+    //
+
+    // check that the actual resampling rate is close to the target
+    float r_actual = (float)ny / (float)nx;
+    float fy = fx / r;      // expected output frequency
+
+    // run FFT and ensure that carrier has moved and that image
+    // frequencies and distortion have been adequately suppressed
+    unsigned int nfft = 1 << liquid_nextpow2(ny);
+    float complex yfft[nfft];   // fft input
+    float complex Yfft[nfft];   // fft output
+    for (i=0; i<nfft; i++)
+        yfft[i] = i < ny ? y[i] : 0.0f;
+    fft_run(nfft, yfft, Yfft, LIQUID_FFT_FORWARD, 0);
+    fft_shift(Yfft, nfft);  // run FFT shift
+
+    // find peak frequency
+    float Ypeak = 0.0f;
+    float fpeak = 0.0f;
+    float max_sidelobe = -1e9f;     // maximum side-lobe [dB]
+    float main_lobe_width = 0.07f;  // TODO: figure this out from Kaiser's equations
+    for (i=0; i<nfft; i++) {
+        // normalized output frequency
+        float f = (float)i/(float)nfft - 0.5f;
+
+        // scale FFT output appropriately
+        Yfft[i] /= (r * wsum);
+        float Ymag = 20*log10f( cabsf(Yfft[i]) );
+
+        // find frequency location of maximum magnitude
+        if (Ymag > Ypeak || i==0) {
+            Ypeak = Ymag;
+            fpeak = f;
+        }
+
+        // find peak side-lobe value, ignoring frequencies
+        // within a certain range of signal frequency
+        if ( fabsf(f-fy) > main_lobe_width )
+            max_sidelobe = Ymag > max_sidelobe ? Ymag : max_sidelobe;
+    }
+
+    if (liquid_autotest_verbose) {
+        // print results
+        printf("  desired resampling rate   :   %12.8f\n", r);
+        printf("  measured resampling rate  :   %12.8f    (%u/%u)\n", r_actual, ny, nx);
+        printf("  peak spectrum             :   %12.8f dB (expected 0.0 dB)\n", Ypeak);
+        printf("  peak frequency            :   %12.8f    (expected %-12.8f)\n", fpeak, fy);
+        printf("  max sidelobe              :   %12.8f dB (expected at least %.2f dB)\n", max_sidelobe, -As);
+    }
+    CONTEND_DELTA(     r_actual, r,    0.01f ); // check actual output sample rate
+    CONTEND_DELTA(     Ypeak,    0.0f, 0.25f ); // peak should be about 0 dB
+    CONTEND_DELTA(     fpeak,    fy,   0.01f ); // peak frequency should be nearly 0.2
+    CONTEND_LESS_THAN( max_sidelobe, -As );     // maximum side-lobe should be sufficiently low
+
+#if 0
+    // export results for debugging
+    char filename[] = "resamp_crcf_autotest.m";
+    FILE*fid = fopen(filename,"w");
+    fprintf(fid,"%% %s: auto-generated file\n",filename);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"r    = %12.8f;\n", r);
+    fprintf(fid,"nx   = %u;\n", nx);
+    fprintf(fid,"ny   = %u;\n", ny);
+    fprintf(fid,"nfft = %u;\n", nfft);
+
+    fprintf(fid,"Y = zeros(1,nfft);\n");
+    for (i=0; i<nfft; i++)
+        fprintf(fid,"Y(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(Yfft[i]), cimagf(Yfft[i]));
+
+    fprintf(fid,"\n\n");
+    fprintf(fid,"%% plot frequency-domain result\n");
+    fprintf(fid,"f=[0:(nfft-1)]/nfft-0.5;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,20*log10(abs(Y)),'Color',[0.25 0.5 0.0],'LineWidth',2);\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('normalized frequency');\n");
+    fprintf(fid,"ylabel('PSD [dB]');\n");
+    fprintf(fid,"axis([-0.5 0.5 -120 20]);\n");
+
+    fclose(fid);
+    printf("results written to %s\n",filename);
+#endif
+}
diff --git a/src/filter/tests/symsync_crcf_autotest.c b/src/filter/tests/symsync_crcf_autotest.c
new file mode 100644
index 0000000..30383e5
--- /dev/null
+++ b/src/filter/tests/symsync_crcf_autotest.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// symsync_crcf_autotest.c : test symbol timing synchronizer
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+void symsync_crcf_test(unsigned int _k,
+                       unsigned int _m,
+                       float        _beta,
+                       float        _tau,
+                       float        _rate)
+{
+    // options
+    float        tol        =  0.2f;    // error tolerance
+    unsigned int k          =  _k;      // samples/symbol (input)
+    unsigned int m          =  _m;      // filter delay (symbols)
+    float        beta       =  _beta;   // filter excess bandwidth factor
+    unsigned int num_filters= 32;       // number of filters in the bank
+
+    unsigned int num_symbols_init=200;  // number of initial symbols
+    unsigned int num_symbols_test=100;  // number of testing symbols
+
+    // transmit/receive filter types
+    liquid_firfilt_type ftype_tx = LIQUID_FIRFILT_ARKAISER;
+    liquid_firfilt_type ftype_rx = LIQUID_FIRFILT_ARKAISER;
+
+    float bt    =  0.02f;               // loop filter bandwidth
+    float tau   =  _tau;                // fractional symbol offset
+    float rate  =  _rate;               // resampled rate
+
+    // derived values
+    unsigned int num_symbols = num_symbols_init + num_symbols_test;
+    unsigned int num_samples = k*num_symbols;
+    unsigned int num_samples_resamp = (unsigned int) ceilf(num_samples*rate*1.1f) + 4;
+    
+    // compute delay
+    while (tau < 0) tau += 1.0f;    // ensure positive tau
+    float g = k*tau;                // number of samples offset
+    int ds=floorf(g);               // additional symbol delay
+    float dt = (g - (float)ds);     // fractional sample offset
+    if (dt > 0.5f) {                // force dt to be in [0.5,0.5]
+        dt -= 1.0f;
+        ds++;
+    }
+
+    unsigned int i;
+
+    // allocate arrays
+    float complex s[num_symbols];       // data symbols
+    float complex x[num_samples];       // interpolated samples
+    float complex y[num_samples_resamp];// resampled data (resamp_crcf)
+    float complex z[num_symbols + 64];  // synchronized symbols
+
+    // generate pseudo-random QPSK symbols
+    // NOTE: by using an m-sequence generator this sequence will be identical
+    //       each time this test is run
+    msequence ms = msequence_create_default(10);
+    for (i=0; i<num_symbols; i++) {
+        int si = msequence_generate_symbol(ms, 1);
+        int sq = msequence_generate_symbol(ms, 1);
+        s[i] = (si ? -1.0f : 1.0f) * M_SQRT1_2 +
+               (sq ? -1.0f : 1.0f) * M_SQRT1_2 * _Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // 
+    // create and run interpolator
+    //
+
+    // design interpolating filter
+    firinterp_crcf interp = firinterp_crcf_create_prototype(ftype_tx,k,m,beta,dt);
+
+    // interpolate block of samples
+    firinterp_crcf_execute_block(interp, s, num_symbols, x);
+
+    // destroy interpolator
+    firinterp_crcf_destroy(interp);
+
+    // 
+    // run resampler
+    //
+
+    // create resampler
+    unsigned int resamp_len = 10*k; // resampling filter semi-length (filter delay)
+    float resamp_bw = 0.45f;        // resampling filter bandwidth
+    float resamp_As = 60.0f;        // resampling filter stop-band attenuation
+    unsigned int resamp_npfb = 64;  // number of filters in bank
+    resamp_crcf resamp = resamp_crcf_create(rate, resamp_len, resamp_bw, resamp_As, resamp_npfb);
+
+    // run resampler on block
+    unsigned int ny;
+    resamp_crcf_execute_block(resamp, x, num_samples, y, &ny);
+
+    // destroy resampler
+    resamp_crcf_destroy(resamp);
+
+    // 
+    // create and run symbol synchronizer
+    //
+
+    // create symbol synchronizer
+    symsync_crcf sync = symsync_crcf_create_rnyquist(ftype_rx, k, m, beta, num_filters);
+
+    // set loop filter bandwidth
+    symsync_crcf_set_lf_bw(sync,bt);
+
+    // execute on entire block of samples
+    unsigned int nz;
+    symsync_crcf_execute(sync, y, ny, z, &nz);
+
+    // destroy synchronizer
+    symsync_crcf_destroy(sync);
+
+    // compute total delay through system
+    // (initial filter + resampler + matched filter)
+    unsigned int delay = m + 10 + m;
+
+    if (liquid_autotest_verbose) {
+        printf("symsync_crcf_test(),\n");
+        printf("    k       :   %u\n",      k);
+        printf("    m       :   %u\n",      m);
+        printf("    beta    :   %-8.4f\n",   beta);
+        printf("    tau     :   %-8.4f\n",   tau);
+        printf("    rate    :   %-12.8f\n",  rate);
+        printf("output symbols:\n");
+    }
+
+    // compare (and print) results
+    for (i=nz-num_symbols_test; i<nz; i++) {
+        // compute error
+        float err = cabsf( z[i] - s[i-delay] );
+        
+        // assert that error is below tolerance
+        CONTEND_LESS_THAN( err, tol );
+
+        // print formatted results if desired
+        if (liquid_autotest_verbose) {
+            printf("  sym_out(%4u) = %8.4f + j*%8.4f; %% {%8.4f + j*%8.4f} e = %12.8f %s\n",
+                    i+1,
+                    crealf(z[i]      ), cimagf(z[i]      ),
+                    crealf(s[i-delay]), cimagf(s[i-delay]),
+                    err, err < tol ? "" : "*");
+        }
+    }
+
+}
+
+// autotest scenarios
+void autotest_symsync_crcf_scenario_0() { symsync_crcf_test(2, 7, 0.35,  0.00, 1.0f    ); }
+void autotest_symsync_crcf_scenario_1() { symsync_crcf_test(2, 7, 0.35, -0.25, 1.0f    ); }
+void autotest_symsync_crcf_scenario_2() { symsync_crcf_test(2, 7, 0.35, -0.25, 1.0001f ); }
+void autotest_symsync_crcf_scenario_3() { symsync_crcf_test(2, 7, 0.35, -0.25, 0.9999f ); }
+
diff --git a/src/filter/tests/symsync_rrrf_autotest.c b/src/filter/tests/symsync_rrrf_autotest.c
new file mode 100644
index 0000000..5b776de
--- /dev/null
+++ b/src/filter/tests/symsync_rrrf_autotest.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// symsync_rrrf_autotest.c : test symbol timing synchronizer
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+void symsync_rrrf_test(unsigned int _k,
+                       unsigned int _m,
+                       float        _beta,
+                       float        _tau,
+                       float        _rate)
+{
+    // options
+    float        tol        =  0.2f;    // error tolerance
+    unsigned int k          =  _k;      // samples/symbol (input)
+    unsigned int m          =  _m;      // filter delay (symbols)
+    float        beta       =  _beta;   // filter excess bandwidth factor
+    unsigned int num_filters= 32;       // number of filters in the bank
+
+    unsigned int num_symbols_init=200;  // number of initial symbols
+    unsigned int num_symbols_test=100;  // number of testing symbols
+
+    // transmit/receive filter types
+    liquid_firfilt_type ftype_tx = LIQUID_FIRFILT_ARKAISER;
+    liquid_firfilt_type ftype_rx = LIQUID_FIRFILT_ARKAISER;
+
+    float bt    =  0.02f;               // loop filter bandwidth
+    float tau   =  _tau;                // fractional symbol offset
+    float rate  =  _rate;               // resampled rate
+
+    // derived values
+    unsigned int num_symbols = num_symbols_init + num_symbols_test;
+    unsigned int num_samples = k*num_symbols;
+    unsigned int num_samples_resamp = (unsigned int) ceilf(num_samples*rate*1.1f) + 4;
+    
+    // compute delay
+    while (tau < 0) tau += 1.0f;    // ensure positive tau
+    float g = k*tau;                // number of samples offset
+    int ds=floorf(g);               // additional symbol delay
+    float dt = (g - (float)ds);     // fractional sample offset
+    if (dt > 0.5f) {                // force dt to be in [0.5,0.5]
+        dt -= 1.0f;
+        ds++;
+    }
+
+    unsigned int i;
+
+    // allocate arrays
+    float s[num_symbols];           // data symbols
+    float x[num_samples];           // interpolated samples
+    float y[num_samples_resamp];    // resampled data (resamp_rrrf)
+    float z[num_symbols + 64];      // synchronized symbols
+
+    // generate pseudo-random BPSK symbols
+    // NOTE: by using an m-sequence generator this sequence will be identical
+    //       each time this test is run
+    msequence ms = msequence_create_default(10);
+    for (i=0; i<num_symbols; i++)
+        s[i] = (msequence_generate_symbol(ms,1) ? -1.0f : 1.0f);
+    msequence_destroy(ms);
+
+    // 
+    // create and run interpolator
+    //
+
+    // design interpolating filter
+    firinterp_rrrf interp = firinterp_rrrf_create_prototype(ftype_tx,k,m,beta,dt);
+
+    // interpolate block of samples
+    firinterp_rrrf_execute_block(interp, s, num_symbols, x);
+
+    // destroy interpolator
+    firinterp_rrrf_destroy(interp);
+
+    // 
+    // run resampler
+    //
+
+    // create resampler
+    unsigned int resamp_len = 10*k; // resampling filter semi-length (filter delay)
+    float resamp_bw = 0.45f;        // resampling filter bandwidth
+    float resamp_As = 60.0f;        // resampling filter stop-band attenuation
+    unsigned int resamp_npfb = 64;  // number of filters in bank
+    resamp_rrrf resamp = resamp_rrrf_create(rate, resamp_len, resamp_bw, resamp_As, resamp_npfb);
+
+    // run resampler on block
+    unsigned int ny;
+    resamp_rrrf_execute_block(resamp, x, num_samples, y, &ny);
+
+    // destroy resampler
+    resamp_rrrf_destroy(resamp);
+
+    // 
+    // create and run symbol synchronizer
+    //
+
+    // create symbol synchronizer
+    symsync_rrrf sync = symsync_rrrf_create_rnyquist(ftype_rx, k, m, beta, num_filters);
+
+    // set loop filter bandwidth
+    symsync_rrrf_set_lf_bw(sync,bt);
+
+    // execute on entire block of samples
+    unsigned int nz;
+    symsync_rrrf_execute(sync, y, ny, z, &nz);
+
+    // destroy synchronizer
+    symsync_rrrf_destroy(sync);
+
+    // compute total delay through system
+    // (initial filter + resampler + matched filter)
+    unsigned int delay = m + 10 + m;
+
+    if (liquid_autotest_verbose) {
+        printf("symsync_rrrf_test(),\n");
+        printf("    k       :   %u\n",      k);
+        printf("    m       :   %u\n",      m);
+        printf("    beta    :   %-8.4f\n",   beta);
+        printf("    tau     :   %-8.4f\n",   tau);
+        printf("    rate    :   %-12.8f\n",  rate);
+        printf("output symbols:\n");
+    }
+
+    // compare (and print) results
+    for (i=nz-num_symbols_test; i<nz; i++) {
+        // compute error
+        float err = fabsf( z[i] - s[i-delay] );
+        
+        // assert that error is below tolerance
+        CONTEND_LESS_THAN( err, tol );
+
+        // print formatted results if desired
+        if (liquid_autotest_verbose) {
+            printf("  sym_out(%4u) = %8.4f; %% {%8.4f} e = %12.8f %s\n",
+                    i+1,
+                    z[i],
+                    s[i-delay],
+                    err, err < tol ? "" : "*");
+        }
+    }
+
+}
+
+// autotest scenarios
+void autotest_symsync_rrrf_scenario_0() { symsync_rrrf_test(2, 7, 0.35,  0.00, 1.0f    ); }
+void autotest_symsync_rrrf_scenario_1() { symsync_rrrf_test(2, 7, 0.35, -0.25, 1.0f    ); }
+void autotest_symsync_rrrf_scenario_2() { symsync_rrrf_test(2, 7, 0.35, -0.25, 1.0001f ); }
+void autotest_symsync_rrrf_scenario_3() { symsync_rrrf_test(2, 7, 0.35, -0.25, 0.9999f ); }
+
diff --git a/src/framing/bench/bpacketsync_benchmark.c b/src/framing/bench/bpacketsync_benchmark.c
new file mode 100644
index 0000000..1753d92
--- /dev/null
+++ b/src/framing/bench/bpacketsync_benchmark.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+// callback function
+static int bpacketsync_benchmark_callback(unsigned char *  _payload,
+                                          int              _payload_valid,
+                                          unsigned int     _payload_len,
+                                          framesyncstats_s _stats,
+                                          void *           _userdata)
+{
+    if (!_payload_valid)
+        return 0;
+
+    // increment number of packets found and return
+    unsigned int * num_packets_found = (unsigned int *) _userdata;
+    *num_packets_found += 1;
+    return 0;
+}
+
+void benchmark_bpacketsync(struct rusage *_start,
+                           struct rusage *_finish,
+                           unsigned long int *_num_iterations)
+{
+    // adjust number of iterations
+    *_num_iterations *= 4;
+
+    // options
+    unsigned int dec_msg_len = 64;      // original data message length
+    crc_scheme check = LIQUID_CRC_NONE; // data integrity check
+    fec_scheme fec0 = LIQUID_FEC_NONE;  // inner code
+    fec_scheme fec1 = LIQUID_FEC_NONE;  // outer code
+
+    // create packet generator
+    bpacketgen pg = bpacketgen_create(0, dec_msg_len, check, fec0, fec1);
+
+    // compute packet length
+    unsigned int enc_msg_len = bpacketgen_get_packet_len(pg);
+
+    // initialize arrays
+    unsigned char msg_org[dec_msg_len]; // original message
+    unsigned char msg_enc[enc_msg_len]; // encoded message
+
+    unsigned int num_packets_found=0;
+
+    // create packet synchronizer
+    bpacketsync ps = bpacketsync_create(0, bpacketsync_benchmark_callback, (void*)&num_packets_found);
+
+    unsigned long int i;
+    // initialize original data message
+    for (i=0; i<dec_msg_len; i++)
+        msg_org[i] = rand() % 256;
+
+    // encode packet
+    bpacketgen_encode(pg,msg_org,msg_enc);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // push packet through synchronizer
+        bpacketsync_execute_byte(ps, msg_enc[(4*i+0)%enc_msg_len]);
+        bpacketsync_execute_byte(ps, msg_enc[(4*i+1)%enc_msg_len]);
+        bpacketsync_execute_byte(ps, msg_enc[(4*i+2)%enc_msg_len]);
+        bpacketsync_execute_byte(ps, msg_enc[(4*i+3)%enc_msg_len]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    printf("found %u packets\n", num_packets_found);
+
+    // clean up allocated objects
+    bpacketgen_destroy(pg);
+    bpacketsync_destroy(ps);
+}
+
diff --git a/src/framing/bench/bpresync_benchmark.c b/src/framing/bench/bpresync_benchmark.c
new file mode 100644
index 0000000..8ad24fd
--- /dev/null
+++ b/src/framing/bench/bpresync_benchmark.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+// Helper function to keep code base small
+void bpresync_cccf_bench(struct rusage *     _start,
+                         struct rusage *     _finish,
+                         unsigned long int * _num_iterations,
+                         unsigned int        _n,
+                         unsigned int        _m)
+{
+    // adjust number of iterations
+    *_num_iterations *= 4;
+    *_num_iterations /= _n;
+    *_num_iterations /= _m;
+
+    // generate sequence (random)
+    float complex h[_n];
+    unsigned long int i;
+    for (i=0; i<_n; i++) {
+        h[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    // generate synchronizer
+    bpresync_cccf q = bpresync_cccf_create(h, _n, 0.1f, _m);
+
+    // input sequence (random)
+    float complex x[7];
+    for (i=0; i<7; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    float complex rxy;
+    float dphi_hat;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // push input sequence through synchronizer
+        bpresync_cccf_push(q, x[0]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[1]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[2]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[3]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[4]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[5]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+        bpresync_cccf_push(q, x[6]);  bpresync_cccf_correlate(q, &rxy, &dphi_hat);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 7;
+
+    // clean up allocated objects
+    bpresync_cccf_destroy(q);
+}
+
+#define BPRESYNC_CCCF_BENCHMARK_API(N,M)    \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ bpresync_cccf_bench(_start, _finish, _num_iterations, N, M); }
+
+void benchmark_bpresync_cccf_16   BPRESYNC_CCCF_BENCHMARK_API(16,   6);
+void benchmark_bpresync_cccf_32   BPRESYNC_CCCF_BENCHMARK_API(32,   6);
+void benchmark_bpresync_cccf_64   BPRESYNC_CCCF_BENCHMARK_API(64,   6);
+void benchmark_bpresync_cccf_128  BPRESYNC_CCCF_BENCHMARK_API(128,  6);
+void benchmark_bpresync_cccf_256  BPRESYNC_CCCF_BENCHMARK_API(256,  6);
+
diff --git a/src/framing/bench/bsync_benchmark.c b/src/framing/bench/bsync_benchmark.c
new file mode 100644
index 0000000..eca86d7
--- /dev/null
+++ b/src/framing/bench/bsync_benchmark.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+// Helper function to keep code base small
+void bsync_cccf_bench(struct rusage *     _start,
+                      struct rusage *     _finish,
+                      unsigned long int * _num_iterations,
+                      unsigned int        _n)
+{
+    // adjust number of iterations
+    *_num_iterations *= 4;
+    *_num_iterations /= _n;
+
+    // generate sequence (random)
+    float complex h[_n];
+    unsigned long int i;
+    for (i=0; i<_n; i++) {
+        h[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    // generate synchronizer
+    bsync_cccf q = bsync_cccf_create(_n,h);
+
+    // input sequence (random)
+    float complex x[7];
+    for (i=0; i<7; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    float complex rxy;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // push input sequence through synchronizer
+        bsync_cccf_correlate(q, x[0], &rxy);
+        bsync_cccf_correlate(q, x[1], &rxy);
+        bsync_cccf_correlate(q, x[2], &rxy);
+        bsync_cccf_correlate(q, x[3], &rxy);
+        bsync_cccf_correlate(q, x[4], &rxy);
+        bsync_cccf_correlate(q, x[5], &rxy);
+        bsync_cccf_correlate(q, x[6], &rxy);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 7;
+
+    // clean up allocated objects
+    bsync_cccf_destroy(q);
+}
+
+#define BSYNC_CCCF_BENCHMARK_API(N)         \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ bsync_cccf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_bsync_cccf_16   BSYNC_CCCF_BENCHMARK_API(16)
+void benchmark_bsync_cccf_32   BSYNC_CCCF_BENCHMARK_API(32)
+void benchmark_bsync_cccf_64   BSYNC_CCCF_BENCHMARK_API(64)
+void benchmark_bsync_cccf_128  BSYNC_CCCF_BENCHMARK_API(128)
+void benchmark_bsync_cccf_256  BSYNC_CCCF_BENCHMARK_API(256)
+
diff --git a/src/framing/bench/detector_benchmark.c b/src/framing/bench/detector_benchmark.c
new file mode 100644
index 0000000..1024108
--- /dev/null
+++ b/src/framing/bench/detector_benchmark.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+// Helper function to keep code base small
+void detector_cccf_bench(struct rusage *     _start,
+                         struct rusage *     _finish,
+                         unsigned long int * _num_iterations,
+                         unsigned int        _n)
+{
+    // adjust number of iterations
+    *_num_iterations *= 4;
+    *_num_iterations /= _n;
+
+    // generate sequence (random)
+    float complex h[_n];
+    unsigned long int i;
+    for (i=0; i<_n; i++) {
+        h[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    // generate synchronizer
+    float threshold = 0.5f;
+    float dphi_max  = 0.07f;
+    detector_cccf q = detector_cccf_create(h, _n, threshold, dphi_max);
+
+    // input sequence (random)
+    float complex x[7];
+    for (i=0; i<7; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    float tau_hat;
+    float dphi_hat;
+    float gamma_hat;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    int detected = 0;
+    for (i=0; i<(*_num_iterations); i++) {
+        // push input sequence through synchronizer
+        detected ^= detector_cccf_correlate(q, x[0], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[1], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[2], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[3], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[4], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[5], &tau_hat, & dphi_hat, &gamma_hat);
+        detected ^= detector_cccf_correlate(q, x[6], &tau_hat, & dphi_hat, &gamma_hat);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 7;
+
+    // clean up allocated objects
+    detector_cccf_destroy(q);
+}
+
+#define DETECTOR_CCCF_BENCHMARK_API(N)      \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ detector_cccf_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_detector_cccf_16   DETECTOR_CCCF_BENCHMARK_API(16);
+void benchmark_detector_cccf_32   DETECTOR_CCCF_BENCHMARK_API(32);
+void benchmark_detector_cccf_64   DETECTOR_CCCF_BENCHMARK_API(64);
+void benchmark_detector_cccf_128  DETECTOR_CCCF_BENCHMARK_API(128);
+void benchmark_detector_cccf_256  DETECTOR_CCCF_BENCHMARK_API(256);
+
diff --git a/src/framing/bench/flexframesync_benchmark.c b/src/framing/bench/flexframesync_benchmark.c
new file mode 100644
index 0000000..611bc5a
--- /dev/null
+++ b/src/framing/bench/flexframesync_benchmark.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <assert.h>
+#include "liquid.h"
+
+typedef struct {
+    unsigned char * header;
+    unsigned char * payload;
+    unsigned int num_frames_tx;
+    unsigned int num_frames_detected;
+    unsigned int num_headers_valid;
+    unsigned int num_payloads_valid;
+} framedata;
+
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    //printf("callback invoked\n");
+    framedata * fd = (framedata*) _userdata;
+
+    // increment number of frames detected
+    fd->num_frames_detected++;
+
+    // check if header is valid
+    if (_header_valid)
+        fd->num_headers_valid++;
+
+    // check if payload is valid
+    if (_payload_valid)
+        fd->num_payloads_valid++;
+
+    return 0;
+}
+
+// Helper function to keep code base small
+void benchmark_flexframesync(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+{
+    *_num_iterations /= 128;
+    unsigned long int i;
+
+    // create flexframegen object
+    flexframegenprops_s fgprops;
+    flexframegenprops_init_default(&fgprops);
+    fgprops.check      = LIQUID_CRC_32;
+    fgprops.fec0       = LIQUID_FEC_NONE;
+    fgprops.fec1       = LIQUID_FEC_NONE;
+    fgprops.mod_scheme = LIQUID_MODEM_QPSK;
+    flexframegen fg = flexframegen_create(&fgprops);
+    flexframegen_print(fg);
+
+    // frame data
+    unsigned int payload_len = 8;
+    unsigned char header[14];
+    unsigned char payload[payload_len];
+    // initialize header, payload
+    for (i=0; i<14; i++)
+        header[i] = i;
+    for (i=0; i<payload_len; i++)
+        payload[i] = rand() & 0xff;
+    framedata fd = {header, payload, 0, 0, 0, 0};
+
+    // create flexframesync object
+    flexframesync fs = flexframesync_create(callback,(void*)&fd);
+    flexframesync_print(fs);
+
+    // generate the frame
+    flexframegen_assemble(fg, header, payload, payload_len);
+    unsigned int frame_len = flexframegen_getframelen(fg);
+    float complex frame[frame_len];
+    int frame_complete = 0;
+    while (!frame_complete) {
+        frame_complete = flexframegen_write_samples(fg, frame, frame_len);
+    }
+    // add some noise
+    for (i=0; i<frame_len; i++)
+        frame[i] += 0.02f*(randnf() + _Complex_I*randnf());
+
+    // 
+    // start trials
+    //
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        flexframesync_execute(fs, frame, frame_len);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // print results
+    fd.num_frames_tx = *_num_iterations;
+    printf("  frames detected/header/payload/transmitted:   %6u / %6u / %6u / %6u\n",
+            fd.num_frames_detected,
+            fd.num_headers_valid,
+            fd.num_payloads_valid,
+            fd.num_frames_tx);
+
+    // destroy objects
+    flexframegen_destroy(fg);
+    flexframesync_destroy(fs);
+}
+
diff --git a/src/framing/bench/framesync64_benchmark.c b/src/framing/bench/framesync64_benchmark.c
new file mode 100644
index 0000000..8032eba
--- /dev/null
+++ b/src/framing/bench/framesync64_benchmark.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <math.h>
+#include "liquid.h"
+
+typedef struct {
+    unsigned int num_frames_tx;         // number of transmitted frames
+    unsigned int num_frames_detected;   // number of received frames (detected)
+    unsigned int num_frames_valid;      // number of valid payloads
+} framedata;
+
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    //printf("callback invoked, payload valid: %s\n", _payload_valid ? "yes" : "no");
+    framedata * fd = (framedata*) _userdata;
+    fd->num_frames_detected += 1;
+    fd->num_frames_valid    += _payload_valid ? 1 : 0;
+    return 0;
+}
+
+// Helper function to keep code base small
+void benchmark_framesync64(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+{
+    *_num_iterations /= 128;
+    unsigned long int i;
+
+    framegen64 fg = framegen64_create();
+    framegen64_print(fg);
+
+    // frame data
+    unsigned char header[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+    unsigned char payload[64];
+    // initialize payload
+    for (i=0; i<64; i++)
+        payload[i] = rand() & 0xff;
+    framedata fd = {0, 0, 0};
+
+    // create framesync64 object
+    framesync64 fs = framesync64_create(callback,(void*)&fd);
+    framesync64_print(fs);
+
+    // generate the frame
+    //unsigned int frame_len = framegen64_getframelen(fg);
+    unsigned int frame_len = LIQUID_FRAME64_LEN;
+    float complex frame[frame_len];
+    framegen64_execute(fg, header, payload, frame);
+
+    // add some noise
+    for (i=0; i<frame_len; i++)
+        frame[i] += 0.01f*(randnf() + _Complex_I*randnf()) * M_SQRT1_2;
+
+    // 
+    // start trials
+    //
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        framesync64_execute(fs, frame, frame_len);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+
+    fd.num_frames_tx = *_num_iterations;
+    printf("  frames detected/valid/transmitted  :   %6u / %6u / %6u\n",
+            fd.num_frames_detected,
+            fd.num_frames_valid,
+            fd.num_frames_tx);
+
+    framegen64_destroy(fg);
+    framesync64_destroy(fs);
+}
+
diff --git a/src/framing/bench/gmskframesync_benchmark.c b/src/framing/bench/gmskframesync_benchmark.c
new file mode 100644
index 0000000..b8f70d3
--- /dev/null
+++ b/src/framing/bench/gmskframesync_benchmark.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <math.h>
+#include <assert.h>
+#include "liquid.h"
+
+typedef struct {
+    unsigned char * header;
+    unsigned char * payload;
+    unsigned int num_frames_tx;
+    unsigned int num_frames_detected;
+    unsigned int num_headers_valid;
+    unsigned int num_payloads_valid;
+} framedata;
+
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    //printf("callback invoked\n");
+    framedata * fd = (framedata*) _userdata;
+
+    // increment number of frames detected
+    fd->num_frames_detected++;
+
+    // check if header is valid
+    if (_header_valid)
+        fd->num_headers_valid++;
+
+    // check if payload is valid
+    if (_payload_valid)
+        fd->num_payloads_valid++;
+
+    return 0;
+}
+
+// benchmark regular frame synchronizer with short frames; effectively
+// test acquisition complexity
+void benchmark_gmskframesync(struct rusage *     _start,
+                             struct rusage *     _finish,
+                             unsigned long int * _num_iterations)
+{
+    *_num_iterations /= 128;
+    unsigned long int i;
+
+    // options
+    unsigned int payload_len = 8;       // length of payload (bytes)
+    crc_scheme check = LIQUID_CRC_32;   // data validity check
+    fec_scheme fec0  = LIQUID_FEC_NONE; // inner forward error correction
+    fec_scheme fec1  = LIQUID_FEC_NONE; // outer forward error correction
+    float SNRdB = 30.0f;                // SNR
+
+    // derived values
+    float nstd  = powf(10.0f, -SNRdB/20.0f);
+
+    // create gmskframegen object
+    gmskframegen fg = gmskframegen_create();
+
+    // frame data
+    unsigned char header[14];
+    unsigned char payload[payload_len];
+    // initialize header, payload
+    for (i=0; i<14; i++)
+        header[i] = i;
+    for (i=0; i<payload_len; i++)
+        payload[i] = rand() & 0xff;
+    framedata fd = {header, payload, 0, 0, 0, 0};
+
+    // create gmskframesync object
+    gmskframesync fs = gmskframesync_create(callback,(void*)&fd);
+    //gmskframesync_print(fs);
+
+    // generate the frame
+    gmskframegen_assemble(fg, header, payload, payload_len, check, fec0, fec1);
+    gmskframegen_print(fg);
+    unsigned int frame_len = gmskframegen_getframelen(fg);
+    float complex frame[frame_len];
+    int frame_complete = 0;
+    unsigned int n=0;
+    while (!frame_complete) {
+        assert(n < frame_len);
+        frame_complete = gmskframegen_write_samples(fg, &frame[n]);
+        n += 2;
+    }
+    // add some noise
+    for (i=0; i<frame_len; i++)
+        frame[i] += nstd*(randnf() + _Complex_I*randnf());
+
+    // 
+    // start trials
+    //
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        gmskframesync_execute(fs, frame, frame_len);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // print results
+    fd.num_frames_tx = *_num_iterations;
+    printf("  frames detected/header/payload/transmitted:   %6u / %6u / %6u / %6u\n",
+            fd.num_frames_detected,
+            fd.num_headers_valid,
+            fd.num_payloads_valid,
+            fd.num_frames_tx);
+
+    // destroy objects
+    gmskframegen_destroy(fg);
+    gmskframesync_destroy(fs);
+}
+
+// benchmark regular frame synchronizer with noise; essentially test
+// complexity when no signal is present
+void benchmark_gmskframesync_noise(struct rusage *     _start,
+                                   struct rusage *     _finish,
+                                   unsigned long int * _num_iterations)
+{
+    *_num_iterations /= 400;
+    unsigned long int i;
+
+    // options
+    float SNRdB = 20.0f;                // SNR
+
+    // derived values
+    float nstd  = powf(10.0f, -SNRdB/20.0f);
+
+    // create frame synchronizer
+    gmskframesync fs = gmskframesync_create(NULL, NULL);
+
+    // allocate memory for noise buffer and initialize
+    unsigned int num_samples = 1024;
+    float complex y[num_samples];
+    for (i=0; i<num_samples; i++)
+        y[i] = nstd*(randnf() + randnf()*_Complex_I)*M_SQRT1_2;
+
+    // 
+    // start trials
+    //
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // push samples through synchronizer
+        gmskframesync_execute(fs, y, num_samples);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    // scale result by number of samples in buffer
+    *_num_iterations *= num_samples;
+
+    // destroy framing objects
+    gmskframesync_destroy(fs);
+}
+
diff --git a/src/framing/bench/presync_benchmark.c b/src/framing/bench/presync_benchmark.c
new file mode 100644
index 0000000..e3c7678
--- /dev/null
+++ b/src/framing/bench/presync_benchmark.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+
+#include "liquid.internal.h"
+
+// Helper function to keep code base small
+void presync_cccf_bench(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int        _n,
+                        unsigned int        _m)
+{
+    // adjust number of iterations
+    *_num_iterations *= 4;
+    *_num_iterations /= _n;
+    *_num_iterations /= _m;
+
+    // generate sequence (random)
+    float complex h[_n];
+    unsigned long int i;
+    for (i=0; i<_n; i++) {
+        h[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    // generate synchronizer
+    presync_cccf q = presync_cccf_create(h, _n, 0.1f, _m);
+
+    // input sequence (random)
+    float complex x[7];
+    for (i=0; i<7; i++) {
+        x[i] = (rand() % 2 ? 1.0f : -1.0f) +
+               (rand() % 2 ? 1.0f : -1.0f)*_Complex_I;
+    }
+
+    float complex rxy;
+    float dphi_hat;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // push input sequence through synchronizer
+        presync_cccf_push(q, x[0]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[1]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[2]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[3]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[4]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[5]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+        presync_cccf_push(q, x[6]);  presync_cccf_correlate(q, &rxy, &dphi_hat);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 7;
+
+    // clean up allocated objects
+    presync_cccf_destroy(q);
+}
+
+#define PRESYNC_CCCF_BENCHMARK_API(N,M)     \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ presync_cccf_bench(_start, _finish, _num_iterations, N, M); }
+
+void benchmark_presync_cccf_16   PRESYNC_CCCF_BENCHMARK_API(16,   6);
+void benchmark_presync_cccf_32   PRESYNC_CCCF_BENCHMARK_API(32,   6);
+void benchmark_presync_cccf_64   PRESYNC_CCCF_BENCHMARK_API(64,   6);
+void benchmark_presync_cccf_128  PRESYNC_CCCF_BENCHMARK_API(128,  6);
+void benchmark_presync_cccf_256  PRESYNC_CCCF_BENCHMARK_API(256,  6);
+
diff --git a/src/framing/packetizer.readme.txt b/src/framing/packetizer.readme.txt
new file mode 100644
index 0000000..7ae7737
--- /dev/null
+++ b/src/framing/packetizer.readme.txt
@@ -0,0 +1,58 @@
+======================================
+ packetizer
+======================================
+
+The liquid packetizer is a structure for abstracting multi-level forward
+error-correction from the user.
+The packetizer accepts a buffer of uncoded data bytes and adds a
+cyclic redundancy check (crc) before applying two levels of forward error-
+correction and bit-level interleaving.  The user may choose any two 
+supported FEC schemes (including none) and the packetizer object will
+handle buffering and data management internally, providing a truly abstract
+interface.  The same is true for the packet decoder which accepts an array
+of possibly corrupt data and attempts to recover the original message using
+the FEC schemes provided.  The packet decoder returns the validity of the
+resulting CRC as well as its best effort of decoding the message.
+
+The packetizer also allows for re-structuring if the user wishes to change
+error-correction schemes or data lengths.  This is accomplished with the
+packetier_recreate() method.
+
+Here is a minimal example demonstrating the packetizer's most basic
+functionality:
+
+    #include <liquid/liquid.h>
+    ...
+    {
+        // set up the options
+        unsigned int n=16;                      // uncoded data length
+        crc_scheme check = LIQUID_CRC_32;       // data integrity check
+        fec_scheme fec0 = LIQUID_FEC_HAMMING74; // inner code
+        fec_scheme fec1 = LIQUID_FEC_REP3;      // outer code
+
+        // compute resulting packet length
+        unsigned int k = packetizer_compute_enc_msg_len(n,fec0,fec1);
+
+        // set up the arrays
+        unsigned char msg[n];               // original message
+        unsigned char packet[k];            // encoded message
+        unsigned char msg_dec[n];           // decoded message
+        int crc_pass;                       // decoder validity check
+
+        // create the packetizer object
+        packetizer p = packetizer_create(n,check,fec0,fec1);
+
+        // initialize msg here
+        unsigned int i;
+        for (i=0; i<n; i++) msg[i] = i & 0xff;
+
+        // encode the packet
+        packetizer_encode(p,msg,packet);
+
+        // decode the packet, returning validity
+        crc_pass = packetizer_decode(p,packet,msg_dec);
+
+        // destroy the packetizer object
+        packetizer_destroy(p);
+    }
+
diff --git a/src/framing/readme.flexframe.txt b/src/framing/readme.flexframe.txt
new file mode 100644
index 0000000..6a7626c
--- /dev/null
+++ b/src/framing/readme.flexframe.txt
@@ -0,0 +1,43 @@
+======================================
+ Flexible frame structure
+======================================
+
+signal
+  ^
+  |     .+---------+-----+--------+----//---+.
+  |    / |         |     |        |         | \
+  |  r/u | phasing | p/n | header | payload | r\d
+  |__/   |         |     |        |         |   \__
+  +-------------------------------------------------> time
+
+new header options:
+    header      check   fec1        bytes   bpsk syms   1% FER SNR
+    [[14 + 5 + crc16] + h128 ]  >   32  >   256         3.0 dB /  2.9 dB soft
+    [[14 + 5 + crc24] + g2412]  >   45  >   360          -  dB / -
+    [[14 + 5 + crc24] + v27  ]  >   46  >   368          -  dB / -0.3 dB soft
+    [[14 + 5 + crc24] + v39  ]  >   69  >   552          -  dB / -1.1 dB soft
+    
+    concatenated code:
+    14 + 6 + 4 = 24 + secded7264 > 27 + h84 > 54
+    [[[14 + 6 + crc32] + secded7264] + h84] > 54  > 432  -  dB /  0.2 dB soft
+
+section             # syms          Description
+ramp up (r/u)       0+              phasing (1010...) with ramp up
+phasing pattern     0+              10101010...
+p/n sequence        64              BPSK P/N sequence (m-sequence, g=0x005b)
+header              256
+                    # bytes
+    crc             2               cyclic redundancy check
+    packet fec      2               payload fec (inner/outer), check
+    mod scheme/bps  1               modulation scheme, depth
+    payload_len     2               # bytes in payload (up to 65535)
+    user data       14              space for user-defined data
+    total           19
+    encoded         32              Hamming(12,8)
+payload             [variable]      data payload using arbitrary linear
+                                    modulation
+ramp down (r\d)     0+              phasing (1010...) with ramp down
+
+The header includes a mandatory 32-bit crc with 1/2 rate forward error-
+correction code totaling 256 bits (32 bytes) of encoded data.
+
diff --git a/src/framing/readme.framesync64.md b/src/framing/readme.framesync64.md
new file mode 100644
index 0000000..911ad74
--- /dev/null
+++ b/src/framing/readme.framesync64.md
@@ -0,0 +1,38 @@
+
+frame64 description
+-------------------
+
+## Structure ##
+
+    signal
+      ^
+      |     .+-------+---------------------------+.
+      |    / |       |                           | \
+      |  r/u |  p/n  |    header/payload         | r\d
+      |__/   |       |                           |   \__
+      +--------------------------------------------------> time
+
+    section             length          Description
+    ramp-up             3 symbols       filter ramp/up
+    p/n sequence        64 symbols      BPSK p/n sequence (m-sequence, g=0x0043)
+    header/payload      600 symbols     64-byte packet payload with 8-byte header,
+                                        24-bit crc, encoded with the Golay(24,12) FEC,
+                                        (150 bytes encoded)
+                                        modulated with QPSK
+    ramp-dn             3 symbols       filter ramp\down
+    total:              670 symbols
+
+    interpolated:       1340 samples    interpolated using half-rate square-
+                                        root Nyquist pulse-shaping filter at
+                                        2 samples/symbol
+
+## Description ##
+
+The frame structure is simply a 64-bit pseudo-random p/n sequence
+modulated with BPSK followed by a QPSK payload. The payload has a fixed
+length of 64 bytes (with an 8-byte 'header'), a 24-bit cyclic redudancy
+check, and encoded with a half-rate Golay(24,12) block code. The entire
+frame uses a square-root Nyquist pulse shape with an excess bandwidth
+of 0.5 and interpolated at two samples per symbol. The resulting frame
+length is exactly 1340 samples.
+
diff --git a/src/framing/readme.gmskframe.txt b/src/framing/readme.gmskframe.txt
new file mode 100644
index 0000000..58fe147
--- /dev/null
+++ b/src/framing/readme.gmskframe.txt
@@ -0,0 +1,53 @@
+
+GMSK frame structure
+====================
+
+The gmskframegen and gmskframesync object facilite sending packets over
+the air with Gauss minimum-shift keying (GMSK). This modulation scheme
+is a special case of binary frequency-shift keying (BFSK) which uses
+the minimum frequency separation between tones and uses Gauss pulse
+shaping to reduce its occupied bandwidth. Its spectral efficiency is
+nominally 1 b/s/Hz, and the framing structure allows any length payload
+up to 65,535 bytes with two levels of forward error-correction coding.
+
+    signal
+      ^
+      |     .+---------+-----+--------+----//---+.
+      |    / |         |     |        |         | \
+      |  r/u | phasing | p/n | header | payload | r\d
+      |__/   |         |     |        |         |   \__
+      +-------------------------------------------------> time
+
+section             # bits          Description
+ramp up (r/u)       8               phasing (1010...) with ramp up
+phasing pattern     40              10101010...
+p/n sequence        64              BPSK P/N sequence (m-sequence, g=0x005b)
+header              208             includes payload length, fec schemes,
+                                    crc, and 8 bytes of user-defined data,
+                                    all encoded with the Hamming(12,8)
+                                    block code
+payload             [variable]      data payload using arbitrary forward
+                                    error correction
+ramp down (r\d)     8               phasing (1010...) with ramp down
+
+The approximate spectral efficiency of the frame is determined by the
+following equation:
+
+       payload*8/rate         1
+  ---------------------- * --------  bits/second/Hertz
+   328 + payload*8/rate     1 + BT
+
+where 'payload' is the length of the payload (in bytes), 'rate' is
+the rate of the pair of forward error-correction codes, and 'BT' is
+the bandwidth-time constant of the GMSK signaling. Note that 328 is
+the number of bits of overhead associated with the frame (e.g. sending
+the header, etc.)
+
+For example, if the payload is 1024 bytes encoded with a half-rate
+convolutional code, and modulated with a bandwdith-time constant of
+BT=0.5, the approximate spectral efficiency is
+
+     1024*8/(1/2)         1
+  ------------------ * --------- = 0.654 b/s/Hz
+  328 + 1024*8/(1/2)    1 + 0.5
+
diff --git a/src/framing/readme.ofdm.txt b/src/framing/readme.ofdm.txt
new file mode 100644
index 0000000..12f6f2a
--- /dev/null
+++ b/src/framing/readme.ofdm.txt
@@ -0,0 +1,116 @@
+=========================================================================
+
+  OFDM flexible framing structure
+
+=========================================================================
+
+signal
+  ^
+  |   +----+----+----+----+----+-//-+----+----+-//-+----+
+  |   |    |    |    |    |              |              |
+  |   | S0 | S0 | .. | S1 |  ..header..  | ..payload..  |
+  |___|    |    |    |    |              |              |__
+  +---------------------------------------------------------> time
+
+
+             spectral null    ^    pilot subcarrier
+                  /           |     /
+     *----*----*    *----*----+----*----*----*----*----*
+     |         |    |         |                        |
+     |         |    |         |      ...               |
+  ___/         \____/         |                        \___
+  +---------------------------+----------------------------+ freq
+-Fs/2                         0                         +Fs/2
+
+
+Overview:
+    The ofdmflexframe family of objects (generator and synchronizer)
+    realize a simple way to load data onto an OFDM physical layer
+    system. OFDM has several benefits over traditional 'narrowband'
+    communications systems.
+    These objects allow the user to abstractly specify the number of
+    subcarriers, their assignment (null/pilot/data), forward
+    error-correction and modulation scheme.
+    Furthermore, the framing structure includes a provision for a brief
+    user-defined header which can be used for source/destination
+    address, packet identifier, etc.
+
+Operational description:
+    The structure of the frame consists of three main components: the
+    preamble, the header, and the payload.
+    
+  Preamble
+    The preamble consists of two types of phasing symbols: the S0 and S1
+    sequences. The S0 symbols are necessary for coarse carrier frequency
+    and timing offsets while the S1 sequence is used for fine timing
+    acquisition and equalizer gain estimation.  The transmitter
+    generates multiple S0 symbols (minimally 2, but usually 3 or more)
+    and just a single S1 symbol. This aligns the receiver's timing to
+    that of the transmitter, signalling the start of the header.
+
+  Header
+    The header consists of one or more OFDM symbols; the exact number of
+    OFDM symbols depends on the number of subcarriers allocated and the
+    assignment of these subcarriers (null/pilot/data). The header
+    carries exactly 14 bytes of information, 6 of which are used
+    internally and the remaining 8 are user-defined. The internal data
+    signal the receiver of the modulation, forward error-correction, and
+    data validation schemes of the payload as well as its length in
+    bytes. These data are encoded with a forward error-correction scheme
+    and modulated onto the first several OFDM symbols.
+
+  Payload
+    The payload consists of zero or more OFDM symbols. Like the header,
+    the exact number of OFDM symbols depends on the number of
+    subcarriers allocated and the assignment of these subcarriers.
+
+OFDM frame gen/sync parameters (must match on both TX and RX side):
+    M       :   Total number of subcarriers
+    p*      :   subcarrier allocation
+    cplen   :   cyclic prefix length (samples)
+
+    M0      :   number of null subcarriers
+    Mp      :   number of pilot subcarriers
+    Md      :   number of data subcarriers
+
+    num_S0  :   number of S0 symbols
+    num_S1  :   number of S1 symbols (must be 1, non-negotiable)
+
+Notes:
+    * The length of each data symbol is equal to the number of
+      subcarriers plus the cyclic prefix length (e.g. if N=64 and
+      cplen=16, each data symbol is 80 samples long) with the exception
+      of the S0 training symbols which are simply the number of
+      subcarriers.
+    * The S0 symbols do NOT have a cyclic prefix; this is
+      necessary to preserve continuity between symbols. Unlike 802.11a
+      (and derivative protocols) in which a fixed cyclic prefix has
+      been designed into the system, the flexibility of liquid-dsp's
+      OFDM framing structure requires that no prefix is added to
+      maintain temporal continuity.
+
+Details:
+    section num samples     Description/purpose
+
+    S0      M*num_S0        Initial preamble for coarse carrier
+                            frequency, phase, and timing offset
+                            estimates
+
+    S1      M+cplen         Secondary preamble for timing disambiguation
+                            and equalization
+
+    header  [variable]      Framing descriptor
+                            user-defined: 8 bytes
+                            internal    : 6 bytes
+                            encoded with Hamming(12,8), 16-bit CRC: 24 bytes
+                            modulated with QPSK : 96 modem symbols
+                            ...
+
+    payload [variable]      user-defined length...
+
+TODO:
+    * Iteratively improve initial carrier frequency, phase, timing
+      offset by averaging over S0 symbols (until S1 symbol is found)
+    * Make cyclic prefix length discoverable on the receiver by
+      observing phase slope of S1
+
diff --git a/src/framing/src/bpacketgen.c b/src/framing/src/bpacketgen.c
new file mode 100644
index 0000000..e8dccaf
--- /dev/null
+++ b/src/framing/src/bpacketgen.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// bpacketgen
+//
+// binary packet generator/encoder
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// bpacketgen object structure
+struct bpacketgen_s {
+    // options
+    unsigned int g;                 // p/n sequence genpoly
+    unsigned int pnsequence_len;    // p/n sequence length (bytes)
+    unsigned int dec_msg_len;       // payload length
+    crc_scheme crc;                 // payload check
+    fec_scheme fec0;                // payload fec (inner)
+    fec_scheme fec1;                // payload fec (outer)
+    
+    // derived values
+    unsigned int enc_msg_len;       // encoded mesage length
+    unsigned int header_len;        // header length (12 bytes encoded)
+    unsigned int packet_len;        // total packet length
+
+    // arrays
+    unsigned char * pnsequence;     // p/n sequence
+
+    // bpacket header
+    //  0   :   version number
+    //  1   :   crc
+    //  2   :   fec0
+    //  3   :   fec1
+    //  4:5 :   payload length
+    unsigned char header_dec[6];    // uncoded bytes
+    unsigned char header_enc[12];   // 12 = 6 + crc16 at hamming(12,8)
+
+    // objects
+    msequence ms;
+    packetizer p_header;
+    packetizer p_payload;
+};
+
+
+// create bpacketgen object
+//  _m              :   p/n sequence length (ignored)
+//  _dec_msg_len    :   decoded message length (original uncoded data)
+//  _crc            :   data validity check (e.g. cyclic redundancy check)
+//  _fec0           :   inner forward error-correction code scheme
+//  _fec1           :   outer forward error-correction code scheme
+bpacketgen bpacketgen_create(unsigned int _m,
+                             unsigned int _dec_msg_len,
+                             int _crc,
+                             int _fec0,
+                             int _fec1)
+{
+    // validate input
+
+    // create bpacketgen object
+    bpacketgen q = (bpacketgen) malloc(sizeof(struct bpacketgen_s));
+    q->dec_msg_len  = _dec_msg_len;
+    q->crc          = _crc;
+    q->fec0         = _fec0;
+    q->fec1         = _fec1;
+
+    // implied values
+    q->g = 0;
+    q->pnsequence_len = 8;
+
+    // derived values
+    q->enc_msg_len = packetizer_compute_enc_msg_len(q->dec_msg_len,
+                                                    q->crc,
+                                                    q->fec0,
+                                                    q->fec1);
+    q->header_len = packetizer_compute_enc_msg_len(6, LIQUID_CRC_16, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING128);
+    bpacketgen_compute_packet_len(q);
+
+    // arrays
+    q->pnsequence = (unsigned char*) malloc((q->pnsequence_len)*sizeof(unsigned char));
+
+    // create m-sequence generator
+    // TODO : configure sequence from generator polynomial
+    q->ms = msequence_create_default(6);
+
+    // create header packet encoder
+    q->p_header = packetizer_create(6, LIQUID_CRC_16, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING128);
+    assert(q->header_len == packetizer_get_enc_msg_len(q->p_header));
+
+    // create payload packet encoder
+    q->p_payload = packetizer_create(q->dec_msg_len,
+                                     q->crc,
+                                     q->fec0,
+                                     q->fec1);
+
+    // assemble semi-static framing structures
+    bpacketgen_assemble_header(q);
+    bpacketgen_assemble_pnsequence(q);
+
+    return q;
+}
+
+// re-create bpacketgen object from old object
+//  _q              :   old bpacketgen object
+//  _m              :   p/n sequence length (ignored)
+//  _dec_msg_len    :   decoded message length (original uncoded data)
+//  _crc            :   data validity check (e.g. cyclic redundancy check)
+//  _fec0           :   inner forward error-correction code scheme
+//  _fec1           :   outer forward error-correction code scheme
+bpacketgen bpacketgen_recreate(bpacketgen _q,
+                               unsigned int _m,
+                               unsigned int _dec_msg_len,
+                               int _crc,
+                               int _fec0,
+                               int _fec1)
+{
+    // validate input
+
+    // re-create internal packetizer object
+    _q->dec_msg_len = _dec_msg_len;
+    _q->crc         = _crc;
+    _q->fec0        = _fec0;
+    _q->fec1        = _fec1;
+
+    // derived values
+    _q->enc_msg_len = packetizer_compute_enc_msg_len(_q->dec_msg_len,
+                                                     _q->crc,
+                                                     _q->fec0,
+                                                     _q->fec1);
+    _q->header_len = packetizer_compute_enc_msg_len(6, LIQUID_CRC_16, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING128);
+    bpacketgen_compute_packet_len(_q);
+
+    // arrays
+    _q->g = 0;
+    _q->pnsequence_len = 8;
+    _q->pnsequence = (unsigned char*) realloc(_q->pnsequence, (_q->pnsequence_len)*sizeof(unsigned char));
+
+    // re-create m-sequence generator
+    // TODO : configure sequence from generator polynomial
+    msequence_destroy(_q->ms);
+    _q->ms = msequence_create_default(6);
+
+    // re-create payload packet encoder
+    _q->p_payload = packetizer_recreate(_q->p_payload,
+                                        _q->dec_msg_len,
+                                        _q->crc,
+                                        _q->fec0,
+                                        _q->fec1);
+
+    // assemble semi-static framing structures
+    bpacketgen_assemble_header(_q);
+    bpacketgen_assemble_pnsequence(_q);
+
+    return _q;
+}
+
+// destroy bpacketgen object, freeing all internally-allocated memory
+void bpacketgen_destroy(bpacketgen _q)
+{
+    // free arrays
+    free(_q->pnsequence);
+
+    // destroy internal objects
+    msequence_destroy(_q->ms);
+    packetizer_destroy(_q->p_header);
+    packetizer_destroy(_q->p_payload);
+
+    // free main object memory
+    free(_q);
+}
+
+// print bpacketgen internals
+void bpacketgen_print(bpacketgen _q)
+{
+    printf("bpacketgen:\n");
+    printf("    p/n poly    :   0x%.4x\n", _q->g);
+    printf("    p/n len     :   %u bytes\n", _q->pnsequence_len);
+    printf("    header len  :   %u bytes\n", _q->header_len);
+    printf("    payload len :   %u bytes\n", _q->dec_msg_len);
+    printf("    crc         :   %s\n", crc_scheme_str[_q->crc][1]);
+    printf("    fec (inner) :   %s\n", fec_scheme_str[_q->fec0][1]);
+    printf("    fec (outer) :   %s\n", fec_scheme_str[_q->fec1][1]);
+    printf("    packet len  :   %u bytes\n", _q->packet_len);
+    printf("    efficiency  :   %8.2f %%\n", 100.0f*(float)_q->dec_msg_len/(float)_q->packet_len);
+}
+
+// return length of full packet
+unsigned int bpacketgen_get_packet_len(bpacketgen _q)
+{
+    return _q->packet_len;
+}
+
+// encode packet
+void bpacketgen_encode(bpacketgen _q,
+                       unsigned char * _msg_dec,
+                       unsigned char * _packet)
+{
+    // output byte index counter
+    unsigned int n=0;
+
+    // copy p/n sequence
+    memmove(&_packet[n], _q->pnsequence, _q->pnsequence_len*sizeof(unsigned char));
+    n += _q->pnsequence_len;
+
+    // copy header
+    memmove(&_packet[n], _q->header_enc, _q->header_len*sizeof(unsigned char));
+    n += _q->header_len;
+
+    // encode payload
+    packetizer_encode(_q->p_payload,
+                      _msg_dec,
+                      &_packet[n]);
+    n += _q->enc_msg_len;
+
+    // verify length is correct
+    assert(n == _q->packet_len);
+}
+
+
+// 
+// internal methods
+//
+
+// compute packet length
+void bpacketgen_compute_packet_len(bpacketgen _q)
+{
+    _q->packet_len = _q->pnsequence_len +
+                     _q->header_len +
+                     _q->enc_msg_len;
+}
+
+// generate p/n sequence
+void bpacketgen_assemble_pnsequence(bpacketgen _q)
+{
+    // reset m-sequence generator
+    msequence_reset(_q->ms);
+
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_q->pnsequence_len; i++) {
+        unsigned char byte = 0;
+        for (j=0; j<8; j++) {
+            byte <<= 1;
+            byte |= msequence_advance(_q->ms);
+        }
+        _q->pnsequence[i] = byte;
+    }
+}
+
+// assemble packet header
+void bpacketgen_assemble_header(bpacketgen _q)
+{
+    _q->header_dec[0] = BPACKET_VERSION;
+    _q->header_dec[1] = (unsigned char) _q->crc;
+    _q->header_dec[2] = (unsigned char) _q->fec0;
+    _q->header_dec[3] = (unsigned char) _q->fec1;
+    _q->header_dec[4] = (unsigned char) (_q->dec_msg_len >> 8) & 0xff;
+    _q->header_dec[5] = (unsigned char) (_q->dec_msg_len     ) & 0xff;
+
+    // encode header
+    packetizer_encode(_q->p_header,
+                      _q->header_dec,
+                      _q->header_enc);
+}
+
diff --git a/src/framing/src/bpacketsync.c b/src/framing/src/bpacketsync.c
new file mode 100644
index 0000000..28e8a46
--- /dev/null
+++ b/src/framing/src/bpacketsync.c
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// bpacketsync
+//
+// binary packet synchronizer/decoder
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_BPACKETSYNC   0
+
+// bpacketsync object structure
+struct bpacketsync_s {
+    // options
+    unsigned int g;                 // p/n sequence genpoly
+    unsigned int pnsequence_len;    // p/n sequence length (bytes)
+    unsigned int dec_msg_len;       // payload length
+    crc_scheme crc;                 // payload check
+    fec_scheme fec0;                // payload fec (inner)
+    fec_scheme fec1;                // payload fec (outer)
+    
+    // derived values
+    unsigned int enc_msg_len;       // encoded mesage length
+    unsigned int header_len;        // header length (12 bytes encoded)
+    unsigned int packet_len;        // total packet length
+
+    // arrays
+    unsigned char * pnsequence;     // p/n sequence
+    unsigned char * payload_enc;    // payload (encoded)
+    unsigned char * payload_dec;    // payload (decoded)
+
+    // bpacket header
+    //  0   :   version number
+    //  1   :   crc
+    //  2   :   fec0
+    //  3   :   fec1
+    //  4:5 :   payload length
+    unsigned char header_dec[6];    // uncoded bytes
+    unsigned char header_enc[12];   // 12 = 6 + crc16 at hamming(12,8)
+
+    // objects
+    msequence ms;
+    packetizer p_header;
+    packetizer p_payload;
+    bsequence bpn;          // binary p/n sequence
+    bsequence brx;          // binary received sequence
+
+    // status variables
+    enum {
+        BPACKETSYNC_STATE_SEEKPN=0,     // seek p/n sequence
+        BPACKETSYNC_STATE_RXHEADER,     // receive header data
+        BPACKETSYNC_STATE_RXPAYLOAD     // receive payload data
+    } state;
+
+    // counters
+    unsigned int num_bytes_received;
+    unsigned int num_bits_received;
+    unsigned char byte_rx;
+    unsigned char byte_mask;
+
+    // flags
+    int header_valid;
+    int payload_valid;
+
+    // user-defined parameters
+    bpacketsync_callback callback;
+    void * userdata;
+    framesyncstats_s framestats;
+};
+
+bpacketsync bpacketsync_create(unsigned int _m,
+                               bpacketsync_callback _callback,
+                               void * _userdata)
+{
+    // create bpacketsync object
+    bpacketsync q = (bpacketsync) malloc(sizeof(struct bpacketsync_s));
+    q->callback = _callback;
+    q->userdata = _userdata;
+
+    // default values
+    q->dec_msg_len  = 1;
+    q->crc          = LIQUID_CRC_NONE;
+    q->fec0         = LIQUID_FEC_NONE;
+    q->fec1         = LIQUID_FEC_NONE;
+
+    // implied values
+    q->g = 0;
+    q->pnsequence_len = 8;
+
+    // derived values
+    q->enc_msg_len = packetizer_compute_enc_msg_len(q->dec_msg_len,
+                                                    q->crc,
+                                                    q->fec0,
+                                                    q->fec1);
+    q->header_len = packetizer_compute_enc_msg_len(6, LIQUID_CRC_16, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING128);
+
+    // arrays
+    q->pnsequence  = (unsigned char*) malloc((q->pnsequence_len)*sizeof(unsigned char));
+    q->payload_enc = (unsigned char*) malloc((q->enc_msg_len)*sizeof(unsigned char));
+    q->payload_dec = (unsigned char*) malloc((q->dec_msg_len)*sizeof(unsigned char));
+
+    // create m-sequence generator
+    // TODO : configure sequence from generator polynomial
+    q->ms = msequence_create_default(6);
+
+    // create header packet encoder
+    q->p_header = packetizer_create(6, LIQUID_CRC_16, LIQUID_FEC_NONE, LIQUID_FEC_HAMMING128);
+    assert(q->header_len == packetizer_get_enc_msg_len(q->p_header));
+
+    // create payload packet encoder
+    q->p_payload = packetizer_create(q->dec_msg_len,
+                                     q->crc,
+                                     q->fec0,
+                                     q->fec1);
+
+    // create binary sequence objects
+    q->bpn = bsequence_create(q->pnsequence_len*8);
+    q->brx = bsequence_create(q->pnsequence_len*8);
+
+    // assemble semi-static framing structures
+    bpacketsync_assemble_pnsequence(q);
+
+    // reset synchronizer
+    bpacketsync_reset(q);
+
+    return q;
+}
+
+void bpacketsync_destroy(bpacketsync _q)
+{
+    // free arrays
+    free(_q->pnsequence);
+    free(_q->payload_enc);
+    free(_q->payload_dec);
+
+    // destroy internal objects
+    msequence_destroy(_q->ms);
+    packetizer_destroy(_q->p_header);
+    packetizer_destroy(_q->p_payload);
+    bsequence_destroy(_q->bpn);
+    bsequence_destroy(_q->brx);
+
+    // free main object memory
+    free(_q);
+}
+
+void bpacketsync_print(bpacketsync _q)
+{
+    printf("bpacketsync:\n");
+    printf("    p/n poly    :   0x%.4x\n", _q->g);
+    printf("    p/n len     :   %u bytes\n", _q->pnsequence_len);
+    printf("    header len  :   %u bytes\n", _q->header_len);
+    printf("    payload len :   %u bytes\n", _q->dec_msg_len);
+    printf("    crc         :   %s\n", crc_scheme_str[_q->crc][1]);
+    printf("    fec (inner) :   %s\n", fec_scheme_str[_q->fec0][1]);
+    printf("    fec (outer) :   %s\n", fec_scheme_str[_q->fec1][1]);
+    printf("    packet len  :   %u bytes\n", _q->packet_len);
+    printf("    efficiency  :   %8.2f %%\n", 100.0f*(float)_q->dec_msg_len/(float)_q->packet_len);
+}
+
+void bpacketsync_reset(bpacketsync _q)
+{
+    // clear received sequence buffer
+    bsequence_clear(_q->brx);
+
+    // reset counters
+    _q->num_bytes_received  = 0;
+    _q->num_bits_received   = 0;
+    _q->byte_rx             = 0;
+    _q->byte_mask           = 0x00;
+
+    // reset state
+    _q->state = BPACKETSYNC_STATE_SEEKPN;
+}
+
+// run synchronizer on array of input bytes
+//  _q      :   bpacketsync object
+//  _bytes  :   input data array [size: _n x 1]
+//  _n      :   input array size
+void bpacketsync_execute(bpacketsync _q,
+                         unsigned char * _bytes,
+                         unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        bpacketsync_execute_byte(_q, _bytes[i]);
+}
+
+// run synchronizer on input byte
+//  _q      :   bpacketsync object
+//  _byte   :   input byte
+void bpacketsync_execute_byte(bpacketsync _q,
+                              unsigned char _byte)
+{
+    unsigned int j;
+    for (j=0; j<8; j++) {
+        // strip bit from byte
+        unsigned char bit = (_byte >> (8-j-1)) & 1;
+
+        // run synchronizer on bit
+        bpacketsync_execute_bit(_q, bit);
+    }
+}
+
+// run synchronizer on input symbol
+//  _q      :   bpacketsync object
+//  _sym    :   input symbol with _bps significant bits
+//  _bps    :   number of bits in input symbol
+void bpacketsync_execute_sym(bpacketsync _q,
+                             unsigned char _sym,
+                             unsigned int _bps)
+{
+    // validate input
+    if (_bps > 8) {
+        fprintf(stderr,"error: bpacketsync_execute_sym(), bits per symbol must be in [0,8]\n");
+        exit(1);
+    }
+
+    unsigned int j;
+    for (j=0; j<_bps; j++) {
+        // strip bit from byte
+        unsigned char bit = (_sym >> (_bps-j-1)) & 1;
+
+        // run synchronizer on bit
+        bpacketsync_execute_bit(_q, bit);
+    }
+}
+
+
+// execute one bit at a time
+void bpacketsync_execute_bit(bpacketsync _q,
+                             unsigned char _bit)
+{
+    // mask input to ensure one bit of resolution
+    _bit = _bit & 0x01;
+
+    // execute state-specific methods
+    switch (_q->state) {
+    case BPACKETSYNC_STATE_SEEKPN:
+        bpacketsync_execute_seekpn(_q, _bit);
+        break;
+    case BPACKETSYNC_STATE_RXHEADER:
+        bpacketsync_execute_rxheader(_q, _bit);
+        break;
+    case BPACKETSYNC_STATE_RXPAYLOAD:
+        bpacketsync_execute_rxpayload(_q, _bit);
+        break;
+    default:
+        fprintf(stderr,"error: bpacketsync_execute(), invalid state\n");
+        exit(1);
+    }
+}
+
+// 
+// internal methods
+//
+
+void bpacketsync_assemble_pnsequence(bpacketsync _q)
+{
+    // reset m-sequence generator
+    msequence_reset(_q->ms);
+
+    unsigned int i;
+    for (i=0; i<8*_q->pnsequence_len; i++)
+        bsequence_push(_q->bpn, msequence_advance(_q->ms));
+}
+
+void bpacketsync_execute_seekpn(bpacketsync _q,
+                                unsigned char _bit)
+{
+    // push bit into correlator
+    bsequence_push(_q->brx, _bit);
+
+    // compute p/n sequence correlation
+    int rxy = bsequence_correlate(_q->bpn, _q->brx);
+    float r = 2.0f*(float)rxy / (float)(_q->pnsequence_len*8) - 1.0f;
+
+    // check threshold
+    if ( fabsf(r) > 0.8f ) {
+#if DEBUG_BPACKETSYNC
+        printf("p/n sequence found!, rxy = %8.4f\n", r);
+#endif
+
+        // flip polarity of bits if correlation is negative
+        _q->byte_mask = r > 0 ? 0x00 : 0xff;
+
+        // switch operational mode
+        _q->state = BPACKETSYNC_STATE_RXHEADER;
+    }
+}
+
+void bpacketsync_execute_rxheader(bpacketsync _q,
+                                  unsigned char _bit)
+{
+    // push bit into accumulated byte
+    _q->byte_rx <<= 1;
+    _q->byte_rx |= (_bit & 1);
+    _q->num_bits_received++;
+    
+    if (_q->num_bits_received == 8) {
+        // append byte to encoded header array
+        _q->header_enc[_q->num_bytes_received] = _q->byte_rx ^ _q->byte_mask;
+
+        _q->num_bits_received=0;
+        _q->num_bytes_received++;
+
+        if (_q->num_bytes_received == _q->header_len) {
+            
+            _q->num_bits_received  = 0;
+            _q->num_bytes_received = 0;
+
+            // decode header
+            bpacketsync_decode_header(_q);
+
+            // TODO : invoke header callback now
+
+            if (_q->header_valid) {
+                // re-allocate memory for arrays
+                bpacketsync_reconfig(_q);
+
+                // switch operational mode
+                _q->state = BPACKETSYNC_STATE_RXPAYLOAD;
+            } else {
+                // reset synchronizer
+                bpacketsync_reset(_q);
+            }
+        }
+    }
+}
+
+void bpacketsync_execute_rxpayload(bpacketsync _q,
+                                   unsigned char _bit)
+{
+    // push bit into accumulated byte
+    _q->byte_rx <<= 1;
+    _q->byte_rx |= (_bit & 1);
+    _q->num_bits_received++;
+    
+    if (_q->num_bits_received == 8) {
+        // append byte to encoded payload array
+        _q->payload_enc[_q->num_bytes_received] = _q->byte_rx ^ _q->byte_mask;
+
+        _q->num_bits_received=0;
+        _q->num_bytes_received++;
+
+        if (_q->num_bytes_received == _q->enc_msg_len) {
+            
+            _q->num_bits_received  = 0;
+            _q->num_bytes_received = 0;
+
+            // decode payload data
+            bpacketsync_decode_payload(_q);
+
+            // invoke callback
+            if (_q->callback != NULL) {
+                // set frame stats
+                framesyncstats_init_default(&_q->framestats);
+                _q->framestats.check = _q->crc;
+                _q->framestats.fec0  = _q->fec0;
+                _q->framestats.fec1  = _q->fec1;
+
+                _q->callback(_q->payload_dec,
+                             _q->payload_valid,
+                             _q->dec_msg_len,
+                             _q->framestats,
+                             _q->userdata);
+            }
+
+            // reset synchronizer
+            bpacketsync_reset(_q);
+        }
+    }
+}
+
+void bpacketsync_decode_header(bpacketsync _q)
+{
+    // decode header array
+    _q->header_valid = packetizer_decode(_q->p_header,
+                                         _q->header_enc,
+                                         _q->header_dec);
+
+    // return unconditionally if header failed
+    if (!_q->header_valid)
+        return;
+
+    // strip header info
+    int version = _q->header_dec[0];
+    _q->crc  = (crc_scheme) _q->header_dec[1];
+    _q->fec0 = (fec_scheme) _q->header_dec[2];
+    _q->fec1 = (fec_scheme) _q->header_dec[3];
+    _q->dec_msg_len = (_q->header_dec[4] << 8) |
+                      (_q->header_dec[5]     );
+
+    // check version number
+    if (version != BPACKET_VERSION)
+        fprintf(stderr,"warning: bpacketsync, version mismatch!\n");
+
+    // TODO : check crc, fec0, fec1 schemes
+}
+
+void bpacketsync_decode_payload(bpacketsync _q)
+{
+    // decode payload
+    _q->payload_valid = packetizer_decode(_q->p_payload,
+                                          _q->payload_enc,
+                                          _q->payload_dec);
+}
+
+void bpacketsync_reconfig(bpacketsync _q)
+{
+    // reconfigure packetizer
+    _q->p_payload = packetizer_recreate(_q->p_payload,
+                                        _q->dec_msg_len,
+                                        _q->crc,
+                                        _q->fec0,
+                                        _q->fec1);
+
+    // re-compute encoded message (packet) length
+    _q->enc_msg_len = packetizer_get_enc_msg_len(_q->p_payload);
+
+    // re-allocate memory for encoded packet
+    _q->payload_enc = (unsigned char*) realloc(_q->payload_enc,
+                                               _q->enc_msg_len*sizeof(unsigned char));
+
+    // re-allocate memory for decoded packet
+    _q->payload_dec = (unsigned char*) realloc(_q->payload_dec,
+                                               _q->dec_msg_len*sizeof(unsigned char));
+}
+
diff --git a/src/framing/src/bpresync.c b/src/framing/src/bpresync.c
new file mode 100644
index 0000000..c5614b5
--- /dev/null
+++ b/src/framing/src/bpresync.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Binary pre-demod synchronizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+struct BPRESYNC(_s) {
+    unsigned int n;     // sequence length
+    unsigned int m;     // number of binary synchronizers
+    
+    bsequence rx_i;     // received pattern (in-phase)
+    bsequence rx_q;     // received pattern (quadrature)
+    
+    float * dphi;       // array of frequency offsets [size: m x 1]
+    bsequence * sync_i; // synchronization pattern (in-phase)
+    bsequence * sync_q; // synchronization pattern (quadrature)
+
+    float * rxy;        // output correlation [size: m x 1]
+
+    float n_inv;        // 1/n (pre-computed for speed)
+};
+
+/* create binary pre-demod synchronizer                     */
+/*  _v          :   baseband sequence                       */
+/*  _n          :   baseband sequence length                */
+/*  _dphi_max   :   maximum absolute frequency deviation    */
+/*  _m          :   number of correlators                   */
+BPRESYNC() BPRESYNC(_create)(TC *         _v,
+                             unsigned int _n,
+                             float        _dphi_max,
+                             unsigned int _m)
+{
+    // validate input
+    if (_n < 1) {
+        fprintf(stderr, "error: bpresync_%s_create(), invalid input length\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr, "error: bpresync_%s_create(), number of correlators must be at least 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and initialize
+    BPRESYNC() _q = (BPRESYNC()) malloc(sizeof(struct BPRESYNC(_s)));
+    _q->n = _n;
+    _q->m = _m;
+
+    _q->n_inv = 1.0f / (float)(_q->n);
+
+    unsigned int i;
+
+    // create internal receive buffers
+    _q->rx_i = bsequence_create(_q->n);
+    _q->rx_q = bsequence_create(_q->n);
+
+    // create internal array of frequency offsets
+    _q->dphi = (float*) malloc( _q->m*sizeof(float) );
+
+    // create internal synchronizers
+    _q->sync_i = (bsequence*) malloc( _q->m*sizeof(bsequence) );
+    _q->sync_q = (bsequence*) malloc( _q->m*sizeof(bsequence) );
+
+    for (i=0; i<_q->m; i++) {
+
+        _q->sync_i[i] = bsequence_create(_q->n);
+        _q->sync_q[i] = bsequence_create(_q->n);
+
+        // generate signal with frequency offset
+        _q->dphi[i] = (float)i / (float)(_q->m-1)*_dphi_max;
+        unsigned int k;
+        for (k=0; k<_q->n; k++) {
+            TC v_prime = _v[k] * cexpf(-_Complex_I*k*_q->dphi[i]);
+            bsequence_push(_q->sync_i[i], crealf(v_prime)>0);
+            bsequence_push(_q->sync_q[i], cimagf(v_prime)>0);
+        }
+    }
+
+    // allocate memory for cross-correlation
+    _q->rxy = (float*) malloc( _q->m*sizeof(float) );
+
+    // reset object
+    BPRESYNC(_reset)(_q);
+
+    return _q;
+}
+
+void BPRESYNC(_destroy)(BPRESYNC() _q)
+{
+    unsigned int i;
+
+    // free received symbol buffers
+    free(_q->rx_i);
+    free(_q->rx_q);
+
+    // free internal syncrhonizer objects
+    for (i=0; i<_q->m; i++) {
+        bsequence_destroy(_q->sync_i[i]);
+        bsequence_destroy(_q->sync_q[i]);
+    }
+    free(_q->sync_i);
+    free(_q->sync_q);
+
+    // free internal frequency offset array
+    free(_q->dphi);
+
+    // free internal cross-correlation array
+    free(_q->rxy);
+
+    // free main object memory
+    free(_q);
+}
+
+void BPRESYNC(_print)(BPRESYNC() _q)
+{
+    printf("bpresync_%s: %u samples\n", EXTENSION_FULL, _q->n);
+}
+
+void BPRESYNC(_reset)(BPRESYNC() _q)
+{
+    unsigned int i;
+    for (i=0; i<_q->n; i++) {
+        bsequence_push(_q->rx_i, (i+0) % 2);
+        bsequence_push(_q->rx_q, (i+1) % 2);
+    }
+}
+
+// correlate input sequence with particular 
+//  _q          :   pre-demod synchronizer object
+//  _id         :   ...
+void BPRESYNC(_correlatex)(BPRESYNC()      _q,
+                           unsigned int    _id,
+                           float complex * _rxy0,
+                           float complex * _rxy1)
+{
+    // validate input...
+    if (_id >= _q->m) {
+        fprintf(stderr,"error: bpresync_%s_correlatex(), invalid id\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // compute correlations
+    int rxy_ii = 2*bsequence_correlate(_q->sync_i[_id], _q->rx_i) - (int)(_q->n);
+    int rxy_qq = 2*bsequence_correlate(_q->sync_q[_id], _q->rx_q) - (int)(_q->n);
+    int rxy_iq = 2*bsequence_correlate(_q->sync_i[_id], _q->rx_q) - (int)(_q->n);
+    int rxy_qi = 2*bsequence_correlate(_q->sync_q[_id], _q->rx_i) - (int)(_q->n);
+
+    // non-conjugated
+    int rxy_i0 = rxy_ii - rxy_qq;
+    int rxy_q0 = rxy_iq + rxy_qi;
+    *_rxy0 = (rxy_i0 + rxy_q0 * _Complex_I) * _q->n_inv;
+
+    // conjugated
+    int rxy_i1 = rxy_ii + rxy_qq;
+    int rxy_q1 = rxy_iq - rxy_qi;
+    *_rxy1 = (rxy_i1 + rxy_q1 * _Complex_I) * _q->n_inv;
+}
+
+/* push input sample into pre-demod synchronizer            */
+/*  _q          :   pre-demod synchronizer object           */
+/*  _x          :   input sample                            */
+void BPRESYNC(_push)(BPRESYNC() _q,
+                     TI         _x)
+{
+    // push symbol into buffers
+    bsequence_push(_q->rx_i, REAL(_x)>0);
+    bsequence_push(_q->rx_q, IMAG(_x)>0);
+}
+
+/* correlate input sequence                                 */
+/*  _q          :   pre-demod synchronizer object           */
+/*  _rxy        :   output cross correlation                */
+/*  _dphi_hat   :   output frequency offset estimate        */
+void BPRESYNC(_correlate)(BPRESYNC() _q,
+                          TO *       _rxy,
+                          float *    _dphi_hat)
+{
+    unsigned int i;
+    float complex rxy_max = 0;  // maximum cross-correlation
+    float abs_rxy_max = 0;      // absolute value of rxy_max
+    float complex rxy0;
+    float complex rxy1;
+    float dphi_hat = 0.0f;
+    for (i=0; i<_q->m; i++)  {
+
+        BPRESYNC(_correlatex)(_q, i, &rxy0, &rxy1);
+
+        // check non-conjugated value
+        if ( ABS(rxy0) > abs_rxy_max ) {
+            rxy_max     = rxy0;
+            abs_rxy_max = ABS(rxy0);
+            dphi_hat    = _q->dphi[i];
+        }
+
+        // check conjugated value
+        if ( ABS(rxy1) > abs_rxy_max ) {
+            rxy_max     = rxy1;
+            abs_rxy_max = ABS(rxy1);
+            dphi_hat    = -_q->dphi[i];
+        }
+    }
+
+    *_rxy      = rxy_max;
+    *_dphi_hat = dphi_hat;
+}
+
diff --git a/src/framing/src/bpresync_cccf.c b/src/framing/src/bpresync_cccf.c
new file mode 100644
index 0000000..fa58482
--- /dev/null
+++ b/src/framing/src/bpresync_cccf.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Binary pre-demod synchronizer
+//
+
+#include "liquid.internal.h"
+
+// 
+#define BPRESYNC(name)      LIQUID_CONCAT(bpresync_cccf,name)
+
+// print and naming extensions
+#define PRINTVAL(x)         printf("%12.4e + j%12.4e", crealf(x), cimagf(x))
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "cccf"
+
+#define TO                  float complex
+#define TC                  float complex
+#define TI                  float complex
+
+#define ABS(X)              cabsf(X)
+#define REAL(X)             crealf(X)
+#define IMAG(X)             cimagf(X)
+
+#define BSYNC(name)         LIQUID_CONCAT(bsync_cccf,name)
+
+#define TO_COMPLEX
+#define TC_COMPLEX
+#define TI_COMPLEX
+
+// source files
+#include "bpresync.c"
+
diff --git a/src/framing/src/bsync.c b/src/framing/src/bsync.c
new file mode 100644
index 0000000..31a19b7
--- /dev/null
+++ b/src/framing/src/bsync.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// P/N synchronizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+struct BSYNC(_s) {
+    unsigned int n;     // sequence length
+
+    bsequence sync_i;   // synchronization pattern (in-phase)
+    bsequence sym_i;    // received symbols (in-phase)
+//#if defined TC_COMPLEX
+    bsequence sync_q;   // synchronization pattern (quadrature)
+//#endif
+
+//#if defined TI_COMPLEX
+    bsequence sym_q;    // received symbols (quadrature)
+//#endif
+    TO rxy;             // cross correlation
+};
+
+BSYNC() BSYNC(_create)(unsigned int _n, TC * _v)
+{
+    BSYNC() fs = (BSYNC()) malloc(sizeof(struct BSYNC(_s)));
+    fs->n = _n;
+
+    fs->sync_i  = bsequence_create(fs->n);
+#ifdef TC_COMPLEX
+    fs->sync_q  = bsequence_create(fs->n);
+#endif
+
+    fs->sym_i   = bsequence_create(fs->n);
+#ifdef TI_COMPLEX
+    fs->sym_q   = bsequence_create(fs->n);
+#endif
+
+    unsigned int i;
+    for (i=0; i<fs->n; i++) {
+        bsequence_push(fs->sync_i, crealf(_v[i])>0);
+#ifdef TC_COMPLEX
+        bsequence_push(fs->sync_q, cimagf(_v[i])>0);
+#endif
+    }
+
+    return fs;
+}
+
+// TODO : test this method
+BSYNC() BSYNC(_create_msequence)(unsigned int _g,
+                                 unsigned int _k)
+{
+    // validate input
+    if (_k == 0) {
+        fprintf(stderr,"bsync_xxxt_create_msequence(), samples/symbol must be greater than zero\n");
+        exit(1);
+    }
+    unsigned int m = liquid_msb_index(_g) - 1;
+
+    // create/initialize msequence
+    msequence ms = msequence_create(m, _g, 1);
+
+    BSYNC() fs = (BSYNC()) malloc(sizeof(struct BSYNC(_s)));
+    unsigned int n = msequence_get_length(ms);
+
+    fs->sync_i  = bsequence_create(n * _k);
+#ifdef TC_COMPLEX
+    fs->sync_q  = bsequence_create(n * _k);
+#endif
+
+    fs->sym_i   = bsequence_create(n * _k);
+#ifdef TI_COMPLEX
+    fs->sym_q   = bsequence_create(n * _k);
+#endif
+
+    msequence_reset(ms);
+
+#if 0
+    bsequence_init_msequence(fs->sync_i,ms);
+#ifdef TC_COMPLEX
+    msequence_reset(ms);
+    bsequence_init_msequence(fs->sync_q,ms);
+#endif
+#else
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<n; i++) {
+        unsigned int bit = msequence_advance(ms);
+
+        for (j=0; j<_k; j++) {
+            bsequence_push(fs->sync_i, bit);
+#ifdef TC_COMPLEX
+            bsequence_push(fs->sync_q, bit);
+#endif
+        }
+    }
+#endif
+
+    msequence_destroy(ms);
+
+    fs->n = _k*n;
+
+    return fs;
+}
+
+void BSYNC(_destroy)(BSYNC() _fs)
+{
+    bsequence_destroy(_fs->sync_i);
+#ifdef TC_COMPLEX
+    bsequence_destroy(_fs->sync_q);
+#endif
+
+    bsequence_destroy(_fs->sym_i);
+#ifdef TI_COMPLEX
+    bsequence_destroy(_fs->sym_q);
+#endif
+    free(_fs);
+}
+
+void BSYNC(_print)(BSYNC() _fs)
+{
+
+}
+
+void BSYNC(_correlate)(BSYNC() _fs, TI _sym, TO *_y)
+{
+    // push symbol into buffers
+    bsequence_push(_fs->sym_i, crealf(_sym)>0.0 ? 1 : 0);
+#ifdef TI_COMPLEX
+    bsequence_push(_fs->sym_q, cimagf(_sym)>0.0 ? 1 : 0);
+#endif
+
+    // compute dotprod
+#if   defined TC_COMPLEX && defined TI_COMPLEX
+    // cccx
+    TO rxy_ii = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_i) - (float)(_fs->n);
+    TO rxy_qq = 2.*bsequence_correlate(_fs->sync_q, _fs->sym_q) - (float)(_fs->n);
+    TO rxy_iq = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_q) - (float)(_fs->n);
+    TO rxy_qi = 2.*bsequence_correlate(_fs->sync_q, _fs->sym_i) - (float)(_fs->n);
+
+    _fs->rxy = (rxy_ii - rxy_qq) + _Complex_I*(rxy_iq + rxy_qi);
+#elif defined TI_COMPLEX
+    // crcx
+    float rxy_ii = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_i) - (float)(_fs->n);
+    float rxy_iq = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_q) - (float)(_fs->n);
+
+    _fs->rxy = rxy_ii + _Complex_I * rxy_iq;
+#else
+    // rrrx
+    _fs->rxy = 2.*bsequence_correlate(_fs->sync_i, _fs->sym_i) - (float)(_fs->n);
+#endif
+
+    // divide by sequence length
+    *_y = _fs->rxy / (float)(_fs->n);
+}
+
diff --git a/src/framing/src/bsync_cccf.c b/src/framing/src/bsync_cccf.c
new file mode 100644
index 0000000..2073075
--- /dev/null
+++ b/src/framing/src/bsync_cccf.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Framing API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// 
+#define BSYNC(name)         LIQUID_CONCAT(bsync_cccf,name)
+
+#define PRINTVAL(x)         printf("%12.4e + j%12.4e", crealf(x), cimagf(x))
+
+#define TO                  float complex
+#define TC                  float complex
+#define TI                  float complex
+#define ABS(X)              cabsf(X)
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_cccf,name)
+
+#define TO_COMPLEX
+#define TC_COMPLEX
+#define TI_COMPLEX
+
+// source files
+#include "bsync.c"
+
diff --git a/src/framing/src/bsync_crcf.c b/src/framing/src/bsync_crcf.c
new file mode 100644
index 0000000..087adb6
--- /dev/null
+++ b/src/framing/src/bsync_crcf.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Framing API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// 
+#define BSYNC(name)         LIQUID_CONCAT(bsync_crcf,name)
+
+#define PRINTVAL(x)         printf("%12.4e + j%12.4e", crealf(x), cimagf(x))
+
+#define TO                  float complex
+#define TC                  float
+#define TI                  float complex
+#define ABS(X)              cabsf(X)
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_crcf,name)
+
+#define TO_COMPLEX
+#undef  TC_COMPLEX
+#define TI_COMPLEX
+
+// source files
+#include "bsync.c"
+
diff --git a/src/framing/src/bsync_rrrf.c b/src/framing/src/bsync_rrrf.c
new file mode 100644
index 0000000..801fc1c
--- /dev/null
+++ b/src/framing/src/bsync_rrrf.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Framing API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// 
+#define BSYNC(name)         LIQUID_CONCAT(bsync_rrrf,name)
+
+#define PRINTVAL(x)         printf("%12.4e", x)
+
+#define TO                  float
+#define TC                  float
+#define TI                  float
+#define ABS(X)              fabsf(X)
+#define WINDOW(name)        LIQUID_CONCAT(windowf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_rrrf,name)
+
+#undef  TO_COMPLEX
+#undef  TC_COMPLEX
+#undef  TI_COMPLEX
+
+// source files
+#include "bsync.c"
+
diff --git a/src/framing/src/detector_cccf.c b/src/framing/src/detector_cccf.c
new file mode 100644
index 0000000..92b8414
--- /dev/null
+++ b/src/framing/src/detector_cccf.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// detector_cccf.c
+//
+// Pre-demodulation detector
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_DETECTOR              0
+#define DEBUG_DETECTOR_PRINT        0
+#define DEBUG_DETECTOR_BUFFER_LEN   (1600)
+#define DEBUG_DETECTOR_FILENAME     "detector_cccf_debug.m"
+
+// 
+// internal method declarations
+//
+
+// update sum{ |x|^2 }
+void detector_cccf_update_sumsq(detector_cccf _q,
+                                float complex _x);
+
+// compute all dot product outputs
+void detector_cccf_compute_dotprods(detector_cccf _q);
+
+// estimate carrier and timing offsets
+void detector_cccf_estimate_offsets(detector_cccf _q,
+                                    float *       _tau_hat,
+                                    float *       _dphi_hat);
+
+// print debugging information
+void detector_cccf_debug_print(detector_cccf _q,
+                               const char *  _filename);
+
+struct detector_cccf_s {
+    float complex * s;      // sequence
+    unsigned int n;         // sequence length
+    float threshold;        // detection threshold
+    
+    // derived values
+    float n_inv;            // 1/n (pre-computed for speed)
+    
+    windowcf buffer;        // input buffer
+
+    // internal correlators
+    dotprod_cccf * dp;      // vector dot products (pre-spun)
+    unsigned int m;         // number of correlators
+    float   dphi_step;      // step size for each correlator
+    float   dphi_max;       // maximum carrier offset
+    float * dphi;           // correlator frequencies [size: m x 1]
+    float * rxy;            // correlator outputs [size: m x 1]
+    float * rxy0;           // buffered correlator outputs [size: m x 1]
+    float * rxy1;           // buffered correlator outputs [size: m x 1]
+    unsigned int imax;      // index of maximum
+    unsigned int idetect;   // index of detection
+
+    // estimation of E{|x|^2}
+    wdelayf x2;             // buffer of |x|^2 values
+    float x2_sum;           // sum{ |x|^2 }
+    float x2_hat;           // estimate of E{|x|^2}
+
+    // counters/states
+    enum {
+        DETECTOR_STATE_SEEK=0,  // seek sequence
+        DETECTOR_STATE_FINDMAX, // find maximum
+    } state;
+    unsigned int timer;         // sample timer
+
+#if DEBUG_DETECTOR
+    windowcf debug_x;
+    windowf debug_x2;
+    windowf debug_rxy;
+#endif
+};
+
+// create detector_cccf object
+//  _s          :   sequence
+//  _n          :   sequence length
+//  _threshold  :   detection threshold (default: 0.7)
+//  _dphi_max   :   maximum carrier offset
+detector_cccf detector_cccf_create(float complex * _s,
+                                   unsigned int    _n,
+                                   float           _threshold,
+                                   float           _dphi_max)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: detector_cccf_create(), sequence length cannot be zero\n");
+        exit(1);
+    } else if (_threshold <= 0.0f) {
+        fprintf(stderr,"error: detector_cccf_create(), threshold must be greater than zero (0.6 recommended)\n");
+        exit(1);
+    }
+    
+    // allocate memory for main object
+    detector_cccf q = (detector_cccf) malloc(sizeof(struct detector_cccf_s));
+    unsigned int i;
+
+    // set internal properties
+    q->n         = _n;
+    q->threshold = _threshold;
+    q->dphi_max  = _dphi_max;
+
+    // derived values
+    q->n_inv = 1.0f / (float)(q->n);    // 1/n for faster processing
+    q->dphi_step = 0.8f * M_PI / (float)(q->n);
+
+    // compute number of correlators
+    q->m = (int) ceilf( fabsf(_dphi_max / q->dphi_step) );
+    
+    // ensure at least two correlators
+    if (q->m < 2)
+        q->m = 2;
+
+    // re-compute maximum carrier offset
+    q->dphi_max = q->m * q->dphi_step;
+
+    // allocate memory for sequence and copy
+    q->s = (float complex*) malloc((q->n)*sizeof(float complex));
+    memmove(q->s, _s, q->n*sizeof(float complex));
+
+    // create internal buffer
+    q->buffer = windowcf_create(q->n);
+    q->x2     = wdelayf_create(q->n);
+
+    // create internal correlators (dot products)
+    q->dp   = (dotprod_cccf*) malloc((q->m)*sizeof(dotprod_cccf));
+    q->dphi = (float*)        malloc((q->m)*sizeof(float));
+    q->rxy0 = (float*)        malloc((q->m)*sizeof(float));
+    q->rxy1 = (float*)        malloc((q->m)*sizeof(float));
+    q->rxy  = (float*)        malloc((q->m)*sizeof(float));
+    unsigned int k;
+    float complex sconj[q->n];
+    for (k=0; k<q->m; k++) {
+        // pre-spin sequence (slightly over-sampled in frequency)
+        q->dphi[k] = ((float)k - (float)(q->m-1)/2) * q->dphi_step;
+        for (i=0; i<q->n; i++)
+            sconj[i] = conjf(q->s[i]) * cexpf(-_Complex_I*q->dphi[k]*i);
+        q->dp[k] = dotprod_cccf_create(sconj, q->n);
+    }
+
+    // reset state
+    detector_cccf_reset(q);
+
+#if DEBUG_DETECTOR
+    q->debug_x   = windowcf_create(DEBUG_DETECTOR_BUFFER_LEN);
+    q->debug_x2  = windowf_create(DEBUG_DETECTOR_BUFFER_LEN);
+    q->debug_rxy = windowf_create(DEBUG_DETECTOR_BUFFER_LEN);
+#endif
+    // return object
+    return q;
+}
+
+void detector_cccf_destroy(detector_cccf _q)
+{
+#if DEBUG_DETECTOR
+    detector_cccf_debug_print(_q, DEBUG_DETECTOR_FILENAME);
+    windowcf_destroy(_q->debug_x);
+    windowf_destroy(_q->debug_x2);
+    windowf_destroy(_q->debug_rxy);
+#endif
+    // destroy input buffer
+    windowcf_destroy(_q->buffer);
+
+    // destroy internal correlators (dot products)
+    unsigned int k;
+    for (k=0; k<_q->m; k++)
+        dotprod_cccf_destroy(_q->dp[k]);
+    free(_q->dp);
+    free(_q->dphi);
+    free(_q->rxy);
+    free(_q->rxy0);
+    free(_q->rxy1);
+
+    // destroy |x|^2 buffer
+    wdelayf_destroy(_q->x2);
+
+    // free internal buffers/arrays
+    free(_q->s);
+
+    // free main object memory
+    free(_q);
+}
+
+void detector_cccf_print(detector_cccf _q)
+{
+    printf("detector_cccf:\n");
+    printf("    sequence length     :   %-u\n", _q->n);
+    printf("    threshold           :   %8.4f\n", _q->threshold);
+    printf("    maximum carrier     :   %8.4f rad/sample\n", _q->dphi_max);
+    printf("    num. correlators    :   %u\n", _q->m);
+}
+
+void detector_cccf_reset(detector_cccf _q)
+{
+    // reset internal state
+    windowcf_clear(_q->buffer);
+    wdelayf_clear(_q->x2);
+
+    // reset internal state
+    _q->timer   = _q->n;                // reset timer
+    _q->state   = DETECTOR_STATE_SEEK;  // set state to seek threshold
+    _q->imax    = 0;                    // index of maximum rxy value
+    _q->idetect = 0;                    // index of detected maximum
+    _q->x2_sum  = 0.0f;                 // sum{ |x|^2 }
+    
+    // clear cross-correlator outputs
+    //memset(_q->rxy, 0x00, sizeof(_q->rxy));
+    memset(_q->rxy0, 0x00, _q->m*sizeof(float));
+    memset(_q->rxy1, 0x00, _q->m*sizeof(float));
+}
+
+// Run sample through pre-demod detector's correlator.
+// Returns '1' if signal was detected, '0' otherwise
+//  _q          :   pre-demod detector
+//  _x          :   input sample
+//  _tau_hat    :   fractional sample offset estimate (set when detected)
+//  _dphi_hat   :   carrier frequency offset estimate (set when detected)
+//  _gamma_hat  :   channel gain estimate (set when detected)
+int detector_cccf_correlate(detector_cccf _q,
+                            float complex _x,
+                            float *       _tau_hat,
+                            float *       _dphi_hat,
+                            float *       _gamma_hat)
+{
+    // push sample into buffer
+    windowcf_push(_q->buffer, _x);
+
+    // update sum{|x|^2}
+    detector_cccf_update_sumsq(_q, _x);
+
+#if DEBUG_DETECTOR
+    windowcf_push(_q->debug_x, _x);
+    windowf_push(_q->debug_x2, _q->x2_hat);
+#endif
+    // return if no timeout
+    if (_q->timer) {
+        // hasn't timed out yet
+        //printf("timer = %u\n", _q->timer);
+        _q->timer--;
+#if DEBUG_DETECTOR
+        windowf_push(_q->debug_rxy, 0.0f);
+#endif
+        return 0;
+    }
+
+    // save previous correlator outputs
+    memmove(_q->rxy0, _q->rxy1, _q->m*sizeof(float));
+    memmove(_q->rxy1, _q->rxy,  _q->m*sizeof(float));
+
+    // compute vector dot products
+    detector_cccf_compute_dotprods(_q);
+
+    // find max{rxy}
+    float rxy_abs = _q->rxy[ _q->imax ];
+
+#if DEBUG_DETECTOR
+    windowf_push(_q->debug_rxy, rxy_abs);
+#endif
+    
+    if (_q->state == DETECTOR_STATE_SEEK) {
+        // check to see if value exceeds threshold
+        if (rxy_abs > _q->threshold) {
+#if DEBUG_DETECTOR_PRINT
+            printf("threshold exceeded:      rxy = %8.4f\n", rxy_abs);
+#endif
+            _q->idetect = _q->imax;
+            _q->state = DETECTOR_STATE_FINDMAX;
+        }
+    } else if (_q->state == DETECTOR_STATE_FINDMAX) {
+        // see if this new value exceeds maximum
+        if ( _q->rxy[_q->imax] > _q->rxy1[_q->idetect] ) {
+#if DEBUG_DETECTOR_PRINT
+            printf("maximum not yet reached: rxy = %8.4f\n", rxy_abs);
+#endif
+            // set new index of maximum
+            _q->idetect = _q->imax;
+        } else {
+            // peak was found last time; run estimates, reset values,
+            // and return
+#if DEBUG_DETECTOR_PRINT
+            printf("maximum found:           rxy = %8.4f\n", rxy_abs);
+#endif
+            
+            // estimate timing and carrier offsets
+            detector_cccf_estimate_offsets(_q, _tau_hat, _dphi_hat);
+
+            *_gamma_hat = sqrtf(_q->x2_hat);
+
+            // soft state reset
+            _q->state = DETECTOR_STATE_SEEK;
+            // set timer to allow signal to settle
+            _q->timer = _q->n/4;
+
+            return 1;
+        }
+    } else {
+        fprintf(stderr,"error: detector_cccf_correlate(), unknown/unsupported internal state\n");
+        exit(1);
+    }
+
+    return 0;
+}
+
+// 
+// internal methods
+//
+
+// compute sum{ |x|^2 }
+void detector_cccf_update_sumsq(detector_cccf _q,
+                                float complex _x)
+{
+    // update estimate of signal magnitude
+    float x2_n = crealf(_x * conjf(_x));    // |x[n-1]|^2 (input sample)
+    float x2_0;                             // |x[0]  |^2 (oldest sample)
+    wdelayf_read(_q->x2, &x2_0);            // read oldest sample
+    wdelayf_push(_q->x2, x2_n);             // push newest sample
+    _q->x2_sum = _q->x2_sum + x2_n - x2_0;  // update sum( |x|^2 ) of last 'n' input samples
+#if 0
+    // filtered estimate of E{ |x|^2 }
+    _q->x2_hat = 0.8f*_q->x2_hat + 0.2f*_q->x2_sum*_q->n_inv;
+#else
+    // unfiltered estimate of E{ |x|^2 }
+    _q->x2_hat = _q->x2_sum * _q->n_inv;
+#endif
+
+}
+
+// compute all dot product outputs
+void detector_cccf_compute_dotprods(detector_cccf _q)
+{
+    // read buffer
+    float complex * r;
+    windowcf_read(_q->buffer, &r);
+
+    // compute dot products
+    // TODO: compute conjugate as well
+    unsigned int k;
+    float complex rxy;
+#if DEBUG_DETECTOR_PRINT
+    printf("  rxy : ");
+#endif
+    float rxy_max = 0;
+    // TODO: peridically re-compute scaling factor)
+    for (k=0; k<_q->m; k++) {
+        // execute vector dot product
+        dotprod_cccf_execute(_q->dp[k], r, &rxy);
+
+        // save scaled magnitude
+        // TODO: compute scaled squared magnitude so as not to have
+        //       to compute square root
+        _q->rxy[k] = cabsf(rxy) * _q->n_inv / sqrtf(_q->x2_hat);
+#if DEBUG_DETECTOR_PRINT
+        printf("%6.4f (%6.4f) ", _q->rxy[k], _q->dphi[k]);
+#endif
+
+        // find index of maximum
+        if (_q->rxy[k] > rxy_max) {
+            rxy_max = _q->rxy[k];
+            _q->imax = k;
+        }
+    }
+#if DEBUG_DETECTOR_PRINT
+    printf("\n");
+#endif
+}
+
+// estimate carrier and timing offsets
+void detector_cccf_estimate_offsets(detector_cccf _q,
+                                    float *       _tau_hat,
+                                    float *       _dphi_hat)
+{
+    // check length of...
+    if (_q->m == 1) {
+        // compute carrier offset
+        //*_dphi_hat = _q->dphi[_q->idetect];
+        *_dphi_hat = 0.0f;
+
+        // interpolate to find timing offset
+        //*_tau_hat  = 0.5f*(_q->rxy2 - _q->rxy0) / (_q->rxy2 + _q->rxy0 - 2*_q->rxy1);
+        *_tau_hat = 0.0f;
+        return;
+    }
+
+    // _q->rxy0:          [rm0]
+    //                      |
+    //                      |
+    //                      |
+    // _q->rxy1:  [r0m]---[r00]---[r0p]-->freq
+    //                      |
+    //                      |
+    //                      |
+    //                    [rp0]
+    //                      |
+    // _q->rxy              V time
+    //
+    float rm0 = _q->rxy0[_q->idetect];
+    float r0m = _q->idetect==0 ? _q->rxy1[_q->idetect+1] : _q->rxy1[_q->idetect-1];
+    float r00 = _q->rxy1[_q->idetect];
+    float r0p = _q->idetect==_q->m-1 ? _q->rxy1[_q->idetect-1] : _q->rxy1[_q->idetect+1];
+    float rp0 = _q->rxy[_q->idetect];
+
+#if DEBUG_DETECTOR_PRINT
+    // print values for interpolation
+    printf("idetect : %u\n", _q->idetect);
+    printf("             [%8.5f]\n", rm0);
+    printf("                  |\n");
+    printf("                  |\n");
+    printf("[%8.5f]---[%8.5f]----[%8.5f]--> freq\n", r0m, r00, r0p);
+    printf("              %8.5f\n", _q->dphi[_q->idetect]);
+    printf("                  |\n");
+    printf("             [%8.5f]\n", rp0);
+    printf("                  |\n");
+    printf("                  V time\n");
+#endif
+
+    // interpolate frequency offset estimate
+    float dphi_detect = _q->dphi[_q->idetect];       // frequency from detection
+    *_dphi_hat        = dphi_detect - _q->dphi_step * 0.5f*(r0p - r0m) / (r0p + r0m - 2*r00);
+
+    // interpolate timing offset estimate
+    *_tau_hat  =  0.5f*(rp0 - rm0) / (rp0 + rm0 - 2*r00);
+
+    // force result to be in proper range
+    if (*_tau_hat < -0.499f) *_tau_hat = -0.499f;
+    if (*_tau_hat >  0.499f) *_tau_hat =  0.499f;
+}
+
+#if 0
+float detector_cccf_estimate_dphi(detector_cccf _q)
+{
+    // read buffer...
+    float complex * r;
+    windowcf_read(_q->buffer, &r);
+
+    //
+    float complex r0 = 0.0f;
+    float complex r1 = 0.0f;
+    float complex metric = 0.0f;
+    unsigned int i;
+    for (i=0; i<_q->n; i++) {
+        r0 = r1;
+        r1 = r[i] * conjf(_q->s[i]);
+
+        metric += r1 * conjf(r0);
+    }
+
+    return cargf(metric);
+}
+#endif
+
+void detector_cccf_debug_print(detector_cccf _q,
+                               const char *  _filename)
+{
+#if DEBUG_DETECTOR
+    FILE * fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: detector_cccf_debug_print(), could not open '%s' for writing\n", _filename);
+        return;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", DEBUG_DETECTOR_FILENAME);
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"N = %u;\n", DEBUG_DETECTOR_BUFFER_LEN);
+    unsigned int i;
+    float complex * rc;
+    float * r;
+
+    fprintf(fid,"x = zeros(1,N);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_DETECTOR_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    fprintf(fid,"rxy = zeros(1,N);\n");
+    windowf_read(_q->debug_rxy, &r);
+    for (i=0; i<DEBUG_DETECTOR_BUFFER_LEN; i++)
+        fprintf(fid,"rxy(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"x2 = zeros(1,N);\n");
+    windowf_read(_q->debug_x2, &r);
+    for (i=0; i<DEBUG_DETECTOR_BUFFER_LEN; i++)
+        fprintf(fid,"x2(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"t = 0:(N-1);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(t,real(x),t,imag(x));\n");
+    fprintf(fid,"  ylabel('received signal, x');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(t, rxy);\n");
+    fprintf(fid,"  ylabel('rxy');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(t, x2);\n");
+    fprintf(fid,"  ylabel('rssi');\n");
+    fprintf(fid,"  grid on;\n");
+
+    fclose(fid);
+    printf("detector_cccf/debug: results written to '%s'\n", _filename);
+#else
+    fprintf(stderr,"detector_cccf_debug_print(): compile-time debugging disabled\n");
+#endif
+}
diff --git a/src/framing/src/flexframegen.c b/src/framing/src/flexframegen.c
new file mode 100644
index 0000000..891bf2d
--- /dev/null
+++ b/src/framing/src/flexframegen.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// flexframegen.c
+//
+// flexible frame generator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FLEXFRAMEGEN 1
+
+// reconfigure internal properties
+void          flexframegen_reconfigure      (flexframegen _q);
+float complex flexframegen_generate_symbol  (flexframegen _q);
+float complex flexframegen_generate_preamble(flexframegen _q);
+float complex flexframegen_generate_header  (flexframegen _q);
+float complex flexframegen_generate_payload (flexframegen _q);
+float complex flexframegen_generate_tail    (flexframegen _q);
+
+// default flexframegen properties
+static flexframegenprops_s flexframegenprops_default = {
+    LIQUID_CRC_16,      // check
+    LIQUID_FEC_NONE,    // fec0
+    LIQUID_FEC_NONE,    // fec1
+    LIQUID_MODEM_BPSK,  // mod_scheme
+};
+
+void flexframegenprops_init_default(flexframegenprops_s * _props)
+{
+    memmove(_props, &flexframegenprops_default, sizeof(flexframegenprops_s));
+}
+
+struct flexframegen_s {
+    // interpolator
+    unsigned int    k;                  // interp samples/symbol (fixed at 2)
+    unsigned int    m;                  // interp filter delay (symbols)
+    float           beta;               // excess bandwidth factor
+    firinterp_crcf  interp;             // interpolator object
+    float complex   buf_interp[2];      // output interpolator buffer [size: k x 1]
+
+    flexframegenprops_s props;          // payload properties
+
+    // preamble
+    float complex * preamble_pn;        // p/n sequence
+
+    // header
+    unsigned char * header;             // header data
+    qpacketmodem    header_encoder;     // header encoder/modulator
+    unsigned int    header_mod_len;     // header length (encoded/modulated)
+    float complex * header_mod;         // header symbols (encoded/modulated)
+    qpilotgen       header_pilotgen;    // header pilot symbol generator
+    unsigned int    header_sym_len;     // header length (pilots added)
+    float complex * header_sym;         // header symbols (pilots added)
+
+    // payload
+    unsigned int    payload_dec_len;    // length of decoded
+    qpacketmodem    payload_encoder;    // packet encoder/modulator
+    unsigned int    payload_sym_len;    // length of encoded/modulated payload
+    float complex * payload_sym;        // encoded payload symbols
+
+    // counters/states
+    unsigned int    symbol_counter;     // output symbol number
+    unsigned int    sample_counter;     // output sample number
+    int             frame_assembled;    // frame assembled flag
+    int             frame_complete;     // frame completed flag
+    enum {
+                    STATE_PREAMBLE=0,   // write preamble p/n sequence
+                    STATE_HEADER,       // write header symbols
+                    STATE_PAYLOAD,      // write payload symbols
+                    STATE_TAIL,         // tail symbols
+    }               state;              // write state
+};
+
+flexframegen flexframegen_create(flexframegenprops_s * _fgprops)
+{
+    flexframegen q = (flexframegen) malloc(sizeof(struct flexframegen_s));
+    unsigned int i;
+
+    // create pulse-shaping filter
+    q->k      = 2;
+    q->m      = 7;
+    q->beta   = 0.25f;
+    q->interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_ARKAISER,q->k,q->m,q->beta,0);
+
+    // generate pn sequence
+    q->preamble_pn = (float complex *) malloc(64*sizeof(float complex));
+    msequence ms = msequence_create(7, 0x0089, 1);
+    for (i=0; i<64; i++) {
+        q->preamble_pn[i] = (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2) +
+                            (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2) * _Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // create header encoder/modulator
+    q->header     = (unsigned char *) malloc(FLEXFRAME_H_DEC*sizeof(unsigned char));
+    q->header_encoder = qpacketmodem_create();
+    qpacketmodem_configure(q->header_encoder,
+                           FLEXFRAME_H_DEC,
+                           FLEXFRAME_H_CRC,
+                           FLEXFRAME_H_FEC0,
+                           FLEXFRAME_H_FEC1,
+                           LIQUID_MODEM_QPSK);
+    q->header_mod_len = qpacketmodem_get_frame_len(q->header_encoder);
+    q->header_mod     = (float complex *) malloc(q->header_mod_len*sizeof(float complex));
+
+    // create header pilot sequence generator
+    q->header_pilotgen = qpilotgen_create(q->header_mod_len, 16);
+    q->header_sym_len  = qpilotgen_get_frame_len(q->header_pilotgen);
+    q->header_sym      = (float complex *) malloc(q->header_sym_len*sizeof(float complex));
+    //printf("header: %u bytes > %u mod > %u sym\n", 64, q->header_mod_len, q->header_sym_len);
+
+    // payload encoder/modulator (initialize with default parameters to be reconfigured later)
+    q->payload_encoder = qpacketmodem_create();
+    q->payload_dec_len = 64;
+    q->payload_sym_len = qpacketmodem_get_frame_len(q->payload_encoder);
+    q->payload_sym     = (float complex *) malloc( q->payload_sym_len*sizeof(float complex));
+
+    // reset object
+    flexframegen_reset(q);
+
+    // set payload properties
+    flexframegen_setprops(q, _fgprops);
+
+    // return pointer to main object
+    return q;
+}
+
+void flexframegen_destroy(flexframegen _q)
+{
+    // destroy internal objects
+    firinterp_crcf_destroy(_q->interp);
+    qpacketmodem_destroy  (_q->header_encoder);
+    qpilotgen_destroy     (_q->header_pilotgen);
+    qpacketmodem_destroy  (_q->payload_encoder);
+
+    // free buffers/arrays
+    free(_q->preamble_pn);  // preamble symbols
+    free(_q->header);       // header bytes
+    free(_q->header_mod);   // encoded/modulated header symbols 
+    free(_q->payload_sym);  // encoded/modulated payload symbols
+
+    // destroy frame generator
+    free(_q);
+}
+
+// print flexframegen object internals
+void flexframegen_print(flexframegen _q)
+{
+    unsigned int num_frame_symbols =
+            64 +                    // preamble p/n sequence length
+            _q->header_sym_len +    // header symbols
+            _q->payload_sym_len +   // number of modulation symbols
+            2*_q->m;                // number of tail symbols
+    unsigned int num_frame_bits = 8*_q->payload_dec_len;
+    float eta = (float)num_frame_bits / (float)num_frame_symbols;
+
+    printf("flexframegen:\n");
+    printf("  head          : %u symbols\n", _q->m);
+    printf("  preamble      : %u\n", 64);
+    printf("  header        : %u symbols (%u bytes)\n", _q->header_sym_len, FLEXFRAME_H_DEC);
+    printf("  payload       : %u symbols (%u bytes)\n", _q->payload_sym_len, _q->payload_dec_len);
+    printf("    payload crc : %s\n", crc_scheme_str[_q->props.check][1]);
+    printf("    fec (inner) : %s\n", fec_scheme_str[_q->props.fec0][1]);
+    printf("    fec (outer) : %s\n", fec_scheme_str[_q->props.fec1][1]);
+    printf("    mod scheme  : %s\n", modulation_types[_q->props.mod_scheme].name);
+    printf("  tail          : %u symbols\n", _q->m);
+    printf("  total         : %u symbols\n", num_frame_symbols);
+    printf("  efficiency    : %.2f bits/second/Hz\n", eta);
+}
+
+// reset flexframegen object internals
+void flexframegen_reset(flexframegen _q)
+{
+    // reset internal counters and state
+    _q->symbol_counter  = 0;
+    _q->sample_counter  = 0;
+    _q->frame_assembled = 0;
+    _q->frame_complete  = 0;
+    _q->state           = STATE_PREAMBLE;
+}
+
+// is frame assembled?
+int flexframegen_is_assembled(flexframegen _q)
+{
+    return _q->frame_assembled;
+}
+
+// get flexframegen properties
+//  _q      :   frame generator object
+//  _props  :   frame generator properties structure pointer
+void flexframegen_getprops(flexframegen          _q,
+                           flexframegenprops_s * _props)
+{
+    // copy properties structure to output pointer
+    memmove(_props, &_q->props, sizeof(flexframegenprops_s));
+}
+
+// set flexframegen properties
+//  _q      :   frame generator object
+//  _props  :   frame generator properties structure pointer
+int flexframegen_setprops(flexframegen          _q,
+                          flexframegenprops_s * _props)
+{
+    // if frame is already assembled, give warning
+    if (_q->frame_assembled) {
+        fprintf(stderr, "warning: flexframegen_setprops(), frame is already assembled; must reset() first\n");
+        return -1;
+    }
+
+    // if properties object is NULL, initialize with defaults
+    if (_props == NULL) {
+        flexframegen_setprops(_q, &flexframegenprops_default);
+        return 0;
+    }
+
+    // validate input
+    if (_props->check == LIQUID_CRC_UNKNOWN || _props->check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr, "error: flexframegen_setprops(), invalid/unsupported CRC scheme\n");
+        exit(1);
+    } else if (_props->fec0 == LIQUID_FEC_UNKNOWN || _props->fec1 == LIQUID_FEC_UNKNOWN) {
+        fprintf(stderr, "error: flexframegen_setprops(), invalid/unsupported FEC scheme\n");
+        exit(1);
+    } else if (_props->mod_scheme == LIQUID_MODEM_UNKNOWN ) {
+        fprintf(stderr, "error: flexframegen_setprops(), invalid/unsupported modulation scheme\n");
+        exit(1);
+    }
+
+    // TODO : determine if re-configuration is necessary
+
+    // copy properties to internal structure
+    memmove(&_q->props, _props, sizeof(flexframegenprops_s));
+
+    // reconfigure payload buffers (reallocate as necessary)
+    flexframegen_reconfigure(_q);
+
+    return 0;
+}
+
+// get frame length (number of samples)
+unsigned int flexframegen_getframelen(flexframegen _q)
+{
+    if (!_q->frame_assembled) {
+        fprintf(stderr,"warning: flexframegen_getframelen(), frame not assembled!\n");
+        return 0;
+    }
+    unsigned int num_frame_symbols =
+            64 +                    // preamble p/n sequence length
+            _q->header_sym_len +    // header symbols
+            _q->payload_sym_len +   // number of modulation symbols
+            2*_q->m;                // number of tail symbols
+
+    return num_frame_symbols*_q->k; // k samples/symbol
+}
+
+// exectue frame generator (create the frame)
+//  _q              :   frame generator object
+//  _header         :   user-defined header
+//  _payload        :   variable payload buffer (configured by setprops method)
+//  _payload_dec_len:   length of payload
+void flexframegen_assemble(flexframegen          _q,
+                           const unsigned char * _header,
+                           const unsigned char * _payload,
+                           unsigned int          _payload_dec_len)
+{
+    // reset object
+    flexframegen_reset(_q);
+
+    // set decoded payload length
+    _q->payload_dec_len = _payload_dec_len;
+
+    // copy user-defined header to internal
+    memmove(_q->header, _header, FLEXFRAME_H_USER*sizeof(unsigned char));
+
+    // first several bytes of header are user-defined
+    unsigned int n = FLEXFRAME_H_USER;
+
+    // add FLEXFRAME_PROTOCOL
+    _q->header[n+0] = FLEXFRAME_PROTOCOL;
+
+    // add payload length
+    _q->header[n+1] = (_q->payload_dec_len >> 8) & 0xff;
+    _q->header[n+2] = (_q->payload_dec_len     ) & 0xff;
+
+    // add modulation scheme/depth (pack into single byte)
+    _q->header[n+3]  = (unsigned int)(_q->props.mod_scheme);
+
+    // add CRC, forward error-correction schemes
+    //  CRC     : most-significant  3 bits of [n+4]
+    //  fec0    : least-significant 5 bits of [n+4]
+    //  fec1    : least-significant 5 bits of [n+5]
+    _q->header[n+4]  = (_q->props.check & 0x07) << 5;
+    _q->header[n+4] |= (_q->props.fec0) & 0x1f;
+    _q->header[n+5]  = (_q->props.fec1) & 0x1f;
+
+    // encode/modulate header
+    qpacketmodem_encode(_q->header_encoder, _q->header, _q->header_mod);
+
+    // add pilots
+    qpilotgen_execute(_q->header_pilotgen, _q->header_mod, _q->header_sym);
+
+    // reconfigure payload
+    flexframegen_reconfigure(_q);
+
+    // encode/modulate payload
+    qpacketmodem_encode(_q->payload_encoder, _payload, _q->payload_sym);
+
+    // set assembled flag
+    _q->frame_assembled = 1;
+}
+
+// write samples of assembled frame, two samples at a time, returning
+// '1' when frame is complete, '0' otherwise. Zeros will be written
+// to the buffer if the frame is not assembled
+//  _q          :   frame generator object
+//  _buffer     :   output buffer [size: _buffer_len x 1]
+//  _buffer_len :   output buffer length
+int flexframegen_write_samples(flexframegen    _q,
+                               float complex * _buffer,
+                               unsigned int    _buffer_len)
+{
+    unsigned int i;
+    for (i=0; i<_buffer_len; i++) {
+        // determine if new sample needs to be written
+        if (_q->sample_counter == 0) {
+            // generate new symbol
+            float complex sym = flexframegen_generate_symbol(_q);
+
+            // interpolate result
+            firinterp_crcf_execute(_q->interp, sym, _q->buf_interp);
+        }
+        
+        // write output sample from interpolator buffer
+        _buffer[i] = _q->buf_interp[_q->sample_counter];
+            
+        // adjust sample counter
+        _q->sample_counter = (_q->sample_counter + 1) % _q->k;
+    }
+
+    return _q->frame_complete;
+}
+
+//
+// internal
+//
+
+// reconfigure internal buffers, objects, etc.
+void flexframegen_reconfigure(flexframegen _q)
+{
+    // configure payload encoder/modulator
+    qpacketmodem_configure(_q->payload_encoder,
+                           _q->payload_dec_len,
+                           _q->props.check,
+                           _q->props.fec0,
+                           _q->props.fec1,
+                           _q->props.mod_scheme);
+
+    // re-allocate memory for encoded message
+    _q->payload_sym_len = qpacketmodem_get_frame_len(_q->payload_encoder);
+    _q->payload_sym = (float complex*) realloc(_q->payload_sym,
+                                               _q->payload_sym_len*sizeof(float complex));
+
+    // ensure payload was reallocated appropriately
+    if (_q->payload_sym == NULL) {
+        fprintf(stderr,"error: flexframegen_reconfigure(), could not re-allocate payload array\n");
+        exit(1);
+    }
+}
+
+// fill interpolator buffer
+float complex flexframegen_generate_symbol(flexframegen _q)
+{
+    // write zeros to buffer if frame is not assembled
+    if (!_q->frame_assembled)
+        return 0.0f;
+
+    switch (_q->state) {
+    case STATE_PREAMBLE: return flexframegen_generate_preamble(_q); break;
+    case STATE_HEADER:   return flexframegen_generate_header  (_q); break;
+    case STATE_PAYLOAD:  return flexframegen_generate_payload (_q); break;
+    case STATE_TAIL:     return flexframegen_generate_tail    (_q); break;
+    default:
+        fprintf(stderr,"error: flexframegen_generate_symbol(), unknown/unsupported internal state\n");
+        exit(1);
+    }
+
+    return 0.0f;
+}
+
+// generate preamble
+float complex flexframegen_generate_preamble(flexframegen _q)
+{
+    float complex symbol = _q->preamble_pn[_q->symbol_counter++];
+
+    // check state
+    if (_q->symbol_counter == 64) {
+        _q->symbol_counter = 0;
+        _q->state = STATE_HEADER;
+    }
+    return symbol;
+}
+
+// generate header
+float complex flexframegen_generate_header(flexframegen _q)
+{
+    float complex symbol = _q->header_sym[_q->symbol_counter++];
+
+    // check state
+    if (_q->symbol_counter == _q->header_sym_len) {
+        _q->symbol_counter = 0;
+        _q->state = STATE_PAYLOAD;
+    }
+    return symbol;
+}
+
+// generate payload
+float complex flexframegen_generate_payload(flexframegen _q)
+{
+    float complex symbol = _q->payload_sym[_q->symbol_counter++];
+
+    // check state
+    if (_q->symbol_counter == _q->payload_sym_len) {
+        _q->symbol_counter = 0;
+        _q->state = STATE_TAIL;
+    }
+    return symbol;
+}
+
+// generate tail
+float complex flexframegen_generate_tail(flexframegen _q)
+{
+    // increment symbol counter
+    _q->symbol_counter++;
+
+    // check state
+    if (_q->symbol_counter == 2*_q->m) {
+        _q->symbol_counter  = 0;
+        _q->frame_complete  = 1;
+        _q->frame_assembled = 0;
+    }
+
+    return 0.0f;
+}
+
diff --git a/src/framing/src/flexframesync.c b/src/framing/src/flexframesync.c
new file mode 100644
index 0000000..8c78ba0
--- /dev/null
+++ b/src/framing/src/flexframesync.c
@@ -0,0 +1,854 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// flexframesync.c
+//
+// basic frame synchronizer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FLEXFRAMESYNC         1
+#define DEBUG_FLEXFRAMESYNC_PRINT   0
+#define DEBUG_FILENAME              "flexframesync_internal_debug.m"
+#define DEBUG_BUFFER_LEN            (2000)
+
+#define FLEXFRAMESYNC_ENABLE_EQ     0
+
+// push samples through detection stage
+void flexframesync_execute_seekpn(flexframesync _q,
+                                  float complex _x);
+
+// step receiver mixer, matched filter, decimator
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int flexframesync_step(flexframesync   _q,
+                       float complex   _x,
+                       float complex * _y);
+
+// push samples through synchronizer, saving received p/n symbols
+void flexframesync_execute_rxpreamble(flexframesync _q,
+                                      float complex _x);
+
+// decode header and reconfigure payload
+void flexframesync_decode_header(flexframesync _q);
+
+// receive header symbols
+void flexframesync_execute_rxheader(flexframesync _q,
+                                    float complex _x);
+
+// receive payload symbols
+void flexframesync_execute_rxpayload(flexframesync _q,
+                                     float complex _x);
+
+// flexframesync object structure
+struct flexframesync_s {
+    // callback
+    framesync_callback  callback;       // user-defined callback function
+    void *              userdata;       // user-defined data structure
+    framesyncstats_s    framesyncstats; // frame statistic object (synchronizer)
+    framedatastats_s    framedatastats; // frame statistic object (synchronizer)
+    
+    // synchronizer objects
+    unsigned int    m;                  // filter delay (symbols)
+    float           beta;               // filter excess bandwidth factor
+    qdetector_cccf  detector;           // pre-demod detector
+    float           tau_hat;            // fractional timing offset estimate
+    float           dphi_hat;           // carrier frequency offset estimate
+    float           phi_hat;            // carrier phase offset estimate
+    float           gamma_hat;          // channel gain estimate
+    nco_crcf        mixer;              // carrier frequency recovery (coarse)
+    nco_crcf        pll;                // carrier frequency recovery (fine)
+
+    // timing recovery objects, states
+    firpfb_crcf     mf;                 // matched filter decimator
+    unsigned int    npfb;               // number of filters in symsync
+    int             mf_counter;         // matched filter output timer
+    unsigned int    pfb_index;          // filterbank index
+#if FLEXFRAMESYNC_ENABLE_EQ
+    eqlms_cccf      equalizer;          // equalizer (trained on p/n sequence)
+#endif
+
+    // preamble
+    float complex * preamble_pn;        // known 64-symbol p/n sequence
+    float complex * preamble_rx;        // received p/n symbols
+    
+    // header
+    float complex * header_sym;         // header symbols with pilots (received)
+    unsigned int    header_sym_len;     // header symbols with pilots (length)
+    qpilotsync      header_pilotsync;   // header demodulator/decoder
+    float complex * header_mod;         // header symbols (received)
+    unsigned int    header_mod_len;     // header symbols (length)
+    qpacketmodem    header_decoder;     // header demodulator/decoder
+    unsigned char * header_dec;         // header bytes (decoded)
+    int             header_valid;       // header CRC flag
+    
+    // payload
+    modem           payload_demod;      // payload demod (for phase recovery only)
+    float complex * payload_sym;        // payload symbols (received)
+    unsigned int    payload_sym_len;    // payload symbols (length)
+    qpacketmodem    payload_decoder;    // payload demodulator/decoder
+    unsigned char * payload_dec;        // payload data (bytes)
+    unsigned int    payload_dec_len;    // payload data (length)
+    int             payload_valid;      // payload CRC flag
+    
+    // status variables
+    unsigned int    preamble_counter;   // counter: num of p/n syms received
+    unsigned int    symbol_counter;     // counter: num of symbols received
+    enum {
+        FLEXFRAMESYNC_STATE_DETECTFRAME=0,  // detect frame (seek p/n sequence)
+        FLEXFRAMESYNC_STATE_RXPREAMBLE,     // receive p/n sequence
+        FLEXFRAMESYNC_STATE_RXHEADER,       // receive header data
+        FLEXFRAMESYNC_STATE_RXPAYLOAD,      // receive payload data
+    }               state;                  // receiver state
+
+#if DEBUG_FLEXFRAMESYNC
+    int         debug_enabled;          // debugging enabled?
+    int         debug_objects_created;  // debugging objects created?
+    int         debug_qdetector_flush;  // debug: flag to set if we are flushing detector
+    windowcf    debug_x;                // debug: raw input samples
+#endif
+};
+
+// create flexframesync object
+//  _callback       :   callback function invoked when frame is received
+//  _userdata       :   user-defined data object passed to callback
+flexframesync flexframesync_create(framesync_callback _callback,
+                                   void *             _userdata)
+{
+    flexframesync q = (flexframesync) malloc(sizeof(struct flexframesync_s));
+    q->callback = _callback;
+    q->userdata = _userdata;
+    q->m        = 7;    // filter delay (symbols)
+    q->beta     = 0.3f; // excess bandwidth factor
+
+    unsigned int i;
+
+    // generate p/n sequence
+    q->preamble_pn = (float complex*) malloc(64*sizeof(float complex));
+    q->preamble_rx = (float complex*) malloc(64*sizeof(float complex));
+    msequence ms = msequence_create(7, 0x0089, 1);
+    for (i=0; i<64; i++) {
+        q->preamble_pn[i] = (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2) +
+                            (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2)*_Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // create frame detector
+    unsigned int k = 2; // samples/symbol
+    q->detector = qdetector_cccf_create_linear(q->preamble_pn, 64, LIQUID_FIRFILT_ARKAISER, k, q->m, q->beta);
+    qdetector_cccf_set_threshold(q->detector, 0.5f);
+
+    // create symbol timing recovery filters
+    q->npfb = 32;   // number of filters in the bank
+    q->mf   = firpfb_crcf_create_rnyquist(LIQUID_FIRFILT_ARKAISER, q->npfb,k,q->m,q->beta);
+
+#if FLEXFRAMESYNC_ENABLE_EQ
+    // create equalizer
+    unsigned int p = 3;
+    q->equalizer = eqlms_cccf_create_lowpass(2*k*p+1, 0.4f);
+    eqlms_cccf_set_bw(q->equalizer, 0.05f);
+#endif
+
+    // create down-coverters for carrier phase tracking
+    q->mixer = nco_crcf_create(LIQUID_NCO);
+    q->pll   = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_pll_set_bandwidth(q->pll, 1e-4f); // very low bandwidth
+    
+    // header demodulator/decoder
+    q->header_dec     = (unsigned char *) malloc(FLEXFRAME_H_DEC*sizeof(unsigned char));
+    q->header_decoder = qpacketmodem_create();
+    qpacketmodem_configure(q->header_decoder,
+                           FLEXFRAME_H_DEC,
+                           FLEXFRAME_H_CRC,
+                           FLEXFRAME_H_FEC0,
+                           FLEXFRAME_H_FEC1,
+                           LIQUID_MODEM_QPSK);
+    q->header_mod_len = qpacketmodem_get_frame_len(q->header_decoder);
+    q->header_mod     = (float complex*) malloc(q->header_mod_len*sizeof(float complex));
+
+    // header pilot synchronizer
+    q->header_pilotsync = qpilotsync_create(q->header_mod_len, 16);
+    q->header_sym_len   = qpilotsync_get_frame_len(q->header_pilotsync);
+    q->header_sym       = (float complex*) malloc(q->header_sym_len*sizeof(float complex));
+    
+    // payload demodulator for phase recovery
+    q->payload_demod = modem_create(LIQUID_MODEM_QPSK);
+
+    // create payload demodulator/decoder object
+    q->payload_dec_len = 64;
+    int check      = LIQUID_CRC_24;
+    int fec0       = LIQUID_FEC_NONE;
+    int fec1       = LIQUID_FEC_GOLAY2412;
+    int mod_scheme = LIQUID_MODEM_QPSK;
+    q->payload_decoder = qpacketmodem_create();
+    qpacketmodem_configure(q->payload_decoder, q->payload_dec_len, check, fec0, fec1, mod_scheme);
+    //qpacketmodem_print(q->payload_decoder);
+    //assert( qpacketmodem_get_frame_len(q->payload_decoder)==600 );
+    q->payload_sym_len = qpacketmodem_get_frame_len(q->payload_decoder);
+
+    // allocate memory for payload symbols and recovered data bytes
+    q->payload_sym = (float complex*) malloc(q->payload_sym_len*sizeof(float complex));
+    q->payload_dec = (unsigned char*) malloc(q->payload_dec_len*sizeof(unsigned char));
+
+    // reset global data counters
+    flexframesync_reset_framedatastats(q);
+
+#if DEBUG_FLEXFRAMESYNC
+    // set debugging flags, objects to NULL
+    q->debug_enabled         = 0;
+    q->debug_objects_created = 0;
+    q->debug_qdetector_flush = 0;
+    q->debug_x               = NULL;
+#endif
+
+    // reset state and return
+    flexframesync_reset(q);
+    return q;
+}
+
+// destroy frame synchronizer object, freeing all internal memory
+void flexframesync_destroy(flexframesync _q)
+{
+#if DEBUG_FLEXFRAMESYNC
+    // clean up debug objects (if created)
+    if (_q->debug_objects_created)
+        windowcf_destroy(_q->debug_x);
+#endif
+
+    // free allocated arrays
+    free(_q->preamble_pn);
+    free(_q->preamble_rx);
+    free(_q->header_sym);
+    free(_q->header_mod);
+    free(_q->header_dec);
+    free(_q->payload_sym);
+    free(_q->payload_dec);
+
+    // destroy synchronization objects
+    qpilotsync_destroy    (_q->header_pilotsync); // header demodulator/decoder
+    qpacketmodem_destroy  (_q->header_decoder);   // header demodulator/decoder
+    modem_destroy         (_q->payload_demod);    // payload demodulator (for PLL)
+    qpacketmodem_destroy  (_q->payload_decoder);  // payload demodulator/decoder
+    qdetector_cccf_destroy(_q->detector);         // frame detector
+    firpfb_crcf_destroy   (_q->mf);               // matched filter
+    nco_crcf_destroy      (_q->mixer);            // oscillator (coarse)
+    nco_crcf_destroy      (_q->pll);              // oscillator (fine)
+#if FLEXFRAMESYNC_ENABLE_EQ
+    eqlms_cccf_destroy    (_q->equalizer);        // LMS equalizer
+#endif
+
+    // free main object memory
+    free(_q);
+}
+
+// print frame synchronizer object internals
+void flexframesync_print(flexframesync _q)
+{
+    printf("flexframesync:\n");
+    framedatastats_print(&_q->framedatastats);
+}
+
+// reset frame synchronizer object
+void flexframesync_reset(flexframesync _q)
+{
+    // reset binary pre-demod synchronizer
+    qdetector_cccf_reset(_q->detector);
+
+    // reset carrier recovery objects
+    nco_crcf_reset(_q->mixer);
+    nco_crcf_reset(_q->pll);
+
+    // reset symbol timing recovery state
+    firpfb_crcf_reset(_q->mf);
+        
+    // reset state
+    _q->state           = FLEXFRAMESYNC_STATE_DETECTFRAME;
+    _q->preamble_counter= 0;
+    _q->symbol_counter  = 0;
+    
+    // reset frame statistics
+    _q->framesyncstats.evm = 0.0f;
+}
+
+// execute frame synchronizer
+//  _q  :   frame synchronizer object
+//  _x  :   input sample array [size: _n x 1]
+//  _n  :   number of input samples
+void flexframesync_execute(flexframesync   _q,
+                           float complex * _x,
+                           unsigned int    _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+#if DEBUG_FLEXFRAMESYNC
+        // write samples to debug buffer
+        // NOTE: the debug_qdetector_flush prevents samples from being written twice
+        if (_q->debug_enabled && !_q->debug_qdetector_flush)
+            windowcf_push(_q->debug_x, _x[i]);
+#endif
+        switch (_q->state) {
+        case FLEXFRAMESYNC_STATE_DETECTFRAME:
+            // detect frame (look for p/n sequence)
+            flexframesync_execute_seekpn(_q, _x[i]);
+            break;
+        case FLEXFRAMESYNC_STATE_RXPREAMBLE:
+            // receive p/n sequence symbols
+            flexframesync_execute_rxpreamble(_q, _x[i]);
+            break;
+        case FLEXFRAMESYNC_STATE_RXHEADER:
+            // receive header symbols
+            flexframesync_execute_rxheader(_q, _x[i]);
+            break;
+        case FLEXFRAMESYNC_STATE_RXPAYLOAD:
+            // receive payload symbols
+            flexframesync_execute_rxpayload(_q, _x[i]);
+            break;
+        default:
+            fprintf(stderr,"error: flexframesync_exeucte(), unknown/unsupported state\n");
+            exit(1);
+        }
+    }
+}
+
+// 
+// internal methods
+//
+
+// execute synchronizer, seeking p/n sequence
+//  _q      :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void flexframesync_execute_seekpn(flexframesync _q,
+                                  float complex _x)
+{
+    // push through pre-demod synchronizer
+    float complex * v = qdetector_cccf_execute(_q->detector, _x);
+
+    // check if frame has been detected
+    if (v != NULL) {
+        // get estimates
+        _q->tau_hat   = qdetector_cccf_get_tau  (_q->detector);
+        _q->gamma_hat = qdetector_cccf_get_gamma(_q->detector);
+        _q->dphi_hat  = qdetector_cccf_get_dphi (_q->detector);
+        _q->phi_hat   = qdetector_cccf_get_phi  (_q->detector);
+
+#if DEBUG_FLEXFRAMESYNC_PRINT
+        printf("***** frame detected! tau-hat:%8.4f, dphi-hat:%8.4f, gamma:%8.2f dB\n",
+                _q->tau_hat, _q->dphi_hat, 20*log10f(_q->gamma_hat));
+#endif
+
+        // set appropriate filterbank index
+        if (_q->tau_hat > 0) {
+            _q->pfb_index = (unsigned int)(      _q->tau_hat  * _q->npfb) % _q->npfb;
+            _q->mf_counter = 0;
+        } else {
+            _q->pfb_index = (unsigned int)((1.0f+_q->tau_hat) * _q->npfb) % _q->npfb;
+            _q->mf_counter = 1;
+        }
+        
+        // output filter scale (gain estimate, scaled by 1/2 for k=2 samples/symbol)
+        firpfb_crcf_set_scale(_q->mf, 0.5f / _q->gamma_hat);
+
+        // set frequency/phase of mixer
+        nco_crcf_set_frequency(_q->mixer, _q->dphi_hat);
+        nco_crcf_set_phase    (_q->mixer, _q->phi_hat );
+
+        // update state
+        _q->state = FLEXFRAMESYNC_STATE_RXPREAMBLE;
+
+#if DEBUG_FLEXFRAMESYNC
+        // the debug_qdetector_flush prevents samples from being written twice
+        _q->debug_qdetector_flush = 1;
+#endif
+        // run buffered samples through synchronizer
+        unsigned int buf_len = qdetector_cccf_get_buf_len(_q->detector);
+        flexframesync_execute(_q, v, buf_len);
+#if DEBUG_FLEXFRAMESYNC
+        _q->debug_qdetector_flush = 0;
+#endif
+    }
+}
+
+// step receiver mixer, matched filter, decimator
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int flexframesync_step(flexframesync   _q,
+                       float complex   _x,
+                       float complex * _y)
+{
+    // mix sample down
+    float complex v;
+    nco_crcf_mix_down(_q->mixer, _x, &v);
+    nco_crcf_step    (_q->mixer);
+    
+    // push sample into filterbank
+    firpfb_crcf_push   (_q->mf, v);
+    firpfb_crcf_execute(_q->mf, _q->pfb_index, &v);
+
+#if FLEXFRAMESYNC_ENABLE_EQ
+    // push sample through equalizer
+    eqlms_cccf_push(_q->equalizer, v);
+#endif
+
+    // increment counter to determine if sample is available
+    _q->mf_counter++;
+    int sample_available = (_q->mf_counter >= 1) ? 1 : 0;
+    
+    // set output sample if available
+    if (sample_available) {
+#if FLEXFRAMESYNC_ENABLE_EQ
+        // compute equalizer output
+        eqlms_cccf_execute(_q->equalizer, &v);
+#endif
+
+        // set output
+        *_y = v;
+
+        // decrement counter by k=2 samples/symbol
+        _q->mf_counter -= 2;
+    }
+
+    // return flag
+    return sample_available;
+}
+
+// execute synchronizer, receiving p/n sequence
+//  _q     :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void flexframesync_execute_rxpreamble(flexframesync _q,
+                                      float complex _x)
+{
+    // step synchronizer
+    float complex mf_out = 0.0f;
+    int sample_available = flexframesync_step(_q, _x, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+
+        // save output in p/n symbols buffer
+#if FLEXFRAMESYNC_ENABLE_EQ
+        unsigned int delay = 2*_q->m + 3; // delay from matched filter and equalizer
+#else
+        unsigned int delay = 2*_q->m;     // delay from matched filter
+#endif
+        if (_q->preamble_counter >= delay) {
+            unsigned int index = _q->preamble_counter-delay;
+
+            _q->preamble_rx[index] = mf_out;
+        
+#if FLEXFRAMESYNC_ENABLE_EQ
+            // train equalizer
+            eqlms_cccf_step(_q->equalizer, _q->preamble_pn[index], mf_out);
+#endif
+        }
+
+        // update p/n counter
+        _q->preamble_counter++;
+
+        // update state
+        if (_q->preamble_counter == 64 + delay)
+            _q->state = FLEXFRAMESYNC_STATE_RXHEADER;
+    }
+}
+
+// execute synchronizer, receiving header
+//  _q      :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void flexframesync_execute_rxheader(flexframesync _q,
+                                    float complex _x)
+{
+    // step synchronizer
+    float complex mf_out = 0.0f;
+    int sample_available = flexframesync_step(_q, _x, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // save payload symbols (modem input/output)
+        _q->header_sym[_q->symbol_counter] = mf_out;
+
+        // increment counter
+        _q->symbol_counter++;
+
+        if (_q->symbol_counter == _q->header_sym_len) {
+            // decode header
+            flexframesync_decode_header(_q);
+
+            if (_q->header_valid) {
+                // continue on to decoding payload
+                _q->symbol_counter = 0;
+                _q->state = FLEXFRAMESYNC_STATE_RXPAYLOAD;
+                return;
+            }
+
+            // update statistics
+            _q->framedatastats.num_frames_detected++;
+
+            // header invalid: invoke callback
+            if (_q->callback != NULL) {
+                // set framestats internals
+                _q->framesyncstats.evm           = 0.0f; //20*log10f(sqrtf(_q->framesyncstats.evm / 600));
+                _q->framesyncstats.rssi          = 20*log10f(_q->gamma_hat);
+                _q->framesyncstats.cfo           = nco_crcf_get_frequency(_q->mixer);
+                _q->framesyncstats.framesyms     = NULL;
+                _q->framesyncstats.num_framesyms = 0;
+                _q->framesyncstats.mod_scheme    = LIQUID_MODEM_UNKNOWN;
+                _q->framesyncstats.mod_bps       = 0;
+                _q->framesyncstats.check         = LIQUID_CRC_UNKNOWN;
+                _q->framesyncstats.fec0          = LIQUID_FEC_UNKNOWN;
+                _q->framesyncstats.fec1          = LIQUID_FEC_UNKNOWN;
+
+                // invoke callback method
+                _q->callback(_q->header_dec,
+                             _q->header_valid,
+                             NULL,  // payload
+                             0,     // payload length
+                             0,     // payload valid,
+                             _q->framesyncstats,
+                             _q->userdata);
+            }
+
+            // reset frame synchronizer
+            flexframesync_reset(_q);
+            return;
+        }
+    }
+}
+
+// decode header
+void flexframesync_decode_header(flexframesync _q)
+{
+    // recover data symbols from pilots
+    qpilotsync_execute(_q->header_pilotsync, _q->header_sym, _q->header_mod);
+
+    // decode payload
+    _q->header_valid = qpacketmodem_decode(_q->header_decoder,
+                                           _q->header_mod,
+                                           _q->header_dec);
+
+    if (!_q->header_valid)
+        return;
+
+    // set fine carrier frequency and phase
+    float dphi_hat = qpilotsync_get_dphi(_q->header_pilotsync);
+    float  phi_hat = qpilotsync_get_phi (_q->header_pilotsync);
+    //printf("residual offset: dphi=%12.8f, phi=%12.8f\n", dphi_hat, phi_hat);
+    nco_crcf_set_frequency(_q->pll, dphi_hat);
+    nco_crcf_set_phase    (_q->pll, phi_hat + dphi_hat * _q->header_sym_len);
+
+    // first several bytes of header are user-defined
+    unsigned int n = FLEXFRAME_H_USER;
+
+    // first byte is for expansion/version validation
+    unsigned int protocol = _q->header_dec[n+0];
+    if (protocol != FLEXFRAME_PROTOCOL) {
+        fprintf(stderr,"warning: flexframesync_decode_header(), invalid framing protocol %u (expected %u)\n", protocol, FLEXFRAME_PROTOCOL);
+        _q->header_valid = 0;
+        return;
+    }
+
+    // strip off payload length
+    unsigned int payload_dec_len = (_q->header_dec[n+1] << 8) | (_q->header_dec[n+2]);
+    _q->payload_dec_len = payload_dec_len;
+
+    // strip off modulation scheme/depth
+    unsigned int mod_scheme = _q->header_dec[n+3];
+
+    // strip off CRC, forward error-correction schemes
+    //  CRC     : most-significant 3 bits of [n+4]
+    //  fec0    : least-significant 5 bits of [n+4]
+    //  fec1    : least-significant 5 bits of [n+5]
+    unsigned int check = (_q->header_dec[n+4] >> 5 ) & 0x07;
+    unsigned int fec0  = (_q->header_dec[n+4]      ) & 0x1f;
+    unsigned int fec1  = (_q->header_dec[n+5]      ) & 0x1f;
+
+    // validate properties
+    if (mod_scheme == 0 || mod_scheme >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"warning: flexframesync_decode_header(), invalid modulation scheme\n");
+        _q->header_valid = 0;
+        return;
+    } else if (check == LIQUID_CRC_UNKNOWN || check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: flexframesync_decode_header(), decoded CRC exceeds available\n");
+        _q->header_valid = 0;
+        return;
+    } else if (fec0 == LIQUID_FEC_UNKNOWN || fec0 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: flexframesync_decode_header(), decoded FEC (inner) exceeds available\n");
+        _q->header_valid = 0;
+        return;
+    } else if (fec1 == LIQUID_FEC_UNKNOWN || fec1 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: flexframesync_decode_header(), decoded FEC (outer) exceeds available\n");
+        _q->header_valid = 0;
+        return;
+    }
+
+    // re-create payload demodulator for phase-locked loop
+    _q->payload_demod = modem_recreate(_q->payload_demod, mod_scheme);
+
+    // reconfigure payload demodulator/decoder
+    qpacketmodem_configure(_q->payload_decoder,
+                           payload_dec_len, check, fec0, fec1, mod_scheme);
+
+    // set length appropriately
+    _q->payload_sym_len = qpacketmodem_get_frame_len(_q->payload_decoder);
+
+    // re-allocate buffers accordingly
+    _q->payload_sym = (float complex*) realloc(_q->payload_sym, (_q->payload_sym_len)*sizeof(float complex));
+    _q->payload_dec = (unsigned char*) realloc(_q->payload_dec, (_q->payload_dec_len)*sizeof(unsigned char));
+
+    if (_q->payload_sym == NULL || _q->payload_dec == NULL) {
+        fprintf(stderr,"error: flexframesync_decode_header(), could not re-allocate payload arrays\n");
+        _q->header_valid = 0;
+        return;
+    }
+
+#if DEBUG_FLEXFRAMESYNC_PRINT
+    // print results
+    printf("flexframesync_decode_header():\n");
+    printf("    header crc      : %s\n", _q->header_valid ? "pass" : "FAIL");
+    printf("    check           : %s\n", crc_scheme_str[check][1]);
+    printf("    fec (inner)     : %s\n", fec_scheme_str[fec0][1]);
+    printf("    fec (outer)     : %s\n", fec_scheme_str[fec1][1]);
+    printf("    mod scheme      : %s\n", modulation_types[mod_scheme].name);
+    printf("    payload sym len : %u\n", _q->payload_sym_len);
+    printf("    payload dec len : %u\n", _q->payload_dec_len);
+    printf("    user data       :");
+    unsigned int i;
+    for (i=0; i<FLEXFRAME_H_USER; i++)
+        printf(" %.2x", _q->header_dec[i]);
+    printf("\n");
+#endif
+}
+
+
+// execute synchronizer, receiving payload
+//  _q      :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void flexframesync_execute_rxpayload(flexframesync _q,
+                                     float complex _x)
+{
+    // step synchronizer
+    float complex mf_out = 0.0f;
+    int sample_available = flexframesync_step(_q, _x, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // TODO: clean this up
+        // mix down with fine-tuned oscillator
+        nco_crcf_mix_down(_q->pll, mf_out, &mf_out);
+        // track phase, accumulate error-vector magnitude
+        unsigned int sym;
+        modem_demodulate(_q->payload_demod, mf_out, &sym);
+        float phase_error = modem_get_demodulator_phase_error(_q->payload_demod);
+        float evm         = modem_get_demodulator_evm        (_q->payload_demod);
+        nco_crcf_pll_step(_q->pll, phase_error);
+        nco_crcf_step(_q->pll);
+        _q->framesyncstats.evm += evm*evm;
+
+        // save payload symbols (modem input/output)
+        _q->payload_sym[_q->symbol_counter] = mf_out;
+
+        // increment counter
+        _q->symbol_counter++;
+
+        if (_q->symbol_counter == _q->payload_sym_len) {
+            // decode payload
+            _q->payload_valid = qpacketmodem_decode(_q->payload_decoder,
+                                                    _q->payload_sym,
+                                                    _q->payload_dec);
+
+            // update statistics
+            _q->framedatastats.num_frames_detected++;
+            _q->framedatastats.num_headers_valid++;
+            _q->framedatastats.num_payloads_valid += _q->payload_valid;
+            _q->framedatastats.num_bytes_received += _q->payload_dec_len;
+
+            // invoke callback
+            if (_q->callback != NULL) {
+                // set framestats internals
+                int ms = qpacketmodem_get_modscheme(_q->payload_decoder);
+                _q->framesyncstats.evm           = 10*log10f(_q->framesyncstats.evm / (float)_q->payload_sym_len);
+                _q->framesyncstats.rssi          = 20*log10f(_q->gamma_hat);
+                _q->framesyncstats.cfo           = nco_crcf_get_frequency(_q->mixer);
+                _q->framesyncstats.framesyms     = _q->payload_sym;
+                _q->framesyncstats.num_framesyms = _q->payload_sym_len;
+                _q->framesyncstats.mod_scheme    = ms;
+                _q->framesyncstats.mod_bps       = modulation_types[ms].bps;
+                _q->framesyncstats.check         = qpacketmodem_get_crc(_q->payload_decoder);
+                _q->framesyncstats.fec0          = qpacketmodem_get_fec0(_q->payload_decoder);
+                _q->framesyncstats.fec1          = qpacketmodem_get_fec1(_q->payload_decoder);
+
+                // invoke callback method
+                _q->callback(_q->header_dec,
+                             _q->header_valid,
+                             _q->payload_dec,
+                             _q->payload_dec_len,
+                             _q->payload_valid,
+                             _q->framesyncstats,
+                             _q->userdata);
+            }
+
+            // reset frame synchronizer
+            flexframesync_reset(_q);
+            return;
+        }
+    }
+}
+
+// reset frame data statistics
+void flexframesync_reset_framedatastats(flexframesync _q)
+{
+    framedatastats_reset(&_q->framedatastats);
+}
+
+// retrieve frame data statistics
+framedatastats_s flexframesync_get_framedatastats(flexframesync _q)
+{
+    return _q->framedatastats;
+}
+
+// enable debugging
+void flexframesync_debug_enable(flexframesync _q)
+{
+    // create debugging objects if necessary
+#if DEBUG_FLEXFRAMESYNC
+    if (_q->debug_objects_created)
+        return;
+
+    // create debugging objects
+    _q->debug_x = windowcf_create(DEBUG_BUFFER_LEN);
+
+    // set debugging flags
+    _q->debug_enabled = 1;
+    _q->debug_objects_created = 1;
+#else
+    fprintf(stderr,"flexframesync_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+// disable debugging
+void flexframesync_debug_disable(flexframesync _q)
+{
+    // disable debugging
+#if DEBUG_FLEXFRAMESYNC
+    _q->debug_enabled = 0;
+#else
+    fprintf(stderr,"flexframesync_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+// print debugging information
+void flexframesync_debug_print(flexframesync _q,
+                               const char *  _filename)
+{
+#if DEBUG_FLEXFRAMESYNC
+    if (!_q->debug_objects_created) {
+        fprintf(stderr,"error: flexframesync_debug_print(), debugging objects don't exist; enable debugging first\n");
+        return;
+    }
+    unsigned int i;
+    float complex * rc;
+    FILE* fid = fopen(_filename,"w");
+    fprintf(fid,"%% %s: auto-generated file", _filename);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"n = %u;\n", DEBUG_BUFFER_LEN);
+    
+    // main figure
+    fprintf(fid,"figure('Color','white','position',[100 100 800 600]);\n");
+
+    // write x
+    fprintf(fid,"x = zeros(1,n);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"\n\n");
+    fprintf(fid,"subplot(3,2,1:2);\n");
+    fprintf(fid,"plot(1:length(x),real(x), 1:length(x),imag(x));\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('received signal, x');\n");
+
+    // write p/n sequence
+    fprintf(fid,"preamble_pn = zeros(1,64);\n");
+    rc = _q->preamble_pn;
+    for (i=0; i<64; i++)
+        fprintf(fid,"preamble_pn(%4u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write p/n symbols
+    fprintf(fid,"preamble_rx = zeros(1,64);\n");
+    rc = _q->preamble_rx;
+    for (i=0; i<64; i++)
+        fprintf(fid,"preamble_rx(%4u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write recovered header symbols (after qpilotsync)
+    fprintf(fid,"header_mod = zeros(1,%u);\n", _q->header_mod_len);
+    rc = _q->header_mod;
+    for (i=0; i<_q->header_mod_len; i++)
+        fprintf(fid,"header_mod(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write raw payload symbols
+    fprintf(fid,"payload_sym = zeros(1,%u);\n", _q->payload_sym_len);
+    rc = _q->payload_sym;
+    for (i=0; i<_q->payload_sym_len; i++)
+        fprintf(fid,"payload_sym(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    fprintf(fid,"subplot(3,2,[3 5]);\n");
+    fprintf(fid,"plot(real(header_mod),imag(header_mod),'o');\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"title('Received Header Symbols');\n");
+
+    fprintf(fid,"subplot(3,2,[4 6]);\n");
+    fprintf(fid,"plot(real(payload_sym),imag(payload_sym),'+');\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"title('Received Payload Symbols');\n");
+
+    fprintf(fid,"\n\n");
+    fclose(fid);
+
+    printf("flexframesync/debug: results written to %s\n", _filename);
+#else
+    fprintf(stderr,"flexframesync_debug_print(): compile-time debugging disabled\n");
+#endif
+}
+
diff --git a/src/framing/src/framedatastats.c b/src/framing/src/framedatastats.c
new file mode 100644
index 0000000..9823672
--- /dev/null
+++ b/src/framing/src/framedatastats.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// framedatastats.c
+//
+// Default and generic frame statistics
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+// reset framedatastats object
+void framedatastats_reset(framedatastats_s * _stats)
+{
+    if (_stats == NULL)
+        return;
+
+    _stats->num_frames_detected = 0;
+    _stats->num_headers_valid   = 0;
+    _stats->num_payloads_valid  = 0;
+    _stats->num_bytes_received  = 0;
+}
+
+// print framedatastats object
+void framedatastats_print(framedatastats_s * _stats)
+{
+    if (_stats == NULL)
+        return;
+
+    float percent_headers  = 0.0f;
+    float percent_payloads = 0.0f;
+    if (_stats->num_frames_detected > 0) {
+        percent_headers  = 100.0f*(float)_stats->num_headers_valid  / (float)_stats->num_frames_detected;
+        percent_payloads = 100.0f*(float)_stats->num_payloads_valid / (float)_stats->num_frames_detected;
+    }
+
+    printf("  frames detected   : %u\n", _stats->num_frames_detected);
+    printf("  headers valid     : %-6u (%8.4f %%)\n", _stats->num_headers_valid,  percent_headers);
+    printf("  payloads valid    : %-6u (%8.4f %%)\n", _stats->num_payloads_valid, percent_payloads);
+    printf("  bytes received    : %lu\n", _stats->num_bytes_received);
+}
+
diff --git a/src/framing/src/framegen64.c b/src/framing/src/framegen64.c
new file mode 100644
index 0000000..e1eff04
--- /dev/null
+++ b/src/framing/src/framegen64.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// framegen64.c
+//
+// frame64 generator: 8-byte header, 64-byte payload, 1340-sample frame
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+struct framegen64_s {
+    qpacketmodem    enc;                // packet encoder/modulator
+    qpilotgen       pilotgen;           // pilot symbol generator
+    float complex   pn_sequence[64];    // 64-symbol p/n sequence
+    unsigned char   payload_dec[150];   // 600 = 150 bytes * 8 bits/bytes / 2 bits/symbol
+    float complex   payload_sym[600];   // modulated payload symbols
+    float complex   payload_tx[630];    // modulated payload symbols with pilots
+    unsigned int    m;                  // filter delay (symbols)
+    float           beta;               // filter excess bandwidth factor
+    firinterp_crcf interp;              // pulse-shaping filter
+};
+
+// create framegen64 object
+framegen64 framegen64_create()
+{
+    framegen64 q = (framegen64) malloc(sizeof(struct framegen64_s));
+    q->m    = 7;
+    q->beta = 0.3f;
+
+    unsigned int i;
+
+    // generate pn sequence
+    msequence ms = msequence_create(7, 0x0089, 1);
+    for (i=0; i<64; i++) {
+        q->pn_sequence[i] = (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2) +
+                            (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2)*_Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // create payload encoder/modulator object
+    int check      = LIQUID_CRC_24;
+    int fec0       = LIQUID_FEC_NONE;
+    int fec1       = LIQUID_FEC_GOLAY2412;
+    int mod_scheme = LIQUID_MODEM_QPSK;
+    q->enc         = qpacketmodem_create();
+    qpacketmodem_configure(q->enc, 72, check, fec0, fec1, mod_scheme);
+    //qpacketmodem_print(q->enc);
+    assert( qpacketmodem_get_frame_len(q->enc)==600 );
+
+    // create pilot generator
+    q->pilotgen = qpilotgen_create(600, 21);
+    assert( qpilotgen_get_frame_len(q->pilotgen)==630 );
+
+    // create pulse-shaping filter (k=2)
+    q->interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_ARKAISER,2,q->m,q->beta,0);
+
+    // return main object
+    return q;
+}
+
+// destroy framegen64 object
+void framegen64_destroy(framegen64 _q)
+{
+    // destroy internal objects
+    qpacketmodem_destroy(_q->enc);
+    qpilotgen_destroy(_q->pilotgen);
+
+    // free main object memory
+    free(_q);
+}
+
+// print framegen64 object internals
+void framegen64_print(framegen64 _q)
+{
+    float eta = (float) (8*(64 + 8)) / (float) (LIQUID_FRAME64_LEN/2);
+    printf("framegen64 [m=%u, beta=%4.2f]:\n", _q->m, _q->beta);
+    printf("  preamble/etc.\n");
+    printf("    * ramp/up symbols       :   %3u\n", _q->m);
+    printf("    * p/n symbols           :   %3u\n", 64);
+    printf("    * ramp\\down symbols     :   %3u\n", _q->m);
+    printf("    * zero padding          :   %3u\n", 12);
+    printf("  payload\n");
+#if 0
+    printf("    * payload crc           :   %s\n", crc_scheme_str[_q->check][1]);
+    printf("    * fec (inner)           :   %s\n", fec_scheme_str[_q->fec0][1]);
+    printf("    * fec (outer)           :   %s\n", fec_scheme_str[_q->fec1][1]);
+#endif
+    printf("    * payload len, uncoded  :   %3u bytes\n", 64);
+    printf("    * payload len, coded    :   %3u bytes\n", 150);
+    printf("    * modulation scheme     :   %s\n", modulation_types[LIQUID_MODEM_QPSK].name);
+    printf("    * payload symbols       :   %3u\n", 600);
+    printf("    * pilot symbols         :   %3u\n", 30);
+    printf("  summary\n");
+    printf("    * total symbols         :   %3u\n", LIQUID_FRAME64_LEN/2);
+    printf("    * spectral efficiency   :   %6.4f b/s/Hz\n", eta);
+}
+
+// execute frame generator (creates a frame)
+//  _q          :   frame generator object
+//  _header     :   8-byte header data
+//  _payload    :   64-byte payload data
+//  _frame      :   output frame samples [size: LIQUID_FRAME64_LEN x 1]
+void framegen64_execute(framegen64      _q,
+                        unsigned char * _header,
+                        unsigned char * _payload,
+                        float complex * _frame)
+{
+    unsigned int i;
+
+    // concatenate header and payload
+    memmove(&_q->payload_dec[0], _header,   8*sizeof(unsigned char));
+    memmove(&_q->payload_dec[8], _payload, 64*sizeof(unsigned char));
+
+    // run packet encoder and modulator
+    qpacketmodem_encode(_q->enc, _q->payload_dec, _q->payload_sym);
+
+    // add pilot symbols
+    qpilotgen_execute(_q->pilotgen, _q->payload_sym, _q->payload_tx);
+
+    unsigned int n=0;
+
+    // reset interpolator
+    firinterp_crcf_reset(_q->interp);
+
+    // p/n sequence
+    for (i=0; i<64; i++) {
+        firinterp_crcf_execute(_q->interp, _q->pn_sequence[i], &_frame[n]);
+        n+=2;
+    }
+
+    // frame payload
+    for (i=0; i<630; i++) {
+        firinterp_crcf_execute(_q->interp, _q->payload_tx[i], &_frame[n]);
+        n+=2;
+    }
+
+    // interpolator settling
+    for (i=0; i<2*_q->m + 2 + 10; i++) {
+        firinterp_crcf_execute(_q->interp, 0.0f, &_frame[n]);
+        n+=2;
+    }
+
+    assert(n==LIQUID_FRAME64_LEN);
+}
+
+
diff --git a/src/framing/src/framesync64.c b/src/framing/src/framesync64.c
new file mode 100644
index 0000000..4f65540
--- /dev/null
+++ b/src/framing/src/framesync64.c
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// framesync64.c
+//
+// basic frame synchronizer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FRAMESYNC64           1
+#define DEBUG_FRAMESYNC64_PRINT     0
+#define DEBUG_FILENAME              "framesync64_internal_debug.m"
+#define DEBUG_BUFFER_LEN            (1600)
+
+#define FRAMESYNC64_ENABLE_EQ       0
+
+// push samples through detection stage
+void framesync64_execute_seekpn(framesync64   _q,
+                                float complex _x);
+
+// step receiver mixer, matched filter, decimator
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int framesync64_step(framesync64     _q,
+                     float complex   _x,
+                     float complex * _y);
+
+// push samples through synchronizer, saving received p/n symbols
+void framesync64_execute_rxpreamble(framesync64   _q,
+                                    float complex _x);
+
+// receive payload symbols
+void framesync64_execute_rxpayload(framesync64   _q,
+                                   float complex _x);
+
+// framesync64 object structure
+struct framesync64_s {
+    // callback
+    framesync_callback  callback;   // user-defined callback function
+    void *              userdata;   // user-defined data structure
+    framesyncstats_s    framestats; // frame statistic object
+    
+    // synchronizer objects
+    unsigned int        m;          // filter delay (symbols)
+    float               beta;       // filter excess bandwidth factor
+    qdetector_cccf      detector;   // pre-demod detector
+    float               tau_hat;    // fractional timing offset estimate
+    float               dphi_hat;   // carrier frequency offset estimate
+    float               phi_hat;    // carrier phase offset estimate
+    float               gamma_hat;  // channel gain estimate
+    nco_crcf            mixer;      // coarse carrier frequency recovery
+
+    // timing recovery objects, states
+    firpfb_crcf         mf;         // matched filter decimator
+    unsigned int        npfb;       // number of filters in symsync
+    int                 mf_counter; // matched filter output timer
+    unsigned int        pfb_index;  // filterbank index
+#if FRAMESYNC64_ENABLE_EQ
+    eqlms_cccf          equalizer;  // equalizer (trained on p/n sequence)
+#endif
+
+    // preamble
+    float complex preamble_pn[64];  // known 64-symbol p/n sequence
+    float complex preamble_rx[64];  // received p/n symbols
+    
+    // payload decoder
+    float complex payload_rx [630]; // received payload symbols with pilots
+    float complex payload_sym[600]; // received payload symbols
+    unsigned char payload_dec[ 72]; // decoded payload bytes
+    qpacketmodem  dec;              // packet demodulator/decoder
+    qpilotsync    pilotsync;        // pilot extraction, carrier recovery
+    int           payload_valid;    // did payload pass crc?
+    
+    // status variables
+    enum {
+        FRAMESYNC64_STATE_DETECTFRAME=0,    // detect frame (seek p/n sequence)
+        FRAMESYNC64_STATE_RXPREAMBLE,       // receive p/n sequence
+        FRAMESYNC64_STATE_RXPAYLOAD,        // receive payload data
+    }            state;
+    unsigned int preamble_counter;  // counter: num of p/n syms received
+    unsigned int payload_counter;   // counter: num of payload syms received
+
+#if DEBUG_FRAMESYNC64
+    int debug_enabled;              // debugging enabled?
+    int debug_objects_created;      // debugging objects created?
+    windowcf debug_x;               // debug: raw input samples
+#endif
+};
+
+// create framesync64 object
+//  _callback       :   callback function invoked when frame is received
+//  _userdata       :   user-defined data object passed to callback
+framesync64 framesync64_create(framesync_callback _callback,
+                               void *             _userdata)
+{
+    framesync64 q = (framesync64) malloc(sizeof(struct framesync64_s));
+    q->callback = _callback;
+    q->userdata = _userdata;
+    q->m        = 7;    // filter delay (symbols)
+    q->beta     = 0.3f; // excess bandwidth factor
+
+    unsigned int i;
+
+    // generate p/n sequence
+    msequence ms = msequence_create(7, 0x0089, 1);
+    for (i=0; i<64; i++) {
+        q->preamble_pn[i] = (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2) +
+                            (msequence_advance(ms) ? M_SQRT1_2 : -M_SQRT1_2)*_Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // create frame detector
+    unsigned int k    = 2;    // samples/symbol
+    q->detector = qdetector_cccf_create_linear(q->preamble_pn, 64, LIQUID_FIRFILT_ARKAISER, k, q->m, q->beta);
+    qdetector_cccf_set_threshold(q->detector, 0.5f);
+
+    // create symbol timing recovery filters
+    q->npfb = 32;   // number of filters in the bank
+    q->mf   = firpfb_crcf_create_rnyquist(LIQUID_FIRFILT_ARKAISER, q->npfb,k,q->m,q->beta);
+
+#if FRAMESYNC64_ENABLE_EQ
+    // create equalizer
+    unsigned int p = 3;
+    q->equalizer = eqlms_cccf_create_lowpass(2*k*p+1, 0.4f);
+    eqlms_cccf_set_bw(q->equalizer, 0.05f);
+#endif
+
+    // create down-coverters for carrier phase tracking
+    q->mixer = nco_crcf_create(LIQUID_NCO);
+    
+    // create payload demodulator/decoder object
+    int check      = LIQUID_CRC_24;
+    int fec0       = LIQUID_FEC_NONE;
+    int fec1       = LIQUID_FEC_GOLAY2412;
+    int mod_scheme = LIQUID_MODEM_QPSK;
+    q->dec         = qpacketmodem_create();
+    qpacketmodem_configure(q->dec, 72, check, fec0, fec1, mod_scheme);
+    //qpacketmodem_print(q->dec);
+    assert( qpacketmodem_get_frame_len(q->dec)==600 );
+
+    // create pilot synchronizer
+    q->pilotsync   = qpilotsync_create(600, 21);
+    assert( qpilotsync_get_frame_len(q->pilotsync)==630 );
+
+#if DEBUG_FRAMESYNC64
+    // set debugging flags, objects to NULL
+    q->debug_enabled         = 0;
+    q->debug_objects_created = 0;
+    q->debug_x               = NULL;
+#endif
+
+    // reset state and return
+    framesync64_reset(q);
+    return q;
+}
+
+// destroy frame synchronizer object, freeing all internal memory
+void framesync64_destroy(framesync64 _q)
+{
+#if DEBUG_FRAMESYNC64
+    // clean up debug objects (if created)
+    if (_q->debug_objects_created) {
+        windowcf_destroy(_q->debug_x);
+    }
+#endif
+
+    // destroy synchronization objects
+    qdetector_cccf_destroy(_q->detector);   // frame detector
+    firpfb_crcf_destroy   (_q->mf);         // matched filter
+    nco_crcf_destroy      (_q->mixer);      // coarse NCO
+    qpacketmodem_destroy  (_q->dec);        // payload demodulator
+    qpilotsync_destroy    (_q->pilotsync);  // pilot synchronizer
+#if FRAMESYNC64_ENABLE_EQ
+    eqlms_cccf_destroy    (_q->equalizer);  // LMS equalizer
+#endif
+
+    // free main object memory
+    free(_q);
+}
+
+// print frame synchronizer object internals
+void framesync64_print(framesync64 _q)
+{
+    printf("framesync64:\n");
+}
+
+// reset frame synchronizer object
+void framesync64_reset(framesync64 _q)
+{
+    // reset binary pre-demod synchronizer
+    qdetector_cccf_reset(_q->detector);
+
+    // reset carrier recovery objects
+    nco_crcf_reset(_q->mixer);
+
+    // reset symbol timing recovery state
+    firpfb_crcf_reset(_q->mf);
+        
+    // reset state
+    _q->state           = FRAMESYNC64_STATE_DETECTFRAME;
+    _q->preamble_counter= 0;
+    _q->payload_counter = 0;
+    
+    // reset frame statistics
+    _q->framestats.evm = 0.0f;
+}
+
+// execute frame synchronizer
+//  _q     :   frame synchronizer object
+//  _x      :   input sample array [size: _n x 1]
+//  _n      :   number of input samples
+void framesync64_execute(framesync64     _q,
+                         float complex * _x,
+                         unsigned int    _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+#if DEBUG_FRAMESYNC64
+        if (_q->debug_enabled)
+            windowcf_push(_q->debug_x, _x[i]);
+#endif
+        switch (_q->state) {
+        case FRAMESYNC64_STATE_DETECTFRAME:
+            // detect frame (look for p/n sequence)
+            framesync64_execute_seekpn(_q, _x[i]);
+            break;
+        case FRAMESYNC64_STATE_RXPREAMBLE:
+            // receive p/n sequence symbols
+            framesync64_execute_rxpreamble(_q, _x[i]);
+            break;
+        case FRAMESYNC64_STATE_RXPAYLOAD:
+            // receive payload symbols
+            framesync64_execute_rxpayload(_q, _x[i]);
+            break;
+        default:
+            fprintf(stderr,"error: framesync64_exeucte(), unknown/unsupported state\n");
+            exit(1);
+        }
+    }
+}
+
+// 
+// internal methods
+//
+
+// execute synchronizer, seeking p/n sequence
+//  _q     :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void framesync64_execute_seekpn(framesync64   _q,
+                                float complex _x)
+{
+    // push through pre-demod synchronizer
+    float complex * v = qdetector_cccf_execute(_q->detector, _x);
+
+    // check if frame has been detected
+    if (v != NULL) {
+        // get estimates
+        _q->tau_hat   = qdetector_cccf_get_tau  (_q->detector);
+        _q->gamma_hat = qdetector_cccf_get_gamma(_q->detector);
+        _q->dphi_hat  = qdetector_cccf_get_dphi (_q->detector);
+        _q->phi_hat   = qdetector_cccf_get_phi  (_q->detector);
+
+#if DEBUG_FRAMESYNC64_PRINT
+        printf("***** frame detected! tau-hat:%8.4f, dphi-hat:%8.4f, gamma:%8.2f dB\n",
+                _q->tau_hat, _q->dphi_hat, 20*log10f(_q->gamma_hat));
+#endif
+
+        // set appropriate filterbank index
+        if (_q->tau_hat > 0) {
+            _q->pfb_index = (unsigned int)(      _q->tau_hat  * _q->npfb) % _q->npfb;
+            _q->mf_counter = 0;
+        } else {
+            _q->pfb_index = (unsigned int)((1.0f+_q->tau_hat) * _q->npfb) % _q->npfb;
+            _q->mf_counter = 1;
+        }
+        
+        // output filter scale
+        firpfb_crcf_set_scale(_q->mf, 0.5f / _q->gamma_hat);
+
+        // set frequency/phase of mixer
+        nco_crcf_set_frequency(_q->mixer, _q->dphi_hat);
+        nco_crcf_set_phase    (_q->mixer, _q->phi_hat );
+
+        // update state
+        _q->state = FRAMESYNC64_STATE_RXPREAMBLE;
+
+        // run buffered samples through synchronizer
+        unsigned int buf_len = qdetector_cccf_get_buf_len(_q->detector);
+        framesync64_execute(_q, v, buf_len);
+    }
+}
+
+// step receiver mixer, matched filter, decimator
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int framesync64_step(framesync64     _q,
+                     float complex   _x,
+                     float complex * _y)
+{
+    // mix sample down
+    float complex v;
+    nco_crcf_mix_down(_q->mixer, _x, &v);
+    nco_crcf_step    (_q->mixer);
+    
+    // push sample into filterbank
+    firpfb_crcf_push   (_q->mf, v);
+    firpfb_crcf_execute(_q->mf, _q->pfb_index, &v);
+
+#if FRAMESYNC64_ENABLE_EQ
+    // push sample through equalizer
+    eqlms_cccf_push(_q->equalizer, v);
+#endif
+
+    // increment counter to determine if sample is available
+    _q->mf_counter++;
+    int sample_available = (_q->mf_counter >= 1) ? 1 : 0;
+    
+    // set output sample if available
+    if (sample_available) {
+#if FRAMESYNC64_ENABLE_EQ
+        // compute equalizer output
+        eqlms_cccf_execute(_q->equalizer, &v);
+#endif
+
+        // set output
+        *_y = v;
+
+        // decrement counter by k=2 samples/symbol
+        _q->mf_counter -= 2;
+    }
+
+    // return flag
+    return sample_available;
+}
+
+// execute synchronizer, receiving p/n sequence
+//  _q     :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void framesync64_execute_rxpreamble(framesync64   _q,
+                                    float complex _x)
+{
+    // step synchronizer
+    float complex mf_out = 0.0f;
+    int sample_available = framesync64_step(_q, _x, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+
+        // save output in p/n symbols buffer
+#if FRAMESYNC64_ENABLE_EQ
+        unsigned int delay = 2*_q->m + 3; // delay from matched filter and equalizer
+#else
+        unsigned int delay = 2*_q->m;     // delay from matched filter
+#endif
+        if (_q->preamble_counter >= delay) {
+            unsigned int index = _q->preamble_counter-delay;
+
+            _q->preamble_rx[index] = mf_out;
+        
+#if FRAMESYNC64_ENABLE_EQ
+            // train equalizer
+            eqlms_cccf_step(_q->equalizer, _q->preamble_pn[index], mf_out);
+#endif
+        }
+
+        // update p/n counter
+        _q->preamble_counter++;
+
+        // update state
+        if (_q->preamble_counter == 64 + delay)
+            _q->state = FRAMESYNC64_STATE_RXPAYLOAD;
+    }
+}
+
+// execute synchronizer, receiving payload
+//  _q      :   frame synchronizer object
+//  _x      :   input sample
+//  _sym    :   demodulated symbol
+void framesync64_execute_rxpayload(framesync64   _q,
+                                   float complex _x)
+{
+    // step synchronizer
+    float complex mf_out = 0.0f;
+    int sample_available = framesync64_step(_q, _x, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // save payload symbols (modem input/output)
+        _q->payload_rx[_q->payload_counter] = mf_out;
+
+        // increment counter
+        _q->payload_counter++;
+
+        if (_q->payload_counter == 630) {
+            // recover data symbols from pilots
+            qpilotsync_execute(_q->pilotsync, _q->payload_rx, _q->payload_sym);
+
+            // decode payload
+            _q->payload_valid = qpacketmodem_decode(_q->dec,
+                                                    _q->payload_sym,
+                                                    _q->payload_dec);
+
+            // invoke callback
+            if (_q->callback != NULL) {
+                // set framestats internals
+                _q->framestats.evm           = 0.0f; //20*log10f(sqrtf(_q->framestats.evm / 600));
+                _q->framestats.rssi          = 20*log10f(_q->gamma_hat);
+                _q->framestats.cfo           = nco_crcf_get_frequency(_q->mixer);
+                _q->framestats.framesyms     = _q->payload_sym;
+                _q->framestats.num_framesyms = 600;
+                _q->framestats.mod_scheme    = LIQUID_MODEM_QPSK;
+                _q->framestats.mod_bps       = 2;
+                _q->framestats.check         = LIQUID_CRC_24;
+                _q->framestats.fec0          = LIQUID_FEC_NONE;
+                _q->framestats.fec1          = LIQUID_FEC_GOLAY2412;
+
+                // invoke callback method
+                _q->callback(&_q->payload_dec[0],   // header is first 8 bytes
+                             _q->payload_valid,
+                             &_q->payload_dec[8],   // payload is last 64 bytes
+                             64,
+                             _q->payload_valid,
+                             _q->framestats,
+                             _q->userdata);
+            }
+
+            // reset frame synchronizer
+            framesync64_reset(_q);
+            return;
+        }
+    }
+}
+
+// enable debugging
+void framesync64_debug_enable(framesync64 _q)
+{
+    // create debugging objects if necessary
+#if DEBUG_FRAMESYNC64
+    if (_q->debug_objects_created)
+        return;
+
+    // create debugging objects
+    _q->debug_x = windowcf_create(DEBUG_BUFFER_LEN);
+
+    // set debugging flags
+    _q->debug_enabled = 1;
+    _q->debug_objects_created = 1;
+#else
+    fprintf(stderr,"framesync64_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+// disable debugging
+void framesync64_debug_disable(framesync64 _q)
+{
+    // disable debugging
+#if DEBUG_FRAMESYNC64
+    _q->debug_enabled = 0;
+#else
+    fprintf(stderr,"framesync64_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+// print debugging information
+void framesync64_debug_print(framesync64  _q,
+                             const char * _filename)
+{
+#if DEBUG_FRAMESYNC64
+    if (!_q->debug_objects_created) {
+        fprintf(stderr,"error: framesync64_debug_print(), debugging objects don't exist; enable debugging first\n");
+        return;
+    }
+    unsigned int i;
+    float complex * rc;
+    FILE* fid = fopen(_filename,"w");
+    fprintf(fid,"%% %s: auto-generated file", _filename);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+    fprintf(fid,"n = %u;\n", DEBUG_BUFFER_LEN);
+
+    // write x
+    fprintf(fid,"x = zeros(1,n);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:length(x),real(x), 1:length(x),imag(x));\n");
+    fprintf(fid,"ylabel('received signal, x');\n");
+
+    // write p/n sequence
+    fprintf(fid,"preamble_pn = zeros(1,64);\n");
+    rc = _q->preamble_pn;
+    for (i=0; i<64; i++)
+        fprintf(fid,"preamble_pn(%4u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write p/n symbols
+    fprintf(fid,"preamble_rx = zeros(1,64);\n");
+    rc = _q->preamble_rx;
+    for (i=0; i<64; i++)
+        fprintf(fid,"preamble_rx(%4u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write raw payload symbols
+    unsigned int payload_sym_len = 600;
+    fprintf(fid,"payload_rx = zeros(1,%u);\n", payload_sym_len);
+    rc = _q->payload_rx;
+    for (i=0; i<payload_sym_len; i++)
+        fprintf(fid,"payload_rx(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    // write payload symbols
+    fprintf(fid,"payload_syms = zeros(1,%u);\n", payload_sym_len);
+    rc = _q->payload_sym;
+    for (i=0; i<payload_sym_len; i++)
+        fprintf(fid,"payload_syms(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(payload_syms),imag(payload_syms),'o');\n");
+    fprintf(fid,"xlabel('in-phase');\n");
+    fprintf(fid,"ylabel('quadrature phase');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"axis square;\n");
+
+    fprintf(fid,"\n\n");
+    fclose(fid);
+
+    printf("framesync64/debug: results written to %s\n", _filename);
+#else
+    fprintf(stderr,"framesync64_debug_print(): compile-time debugging disabled\n");
+#endif
+}
+
diff --git a/src/framing/src/framesyncstats.c b/src/framing/src/framesyncstats.c
new file mode 100644
index 0000000..3be3eec
--- /dev/null
+++ b/src/framing/src/framesyncstats.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// framesyncstats.c
+//
+// Default and generic frame statistics
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+framesyncstats_s framesyncstats_default = {
+    // signal quality
+    0.0f,                   // error vector magnitude
+    0.0f,                   // rssi
+    0.0f,                   // carrier frequency offset
+
+    // demodulated frame symbols
+    NULL,                   // framesyms
+    0,                      // num_framesyms
+
+    // modulation/coding scheme, etc.
+    LIQUID_MODEM_UNKNOWN,   // mod_scheme
+    0,                      // mod_bps
+    LIQUID_CRC_UNKNOWN,     // check
+    LIQUID_FEC_UNKNOWN,     // fec0
+    LIQUID_FEC_UNKNOWN      // fec1
+};
+
+// initialize framesyncstats object on default
+void framesyncstats_init_default(framesyncstats_s * _stats)
+{
+    memmove(_stats, &framesyncstats_default, sizeof(framesyncstats_s));
+}
+// print framesyncstats object
+void framesyncstats_print(framesyncstats_s * _stats)
+{
+    // validate statistics object
+    if (_stats->mod_scheme >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: framesyncstats_print(), invalid modulation scheme\n");
+        exit(1);
+     } else if (_stats->check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr,"error: framesyncstats_print(), invalid CRC scheme\n");
+        exit(1);
+     } else if (_stats->fec0 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"error: framesyncstats_print(), invalid FEC scheme (inner)\n");
+        exit(1);
+     } else if (_stats->fec1 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"error: framesyncstats_print(), invalid FEC scheme (outer)\n");
+        exit(1);
+    }
+
+    printf("    EVM                 :   %12.8f dB\n", _stats->evm);
+    printf("    rssi                :   %12.8f dB\n", _stats->rssi);
+    printf("    carrier offset      :   %12.8f Fs\n", _stats->cfo);
+    printf("    num symbols         :   %u\n", _stats->num_framesyms);
+    printf("    mod scheme          :   %s (%u bits/symbol)\n",
+            modulation_types[_stats->mod_scheme].name,
+            _stats->mod_bps);
+    printf("    validity check      :   %s\n", crc_scheme_str[_stats->check][0]);
+    printf("    fec (inner)         :   %s\n", fec_scheme_str[_stats->fec0][0]);
+    printf("    fec (outer)         :   %s\n", fec_scheme_str[_stats->fec1][0]);
+}
+
diff --git a/src/framing/src/gmskframegen.c b/src/framing/src/gmskframegen.c
new file mode 100644
index 0000000..7057587
--- /dev/null
+++ b/src/framing/src/gmskframegen.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// gmskframegen.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_GMSKFRAMEGEN    0
+
+// gmskframegen
+void gmskframegen_encode_header( gmskframegen _q, const unsigned char * _header);
+void gmskframegen_write_preamble(gmskframegen _q, float complex * _y);
+void gmskframegen_write_header(  gmskframegen _q, float complex * _y);
+void gmskframegen_write_payload( gmskframegen _q, float complex * _y);
+void gmskframegen_write_tail(    gmskframegen _q, float complex * _y);
+
+
+// gmskframe object structure
+struct gmskframegen_s {
+    gmskmod mod;                // GMSK modulator
+    unsigned int k;             // filter samples/symbol
+    unsigned int m;             // filter semi-length (symbols)
+    float BT;                   // filter bandwidth-time product
+
+    // framing lengths (symbols)
+    unsigned int preamble_len;  //
+    unsigned int header_len;    // length of header (encoded)
+    unsigned int payload_len;   //
+    unsigned int tail_len;      //
+
+    // preamble
+    //unsigned int genpoly_header;// generator polynomial
+    msequence ms_preamble;      // preamble p/n sequence
+
+    // header
+    unsigned char * header_dec; // uncoded header [GMSKFRAME_H_DEC]
+    unsigned char * header_enc; // encoded header [GMSKFRAME_H_ENC]
+    packetizer p_header;        // header packetizer
+
+    // payload
+    packetizer p_payload;       // payload packetizer
+    crc_scheme check;           // CRC
+    fec_scheme fec0;            // inner forward error correction
+    fec_scheme fec1;            // outer forward error correction
+    unsigned int dec_msg_len;   // 
+    unsigned int enc_msg_len;   // 
+    unsigned char * payload_enc;// encoded payload
+
+    // framing state
+    enum {
+        STATE_PREAMBLE,         // preamble
+        STATE_HEADER,           // header
+        STATE_PAYLOAD,          // payload (frame)
+        STATE_TAIL,             // tail symbols
+    } state;
+    int frame_assembled;        // frame assembled flag
+    int frame_complete;         // frame completed flag
+    unsigned int symbol_counter;//
+};
+
+// create gmskframegen object
+gmskframegen gmskframegen_create()
+{
+    gmskframegen q = (gmskframegen) malloc(sizeof(struct gmskframegen_s));
+
+    // set internal properties
+    q->k  = 2;      // samples/symbol
+    q->m  = 3;      // filter delay (symbols)
+    q->BT = 0.5f;   // filter bandwidth-time product
+
+    // internal/derived values
+    q->preamble_len = 63;       // number of preamble symbols
+    q->payload_len  =  0;       // number of payload symbols
+    q->tail_len     = 2*q->m;   // number of tail symbols (flush interp)
+
+    // create modulator
+    q->mod = gmskmod_create(q->k, q->m, q->BT);
+
+    // preamble objects/arrays
+    q->ms_preamble = msequence_create(6, 0x6d, 1);
+
+    // header objects/arrays
+    q->header_dec = (unsigned char*)malloc(GMSKFRAME_H_DEC*sizeof(unsigned char));
+    q->header_enc = (unsigned char*)malloc(GMSKFRAME_H_ENC*sizeof(unsigned char));
+    q->header_len = GMSKFRAME_H_ENC * 8;
+    q->p_header   = packetizer_create(GMSKFRAME_H_DEC,
+                                      GMSKFRAME_H_CRC,
+                                      GMSKFRAME_H_FEC,
+                                      LIQUID_FEC_NONE);
+
+    // payload objects/arrays
+    q->dec_msg_len = 0;
+    q->check = LIQUID_CRC_32;
+    q->fec0  = LIQUID_FEC_NONE;
+    q->fec1  = LIQUID_FEC_NONE;
+
+    q->p_payload = packetizer_create(q->dec_msg_len,
+                                     q->check,
+                                     q->fec0,
+                                     q->fec1);
+    q->enc_msg_len = packetizer_get_enc_msg_len(q->p_payload);
+    q->payload_len = 8*q->enc_msg_len;
+
+    // allocate memory for encoded packet
+    q->payload_enc = (unsigned char*) malloc(q->enc_msg_len*sizeof(unsigned char));
+
+    // reset framing object
+    gmskframegen_reset(q);
+
+    // return object
+    return q;
+}
+
+// destroy gmskframegen object
+void gmskframegen_destroy(gmskframegen _q)
+{
+    // destroy gmsk modulator
+    gmskmod_destroy(_q->mod);
+
+    // destroy/free preamble objects/arrays
+    msequence_destroy(_q->ms_preamble);
+
+    // destroy/free header objects/arrays
+    free(_q->header_dec);
+    free(_q->header_enc);
+    packetizer_destroy(_q->p_header);
+
+    // destroy/free payload objects/arrays
+    free(_q->payload_enc);
+    packetizer_destroy(_q->p_payload);
+
+    // free main object memory
+    free(_q);
+}
+
+// reset frame generator object
+void gmskframegen_reset(gmskframegen _q)
+{
+    // reset GMSK modulator
+    gmskmod_reset(_q->mod);
+
+    // reset states
+    _q->state = STATE_PREAMBLE;
+    msequence_reset(_q->ms_preamble);
+    _q->frame_assembled = 0;
+    _q->frame_complete  = 0;
+    _q->symbol_counter  = 0;
+}
+
+// is frame assembled?
+int gmskframegen_is_assembled(gmskframegen _q)
+{
+    return _q->frame_assembled;
+}
+
+// print gmskframegen object internals
+void gmskframegen_print(gmskframegen _q)
+{
+    // plot
+    printf("gmskframegen:\n");
+    printf("  physical properties\n");
+    printf("    samples/symbol  :   %u\n", _q->k);
+    printf("    filter delay    :   %u symbols\n", _q->m);
+    printf("    bandwidth-time  :   %-8.3f\n", _q->BT);
+    printf("  framing properties\n");
+    printf("    preamble        :   %-4u symbols\n", _q->preamble_len);
+    printf("    header          :   %-4u symbols\n", _q->header_len);
+    printf("    payload         :   %-4u symbols\n", _q->payload_len);
+    printf("    tail            :   %-4u symbols\n", _q->tail_len);
+    printf("  packet properties\n");
+    printf("    crc             :   %s\n", crc_scheme_str[_q->check][1]);
+    printf("    fec (inner)     :   %s\n", fec_scheme_str[_q->fec0][1]);
+    printf("    fec (outer)     :   %s\n", fec_scheme_str[_q->fec1][1]);
+    printf("  total samples     :   %-4u sampels\n", gmskframegen_getframelen(_q));
+}
+
+// assemble frame
+//  _q              :   frame generator object
+//  _header         :   raw header
+//  _payload        :   raw payload [size: _payload_len x 1]
+//  _payload_len    :   raw payload length (bytes)
+//  _check          :   data validity check
+//  _fec0           :   inner forward error correction
+//  _fec1           :   outer forward error correction
+void gmskframegen_assemble(gmskframegen          _q,
+                           const unsigned char * _header,
+                           const unsigned char * _payload,
+                           unsigned int          _payload_len,
+                           crc_scheme            _check,
+                           fec_scheme            _fec0,
+                           fec_scheme            _fec1)
+{
+    // re-create frame generator if properties don't match
+    if (_q->dec_msg_len != _payload_len ||
+        _q->check       != _check       ||
+        _q->fec0        != _fec0        ||
+        _q->fec1        != _fec1)
+    {
+        // set properties
+        _q->dec_msg_len = _payload_len;
+        _q->check       = _check;
+        _q->fec0        = _fec0;
+        _q->fec1        = _fec1;
+
+        // re-create payload packetizer
+        _q->p_payload = packetizer_recreate(_q->p_payload, _q->dec_msg_len, _q->check, _q->fec0, _q->fec1);
+        
+        // get packet length
+        _q->enc_msg_len = packetizer_get_enc_msg_len(_q->p_payload);
+        _q->payload_len = 8*_q->enc_msg_len;
+
+        // re-allocate memory
+        _q->payload_enc = (unsigned char*) realloc(_q->payload_enc, _q->enc_msg_len*sizeof(unsigned char));
+    }
+    
+    // set assembled flag
+    _q->frame_assembled = 1;
+
+    // encode header
+    gmskframegen_encode_header(_q, _header);
+
+    // encode payload
+    packetizer_encode(_q->p_payload, _payload, _q->payload_enc);
+}
+
+// get frame length (number of samples)
+unsigned int gmskframegen_getframelen(gmskframegen _q)
+{
+    if (!_q->frame_assembled) {
+        fprintf(stderr,"warning: gmskframegen_getframelen(), frame not assembled!\n");
+        return 0;
+    }
+
+    unsigned int num_frame_symbols =
+            _q->preamble_len +      // number of preamble p/n symbols
+            GMSKFRAME_H_SYM +       // number of header symbols
+            _q->payload_len +       // number of payload symbols
+            2*_q->m;                // number of tail symbols
+
+    return num_frame_symbols*_q->k; // k samples/symbol
+}
+
+// write sample to output buffer
+int gmskframegen_write_samples(gmskframegen _q,
+                               float complex * _y)
+{
+    switch (_q->state) {
+    case STATE_PREAMBLE:
+        // write preamble
+        gmskframegen_write_preamble(_q, _y);
+        break;
+
+    case STATE_HEADER:
+        // write header
+        gmskframegen_write_header(_q, _y);
+        break;
+
+    case STATE_PAYLOAD:
+        // write payload symbols
+        gmskframegen_write_payload(_q, _y);
+        break;
+
+    case STATE_TAIL:
+        // write tail symbols
+        gmskframegen_write_tail(_q, _y);
+        break;
+
+    default:
+        fprintf(stderr,"error: gmskframegen_writesymbol(), unknown/unsupported internal state\n");
+        exit(1);
+    }
+
+    if (_q->frame_complete) {
+        // reset framing object
+#if DEBUG_GMSKFRAMEGEN
+        printf(" ...resetting...\n");
+#endif
+        gmskframegen_reset(_q);
+        return 1;
+    }
+
+    return 0;
+}
+
+
+// 
+// internal methods
+//
+
+void gmskframegen_encode_header(gmskframegen          _q,
+                                const unsigned char * _header)
+{
+    // first 'n' bytes user data
+    memmove(_q->header_dec, _header, GMSKFRAME_H_USER);
+    unsigned int n = GMSKFRAME_H_USER;
+
+    // first byte is for expansion/version validation
+    _q->header_dec[n+0] = GMSKFRAME_VERSION;
+
+    // add payload length
+    _q->header_dec[n+1] = (_q->dec_msg_len >> 8) & 0xff;
+    _q->header_dec[n+2] = (_q->dec_msg_len     ) & 0xff;
+
+    // add CRC, forward error-correction schemes
+    //  CRC     : most-significant 3 bits of [n+4]
+    //  fec0    : least-significant 5 bits of [n+4]
+    //  fec1    : least-significant 5 bits of [n+5]
+    _q->header_dec[n+3]  = (_q->check & 0x07) << 5;
+    _q->header_dec[n+3] |= (_q->fec0) & 0x1f;
+    _q->header_dec[n+4]  = (_q->fec1) & 0x1f;
+
+    // run packet encoder
+    packetizer_encode(_q->p_header, _q->header_dec, _q->header_enc);
+
+    // scramble header
+    scramble_data(_q->header_enc, GMSKFRAME_H_ENC);
+#if 0
+    printf("    header_enc      :");
+    unsigned int i;
+    for (i=0; i<GMSKFRAME_H_ENC; i++)
+        printf(" %.2X", _q->header_enc[i]);
+    printf("\n");
+#endif
+}
+
+void gmskframegen_write_preamble(gmskframegen    _q,
+                                 float complex * _y)
+{
+    unsigned char bit = msequence_advance(_q->ms_preamble);
+    gmskmod_modulate(_q->mod, bit, _y);
+
+    // apply ramping window to first 'm' symbols
+    if (_q->symbol_counter < _q->m) {
+        unsigned int i;
+        for (i=0; i<_q->k; i++)
+            _y[i] *= hamming(_q->symbol_counter*_q->k + i, 2*_q->m*_q->k);
+    }
+
+    _q->symbol_counter++;
+
+    if (_q->symbol_counter == _q->preamble_len) {
+        msequence_reset(_q->ms_preamble);
+        _q->symbol_counter = 0;
+        _q->state = STATE_HEADER;
+    }
+}
+
+void gmskframegen_write_header(gmskframegen    _q,
+                               float complex * _y)
+{
+    div_t d = div(_q->symbol_counter, 8);
+    unsigned int byte_index = d.quot;
+    unsigned int bit_index  = d.rem;
+    unsigned char byte = _q->header_enc[byte_index];
+    unsigned char bit  = (byte >> (8-bit_index-1)) & 0x01;
+
+    gmskmod_modulate(_q->mod, bit, _y);
+
+    _q->symbol_counter++;
+    
+    if (_q->symbol_counter == _q->header_len) {
+        _q->symbol_counter = 0;
+        _q->state = STATE_PAYLOAD;
+    }
+}
+
+void gmskframegen_write_payload(gmskframegen    _q,
+                                float complex * _y)
+{
+    div_t d = div(_q->symbol_counter, 8);
+    unsigned int byte_index = d.quot;
+    unsigned int bit_index  = d.rem;
+    unsigned char byte = _q->payload_enc[byte_index];
+    unsigned char bit  = (byte >> (8-bit_index-1)) & 0x01;
+
+    gmskmod_modulate(_q->mod, bit, _y);
+
+    _q->symbol_counter++;
+    
+    if (_q->symbol_counter == _q->payload_len) {
+        _q->symbol_counter = 0;
+        _q->state = STATE_TAIL;
+    }
+}
+
+void gmskframegen_write_tail(gmskframegen    _q,
+                             float complex * _y)
+{
+    unsigned char bit = rand() % 2;
+    gmskmod_modulate(_q->mod, bit, _y);
+
+    // apply ramping window to last 'm' symbols
+    if (_q->symbol_counter >= _q->m) {
+        unsigned int i;
+        for (i=0; i<_q->k; i++)
+            _y[i] *= hamming(_q->m*_q->k + (_q->symbol_counter-_q->m)*_q->k + i, 2*_q->m*_q->k);
+    }
+
+    _q->symbol_counter++;
+
+    if (_q->symbol_counter == _q->tail_len) {
+        _q->symbol_counter = 0;
+        _q->frame_complete = 1;
+    }
+}
+
diff --git a/src/framing/src/gmskframesync.c b/src/framing/src/gmskframesync.c
new file mode 100644
index 0000000..62c41e1
--- /dev/null
+++ b/src/framing/src/gmskframesync.c
@@ -0,0 +1,930 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// gmskframesync.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <complex.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_GMSKFRAMESYNC             1
+#define DEBUG_GMSKFRAMESYNC_PRINT       0
+#define DEBUG_GMSKFRAMESYNC_FILENAME    "gmskframesync_debug.m"
+#define DEBUG_GMSKFRAMESYNC_BUFFER_LEN  (2000)
+
+// enable pre-demodulation filter (remove out-of-band noise)
+#define GMSKFRAMESYNC_PREFILTER         1
+
+// push buffered p/n sequence through synchronizer
+void gmskframesync_pushpn(gmskframesync _q);
+
+// ...
+void gmskframesync_syncpn(gmskframesync _q);
+
+// update instantaneous frequency estimate
+void gmskframesync_update_fi(gmskframesync _q,
+                             float complex _x);
+
+// update symbol synchronizer internal state (filtered error, index, etc.)
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int gmskframesync_update_symsync(gmskframesync _q,
+                                 float         _x,
+                                 float *       _y);
+
+// execute stages
+void gmskframesync_execute_detectframe(gmskframesync _q, float complex _x);
+void gmskframesync_execute_rxpreamble( gmskframesync _q, float complex _x);
+void gmskframesync_execute_rxheader(   gmskframesync _q, float complex _x);
+void gmskframesync_execute_rxpayload(  gmskframesync _q, float complex _x);
+
+// decode header
+void gmskframesync_decode_header(gmskframesync _q);
+
+// gmskframesync object structure
+struct gmskframesync_s {
+#if GMSKFRAMESYNC_PREFILTER
+    iirfilt_crcf prefilter;         // pre-demodulation filter
+#endif
+    unsigned int k;                 // filter samples/symbol
+    unsigned int m;                 // filter semi-length (symbols)
+    float BT;                       // filter bandwidth-time product
+    framesync_callback callback;    // user-defined callback function
+    void * userdata;                // user-defined data structure
+    framesyncstats_s framestats;    // frame statistic object
+    
+    //
+    float complex x_prime;          // received sample state
+    float fi_hat;                   // instantaneous frequency estimate
+    
+    // timing recovery objects, states
+    firpfb_rrrf mf;                 // matched filter decimator
+    firpfb_rrrf dmf;                // derivative matched filter decimator
+    unsigned int npfb;              // number of filters in symsync
+    float pfb_q;                    // filtered timing error
+    float pfb_soft;                 // soft filterbank index
+    int pfb_index;                  // hard filterbank index
+    int pfb_timer;                  // filterbank output flag
+    float symsync_out;              // symbol synchronizer output
+
+    // synchronizer objects
+    detector_cccf frame_detector;   // pre-demod detector
+    float tau_hat;                  // fractional timing offset estimate
+    float dphi_hat;                 // carrier frequency offset estimate
+    float gamma_hat;                // channel gain estimate
+    windowcf buffer;                // pre-demod buffered samples, size: k*(pn_len+m)
+    nco_crcf nco_coarse;            // coarse carrier frequency recovery
+    
+    // preamble
+    unsigned int preamble_len;      // number of symbols in preamble
+    float * preamble_pn;            // preamble p/n sequence (known)
+    float * preamble_rx;            // preamble p/n sequence (received)
+
+    // header
+    unsigned char * header_mod;
+    unsigned char * header_enc;
+    unsigned char * header_dec;
+    packetizer p_header;
+    int header_valid;
+
+    // payload
+    char payload_byte;              // received byte
+    crc_scheme check;               // payload validity check
+    fec_scheme fec0;                // payload FEC (inner)
+    fec_scheme fec1;                // payload FEC (outer)
+    unsigned int payload_enc_len;   // length of encoded payload
+    unsigned int payload_dec_len;   // payload length (num un-encoded bytes)
+    unsigned char * payload_enc;    // payload data (encoded bytes)
+    unsigned char * payload_dec;    // payload data (encoded bytes)
+    packetizer p_payload;           // payload packetizer
+    int payload_valid;              // did payload pass crc?
+    
+    // status variables
+    enum {
+        STATE_DETECTFRAME=0,        // detect frame (seek p/n sequence)
+        STATE_RXPREAMBLE,           // receive p/n sequence
+        STATE_RXHEADER,             // receive header data
+        STATE_RXPAYLOAD,            // receive payload data
+    } state;
+    unsigned int preamble_counter;  // counter: num of p/n syms received
+    unsigned int header_counter;    // counter: num of header syms received
+    unsigned int payload_counter;   // counter: num of payload syms received
+    // debugging structures
+#if DEBUG_GMSKFRAMESYNC
+    int debug_enabled;              // debugging enabled?
+    int debug_objects_created;      // debugging objects created?
+    windowcf debug_x;               // received samples buffer
+    windowf  debug_fi;              // instantaneous frequency
+    windowf  debug_mf;              // matched filter output
+    windowf  debug_framesyms;       // GMSK output symbols
+#endif
+};
+
+// create GMSK frame synchronizer
+//  _callback   :   callback function
+//  _userdata   :   user data pointer passed to callback function
+gmskframesync gmskframesync_create(framesync_callback _callback,
+                                   void *             _userdata)
+{
+    gmskframesync q = (gmskframesync) malloc(sizeof(struct gmskframesync_s));
+    q->callback = _callback;
+    q->userdata = _userdata;
+    q->k        = 2;        // samples/symbol
+    q->m        = 3;        // filter delay (symbols)
+    q->BT       = 0.5f;     // filter bandwidth-time product
+
+#if GMSKFRAMESYNC_PREFILTER
+    // create default low-pass Butterworth filter
+    q->prefilter = iirfilt_crcf_create_lowpass(3, 0.5f*(1 + q->BT) / (float)(q->k));
+#endif
+
+    unsigned int i;
+
+    // frame detector
+    q->preamble_len = 63;
+    q->preamble_pn = (float*)malloc(q->preamble_len*sizeof(float));
+    q->preamble_rx = (float*)malloc(q->preamble_len*sizeof(float));
+    float complex preamble_samples[q->preamble_len*q->k];
+    msequence ms = msequence_create(6, 0x6d, 1);
+    gmskmod mod = gmskmod_create(q->k, q->m, q->BT);
+
+    for (i=0; i<q->preamble_len + q->m; i++) {
+        unsigned char bit = msequence_advance(ms);
+
+        // save p/n sequence
+        if (i < q->preamble_len)
+            q->preamble_pn[i] = bit ? 1.0f : -1.0f;
+        
+        // modulate/interpolate
+        if (i < q->m) gmskmod_modulate(mod, bit, &preamble_samples[0]);
+        else          gmskmod_modulate(mod, bit, &preamble_samples[(i-q->m)*q->k]);
+    }
+
+    gmskmod_destroy(mod);
+    msequence_destroy(ms);
+
+#if 0
+    // print sequence
+    for (i=0; i<q->preamble_len*q->k; i++)
+        printf("preamble(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(preamble_samples[i]), cimagf(preamble_samples[i]));
+#endif
+    // create frame detector
+    float threshold = 0.5f;     // detection threshold
+    float dphi_max  = 0.05f;    // maximum carrier offset allowable
+    q->frame_detector = detector_cccf_create(preamble_samples, q->preamble_len*q->k, threshold, dphi_max);
+    q->buffer = windowcf_create(q->k*(q->preamble_len+q->m));
+
+    // create symbol timing recovery filters
+    q->npfb = 32;   // number of filters in the bank
+    q->mf   = firpfb_rrrf_create_rnyquist( LIQUID_FIRFILT_GMSKRX,q->npfb,q->k,q->m,q->BT);
+    q->dmf  = firpfb_rrrf_create_drnyquist(LIQUID_FIRFILT_GMSKRX,q->npfb,q->k,q->m,q->BT);
+
+    // create down-coverters for carrier phase tracking
+    q->nco_coarse = nco_crcf_create(LIQUID_NCO);
+
+    // create/allocate header objects/arrays
+    q->header_mod = (unsigned char*)malloc(GMSKFRAME_H_SYM*sizeof(unsigned char));
+    q->header_enc = (unsigned char*)malloc(GMSKFRAME_H_ENC*sizeof(unsigned char));
+    q->header_dec = (unsigned char*)malloc(GMSKFRAME_H_DEC*sizeof(unsigned char));
+    q->p_header   = packetizer_create(GMSKFRAME_H_DEC,
+                                      GMSKFRAME_H_CRC,
+                                      GMSKFRAME_H_FEC,
+                                      LIQUID_FEC_NONE);
+
+    // create/allocate payload objects/arrays
+    q->payload_dec_len = 1;
+    q->check           = LIQUID_CRC_32;
+    q->fec0            = LIQUID_FEC_NONE;
+    q->fec1            = LIQUID_FEC_NONE;
+    q->p_payload = packetizer_create(q->payload_dec_len,
+                                     q->check,
+                                     q->fec0,
+                                     q->fec1);
+    q->payload_enc_len = packetizer_get_enc_msg_len(q->p_payload);
+    q->payload_dec = (unsigned char*) malloc(q->payload_dec_len*sizeof(unsigned char));
+    q->payload_enc = (unsigned char*) malloc(q->payload_enc_len*sizeof(unsigned char));
+
+#if DEBUG_GMSKFRAMESYNC
+    // debugging structures
+    q->debug_enabled         = 0;
+    q->debug_objects_created = 0;
+    q->debug_x               = NULL;
+    q->debug_fi              = NULL;
+    q->debug_mf              = NULL;
+    q->debug_framesyms       = NULL;
+#endif
+
+    // reset synchronizer
+    gmskframesync_reset(q);
+
+    // return synchronizer object
+    return q;
+}
+
+
+// destroy frame synchronizer object, freeing all internal memory
+void gmskframesync_destroy(gmskframesync _q)
+{
+#if DEBUG_GMSKFRAMESYNC
+    // destroy debugging objects
+    if (_q->debug_objects_created) {
+        windowcf_destroy(_q->debug_x);
+        windowf_destroy(_q->debug_fi);
+        windowf_destroy(_q->debug_mf);
+        windowf_destroy( _q->debug_framesyms);
+    }
+#endif
+
+    // destroy synchronizer objects
+#if GMSKFRAMESYNC_PREFILTER
+    iirfilt_crcf_destroy(_q->prefilter);// pre-demodulator filter
+#endif
+    firpfb_rrrf_destroy(_q->mf);                // matched filter
+    firpfb_rrrf_destroy(_q->dmf);               // derivative matched filter
+    nco_crcf_destroy(_q->nco_coarse);           // coarse NCO
+
+    // preamble
+    detector_cccf_destroy(_q->frame_detector);
+    windowcf_destroy(_q->buffer);
+    free(_q->preamble_pn);
+    free(_q->preamble_rx);
+    
+    // header
+    packetizer_destroy(_q->p_header);
+    free(_q->header_mod);
+    free(_q->header_enc);
+    free(_q->header_dec);
+
+    // payload
+    packetizer_destroy(_q->p_payload);
+    free(_q->payload_enc);
+    free(_q->payload_dec);
+
+    // free main object memory
+    free(_q);
+}
+
+// print frame synchronizer object internals
+void gmskframesync_print(gmskframesync _q)
+{
+    printf("gmskframesync:\n");
+}
+
+// reset frame synchronizer object
+void gmskframesync_reset(gmskframesync _q)
+{
+    // reset state and counters
+    _q->state = STATE_DETECTFRAME;
+    _q->preamble_counter = 0;
+    _q->header_counter   = 0;
+    _q->payload_counter  = 0;
+    
+    // clear pre-demod buffer
+    windowcf_clear(_q->buffer);
+
+    // reset internal objects
+    detector_cccf_reset(_q->frame_detector);
+    
+    // reset carrier recovery objects
+    nco_crcf_reset(_q->nco_coarse);
+
+    // reset sample state
+    _q->x_prime = 0.0f;
+    _q->fi_hat  = 0.0f;
+    
+    // reset symbol timing recovery state
+    firpfb_rrrf_reset(_q->mf);
+    firpfb_rrrf_reset(_q->dmf);
+    _q->pfb_q = 0.0f;   // filtered error signal
+        
+}
+
+// execute frame synchronizer
+//  _q      :   frame synchronizer object
+//  _x      :   input sample array [size: _n x 1]
+//  _n      :   number of input samples
+void gmskframesync_execute(gmskframesync   _q,
+                           float complex * _x,
+                           unsigned int    _n)
+{
+    // push through synchronizer
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        float complex xf;   // input sample
+#if GMSKFRAMESYNC_PREFILTER
+        iirfilt_crcf_execute(_q->prefilter, _x[i], &xf);
+#else
+        xf = _x[i];
+#endif
+
+#if DEBUG_GMSKFRAMESYNC
+        if (_q->debug_enabled)
+            windowcf_push(_q->debug_x, xf);
+#endif
+
+        switch (_q->state) {
+        case STATE_DETECTFRAME:
+            // look for p/n sequence
+            gmskframesync_execute_detectframe(_q, xf);
+            break;
+
+        case STATE_RXPREAMBLE:
+            // receive p/n sequence symbols
+            gmskframesync_execute_rxpreamble(_q, xf);
+            break;
+
+        case STATE_RXHEADER:
+            // receive header
+            gmskframesync_execute_rxheader(_q, xf);
+            break;
+
+        case STATE_RXPAYLOAD:
+            // receive payload
+            gmskframesync_execute_rxpayload(_q, xf);
+            break;
+        }
+    }
+}
+
+// 
+// internal methods
+//
+
+// update symbol synchronizer internal state (filtered error, index, etc.)
+//  _q      :   frame synchronizer
+//  _x      :   input sample
+//  _y      :   output symbol
+int gmskframesync_update_symsync(gmskframesync _q,
+                                 float         _x,
+                                 float *       _y)
+{
+    // push sample into filterbanks
+    firpfb_rrrf_push(_q->mf,  _x);
+    firpfb_rrrf_push(_q->dmf, _x);
+
+    //
+    float mf_out  = 0.0f;    // matched-filter output
+    float dmf_out = 0.0f;    // derivatived matched-filter output
+#if DEBUG_GMSKFRAMESYNC
+    if (_q->debug_enabled) {
+        windowf_push(_q->debug_fi, _q->fi_hat);
+        firpfb_rrrf_execute(_q->mf,  _q->pfb_index, &mf_out);
+        windowf_push(_q->debug_mf, mf_out);
+    }
+#endif
+
+
+    int sample_available = 0;
+
+    // compute output if timeout
+    if (_q->pfb_timer <= 0) {
+        sample_available = 1;
+
+        // reset timer
+        _q->pfb_timer = 2;  // k samples/symbol
+
+        firpfb_rrrf_execute(_q->mf,  _q->pfb_index, &mf_out);
+        firpfb_rrrf_execute(_q->dmf, _q->pfb_index, &dmf_out);
+
+        // update filtered timing error
+        // lo  bandwidth parameters: {0.92, 1.20}, about 100 symbols settling time
+        // med bandwidth parameters: {0.98, 0.20}, about 200 symbols settling time
+        // hi  bandwidth parameters: {0.99, 0.05}, about 500 symbols settling time
+        _q->pfb_q = 0.99f*_q->pfb_q + 0.05f*crealf( conjf(mf_out)*dmf_out );
+
+        // accumulate error into soft filterbank value
+        _q->pfb_soft += _q->pfb_q;
+
+        // compute actual filterbank index
+        _q->pfb_index = roundf(_q->pfb_soft);
+
+        // contrain index to be in [0, npfb-1]
+        while (_q->pfb_index < 0) {
+            _q->pfb_index += _q->npfb;
+            _q->pfb_soft  += _q->npfb;
+
+            // adjust pfb output timer
+            _q->pfb_timer--;
+        }
+        while (_q->pfb_index > _q->npfb-1) {
+            _q->pfb_index -= _q->npfb;
+            _q->pfb_soft  -= _q->npfb;
+
+            // adjust pfb output timer
+            _q->pfb_timer++;
+        }
+        //printf("  b/soft    :   %12.8f\n", _q->pfb_soft);
+    }
+
+    // decrement symbol timer
+    _q->pfb_timer--;
+
+    // set output and return
+    *_y = mf_out / (float)(_q->k);
+    
+    return sample_available;
+}
+
+// push buffered p/n sequence through synchronizer
+void gmskframesync_pushpn(gmskframesync _q)
+{
+    unsigned int i;
+
+    // reset filterbanks
+    firpfb_rrrf_reset(_q->mf);
+    firpfb_rrrf_reset(_q->dmf);
+
+    // read buffer
+    float complex * rc;
+    windowcf_read(_q->buffer, &rc);
+
+    // compute delay and filterbank index
+    //  tau_hat < 0 :   delay = 2*k*m-1, index = round(   tau_hat *npfb), flag = 0
+    //  tau_hat > 0 :   delay = 2*k*m-2, index = round((1-tau_hat)*npfb), flag = 0
+    assert(_q->tau_hat < 0.5f && _q->tau_hat > -0.5f);
+    unsigned int delay = 2*_q->k*_q->m - 1; // samples to buffer before computing output
+    _q->pfb_soft       = -_q->tau_hat*_q->npfb;
+    _q->pfb_index      = (int) roundf(_q->pfb_soft);
+    while (_q->pfb_index < 0) {
+        delay         -= 1;
+        _q->pfb_index += _q->npfb;
+        _q->pfb_soft  += _q->npfb;
+    }
+    _q->pfb_timer = 0;
+
+    // set coarse carrier frequency offset
+    nco_crcf_set_frequency(_q->nco_coarse, _q->dphi_hat);
+    
+    unsigned int buffer_len = (_q->preamble_len + _q->m) * _q->k;
+    for (i=0; i<buffer_len; i++) {
+        if (i < delay) {
+            float complex y;
+            nco_crcf_mix_down(_q->nco_coarse, rc[i], &y);
+            nco_crcf_step(_q->nco_coarse);
+
+            // update instantanenous frequency estimate
+            gmskframesync_update_fi(_q, y);
+
+            // push initial samples into filterbanks
+            firpfb_rrrf_push(_q->mf,  _q->fi_hat);
+            firpfb_rrrf_push(_q->dmf, _q->fi_hat);
+        } else {
+            // run remaining samples through p/n sequence recovery
+            gmskframesync_execute_rxpreamble(_q, rc[i]);
+        }
+    }
+
+    // set state (still need a few more samples before entire p/n
+    // sequence has been received)
+    _q->state = STATE_RXPREAMBLE;
+}
+
+// 
+void gmskframesync_syncpn(gmskframesync _q)
+{
+#if 0
+    // compare expected p/n sequence with received
+    unsigned int i;
+    for (i=0; i<_q->preamble_len; i++)
+        printf("  %3u : %12.8f : %12.8f\n", i, _q->preamble_pn[i], _q->preamble_rx[i]);
+#endif
+}
+
+// update instantaneous frequency estimate
+void gmskframesync_update_fi(gmskframesync _q,
+                             float complex _x)
+{
+    // compute differential phase
+    _q->fi_hat = cargf(conjf(_q->x_prime)*_x) * _q->k;
+
+    // update internal state
+    _q->x_prime = _x;
+}
+
+void gmskframesync_execute_detectframe(gmskframesync _q,
+                                       float complex _x)
+{
+    // push sample into pre-demod p/n sequence buffer
+    windowcf_push(_q->buffer, _x);
+
+    // push through pre-demod synchronizer
+    int detected = detector_cccf_correlate(_q->frame_detector,
+                                           _x,
+                                           &_q->tau_hat,
+                                           &_q->dphi_hat,
+                                           &_q->gamma_hat);
+
+    // check if frame has been detected
+    if (detected) {
+        //printf("***** frame detected! tau-hat:%8.4f, dphi-hat:%8.4f, gamma:%8.2f dB\n",
+        //        _q->tau_hat, _q->dphi_hat, 20*log10f(_q->gamma_hat));
+
+        // push buffered samples through synchronizer
+        // NOTE: state will be updated to STATE_RXPREAMBLE internally
+        gmskframesync_pushpn(_q);
+    }
+}
+
+void gmskframesync_execute_rxpreamble(gmskframesync _q,
+                                      float complex _x)
+{
+    // validate input
+    if (_q->preamble_counter == _q->preamble_len) {
+        fprintf(stderr,"warning: gmskframesync_execute_rxpn(), p/n buffer already full!\n");
+        return;
+    }
+
+    // mix signal down
+    float complex y;
+    nco_crcf_mix_down(_q->nco_coarse, _x, &y);
+    nco_crcf_step(_q->nco_coarse);
+
+    // update instantanenous frequency estimate
+    gmskframesync_update_fi(_q, y);
+
+    // update symbol synchronizer
+    float mf_out = 0.0f;
+    int sample_available = gmskframesync_update_symsync(_q, _q->fi_hat, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // save output in p/n symbols buffer
+        _q->preamble_rx[ _q->preamble_counter ] = mf_out / (float)(_q->k);
+
+        // update counter
+        _q->preamble_counter++;
+
+        if (_q->preamble_counter == _q->preamble_len) {
+            gmskframesync_syncpn(_q);
+            _q->state = STATE_RXHEADER;
+        }
+    }
+}
+
+void gmskframesync_execute_rxheader(gmskframesync _q,
+                                    float complex _x)
+{
+    // mix signal down
+    float complex y;
+    nco_crcf_mix_down(_q->nco_coarse, _x, &y);
+    nco_crcf_step(_q->nco_coarse);
+
+    // update instantanenous frequency estimate
+    gmskframesync_update_fi(_q, y);
+
+    // update symbol synchronizer
+    float mf_out = 0.0f;
+    int sample_available = gmskframesync_update_symsync(_q, _q->fi_hat, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // demodulate
+        unsigned char s = mf_out > 0.0f ? 1 : 0;
+
+        // TODO: update evm
+
+        // save bit in buffer
+        _q->header_mod[_q->header_counter] = s;
+
+        // increment header counter
+        _q->header_counter++;
+        if (_q->header_counter == GMSKFRAME_H_SYM) {
+            // decode header
+            gmskframesync_decode_header(_q);
+
+            // invoke callback if header is invalid
+            if (!_q->header_valid && _q->callback != NULL) {
+                // set framestats internals
+                _q->framestats.rssi          = 20*log10f(_q->gamma_hat);
+                _q->framestats.evm           = 0.0f;
+                _q->framestats.framesyms     = NULL;
+                _q->framestats.num_framesyms = 0;
+                _q->framestats.mod_scheme    = LIQUID_MODEM_UNKNOWN;
+                _q->framestats.mod_bps       = 1;
+                _q->framestats.check         = LIQUID_CRC_UNKNOWN;
+                _q->framestats.fec0          = LIQUID_FEC_UNKNOWN;
+                _q->framestats.fec1          = LIQUID_FEC_UNKNOWN;
+
+                // invoke callback method
+                _q->callback(_q->header_dec,
+                             _q->header_valid,
+                             NULL,
+                             0,
+                             0,
+                             _q->framestats,
+                             _q->userdata);
+
+                gmskframesync_reset(_q);
+            }
+
+            // reset if invalid
+            if (!_q->header_valid) {
+                gmskframesync_reset(_q);
+                return;
+            }
+
+            // update state
+            _q->state = STATE_RXPAYLOAD;
+        }
+    }
+}
+
+void gmskframesync_execute_rxpayload(gmskframesync _q,
+                                     float complex _x)
+{
+    // mix signal down
+    float complex y;
+    nco_crcf_mix_down(_q->nco_coarse, _x, &y);
+    nco_crcf_step(_q->nco_coarse);
+
+    // update instantanenous frequency estimate
+    gmskframesync_update_fi(_q, y);
+
+    // update symbol synchronizer
+    float mf_out = 0.0f;
+    int sample_available = gmskframesync_update_symsync(_q, _q->fi_hat, &mf_out);
+
+    // compute output if timeout
+    if (sample_available) {
+        // demodulate
+        unsigned char s = mf_out > 0.0f ? 1 : 0;
+
+        // TODO: update evm
+
+        // save payload
+        _q->payload_byte <<= 1;
+        _q->payload_byte |= s ? 0x01 : 0x00;
+        _q->payload_enc[_q->payload_counter/8] = _q->payload_byte;
+
+        // increment counter
+        _q->payload_counter++;
+
+        if (_q->payload_counter == 8*_q->payload_enc_len) {
+            // decode payload
+            _q->payload_valid = packetizer_decode(_q->p_payload,
+                                                  _q->payload_enc,
+                                                  _q->payload_dec);
+
+            // invoke callback
+            if (_q->callback != NULL) {
+                // set framestats internals
+                _q->framestats.rssi          = 20*log10f(_q->gamma_hat);
+                _q->framestats.evm           = 0.0f;
+                _q->framestats.framesyms     = NULL;
+                _q->framestats.num_framesyms = 0;
+                _q->framestats.mod_scheme    = LIQUID_MODEM_UNKNOWN;
+                _q->framestats.mod_bps       = 1;
+                _q->framestats.check         = _q->check;
+                _q->framestats.fec0          = _q->fec0;
+                _q->framestats.fec1          = _q->fec1;
+
+                // invoke callback method
+                _q->callback(_q->header_dec,
+                             _q->header_valid,
+                             _q->payload_dec,
+                             _q->payload_dec_len,
+                             _q->payload_valid,
+                             _q->framestats,
+                             _q->userdata);
+            }
+
+            // reset frame synchronizer
+            gmskframesync_reset(_q);
+        }
+    }
+}
+
+// decode header and re-configure payload decoder
+void gmskframesync_decode_header(gmskframesync _q)
+{
+    // pack each 1-bit header symbols into 8-bit bytes
+    unsigned int num_written;
+    liquid_pack_bytes(_q->header_mod, GMSKFRAME_H_SYM,
+                      _q->header_enc, GMSKFRAME_H_ENC,
+                      &num_written);
+    assert(num_written==GMSKFRAME_H_ENC);
+
+    // unscramble data
+    unscramble_data(_q->header_enc, GMSKFRAME_H_ENC);
+
+    // run packet decoder
+    _q->header_valid = packetizer_decode(_q->p_header, _q->header_enc, _q->header_dec);
+
+#if DEBUG_GMSKFRAMESYNC_PRINT
+    printf("****** header extracted [%s]\n", _q->header_valid ? "valid" : "INVALID!");
+#endif
+
+    if (!_q->header_valid)
+        return;
+
+    unsigned int n = GMSKFRAME_H_USER;
+
+    // first byte is for expansion/version validation
+    if (_q->header_dec[n+0] != GMSKFRAME_VERSION) {
+        fprintf(stderr,"warning: gmskframesync_decode_header(), invalid framing version\n");
+        _q->header_valid = 0;
+        return;
+    }
+
+    // strip off payload length
+    unsigned int payload_dec_len = (_q->header_dec[n+1] << 8) | (_q->header_dec[n+2]);
+
+    // strip off CRC, forward error-correction schemes
+    //  CRC     : most-significant 3 bits of [n+3]
+    //  fec0    : least-significant 5 bits of [n+3]
+    //  fec1    : least-significant 5 bits of [n+4]
+    unsigned int check = (_q->header_dec[n+3] >> 5 ) & 0x07;
+    unsigned int fec0  = (_q->header_dec[n+3]      ) & 0x1f;
+    unsigned int fec1  = (_q->header_dec[n+4]      ) & 0x1f;
+
+    // validate properties
+    if (check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: gmskframesync_decode_header(), decoded CRC exceeds available\n");
+        check = LIQUID_CRC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+    if (fec0 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: gmskframesync_decode_header(), decoded FEC (inner) exceeds available\n");
+        fec0 = LIQUID_FEC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+    if (fec1 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: gmskframesync_decode_header(), decoded FEC (outer) exceeds available\n");
+        fec1 = LIQUID_FEC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+
+    // print results
+#if DEBUG_GMSKFRAMESYNC_PRINT
+    printf("    properties:\n");
+    printf("      * fec (inner)     :   %s\n", fec_scheme_str[fec0][1]);
+    printf("      * fec (outer)     :   %s\n", fec_scheme_str[fec1][1]);
+    printf("      * CRC scheme      :   %s\n", crc_scheme_str[check][1]);
+    printf("      * payload length  :   %u bytes\n", payload_dec_len);
+#endif
+
+    // configure payload receiver
+    if (_q->header_valid) {
+        // set new packetizer properties
+        _q->payload_dec_len = payload_dec_len;
+        _q->check           = check;
+        _q->fec0            = fec0;
+        _q->fec1            = fec1;
+        
+        // recreate packetizer object
+        _q->p_payload = packetizer_recreate(_q->p_payload,
+                                            _q->payload_dec_len,
+                                            _q->check,
+                                            _q->fec0,
+                                            _q->fec1);
+
+        // re-compute payload encoded message length
+        _q->payload_enc_len = packetizer_get_enc_msg_len(_q->p_payload);
+#if DEBUG_GMSKFRAMESYNC_PRINT
+        printf("      * payload encoded :   %u bytes\n", _q->payload_enc_len);
+#endif
+
+        // re-allocate buffers accordingly
+        _q->payload_enc = (unsigned char*) realloc(_q->payload_enc, _q->payload_enc_len*sizeof(unsigned char));
+        _q->payload_dec = (unsigned char*) realloc(_q->payload_dec, _q->payload_dec_len*sizeof(unsigned char));
+    }
+    //
+}
+
+
+void gmskframesync_debug_enable(gmskframesync _q)
+{
+    // create debugging objects if necessary
+#if DEBUG_GMSKFRAMESYNC
+    if (!_q->debug_objects_created) {
+        _q->debug_x  = windowcf_create(DEBUG_GMSKFRAMESYNC_BUFFER_LEN);
+        _q->debug_fi = windowf_create(DEBUG_GMSKFRAMESYNC_BUFFER_LEN);
+        _q->debug_mf = windowf_create(DEBUG_GMSKFRAMESYNC_BUFFER_LEN);
+        _q->debug_framesyms  = windowf_create(DEBUG_GMSKFRAMESYNC_BUFFER_LEN);
+    }
+    
+    // set debugging flags
+    _q->debug_enabled = 1;
+    _q->debug_objects_created = 1;
+#else
+    fprintf(stderr,"gmskframesync_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+void gmskframesync_debug_disable(gmskframesync _q)
+{
+#if DEBUG_GMSKFRAMESYNC
+    _q->debug_enabled = 0;
+#else
+    fprintf(stderr,"gmskframesync_debug_disable(): compile-time debugging disabled\n");
+#endif
+}
+
+void gmskframesync_debug_print(gmskframesync _q,
+                               const char *  _filename)
+{
+#if DEBUG_GMSKFRAMESYNC
+    if (!_q->debug_objects_created) {
+        fprintf(stderr,"error: gmskframe_debug_print(), debugging objects don't exist; enable debugging first\n");
+        return;
+    }
+
+    FILE* fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr, "error: gmskframesync_debug_print(), could not open '%s' for writing\n", _filename);
+        return;
+    }
+    fprintf(fid,"%% %s: auto-generated file", _filename);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n\n");
+
+    fprintf(fid,"num_samples = %u;\n", DEBUG_GMSKFRAMESYNC_BUFFER_LEN);
+    fprintf(fid,"t = 0:(num_samples-1);\n");
+    unsigned int i;
+    float complex * rc;
+
+    // write x
+    fprintf(fid,"x = zeros(1,num_samples);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_GMSKFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:length(x),real(x), 1:length(x),imag(x));\n");
+    fprintf(fid,"ylabel('received signal, x');\n");
+    fprintf(fid,"\n\n");
+
+    // write instantaneous frequency
+    float * r;
+    fprintf(fid,"fi = zeros(1,num_samples);\n");
+    windowf_read(_q->debug_fi, &r);
+    for (i=0; i<DEBUG_GMSKFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"fi(%4u) = %12.4e;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:length(fi),fi);\n");
+    fprintf(fid,"ylabel('Inst. Freq.');\n");
+    fprintf(fid,"\n\n");
+
+    // write matched filter output
+    fprintf(fid,"mf = zeros(1,num_samples);\n");
+    windowf_read(_q->debug_mf, &r);
+    for (i=0; i<DEBUG_GMSKFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"mf(%4u) = %12.4e;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(1:length(mf),mf);\n");
+    fprintf(fid,"ylabel('MF output');\n");
+    fprintf(fid,"\n\n");
+
+#if 0
+    // write framesyms
+    fprintf(fid,"framesyms = zeros(1,num_samples);\n");
+    windowf_read(_q->debug_framesyms, &r);
+    for (i=0; i<DEBUG_GMSKFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"framesyms(%4u) = %12.4e;\n", i+1, r[i]);
+    fprintf(fid,"\n\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,framesyms,'x')\n");
+    fprintf(fid,"xlabel('time (symbol index)');\n");
+    fprintf(fid,"ylabel('GMSK demodulator output');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"\n\n");
+#endif
+
+    fclose(fid);
+
+    printf("gmskframesync/debug: results written to '%s'\n", _filename);
+#else
+    fprintf(stderr,"gmskframesync_debug_print(): compile-time debugging disabled\n");
+#endif
+
+}
diff --git a/src/framing/src/msource.c b/src/framing/src/msource.c
new file mode 100644
index 0000000..bf00e71
--- /dev/null
+++ b/src/framing/src/msource.c
@@ -0,0 +1,547 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic source generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+// forward declaration of internal single source object and methods
+typedef struct QSOURCE(_s) * QSOURCE();
+
+// internal structure (single source)
+struct QSOURCE(_s) {
+    int id; // unique id
+
+    union {
+        // tone
+        struct {
+            int x;
+        } tone;
+
+        // wide-band noise
+        struct {
+            IIRFILT() filter;
+        } noise;
+
+        // linear modulation
+        struct {
+            SYMSTREAM() symstream;
+        } linmod;
+    } source;
+    
+    enum {
+        QSOURCE_TONE,
+        QSOURCE_NOISE,
+        QSOURCE_MODEM,
+    } type;
+
+    nco_crcf mixer;
+    float    gain;
+    int      enabled;
+};
+
+QSOURCE() QSOURCE(_create_tone)(int _id);
+
+QSOURCE() QSOURCE(_create_noise)(int _id, float _bandwidth);
+
+QSOURCE() QSOURCE(_create_modem)(int          _id,
+                                 int          _ms,
+                                 unsigned int _k,
+                                 unsigned int _m,
+                                 float        _beta);
+
+void QSOURCE(_destroy)(QSOURCE() _q);
+
+void QSOURCE(_print)(QSOURCE() _q);
+
+void QSOURCE(_reset)(QSOURCE() _q);
+
+void QSOURCE(_enable)(QSOURCE() _q);
+void QSOURCE(_disable)(QSOURCE() _q);
+
+void QSOURCE(_set_gain)(QSOURCE() _q,
+                        float     _gain_dB);
+
+void QSOURCE(_set_frequency)(QSOURCE() _q,
+                             float     _dphi);
+
+void QSOURCE(_gen_sample)(QSOURCE() _q,
+                          TO *      _v);
+
+// internal structure
+struct MSOURCE(_s)
+{
+    QSOURCE() *  sources;
+    unsigned int num_sources;
+
+    int id_counter;
+};
+
+//
+// internal methods
+//
+
+// add source to list
+int MSOURCE(_add_source)(MSOURCE() _q,
+                         QSOURCE() _s);
+
+// create msource object with linear modulation
+MSOURCE() MSOURCE(_create)(void)
+{
+    // allocate memory for main object
+    MSOURCE() q = (MSOURCE()) malloc( sizeof(struct MSOURCE(_s)) );
+
+    //
+    q->sources = NULL;
+    q->num_sources = 0;
+    q->id_counter  = 0;
+
+    // reset and return main object
+    MSOURCE(_reset)(q);
+    return q;
+}
+
+// destroy msource object, freeing all internal memory
+void MSOURCE(_destroy)(MSOURCE() _q)
+{
+    // destroy internal objects
+    unsigned int i;
+    for (i=0; i<_q->num_sources; i++)
+        QSOURCE(_destroy)(_q->sources[i]);
+
+    // free list of sources
+    free(_q->sources);
+
+    // free main object
+    free(_q);
+}
+
+// reset msource internal state
+void MSOURCE(_reset)(MSOURCE() _q)
+{
+}
+
+// print
+void MSOURCE(_print)(MSOURCE() _q)
+{
+    printf("msource%s:\n", EXTENSION);
+    unsigned int i;
+    for (i=0; i<_q->num_sources; i++)
+        QSOURCE(_print)(_q->sources[i]);
+}
+
+// add tone source
+int MSOURCE(_add_tone)(MSOURCE() _q)
+{
+    int id = _q->id_counter;
+    _q->id_counter++;
+    QSOURCE() s = QSOURCE(_create_tone)(id);
+    return MSOURCE(_add_source)(_q, s);
+}
+
+// add noise source
+int MSOURCE(_add_noise)(MSOURCE() _q,
+                        float     _bandwidth)
+{
+    // validate input
+    if (_bandwidth <= 0.0f || _bandwidth > 1.0f) {
+        fprintf(stderr,"error: msource%s_add_noise(), noise bandwidth must be in (0,1.0]\n", EXTENSION);
+        exit(1);
+    } else if (_bandwidth >= 0.9995f) {
+        _bandwidth = 0.9995f;
+    }
+
+    int id = _q->id_counter;
+    _q->id_counter++;
+    QSOURCE() s = QSOURCE(_create_noise)(id, _bandwidth);
+    return MSOURCE(_add_source)(_q, s);
+}
+
+// add modem source
+int MSOURCE(_add_modem)(MSOURCE()    _q,
+                        int          _ms,
+                        unsigned int _k,
+                        unsigned int _m,
+                        float        _beta)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: msource%s_create(), samples/symbol must be at least 2\n", EXTENSION);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: msource%s_create(), filter delay must be greater than zero\n", EXTENSION);
+        exit(1);
+    } else if (_beta <= 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: msource%s_create(), filter excess bandwidth must be in (0,1]\n", EXTENSION);
+        exit(1);
+    } else if (_ms == LIQUID_MODEM_UNKNOWN || _ms >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: msource%s_create(), invalid modulation scheme\n", EXTENSION);
+        exit(1);
+    }
+
+    int id = _q->id_counter;
+    _q->id_counter++;
+    QSOURCE() s = QSOURCE(_create_modem)(id, _ms, _k, _m, _beta);
+    return MSOURCE(_add_source)(_q, s);
+}
+
+// remove signal
+void MSOURCE(_remove)(MSOURCE() _q,
+                      int       _id)
+{
+    // find source object matching id
+    unsigned int i;
+    int id_found = 0;
+    for (i=0; i<_q->num_sources; i++) {
+        if (_q->sources[i]->id == _id) {
+            id_found = 1;
+            break;
+        }
+    }
+
+    // check to see if id was found
+    if (!id_found) {
+        fprintf(stderr,"error: qsource%s_remove(), signal id (%d) not found\n",
+                EXTENSION, _id);
+        exit(1);
+    }
+
+    // delete source
+    //printf("deleting source with id %d (requested %d)\n", _q->sources[i]->id, _id);
+    QSOURCE(_destroy)(_q->sources[i]);
+
+    //
+    _q->num_sources--;
+
+    // shift sources down
+    for (; i<_q->num_sources; i++)
+        _q->sources[i] = _q->sources[i+1];
+}
+
+// enable/disable signal
+void MSOURCE(_enable)(MSOURCE() _q,
+                      int       _id)
+{
+    // validate input
+    if (_id > _q->num_sources) {
+        fprintf(stderr,"error: qsource%s_enable(), signal id (%d) out of range (%u)\n",
+                EXTENSION, _id, _q->num_sources);
+        exit(1);
+    }
+
+    // set source gain
+    QSOURCE(_enable)(_q->sources[_id]);
+}
+
+void MSOURCE(_disable)(MSOURCE() _q,
+                       int       _id)
+{
+    // validate input
+    if (_id > _q->num_sources) {
+        fprintf(stderr,"error: qsource%s_disable(), signal id (%d) out of range (%u)\n",
+                EXTENSION, _id, _q->num_sources);
+        exit(1);
+    }
+
+    // set source gain
+    QSOURCE(_disable)(_q->sources[_id]);
+}
+
+// set signal gain
+//  _q      :   msource object
+//  _id     :   source id
+//  _gain_dB:   signal gain in dB
+void MSOURCE(_set_gain)(MSOURCE() _q,
+                        int       _id,
+                        float     _gain_dB)
+{
+    // validate input
+    if (_id > _q->num_sources) {
+        fprintf(stderr,"error: qsource%s_set_gain(), signal id (%d) out of range (%u)\n",
+                EXTENSION, _id, _q->num_sources);
+        exit(1);
+    }
+
+    // set source gain
+    QSOURCE(_set_gain)(_q->sources[_id], _gain_dB);
+}
+
+// set carrier offset to signal
+//  _q      :   msource object
+//  _id     :   source id
+//  _fc     :   carrier offset, fc in [-0.5,0.5]
+void MSOURCE(_set_frequency)(MSOURCE() _q,
+                             int       _id,
+                             float     _dphi)
+{
+    // validate input
+    if (_id > _q->num_sources) {
+        fprintf(stderr,"error: qsource%s_set_frequency(), signal id (%d) out of range (%u)\n",
+                EXTENSION, _id, _q->num_sources);
+        exit(1);
+    
+    }
+    // set source frequency
+    QSOURCE(_set_frequency)(_q->sources[_id], _dphi);
+}
+
+// write block of samples to output buffer
+//  _q      : synchronizer object
+//  _buf    : output buffer [size: _buf_len x 1]
+//  _buf_len: output buffer size
+void MSOURCE(_write_samples)(MSOURCE()    _q,
+                             TO *         _buf,
+                             unsigned int _buf_len)
+{
+    TO sample;
+    TO accumulation;
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_buf_len; i++) {
+        accumulation = 0;
+
+        for (j=0; j<_q->num_sources; j++) {
+            QSOURCE(_gen_sample)(_q->sources[j], &sample);
+            accumulation += sample;
+        }
+
+        _buf[i] = accumulation;
+    }
+}
+
+//
+// internal msource methods
+//
+
+// add source to list
+int MSOURCE(_add_source)(MSOURCE() _q,
+                         QSOURCE() _s)
+{
+    if (_s == NULL)
+        return -1;
+
+    // reallocate
+    if (_q->num_sources == 0) {
+        _q->sources = (QSOURCE()*) malloc(sizeof(QSOURCE()));
+    } else {
+        _q->sources = (QSOURCE()*) realloc(_q->sources,
+                                           (_q->num_sources+1)*sizeof(QSOURCE()));
+    }
+
+    // append new object to end of list
+    _q->sources[_q->num_sources] = _s;
+
+    //
+    _q->num_sources++;
+
+    //
+    return _q->num_sources-1;
+}
+
+// get source by id
+QSOURCE() MSOURCE(_get_source)(MSOURCE() _q,
+                               int       _id)
+{
+    unsigned int i;
+    for (i=0; i<_q->num_sources; i++) {
+        if (_q->sources[i]->id == _id)
+            return _q->sources[i];
+    }
+
+    return NULL;
+}
+
+
+//
+// internal qsource
+//
+QSOURCE() QSOURCE(_create_tone)(int _id)
+{
+    // allocate memory for main object
+    QSOURCE() q = (QSOURCE()) malloc( sizeof(struct QSOURCE(_s)) );
+
+    q->id   = _id;
+    q->type = QSOURCE_TONE;
+
+    q->mixer   = NCO(_create)(LIQUID_VCO);
+    q->gain    = 1;
+    q->enabled = 1;
+
+    // reset and return main object
+    QSOURCE(_reset)(q);
+    return q;
+}
+
+QSOURCE() QSOURCE(_create_noise)(int   _id,
+                                 float _bandwidth)
+{
+    // TODO: validate input
+
+    // allocate memory for main object
+    QSOURCE() q = (QSOURCE()) malloc( sizeof(struct QSOURCE(_s)) );
+
+    q->id   = _id;
+    q->type = QSOURCE_NOISE;
+
+    unsigned int order = 7;
+    q->source.noise.filter = IIRFILT(_create_prototype)(LIQUID_IIRDES_ELLIP,
+                                                        LIQUID_IIRDES_LOWPASS,
+                                                        LIQUID_IIRDES_SOS,
+                                                        order,
+                                                        0.5*_bandwidth, 0.0f,
+                                                        0.1f, 80.0f);
+
+    q->mixer   = NCO(_create)(LIQUID_VCO);
+    q->gain    = 1;
+    q->enabled = 1;
+
+    // reset and return main object
+    QSOURCE(_reset)(q);
+    return q;
+}
+
+QSOURCE() QSOURCE(_create_modem)(int          _id,
+                                 int          _ms,
+                                 unsigned int _k,
+                                 unsigned int _m,
+                                 float        _beta)
+{
+    // allocate memory for main object
+    QSOURCE() q = (QSOURCE()) malloc( sizeof(struct QSOURCE(_s)) );
+
+    q->id   = _id;
+    q->type = QSOURCE_MODEM;
+
+    q->source.linmod.symstream=SYMSTREAM(_create_linear)(LIQUID_FIRFILT_ARKAISER,_k,_m,_beta,_ms);
+
+    q->mixer   = NCO(_create)(LIQUID_VCO);
+    q->gain    = 1;
+    q->enabled = 1;
+
+    // reset and return main object
+    QSOURCE(_reset)(q);
+    return q;
+}
+
+void QSOURCE(_print)(QSOURCE() _q)
+{
+    printf("  qsource%s[%3d] : ", EXTENSION, _q->id);
+    // print type-specific parameters
+    switch (_q->type) {
+    case QSOURCE_TONE:  printf("tone\n");   break;
+    case QSOURCE_NOISE: printf("noise\n");  break;
+    case QSOURCE_MODEM: printf("modem\n");  break;
+    default:
+        fprintf(stderr,"error: qsource%s_print(), internal logic error\n", EXTENSION);
+        exit(1);
+    }
+}
+
+void QSOURCE(_destroy)(QSOURCE() _q)
+{
+    // free internal type-specific objects
+    switch (_q->type) {
+    case QSOURCE_TONE: break;
+    case QSOURCE_NOISE:
+        IIRFILT(_destroy)(_q->source.noise.filter);
+        break;
+    case QSOURCE_MODEM:
+        SYMSTREAM(_destroy)(_q->source.linmod.symstream);
+        break;
+    default:
+        fprintf(stderr,"error: qsource%s_destroy(), internal logic error\n", EXTENSION);
+        exit(1);
+    }
+
+    // destroy mixer object
+    NCO(_destroy)(_q->mixer);
+
+    // free main object memory
+    free(_q);
+}
+
+void QSOURCE(_reset)(QSOURCE() _q)
+{
+}
+
+void QSOURCE(_enable)(QSOURCE() _q)
+{
+    _q->enabled = 1;
+}
+
+void QSOURCE(_disable)(QSOURCE() _q)
+{
+    _q->enabled = 0;
+}
+
+void QSOURCE(_set_gain)(QSOURCE() _q,
+                        float     _gain_dB)
+{
+    // convert from dB
+    _q->gain = powf(10.0f, _gain_dB/20.0f);
+}
+
+void QSOURCE(_set_frequency)(QSOURCE() _q,
+                             float     _dphi)
+{
+    NCO(_set_frequency)(_q->mixer, _dphi);
+}
+
+void QSOURCE(_gen_sample)(QSOURCE() _q,
+                          TO *      _v)
+{
+    TO sample;
+
+    // free internal type-specific objects
+    switch (_q->type) {
+    case QSOURCE_TONE:
+        sample = 1.0f;
+        break;
+    case QSOURCE_NOISE:
+        IIRFILT(_execute)(_q->source.noise.filter, (randnf() + _Complex_I*randnf())*M_SQRT1_2, &sample);
+        break;
+    case QSOURCE_MODEM:
+        SYMSTREAM(_write_samples)(_q->source.linmod.symstream, &sample, 1);
+        break;
+    default:
+        fprintf(stderr,"error: qsource%s_gen_sample(), internal logic error\n", EXTENSION);
+        exit(1);
+    }
+    
+    if (!_q->enabled)
+        sample = 0.0f;
+
+    // apply gain
+    sample *= _q->gain;
+
+    // mix sample up
+    NCO(_mix_up)(_q->mixer, sample, _v);
+
+    // step mixer
+    NCO(_step)(_q->mixer);
+}
+
diff --git a/src/framing/src/msourcecf.c b/src/framing/src/msourcecf.c
new file mode 100644
index 0000000..f5e51a6
--- /dev/null
+++ b/src/framing/src/msourcecf.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION           "cf"
+
+#define TO                  float complex   // output type
+#define T                   float           // primitive type
+
+#define TO_COMPLEX          1
+#define T_COMPLEX           0
+
+// object references
+#define QSOURCE(name)       LIQUID_CONCAT(qsourcecf,name)
+#define MSOURCE(name)       LIQUID_CONCAT(msourcecf,name)
+
+#define IIRFILT(name)       LIQUID_CONCAT(iirfilt_crcf,name)
+#define NCO(name)           LIQUID_CONCAT(nco_crcf,name)
+#define SYMSTREAM(name)     LIQUID_CONCAT(symstreamcf,name)
+
+// source files
+#include "msource.c"
+
diff --git a/src/framing/src/ofdmflexframegen.c b/src/framing/src/ofdmflexframegen.c
new file mode 100644
index 0000000..58eb0d5
--- /dev/null
+++ b/src/framing/src/ofdmflexframegen.c
@@ -0,0 +1,682 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// ofdmflexframegen.c
+//
+// OFDM flexible frame generator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_OFDMFLEXFRAMEGEN            0
+
+// reconfigure internal buffers, objects, etc.
+void ofdmflexframegen_reconfigure(ofdmflexframegen _q);
+
+// encode header
+void ofdmflexframegen_encode_header(ofdmflexframegen _q);
+
+// modulate header
+void ofdmflexframegen_modulate_header(ofdmflexframegen _q);
+
+// write first S0 symbol
+void ofdmflexframegen_write_S0a(ofdmflexframegen _q,
+                                float complex * _buffer);
+
+// write second S0 symbol
+void ofdmflexframegen_write_S0b(ofdmflexframegen _q,
+                                float complex * _buffer);
+
+// write S1 symbol
+void ofdmflexframegen_write_S1(ofdmflexframegen _q,
+                               float complex * _buffer);
+
+// write header symbol
+void ofdmflexframegen_write_header(ofdmflexframegen _q,
+                                   float complex * _buffer);
+
+// write payload symbol
+void ofdmflexframegen_write_payload(ofdmflexframegen _q,
+                                    float complex * _buffer);
+
+// default ofdmflexframegen properties
+static ofdmflexframegenprops_s ofdmflexframegenprops_default = {
+    LIQUID_CRC_32,      // check
+    LIQUID_FEC_NONE,    // fec0
+    LIQUID_FEC_NONE,    // fec1
+    LIQUID_MODEM_QPSK,  // mod_scheme
+    //64                // block_size
+};
+
+void ofdmflexframegenprops_init_default(ofdmflexframegenprops_s * _props)
+{
+    memmove(_props, &ofdmflexframegenprops_default, sizeof(ofdmflexframegenprops_s));
+}
+
+struct ofdmflexframegen_s {
+    unsigned int M;         // number of subcarriers
+    unsigned int cp_len;    // cyclic prefix length
+    unsigned int taper_len; // taper length
+    unsigned char * p;      // subcarrier allocation (null, pilot, data)
+
+    // constants
+    unsigned int M_null;    // number of null subcarriers
+    unsigned int M_pilot;   // number of pilot subcarriers
+    unsigned int M_data;    // number of data subcarriers
+    unsigned int M_S0;      // number of enabled subcarriers in S0
+    unsigned int M_S1;      // number of enabled subcarriers in S1
+
+    // buffers
+    float complex * X;      // frequency-domain buffer
+
+    // internal low-level objects
+    ofdmframegen fg;        // frame generator object
+
+    // options/derived lengths
+    unsigned int num_symbols_header;    // number of header OFDM symbols
+    unsigned int num_symbols_payload;   // number of payload OFDM symbols
+
+    // header
+    modem mod_header;                   // header modulator
+    packetizer p_header;                // header packetizer
+    unsigned char header[OFDMFLEXFRAME_H_DEC];      // header data (uncoded)
+    unsigned char header_enc[OFDMFLEXFRAME_H_ENC];  // header data (encoded)
+    unsigned char header_mod[OFDMFLEXFRAME_H_SYM];  // header symbols
+
+    // payload
+    packetizer p_payload;               // payload packetizer
+    unsigned int payload_dec_len;       // payload length (num un-encoded bytes)
+    modem mod_payload;                  // payload modulator
+    unsigned char * payload_enc;        // payload data (encoded bytes)
+    unsigned char * payload_mod;        // payload data (modulated symbols)
+    unsigned int payload_enc_len;       // length of encoded payload
+    unsigned int payload_mod_len;       // number of modulated symbols in payload
+
+    // counters/states
+    unsigned int symbol_number;         // output symbol number
+    enum {
+        OFDMFLEXFRAMEGEN_STATE_S0a=0,   // write S0 symbol (first)
+        OFDMFLEXFRAMEGEN_STATE_S0b,     // write S0 symbol (second)
+        OFDMFLEXFRAMEGEN_STATE_S1,      // write S1 symbol
+        OFDMFLEXFRAMEGEN_STATE_HEADER,  // write header symbols
+        OFDMFLEXFRAMEGEN_STATE_PAYLOAD  // write payload symbols
+    } state;
+    int frame_assembled;                // frame assembled flag
+    int frame_complete;                 // frame completed flag
+    unsigned int header_symbol_index;   //
+    unsigned int payload_symbol_index;  //
+
+    // properties
+    ofdmflexframegenprops_s props;
+};
+
+// create OFDM flexible framing generator object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+//  _fgprops    :   frame properties (modulation scheme, etc.)
+ofdmflexframegen ofdmflexframegen_create(unsigned int              _M,
+                                         unsigned int              _cp_len,
+                                         unsigned int              _taper_len,
+                                         unsigned char *           _p,
+                                         ofdmflexframegenprops_s * _fgprops)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: ofdmflexframegen_create(), number of subcarriers must be at least 2\n");
+        exit(1);
+    } else if (_M % 2) {
+        fprintf(stderr,"error: ofdmflexframegen_create(), number of subcarriers must be even\n");
+        exit(1);
+    }
+
+    ofdmflexframegen q = (ofdmflexframegen) malloc(sizeof(struct ofdmflexframegen_s));
+    q->M         = _M;          // number of subcarriers
+    q->cp_len    = _cp_len;     // cyclic prefix length
+    q->taper_len = _taper_len;  // taper length
+
+    // allocate memory for transform buffers
+    q->X = (float complex*) malloc((q->M)*sizeof(float complex));
+
+    // allocate memory for subcarrier allocation IDs
+    q->p = (unsigned char*) malloc((q->M)*sizeof(unsigned char));
+    if (_p == NULL) {
+        // initialize default subcarrier allocation
+        ofdmframe_init_default_sctype(q->M, q->p);
+    } else {
+        // copy user-defined subcarrier allocation
+        memmove(q->p, _p, q->M*sizeof(unsigned char));
+    }
+
+    // validate and count subcarrier allocation
+    ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data);
+
+    // create internal OFDM frame generator object
+    q->fg = ofdmframegen_create(q->M, q->cp_len, q->taper_len, q->p);
+
+    // create header objects
+    q->mod_header = modem_create(OFDMFLEXFRAME_H_MOD);
+    q->p_header   = packetizer_create(OFDMFLEXFRAME_H_DEC,
+                                      OFDMFLEXFRAME_H_CRC,
+                                      OFDMFLEXFRAME_H_FEC,
+                                      LIQUID_FEC_NONE);
+    assert(packetizer_get_enc_msg_len(q->p_header)==OFDMFLEXFRAME_H_ENC);
+
+    // compute number of header symbols
+    div_t d = div(OFDMFLEXFRAME_H_SYM, q->M_data);
+    q->num_symbols_header = d.quot + (d.rem ? 1 : 0);
+
+    // initial memory allocation for payload
+    q->payload_dec_len = 1;
+    q->p_payload = packetizer_create(q->payload_dec_len,
+                                     LIQUID_CRC_NONE,
+                                     LIQUID_FEC_NONE,
+                                     LIQUID_FEC_NONE);
+    q->payload_enc_len = packetizer_get_enc_msg_len(q->p_payload);
+    q->payload_enc = (unsigned char*) malloc(q->payload_enc_len*sizeof(unsigned char));
+
+    q->payload_mod_len = 1;
+    q->payload_mod = (unsigned char*) malloc(q->payload_mod_len*sizeof(unsigned char));
+
+    // create payload modem (initially QPSK, overridden by properties)
+    q->mod_payload = modem_create(LIQUID_MODEM_QPSK);
+
+    // initialize properties
+    ofdmflexframegen_setprops(q, _fgprops);
+
+    // reset
+    ofdmflexframegen_reset(q);
+
+    // return pointer to main object
+    return q;
+}
+
+void ofdmflexframegen_destroy(ofdmflexframegen _q)
+{
+    // destroy internal objects
+    ofdmframegen_destroy(_q->fg);       // OFDM frame generator
+    packetizer_destroy(_q->p_header);   // header packetizer
+    modem_destroy(_q->mod_header);      // header modulator
+    packetizer_destroy(_q->p_payload);  // payload packetizer
+    modem_destroy(_q->mod_payload);     // payload modulator
+
+    // free buffers/arrays
+    free(_q->payload_enc);              // encoded payload bytes
+    free(_q->payload_mod);              // modulated payload symbols
+    free(_q->X);                        // frequency-domain buffer
+    free(_q->p);                        // subcarrier allocation
+
+    // free main object memory
+    free(_q);
+}
+
+void ofdmflexframegen_reset(ofdmflexframegen _q)
+{
+    // reset symbol counter and state
+    _q->symbol_number = 0;
+    _q->state = OFDMFLEXFRAMEGEN_STATE_S0a;
+    _q->frame_assembled = 0;
+    _q->frame_complete = 0;
+    _q->header_symbol_index = 0;
+    _q->payload_symbol_index = 0;
+
+    // reset internal OFDM frame generator object
+    // NOTE: this is important for appropriately setting the pilot phases
+    ofdmframegen_reset(_q->fg);
+}
+
+// is frame assembled?
+int ofdmflexframegen_is_assembled(ofdmflexframegen _q)
+{
+    return _q->frame_assembled;
+}
+
+void ofdmflexframegen_print(ofdmflexframegen _q)
+{
+    printf("ofdmflexframegen:\n");
+    printf("    num subcarriers     :   %-u\n", _q->M);
+    printf("      * NULL            :   %-u\n", _q->M_null);
+    printf("      * pilot           :   %-u\n", _q->M_pilot);
+    printf("      * data            :   %-u\n", _q->M_data);
+    printf("    cyclic prefix len   :   %-u\n", _q->cp_len);
+    printf("    taper len           :   %-u\n", _q->taper_len);
+    printf("    properties:\n");
+    printf("      * mod scheme      :   %s\n", modulation_types[_q->props.mod_scheme].fullname);
+    printf("      * fec (inner)     :   %s\n", fec_scheme_str[_q->props.fec0][1]);
+    printf("      * fec (outer)     :   %s\n", fec_scheme_str[_q->props.fec1][1]);
+    printf("      * CRC scheme      :   %s\n", crc_scheme_str[_q->props.check][1]);
+    printf("    frame assembled     :   %s\n", _q->frame_assembled ? "yes" : "no");
+    if (_q->frame_assembled) {
+        printf("    payload:\n");
+        printf("      * decoded bytes   :   %-u\n", _q->payload_dec_len);
+        printf("      * encoded bytes   :   %-u\n", _q->payload_enc_len);
+        printf("      * modulated syms  :   %-u\n", _q->payload_mod_len);
+        printf("    total OFDM symbols  :   %-u\n", ofdmflexframegen_getframelen(_q));
+        printf("      * S0 symbols      :   %-u @ %u\n", 2, _q->M+_q->cp_len);
+        printf("      * S1 symbols      :   %-u @ %u\n", 1, _q->M+_q->cp_len);
+        printf("      * header symbols  :   %-u @ %u\n", _q->num_symbols_header,  _q->M+_q->cp_len);
+        printf("      * payload symbols :   %-u @ %u\n", _q->num_symbols_payload, _q->M+_q->cp_len);
+
+        // compute asymptotic spectral efficiency
+        unsigned int num_bits = 8*_q->payload_dec_len;
+        unsigned int num_samples = (_q->M+_q->cp_len)*(3 + _q->num_symbols_header + _q->num_symbols_payload);
+        printf("    spectral efficiency :   %-6.4f b/s/Hz\n", (float)num_bits / (float)num_samples);
+    }
+}
+
+// get ofdmflexframegen properties
+//  _q      :   frame generator object
+//  _props  :   frame generator properties structure pointer
+void ofdmflexframegen_getprops(ofdmflexframegen _q,
+                               ofdmflexframegenprops_s * _props)
+{
+    // copy properties structure to output pointer
+    memmove(_props, &_q->props, sizeof(ofdmflexframegenprops_s));
+}
+
+void ofdmflexframegen_setprops(ofdmflexframegen _q,
+                               ofdmflexframegenprops_s * _props)
+{
+    // if properties object is NULL, initialize with defaults
+    if (_props == NULL) {
+        ofdmflexframegen_setprops(_q, &ofdmflexframegenprops_default);
+        return;
+    }
+
+    // validate input
+    if (_props->check == LIQUID_CRC_UNKNOWN || _props->check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr, "error: ofdmflexframegen_setprops(), invalid/unsupported CRC scheme\n");
+        exit(1);
+    } else if (_props->fec0 == LIQUID_FEC_UNKNOWN || _props->fec1 == LIQUID_FEC_UNKNOWN) {
+        fprintf(stderr, "error: ofdmflexframegen_setprops(), invalid/unsupported FEC scheme\n");
+        exit(1);
+    } else if (_props->mod_scheme == LIQUID_MODEM_UNKNOWN ) {
+        fprintf(stderr, "error: ofdmflexframegen_setprops(), invalid/unsupported modulation scheme\n");
+        exit(1);
+    }
+
+    // TODO : determine if re-configuration is necessary
+
+    // copy properties to internal structure
+    memmove(&_q->props, _props, sizeof(ofdmflexframegenprops_s));
+
+    // reconfigure internal buffers, objects, etc.
+    ofdmflexframegen_reconfigure(_q);
+}
+
+// get length of frame (symbols)
+//  _q              :   OFDM frame generator object
+unsigned int ofdmflexframegen_getframelen(ofdmflexframegen _q)
+{
+    // number of S0 symbols (2)
+    // number of S1 symbols (1)
+    // number of header symbols
+    // number of payload symbols
+    return  2 + // S0 symbols
+            1 + // S1 symbol
+            _q->num_symbols_header +
+            _q->num_symbols_payload;
+}
+
+// assemble a frame from an array of data
+//  _q              :   OFDM frame generator object
+//  _header         :   frame header
+//  _payload        :   payload data [size: _payload_len x 1]
+//  _payload_len    :   payload data length
+void ofdmflexframegen_assemble(ofdmflexframegen _q,
+                               const unsigned char *  _header,
+                               const unsigned char *  _payload,
+                               unsigned int     _payload_len)
+{
+    // check payload length and reconfigure if necessary
+    if (_payload_len != _q->payload_dec_len) {
+        _q->payload_dec_len = _payload_len;
+        ofdmflexframegen_reconfigure(_q);
+    }
+
+    // set assembled flag
+    _q->frame_assembled = 1;
+
+    // copy user-defined header data
+    memmove(_q->header, _header, OFDMFLEXFRAME_H_USER*sizeof(unsigned char));
+
+    // encode full header
+    ofdmflexframegen_encode_header(_q);
+
+    // modulate header
+    ofdmflexframegen_modulate_header(_q);
+
+    // encode payload
+    packetizer_encode(_q->p_payload, _payload, _q->payload_enc);
+
+    // 
+    // pack modem symbols
+    //
+
+    // clear payload
+    memset(_q->payload_mod, 0x00, _q->payload_mod_len);
+
+    // repack 8-bit payload bytes into 'bps'-bit payload symbols
+    unsigned int bps = modulation_types[_q->props.mod_scheme].bps;
+    unsigned int num_written;
+    liquid_repack_bytes(_q->payload_enc,  8,  _q->payload_enc_len,
+                        _q->payload_mod, bps, _q->payload_mod_len,
+                        &num_written);
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("wrote %u symbols (expected %u)\n", num_written, _q->payload_mod_len);
+#endif
+}
+
+// write symbols of assembled frame
+//  _q              :   OFDM frame generator object
+//  _buffer         :   output buffer [size: N+cp_len x 1]
+int ofdmflexframegen_writesymbol(ofdmflexframegen       _q,
+                                 liquid_float_complex * _buffer)
+{
+    // check if frame is actually assembled
+    if ( !_q->frame_assembled ) {
+        fprintf(stderr,"warning: ofdmflexframegen_writesymbol(), frame not assembled\n");
+        return 1;
+    }
+
+    // increment symbol counter
+    _q->symbol_number++;
+    //printf("writesymbol(): %u\n", _q->symbol_number);
+
+    switch (_q->state) {
+    case OFDMFLEXFRAMEGEN_STATE_S0a:
+        // write S0 symbol (first)
+        ofdmflexframegen_write_S0a(_q, _buffer);
+        break;
+
+    case OFDMFLEXFRAMEGEN_STATE_S0b:
+        // write S0 symbol (second)
+        ofdmflexframegen_write_S0b(_q, _buffer);
+        break;
+
+    case OFDMFLEXFRAMEGEN_STATE_S1:
+        // write S1 symbols
+        ofdmflexframegen_write_S1(_q, _buffer);
+        break;
+
+    case OFDMFLEXFRAMEGEN_STATE_HEADER:
+        // write header symbols
+        ofdmflexframegen_write_header(_q, _buffer);
+        break;
+
+    case OFDMFLEXFRAMEGEN_STATE_PAYLOAD:
+        // write payload symbols
+        ofdmflexframegen_write_payload(_q, _buffer);
+        break;
+
+    default:
+        fprintf(stderr,"error: ofdmflexframegen_writesymbol(), unknown/unsupported internal state\n");
+        exit(1);
+    }
+
+    if (_q->frame_complete) {
+        // reset framing object
+#if DEBUG_OFDMFLEXFRAMEGEN
+        printf(" ...resetting...\n");
+#endif
+        ofdmflexframegen_reset(_q);
+        return 1;
+    }
+
+    return 0;
+}
+
+
+//
+// internal
+//
+
+// reconfigure internal buffers, objects, etc.
+void ofdmflexframegen_reconfigure(ofdmflexframegen _q)
+{
+    // re-create payload packetizer
+    _q->p_payload = packetizer_recreate(_q->p_payload,
+                                        _q->payload_dec_len,
+                                        _q->props.check,
+                                        _q->props.fec0,
+                                        _q->props.fec1);
+
+    // re-allocate memory for encoded message
+    _q->payload_enc_len = packetizer_get_enc_msg_len(_q->p_payload);
+    _q->payload_enc = (unsigned char*) realloc(_q->payload_enc,
+                                               _q->payload_enc_len*sizeof(unsigned char));
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf(">>>> payload : %u (%u encoded)\n", _q->props.payload_len, _q->payload_enc_len);
+#endif
+
+    // re-create modem
+    // TODO : only do this if necessary
+    _q->mod_payload = modem_recreate(_q->mod_payload, _q->props.mod_scheme);
+
+    // re-allocate memory for payload modem symbols
+    unsigned int bps = modulation_types[_q->props.mod_scheme].bps;
+    div_t d = div(8*_q->payload_enc_len, bps);
+    _q->payload_mod_len = d.quot + (d.rem ? 1 : 0);
+    _q->payload_mod = (unsigned char*)realloc(_q->payload_mod,
+                                              _q->payload_mod_len*sizeof(unsigned char));
+
+    // re-compute number of payload OFDM symbols
+    d = div(_q->payload_mod_len, _q->M_data);
+    _q->num_symbols_payload = d.quot + (d.rem ? 1 : 0);
+}
+
+// encode header
+void ofdmflexframegen_encode_header(ofdmflexframegen _q)
+{
+    // first 'n' bytes user data
+    unsigned int n = OFDMFLEXFRAME_H_USER;
+
+    // first byte is for expansion/version validation
+    _q->header[n+0] = OFDMFLEXFRAME_PROTOCOL;
+
+    // add payload length
+    _q->header[n+1] = (_q->payload_dec_len >> 8) & 0xff;
+    _q->header[n+2] = (_q->payload_dec_len     ) & 0xff;
+
+    // add modulation scheme/depth (pack into single byte)
+    _q->header[n+3]  = _q->props.mod_scheme;
+
+    // add CRC, forward error-correction schemes
+    //  CRC     : most-significant 3 bits of [n+4]
+    //  fec0    : least-significant 5 bits of [n+4]
+    //  fec1    : least-significant 5 bits of [n+5]
+    _q->header[n+4]  = (_q->props.check & 0x07) << 5;
+    _q->header[n+4] |= (_q->props.fec0) & 0x1f;
+    _q->header[n+5]  = (_q->props.fec1) & 0x1f;
+
+    // run packet encoder
+    packetizer_encode(_q->p_header, _q->header, _q->header_enc);
+
+    // scramble header
+    scramble_data(_q->header_enc, OFDMFLEXFRAME_H_ENC);
+
+#if 0
+    // print header (decoded)
+    unsigned int i;
+    printf("header tx (dec) : ");
+    for (i=0; i<OFDMFLEXFRAME_H_DEC; i++)
+        printf("%.2X ", _q->header[i]);
+    printf("\n");
+
+    // print header (encoded)
+    printf("header tx (enc) : ");
+    for (i=0; i<OFDMFLEXFRAME_H_ENC; i++)
+        printf("%.2X ", _q->header_enc[i]);
+    printf("\n");
+#endif
+}
+
+// modulate header
+void ofdmflexframegen_modulate_header(ofdmflexframegen _q)
+{
+    // repack 8-bit header bytes into 'bps'-bit payload symbols
+    unsigned int bps = modulation_types[OFDMFLEXFRAME_H_MOD].bps;
+    unsigned int num_written;
+    liquid_repack_bytes(_q->header_enc, 8,   OFDMFLEXFRAME_H_ENC,
+                        _q->header_mod, bps, OFDMFLEXFRAME_H_SYM,
+                        &num_written);
+}
+
+// write first S0 symbol
+void ofdmflexframegen_write_S0a(ofdmflexframegen _q,
+                                float complex * _buffer)
+{
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("writing S0[a] symbol\n");
+#endif
+
+    // write S0 symbol into front of buffer
+    ofdmframegen_write_S0a(_q->fg, _buffer);
+
+    // update state
+    _q->state = OFDMFLEXFRAMEGEN_STATE_S0b;
+}
+
+// write second S0 symbol
+void ofdmflexframegen_write_S0b(ofdmflexframegen _q,
+                                float complex * _buffer)
+{
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("writing S0[b] symbol\n");
+#endif
+
+    // write S0 symbol into front of buffer
+    ofdmframegen_write_S0b(_q->fg, _buffer);
+
+    // update state
+    _q->state = OFDMFLEXFRAMEGEN_STATE_S1;
+}
+
+// write S1 symbol
+void ofdmflexframegen_write_S1(ofdmflexframegen _q,
+                               float complex * _buffer)
+{
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("writing S1 symbol\n");
+#endif
+
+    // write S1 symbol into end of buffer
+    ofdmframegen_write_S1(_q->fg, _buffer);
+
+    // update state
+    _q->symbol_number = 0;
+    _q->state = OFDMFLEXFRAMEGEN_STATE_HEADER;
+}
+
+// write header symbol
+void ofdmflexframegen_write_header(ofdmflexframegen _q,
+                                   float complex * _buffer)
+{
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("writing header symbol\n");
+#endif
+
+    // load data onto data subcarriers
+    unsigned int i;
+    int sctype;
+    for (i=0; i<_q->M; i++) {
+        //
+        sctype = _q->p[i];
+
+        // 
+        if (sctype == OFDMFRAME_SCTYPE_DATA) {
+            // load...
+            if (_q->header_symbol_index < OFDMFLEXFRAME_H_SYM) {
+                // modulate header symbol onto data subcarrier
+                modem_modulate(_q->mod_header, _q->header_mod[_q->header_symbol_index++], &_q->X[i]);
+                //printf("  writing symbol %3u / %3u (x = %8.5f + j%8.5f)\n", _q->header_symbol_index, OFDMFLEXFRAME_H_SYM, crealf(_q->X[i]), cimagf(_q->X[i]));
+            } else {
+                //printf("  random header symbol\n");
+                // load random symbol
+                unsigned int sym = modem_gen_rand_sym(_q->mod_header);
+                modem_modulate(_q->mod_header, sym, &_q->X[i]);
+            }
+        } else {
+            // ignore subcarrier (ofdmframegen handles nulls and pilots)
+            _q->X[i] = 0.0f;
+        }
+    }
+
+    // write symbol
+    ofdmframegen_writesymbol(_q->fg, _q->X, _buffer);
+
+    // check state
+    if (_q->symbol_number == _q->num_symbols_header) {
+        _q->symbol_number = 0;
+        _q->state = OFDMFLEXFRAMEGEN_STATE_PAYLOAD;
+    }
+}
+
+// write payload symbol
+void ofdmflexframegen_write_payload(ofdmflexframegen _q,
+                                    float complex * _buffer)
+{
+#if DEBUG_OFDMFLEXFRAMEGEN
+    printf("writing payload symbol\n");
+#endif
+
+    // load data onto data subcarriers
+    unsigned int i;
+    int sctype;
+    for (i=0; i<_q->M; i++) {
+        //
+        sctype = _q->p[i];
+
+        // 
+        if (sctype == OFDMFRAME_SCTYPE_DATA) {
+            // load...
+            if (_q->payload_symbol_index < _q->payload_mod_len) {
+                // modulate payload symbol onto data subcarrier
+                modem_modulate(_q->mod_payload, _q->payload_mod[_q->payload_symbol_index++], &_q->X[i]);
+            } else {
+                //printf("  random payload symbol\n");
+                // load random symbol
+                unsigned int sym = modem_gen_rand_sym(_q->mod_payload);
+                modem_modulate(_q->mod_payload, sym, &_q->X[i]);
+            }
+        } else {
+            // ignore subcarrier (ofdmframegen handles nulls and pilots)
+            _q->X[i] = 0.0f;
+        }
+    }
+
+    // write symbol
+    ofdmframegen_writesymbol(_q->fg, _q->X, _buffer);
+
+    // check to see if this is the last symbol in the payload
+    if (_q->symbol_number == _q->num_symbols_payload)
+        _q->frame_complete = 1;
+}
+
diff --git a/src/framing/src/ofdmflexframesync.c b/src/framing/src/ofdmflexframesync.c
new file mode 100644
index 0000000..44503bf
--- /dev/null
+++ b/src/framing/src/ofdmflexframesync.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// ofdmflexframesync.c
+//
+// OFDM frame synchronizer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_OFDMFLEXFRAMESYNC 0
+
+#define OFDMFLEXFRAME_H_SOFT (0)
+
+// 
+// ofdmflexframesync
+//
+
+// internal callback
+int ofdmflexframesync_internal_callback(float complex * _X,
+                                        unsigned char * _p,
+                                        unsigned int    _M,
+                                        void * _userdata);
+
+// receive header data
+void ofdmflexframesync_rxheader(ofdmflexframesync _q,
+                                float complex * _X);
+
+// decode header
+void ofdmflexframesync_decode_header(ofdmflexframesync _q);
+
+// receive payload data
+void ofdmflexframesync_rxpayload(ofdmflexframesync _q,
+                                float complex * _X);
+
+
+struct ofdmflexframesync_s {
+    unsigned int M;         // number of subcarriers
+    unsigned int cp_len;    // cyclic prefix length
+    unsigned int taper_len; // taper length
+    unsigned char * p;      // subcarrier allocation (null, pilot, data)
+
+    // constants
+    unsigned int M_null;    // number of null subcarriers
+    unsigned int M_pilot;   // number of pilot subcarriers
+    unsigned int M_data;    // number of data subcarriers
+    unsigned int M_S0;      // number of enabled subcarriers in S0
+    unsigned int M_S1;      // number of enabled subcarriers in S1
+
+    // header
+    modem mod_header;                   // header modulator
+    packetizer p_header;                // header packetizer
+    unsigned char header[OFDMFLEXFRAME_H_DEC];      // header data (uncoded)
+#if OFDMFLEXFRAME_H_SOFT
+    unsigned char header_enc[8*OFDMFLEXFRAME_H_ENC];  // header data (encoded, soft bits)
+    unsigned char header_mod[OFDMFLEXFRAME_H_BPS*OFDMFLEXFRAME_H_SYM];  // header symbols (soft bits)
+#else
+    unsigned char header_enc[OFDMFLEXFRAME_H_ENC];  // header data (encoded)
+    unsigned char header_mod[OFDMFLEXFRAME_H_SYM];  // header symbols
+#endif
+    int header_valid;                   // valid header flag
+
+    // header properties
+    modulation_scheme ms_payload;       // payload modulation scheme
+    unsigned int bps_payload;           // payload modulation depth (bits/symbol)
+    unsigned int payload_len;           // payload length (number of bytes)
+    crc_scheme check;                   // payload validity check
+    fec_scheme fec0;                    // payload FEC (inner)
+    fec_scheme fec1;                    // payload FEC (outer)
+
+    // payload
+    packetizer p_payload;               // payload packetizer
+    modem mod_payload;                  // payload demodulator
+    unsigned char * payload_enc;        // payload data (encoded bytes)
+    unsigned char * payload_dec;        // payload data (decoded bytes)
+    unsigned int payload_enc_len;       // length of encoded payload
+    unsigned int payload_mod_len;       // number of payload modem symbols
+    int payload_valid;                  // valid payload flag
+    float complex * payload_syms;       // received payload symbols
+
+    // callback
+    framesync_callback callback;        // user-defined callback function
+    void * userdata;                    // user-defined data structure
+    framesyncstats_s framestats;        // frame statistic object
+    float evm_hat;                      // average error vector magnitude
+
+    // internal synchronizer objects
+    ofdmframesync fs;                   // internal OFDM frame synchronizer
+
+    // counters/states
+    unsigned int symbol_counter;        // received symbol number
+    enum {
+        OFDMFLEXFRAMESYNC_STATE_HEADER, // extract header
+        OFDMFLEXFRAMESYNC_STATE_PAYLOAD // extract payload symbols
+    } state;
+    unsigned int header_symbol_index;   // number of header symbols received
+    unsigned int payload_symbol_index;  // number of payload symbols received
+    unsigned int payload_buffer_index;  // bit-level index of payload (pack array)
+};
+
+// create ofdmflexframesync object
+//  _M          :   number of subcarriers
+//  _cp_len     :   length of cyclic prefix [samples]
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (PILOT/NULL/DATA) [size: _M x 1]
+//  _callback   :   user-defined callback function
+//  _userdata   :   user-defined data structure passed to callback
+ofdmflexframesync ofdmflexframesync_create(unsigned int       _M,
+                                           unsigned int       _cp_len,
+                                           unsigned int       _taper_len,
+                                           unsigned char *    _p,
+                                           framesync_callback _callback,
+                                           void *             _userdata)
+{
+    ofdmflexframesync q = (ofdmflexframesync) malloc(sizeof(struct ofdmflexframesync_s));
+
+    // validate input
+    if (_M < 8) {
+        fprintf(stderr,"warning: ofdmflexframesync_create(), less than 8 subcarriers\n");
+    } else if (_M % 2) {
+        fprintf(stderr,"error: ofdmflexframesync_create(), number of subcarriers must be even\n");
+        exit(1);
+    } else if (_cp_len > _M) {
+        fprintf(stderr,"error: ofdmflexframesync_create(), cyclic prefix length cannot exceed number of subcarriers\n");
+        exit(1);
+    }
+
+    // set internal properties
+    q->M         = _M;
+    q->cp_len    = _cp_len;
+    q->taper_len = _taper_len;
+    q->callback  = _callback;
+    q->userdata  = _userdata;
+
+    // allocate memory for subcarrier allocation IDs
+    q->p = (unsigned char*) malloc((q->M)*sizeof(unsigned char));
+    if (_p == NULL) {
+        // initialize default subcarrier allocation
+        ofdmframe_init_default_sctype(q->M, q->p);
+    } else {
+        // copy user-defined subcarrier allocation
+        memmove(q->p, _p, q->M*sizeof(unsigned char));
+    }
+
+    // validate and count subcarrier allocation
+    ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data);
+
+    // create internal framing object
+    q->fs = ofdmframesync_create(_M, _cp_len, _taper_len, _p, ofdmflexframesync_internal_callback, (void*)q);
+
+    // create header objects
+    q->mod_header = modem_create(OFDMFLEXFRAME_H_MOD);
+    q->p_header   = packetizer_create(OFDMFLEXFRAME_H_DEC,
+                                      OFDMFLEXFRAME_H_CRC,
+                                      OFDMFLEXFRAME_H_FEC,
+                                      LIQUID_FEC_NONE);
+    assert(packetizer_get_enc_msg_len(q->p_header)==OFDMFLEXFRAME_H_ENC);
+
+    // frame properties (default values to be overwritten when frame
+    // header is received and properly decoded)
+    q->ms_payload   = LIQUID_MODEM_QPSK;
+    q->bps_payload  = 2;
+    q->payload_len  = 1;
+    q->check        = LIQUID_CRC_NONE;
+    q->fec0         = LIQUID_FEC_NONE;
+    q->fec1         = LIQUID_FEC_NONE;
+
+    // create payload objects (initally QPSK, etc but overridden by received properties)
+    q->mod_payload = modem_create(q->ms_payload);
+    q->p_payload   = packetizer_create(q->payload_len, q->check, q->fec0, q->fec1);
+    q->payload_enc_len = packetizer_get_enc_msg_len(q->p_payload);
+    q->payload_enc = (unsigned char*) malloc(q->payload_enc_len*sizeof(unsigned char));
+    q->payload_dec = (unsigned char*) malloc(q->payload_len*sizeof(unsigned char));
+    q->payload_syms = (float complex *) malloc(q->payload_len*sizeof(float complex));
+    q->payload_mod_len = 0;
+
+    // reset state
+    ofdmflexframesync_reset(q);
+
+    // return object
+    return q;
+}
+
+void ofdmflexframesync_destroy(ofdmflexframesync _q)
+{
+    // destroy internal objects
+    ofdmframesync_destroy(_q->fs);
+    packetizer_destroy(_q->p_header);
+    modem_destroy(_q->mod_header);
+    packetizer_destroy(_q->p_payload);
+    modem_destroy(_q->mod_payload);
+
+    // free internal buffers/arrays
+    free(_q->p);
+    free(_q->payload_enc);
+    free(_q->payload_dec);
+    free(_q->payload_syms);
+
+    // free main object memory
+    free(_q);
+}
+
+void ofdmflexframesync_print(ofdmflexframesync _q)
+{
+    printf("ofdmflexframesync:\n");
+    printf("    num subcarriers     :   %-u\n", _q->M);
+    printf("      * NULL            :   %-u\n", _q->M_null);
+    printf("      * pilot           :   %-u\n", _q->M_pilot);
+    printf("      * data            :   %-u\n", _q->M_data);
+    printf("    cyclic prefix len   :   %-u\n", _q->cp_len);
+    printf("    taper len           :   %-u\n", _q->taper_len);
+}
+
+void ofdmflexframesync_reset(ofdmflexframesync _q)
+{
+    // reset internal state
+    _q->state = OFDMFLEXFRAMESYNC_STATE_HEADER;
+
+    // reset internal counters
+    _q->symbol_counter=0;
+    _q->header_symbol_index=0;
+    _q->payload_symbol_index=0;
+    _q->payload_buffer_index=0;
+    
+    // reset error vector magnitude estimate
+    _q->evm_hat = 1e-12f;   // slight offset to ensure no log(0)
+
+    // reset framestats object
+    framesyncstats_init_default(&_q->framestats);
+
+    // reset internal OFDM frame synchronizer object
+    ofdmframesync_reset(_q->fs);
+}
+
+// execute synchronizer object on buffer of samples
+void ofdmflexframesync_execute(ofdmflexframesync _q,
+                               float complex * _x,
+                               unsigned int _n)
+{
+    // push samples through ofdmframesync object
+    ofdmframesync_execute(_q->fs, _x, _n);
+}
+
+// 
+// query methods
+//
+
+// received signal strength indication
+float ofdmflexframesync_get_rssi(ofdmflexframesync _q)
+{
+    return ofdmframesync_get_rssi(_q->fs);
+}
+
+// received carrier frequency offset
+float ofdmflexframesync_get_cfo(ofdmflexframesync _q)
+{
+    return ofdmframesync_get_cfo(_q->fs);
+}
+
+// 
+// debugging methods
+//
+
+// enable debugging for internal ofdm frame synchronizer
+void ofdmflexframesync_debug_enable(ofdmflexframesync _q)
+{
+    ofdmframesync_debug_enable(_q->fs);
+}
+
+// disable debugging for internal ofdm frame synchronizer
+void ofdmflexframesync_debug_disable(ofdmflexframesync _q)
+{
+    ofdmframesync_debug_disable(_q->fs);
+}
+
+// print debugging file for internal ofdm frame synchronizer
+void ofdmflexframesync_debug_print(ofdmflexframesync _q,
+                                   const char *      _filename)
+{
+    ofdmframesync_debug_print(_q->fs, _filename);
+}
+
+//
+// internal methods
+//
+
+// internal callback
+//  _X          :   subcarrier symbols
+//  _p          :   subcarrier allocation
+//  _M          :   number of subcarriers
+//  _userdata   :   user-defined data structure
+int ofdmflexframesync_internal_callback(float complex * _X,
+                                        unsigned char * _p,
+                                        unsigned int    _M,
+                                        void * _userdata)
+{
+#if DEBUG_OFDMFLEXFRAMESYNC
+    printf("******* ofdmflexframesync callback invoked!\n");
+#endif
+    // type-cast userdata as ofdmflexframesync object
+    ofdmflexframesync _q = (ofdmflexframesync) _userdata;
+
+    _q->symbol_counter++;
+
+#if DEBUG_OFDMFLEXFRAMESYNC
+    printf("received symbol %u\n", _q->symbol_counter);
+#endif
+
+    // extract symbols
+    switch (_q->state) {
+    case OFDMFLEXFRAMESYNC_STATE_HEADER:
+        ofdmflexframesync_rxheader(_q, _X);
+        break;
+    case OFDMFLEXFRAMESYNC_STATE_PAYLOAD:
+        ofdmflexframesync_rxpayload(_q, _X);
+        break;
+    default:
+        fprintf(stderr,"error: ofdmflexframesync_internal_callback(), unknown/unsupported internal state\n");
+        exit(1);
+    }
+
+    // return
+    return 0;
+}
+
+// receive header data
+void ofdmflexframesync_rxheader(ofdmflexframesync _q,
+                                float complex * _X)
+{
+#if DEBUG_OFDMFLEXFRAMESYNC
+    printf("  ofdmflexframesync extracting header...\n");
+#endif
+
+    // demodulate header symbols
+    unsigned int i;
+    int sctype;
+    for (i=0; i<_q->M; i++) {
+        // subcarrier type (PILOT/NULL/DATA)
+        sctype = _q->p[i];
+
+        // ignore pilot and null subcarriers
+        if (sctype == OFDMFRAME_SCTYPE_DATA) {
+            // unload header symbols
+            // demodulate header symbol
+            unsigned int sym;
+#if OFDMFLEXFRAME_H_SOFT
+            modem_demodulate_soft(_q->mod_header, _X[i], &sym, &_q->header_mod[OFDMFLEXFRAME_H_BPS*_q->header_symbol_index]);
+#else
+            modem_demodulate(_q->mod_header, _X[i], &sym);
+            _q->header_mod[_q->header_symbol_index] = sym;
+#endif
+            _q->header_symbol_index++;
+            //printf("  extracting symbol %3u / %3u (x = %8.5f + j%8.5f)\n", _q->header_symbol_index, OFDMFLEXFRAME_H_SYM, crealf(_X[i]), cimagf(_X[i]));
+
+            // get demodulator error vector magnitude
+            float evm = modem_get_demodulator_evm(_q->mod_header);
+            _q->evm_hat += evm*evm;
+
+            // header extracted
+            if (_q->header_symbol_index == OFDMFLEXFRAME_H_SYM) {
+                // decode header
+                ofdmflexframesync_decode_header(_q);
+            
+                // compute error vector magnitude estimate
+                _q->framestats.evm = 10*log10f( _q->evm_hat/OFDMFLEXFRAME_H_SYM );
+
+                // invoke callback if header is invalid
+                if (_q->header_valid)
+                    _q->state = OFDMFLEXFRAMESYNC_STATE_PAYLOAD;
+                else {
+                    //printf("**** header invalid!\n");
+                    // set framestats internals
+                    _q->framestats.rssi             = ofdmframesync_get_rssi(_q->fs);
+                    _q->framestats.cfo              = ofdmframesync_get_cfo(_q->fs);
+                    _q->framestats.framesyms        = NULL;
+                    _q->framestats.num_framesyms    = 0;
+                    _q->framestats.mod_scheme       = LIQUID_MODEM_UNKNOWN;
+                    _q->framestats.mod_bps          = 0;
+                    _q->framestats.check            = LIQUID_CRC_UNKNOWN;
+                    _q->framestats.fec0             = LIQUID_FEC_UNKNOWN;
+                    _q->framestats.fec1             = LIQUID_FEC_UNKNOWN;
+
+                    // invoke callback method
+                    _q->callback(_q->header,
+                                 _q->header_valid,
+                                 NULL,
+                                 0,
+                                 0,
+                                 _q->framestats,
+                                 _q->userdata);
+
+                    ofdmflexframesync_reset(_q);
+                }
+                break;
+            }
+        }
+    }
+}
+
+// decode header
+void ofdmflexframesync_decode_header(ofdmflexframesync _q)
+{
+#if OFDMFLEXFRAME_H_SOFT
+#  if 0
+    unsigned int i;
+    // copy soft bits
+    for (i=0; i<8*OFDMFLEXFRAME_H_ENC; i++)
+        _q->header_enc[i] = _q->header_mod[i];
+#  else
+    // TODO: ensure lengths are the same
+    memmove(_q->header_enc, _q->header_mod, 8*OFDMFLEXFRAME_H_ENC);
+#  endif
+
+    // unscramble header using soft bits
+    unscramble_data_soft(_q->header_enc, OFDMFLEXFRAME_H_ENC);
+
+    // run packet decoder
+    _q->header_valid = packetizer_decode_soft(_q->p_header, _q->header_enc, _q->header);
+#else
+    // pack 1-bit header symbols into 8-bit bytes
+    unsigned int num_written;
+    liquid_repack_bytes(_q->header_mod, OFDMFLEXFRAME_H_BPS, OFDMFLEXFRAME_H_SYM,
+                        _q->header_enc, 8,                   OFDMFLEXFRAME_H_ENC,
+                        &num_written);
+    assert(num_written==OFDMFLEXFRAME_H_ENC);
+
+    // unscramble header
+    unscramble_data(_q->header_enc, OFDMFLEXFRAME_H_ENC);
+
+    // run packet decoder
+    _q->header_valid = packetizer_decode(_q->p_header, _q->header_enc, _q->header);
+#endif
+
+#if 0
+    // print header
+    printf("header rx (enc) : ");
+    for (i=0; i<OFDMFLEXFRAME_H_ENC; i++)
+        printf("%.2X ", _q->header_enc[i]);
+    printf("\n");
+
+    // print header
+    printf("header rx (dec) : ");
+    for (i=0; i<OFDMFLEXFRAME_H_DEC; i++)
+        printf("%.2X ", _q->header[i]);
+    printf("\n");
+#endif
+
+#if DEBUG_OFDMFLEXFRAMESYNC
+    printf("****** header extracted [%s]\n", _q->header_valid ? "valid" : "INVALID!");
+#endif
+    if (!_q->header_valid)
+        return;
+
+    unsigned int n = OFDMFLEXFRAME_H_USER;
+
+    // first byte is for expansion/version validation
+    if (_q->header[n+0] != OFDMFLEXFRAME_PROTOCOL) {
+        fprintf(stderr,"warning: ofdmflexframesync_decode_header(), invalid framing version\n");
+        _q->header_valid = 0;
+    }
+
+    // strip off payload length
+    unsigned int payload_len = (_q->header[n+1] << 8) | (_q->header[n+2]);
+
+    // strip off modulation scheme/depth
+    unsigned int mod_scheme = _q->header[n+3];
+    if (mod_scheme == 0 || mod_scheme >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"warning: ofdmflexframesync_decode_header(), invalid modulation scheme\n");
+        _q->header_valid = 0;
+        return;
+    }
+
+    // strip off CRC, forward error-correction schemes
+    //  CRC     : most-significant 3 bits of [n+4]
+    //  fec0    : least-significant 5 bits of [n+4]
+    //  fec1    : least-significant 5 bits of [n+5]
+    unsigned int check = (_q->header[n+4] >> 5 ) & 0x07;
+    unsigned int fec0  = (_q->header[n+4]      ) & 0x1f;
+    unsigned int fec1  = (_q->header[n+5]      ) & 0x1f;
+
+    // validate properties
+    if (check >= LIQUID_CRC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: ofdmflexframesync_decode_header(), decoded CRC exceeds available\n");
+        check = LIQUID_CRC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+    if (fec0 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: ofdmflexframesync_decode_header(), decoded FEC (inner) exceeds available\n");
+        fec0 = LIQUID_FEC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+    if (fec1 >= LIQUID_FEC_NUM_SCHEMES) {
+        fprintf(stderr,"warning: ofdmflexframesync_decode_header(), decoded FEC (outer) exceeds available\n");
+        fec1 = LIQUID_FEC_UNKNOWN;
+        _q->header_valid = 0;
+    }
+
+    // print results
+#if DEBUG_OFDMFLEXFRAMESYNC
+    printf("    properties:\n");
+    printf("      * mod scheme      :   %s\n", modulation_types[mod_scheme].fullname);
+    printf("      * fec (inner)     :   %s\n", fec_scheme_str[fec0][1]);
+    printf("      * fec (outer)     :   %s\n", fec_scheme_str[fec1][1]);
+    printf("      * CRC scheme      :   %s\n", crc_scheme_str[check][1]);
+    printf("      * payload length  :   %u bytes\n", payload_len);
+#endif
+
+    // configure payload receiver
+    if (_q->header_valid) {
+        // configure modem
+        if (mod_scheme != _q->ms_payload) {
+            // set new properties
+            _q->ms_payload  = mod_scheme;
+            _q->bps_payload = modulation_types[mod_scheme].bps;
+
+            // recreate modem (destroy/create)
+            _q->mod_payload = modem_recreate(_q->mod_payload, _q->ms_payload);
+        }
+
+        // set new packetizer properties
+        _q->payload_len = payload_len;
+        _q->check       = check;
+        _q->fec0        = fec0;
+        _q->fec1        = fec1;
+        
+        // recreate packetizer object
+        _q->p_payload = packetizer_recreate(_q->p_payload,
+                                            _q->payload_len,
+                                            _q->check,
+                                            _q->fec0,
+                                            _q->fec1);
+
+        // re-compute payload encoded message length
+        _q->payload_enc_len = packetizer_get_enc_msg_len(_q->p_payload);
+#if DEBUG_OFDMFLEXFRAMESYNC
+        printf("      * payload encoded :   %u bytes\n", _q->payload_enc_len);
+#endif
+
+        // re-allocate buffers accordingly
+        _q->payload_enc = (unsigned char*) realloc(_q->payload_enc, _q->payload_enc_len*sizeof(unsigned char));
+        _q->payload_dec = (unsigned char*) realloc(_q->payload_dec, _q->payload_len*sizeof(unsigned char));
+
+        // re-compute number of modulated payload symbols
+        div_t d = div(8*_q->payload_enc_len, _q->bps_payload);
+        _q->payload_mod_len = d.quot + (d.rem ? 1 : 0);
+        _q->payload_syms = (float complex*) realloc(_q->payload_syms, _q->payload_mod_len*sizeof(float complex));
+#if DEBUG_OFDMFLEXFRAMESYNC
+        printf("      * payload mod syms:   %u symbols\n", _q->payload_mod_len);
+#endif
+    }
+}
+
+// receive payload data
+void ofdmflexframesync_rxpayload(ofdmflexframesync _q,
+                                 float complex * _X)
+{
+    // demodulate paylod symbols
+    unsigned int i;
+    int sctype;
+    for (i=0; i<_q->M; i++) {
+        // subcarrier type (PILOT/NULL/DATA)
+        sctype = _q->p[i];
+
+        // ignore pilot and null subcarriers
+        if (sctype == OFDMFRAME_SCTYPE_DATA) {
+            // unload payload symbols
+            unsigned int sym;
+            modem_demodulate(_q->mod_payload, _X[i], &sym);
+
+            // store received symbol
+            _q->payload_syms[_q->payload_symbol_index] = _X[i];
+
+            // pack decoded symbol into array
+            liquid_pack_array(_q->payload_enc,
+                              _q->payload_enc_len,
+                              _q->payload_buffer_index,
+                              _q->bps_payload,
+                              sym);
+
+            // increment...
+            _q->payload_buffer_index += _q->bps_payload;
+
+            // increment symbol counter
+            _q->payload_symbol_index++;
+
+            if (_q->payload_symbol_index == _q->payload_mod_len) {
+                // payload extracted
+
+                // decode payload
+                _q->payload_valid = packetizer_decode(_q->p_payload, _q->payload_enc, _q->payload_dec);
+#if DEBUG_OFDMFLEXFRAMESYNC
+                printf("****** payload extracted [%s]\n", _q->payload_valid ? "valid" : "INVALID!");
+#endif
+
+                // ignore callback if set to NULL
+                if (_q->callback == NULL) {
+                    ofdmflexframesync_reset(_q);
+                    break;
+                }
+
+                // set framestats internals
+                _q->framestats.rssi             = ofdmframesync_get_rssi(_q->fs);
+                _q->framestats.cfo              = ofdmframesync_get_cfo(_q->fs);
+                _q->framestats.framesyms        = _q->payload_syms;
+                _q->framestats.num_framesyms    = _q->payload_mod_len;
+                _q->framestats.mod_scheme       = _q->ms_payload;
+                _q->framestats.mod_bps          = _q->bps_payload;
+                _q->framestats.check            = _q->check;
+                _q->framestats.fec0             = _q->fec0;
+                _q->framestats.fec1             = _q->fec1;
+
+                // invoke callback method
+                _q->callback(_q->header,
+                             _q->header_valid,
+                             _q->payload_dec,
+                             _q->payload_len,
+                             _q->payload_valid,
+                             _q->framestats,
+                             _q->userdata);
+
+
+                // reset object
+                ofdmflexframesync_reset(_q);
+                break;
+            }
+        }
+    }
+}
+
+
+
diff --git a/src/framing/src/presync.c b/src/framing/src/presync.c
new file mode 100644
index 0000000..a4d84c7
--- /dev/null
+++ b/src/framing/src/presync.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Binary pre-demod synchronizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+struct PRESYNC(_s) {
+    unsigned int n;     // sequence length
+    unsigned int m;     // number of binary synchronizers
+    
+    WINDOW() rx_i;      // received pattern (in-phase)
+    WINDOW() rx_q;      // received pattern (quadrature)
+    
+    float * dphi;       // array of frequency offsets [size: m x 1]
+    DOTPROD() * sync_i; // synchronization pattern (in-phase)
+    DOTPROD() * sync_q; // synchronization pattern (quadrature)
+
+    float * rxy;        // output correlation [size: m x 1]
+
+    float n_inv;        // 1/n (pre-computed for speed)
+};
+
+/* create binary pre-demod synchronizer                     */
+/*  _v          :   baseband sequence                       */
+/*  _n          :   baseband sequence length                */
+/*  _dphi_max   :   maximum absolute frequency deviation    */
+/*  _m          :   number of correlators                   */
+PRESYNC() PRESYNC(_create)(TC *         _v,
+                           unsigned int _n,
+                           float        _dphi_max,
+                           unsigned int _m)
+{
+    // validate input
+    if (_n < 1) {
+        fprintf(stderr, "error: bpresync_%s_create(), invalid input length\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr, "error: bpresync_%s_create(), number of correlators must be at least 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate main object memory and initialize
+    PRESYNC() _q = (PRESYNC()) malloc(sizeof(struct PRESYNC(_s)));
+    _q->n = _n;
+    _q->m = _m;
+
+    _q->n_inv = 1.0f / (float)(_q->n);
+
+    unsigned int i;
+
+    // create internal receive buffers
+    _q->rx_i = WINDOW(_create)(_q->n);
+    _q->rx_q = WINDOW(_create)(_q->n);
+
+    // create internal array of frequency offsets
+    _q->dphi = (float*) malloc( _q->m*sizeof(float) );
+
+    // create internal synchronizers
+    _q->sync_i = (DOTPROD()*) malloc( _q->m*sizeof(DOTPROD()) );
+    _q->sync_q = (DOTPROD()*) malloc( _q->m*sizeof(DOTPROD()) );
+
+    // buffer
+    T vi_prime[_n];
+    T vq_prime[_n];
+    for (i=0; i<_q->m; i++) {
+
+        // generate signal with frequency offset
+        _q->dphi[i] = (float)i / (float)(_q->m-1)*_dphi_max;
+        unsigned int k;
+        for (k=0; k<_q->n; k++) {
+            vi_prime[k] = REAL( _v[k] * cexpf(-_Complex_I*k*_q->dphi[i]) );
+            vq_prime[k] = IMAG( _v[k] * cexpf(-_Complex_I*k*_q->dphi[i]) );
+        }
+
+        _q->sync_i[i] = DOTPROD(_create)(vi_prime, _q->n);
+        _q->sync_q[i] = DOTPROD(_create)(vq_prime, _q->n);
+    }
+
+    // allocate memory for cross-correlation
+    _q->rxy = (float*) malloc( _q->m*sizeof(float) );
+
+    // reset object
+    PRESYNC(_reset)(_q);
+
+    return _q;
+}
+
+void PRESYNC(_destroy)(PRESYNC() _q)
+{
+    unsigned int i;
+
+    // free received symbol buffers
+    WINDOW(_destroy)(_q->rx_i);
+    WINDOW(_destroy)(_q->rx_q);
+
+    // free internal syncrhonizer objects
+    for (i=0; i<_q->m; i++) {
+        DOTPROD(_destroy)(_q->sync_i[i]);
+        DOTPROD(_destroy)(_q->sync_q[i]);
+    }
+    free(_q->sync_i);
+    free(_q->sync_q);
+
+    // free internal frequency offset array
+    free(_q->dphi);
+
+    // free internal cross-correlation array
+    free(_q->rxy);
+
+    // free main object memory
+    free(_q);
+}
+
+void PRESYNC(_print)(PRESYNC() _q)
+{
+    printf("bpresync_%s: %u samples\n", EXTENSION_FULL, _q->n);
+}
+
+void PRESYNC(_reset)(PRESYNC() _q)
+{
+    WINDOW(_clear)(_q->rx_i);
+    WINDOW(_clear)(_q->rx_q);
+}
+
+// correlate input sequence with particular 
+//  _q          :   pre-demod synchronizer object
+//  _id         :   ...
+void PRESYNC(_correlatex)(PRESYNC()       _q,
+                          unsigned int    _id,
+                          float complex * _rxy0,
+                          float complex * _rxy1)
+{
+    // validate input...
+    if (_id >= _q->m) {
+        fprintf(stderr,"error: bpresync_%s_correlatex(), invalid id\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // get buffer pointers
+    T * ri = NULL;
+    T * rq = NULL;
+    WINDOW(_read)(_q->rx_i, &ri);
+    WINDOW(_read)(_q->rx_q, &rq);
+
+    // compute correlations
+    T rxy_ii;   DOTPROD(_execute)(_q->sync_i[_id], ri, &rxy_ii);
+    T rxy_qq;   DOTPROD(_execute)(_q->sync_q[_id], rq, &rxy_qq);
+    T rxy_iq;   DOTPROD(_execute)(_q->sync_i[_id], rq, &rxy_iq);
+    T rxy_qi;   DOTPROD(_execute)(_q->sync_q[_id], ri, &rxy_qi);
+
+    // non-conjugated
+    T rxy_i0 = rxy_ii - rxy_qq;
+    T rxy_q0 = rxy_iq + rxy_qi;
+    *_rxy0 = (rxy_i0 + rxy_q0 * _Complex_I) * _q->n_inv;
+
+    // conjugated
+    T rxy_i1 = rxy_ii + rxy_qq;
+    T rxy_q1 = rxy_iq - rxy_qi;
+    *_rxy1 = (rxy_i1 + rxy_q1 * _Complex_I) * _q->n_inv;
+}
+
+/* push input sample into pre-demod synchronizer            */
+/*  _q          :   pre-demod synchronizer object           */
+/*  _x          :   input sample                            */
+void PRESYNC(_push)(PRESYNC() _q,
+                    TI        _x)
+{
+    // push symbol into buffers
+    WINDOW(_push)(_q->rx_i, REAL(_x));
+    WINDOW(_push)(_q->rx_q, REAL(_x));
+}
+
+/* correlate input sequence                                 */
+/*  _q          :   pre-demod synchronizer object           */
+/*  _rxy        :   output cross correlation                */
+/*  _dphi_hat   :   output frequency offset estimate        */
+void PRESYNC(_correlate)(PRESYNC() _q,
+                         TO *      _rxy,
+                         float *   _dphi_hat)
+{
+    unsigned int i;
+    float complex rxy_max = 0;  // maximum cross-correlation
+    float abs_rxy_max = 0;      // absolute value of rxy_max
+    float complex rxy0;
+    float complex rxy1;
+    float dphi_hat = 0.0f;
+    for (i=0; i<_q->m; i++)  {
+
+        PRESYNC(_correlatex)(_q, i, &rxy0, &rxy1);
+
+        // check non-conjugated value
+        if ( ABS(rxy0) > abs_rxy_max ) {
+            rxy_max     = rxy0;
+            abs_rxy_max = ABS(rxy0);
+            dphi_hat    = _q->dphi[i];
+        }
+
+        // check conjugated value
+        if ( ABS(rxy1) > abs_rxy_max ) {
+            rxy_max     = rxy1;
+            abs_rxy_max = ABS(rxy1);
+            dphi_hat    = -_q->dphi[i];
+        }
+    }
+
+    *_rxy      = rxy_max;
+    *_dphi_hat = dphi_hat;
+}
+
diff --git a/src/framing/src/presync_cccf.c b/src/framing/src/presync_cccf.c
new file mode 100644
index 0000000..347321b
--- /dev/null
+++ b/src/framing/src/presync_cccf.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Binary pre-demod synchronizer
+//
+
+#include "liquid.internal.h"
+
+// 
+#define PRESYNC(name)       LIQUID_CONCAT(presync_cccf,name)
+
+// print and naming extensions
+#define PRINTVAL(x)         printf("%12.4e + j%12.4e", crealf(x), cimagf(x))
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "cccf"
+
+#define T                   float           // primitive type
+#define TO                  float complex   // output type
+#define TC                  float complex   // coefficient type
+#define TI                  float complex   // input type
+
+#define ABS(X)              cabsf(X)
+#define REAL(X)             crealf(X)
+#define IMAG(X)             cimagf(X)
+
+#define WINDOW(name)        LIQUID_CONCAT(windowf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_rrrf,name)
+#define BSYNC(name)         LIQUID_CONCAT(bsync_cccf,name)
+
+#define TO_COMPLEX
+#define TC_COMPLEX
+#define TI_COMPLEX
+
+// source files
+#include "presync.c"
+
diff --git a/src/framing/src/qdetector_cccf.c b/src/framing/src/qdetector_cccf.c
new file mode 100644
index 0000000..dcaa992
--- /dev/null
+++ b/src/framing/src/qdetector_cccf.c
@@ -0,0 +1,608 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// qdetector_cccf.c
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_QDETECTOR              0
+#define DEBUG_QDETECTOR_PRINT        0
+#define DEBUG_QDETECTOR_FILENAME     "qdetector_cccf_debug.m"
+
+// seek signal (initial detection)
+void qdetector_cccf_execute_seek(qdetector_cccf _q,
+                                 float complex  _x);
+
+// align signal in time, compute offset estimates
+void qdetector_cccf_execute_align(qdetector_cccf _q,
+                                  float complex  _x);
+
+// main object definition
+struct qdetector_cccf_s {
+    unsigned int    s_len;          // template (time) length: k * (sequence_len + 2*m)
+    float complex * s;              // template (time), [size: s_len x 1]
+    float complex * S;              // template (freq), [size: nfft x 1]
+    float           s2_sum;         // sum{ s^2 }
+
+    float complex * buf_time_0;     // time-domain buffer (FFT)
+    float complex * buf_freq_0;     // frequence-domain buffer (FFT)
+    float complex * buf_freq_1;     // frequence-domain buffer (IFFT)
+    float complex * buf_time_1;     // time-domain buffer (IFFT)
+    unsigned int    nfft;           // fft size
+    fftplan         fft;            // FFT object:  buf_time_0 > buf_freq_0
+    fftplan         ifft;           // IFFT object: buf_freq_1 > buf_freq_1
+
+    unsigned int    counter;        // sample counter for determining when to compute FFTs
+    float           threshold;      // detection threshold
+    int             range;          // carrier offset search range (subcarriers)
+    unsigned int    num_transforms; // number of transforms taken (debugging)
+
+    float           x2_sum_0;       // sum{ |x|^2 } of first half of buffer
+    float           x2_sum_1;       // sum{ |x|^2 } of second half of buffer
+
+    int             offset;         // FFT offset index for peak correlation (coarse carrier estimate)
+    float           tau_hat;        // timing offset estimate
+    float           gamma_hat;      // signal level estimate (channel gain)
+    float           dphi_hat;       // carrier frequency offset estimate
+    float           phi_hat;        // carrier phase offset estimate
+
+    enum {
+        QDETECTOR_STATE_SEEK,       // seek sequence
+        QDETECTOR_STATE_ALIGN,      // align sequence
+    }               state;          // execution state
+    int             frame_detected; // frame detected?
+};
+
+// create detector with generic sequence
+//  _s      :   sample sequence
+//  _s_len  :   length of sample sequence
+qdetector_cccf qdetector_cccf_create(float complex * _s,
+                                     unsigned int    _s_len)
+{
+    // validate input
+    if (_s_len == 0) {
+        fprintf(stderr,"error: qdetector_cccf_create(), sequence length cannot be zero\n");
+        exit(1);
+    }
+    
+    // allocate memory for main object and set internal properties
+    qdetector_cccf q = (qdetector_cccf) malloc(sizeof(struct qdetector_cccf_s));
+    q->s_len = _s_len;
+
+    // allocate memory and copy sequence
+    q->s = (float complex*) malloc(q->s_len * sizeof(float complex));
+    memmove(q->s, _s, q->s_len*sizeof(float complex));
+    q->s2_sum = liquid_sumsqcf(q->s, q->s_len); // compute sum{ s^2 }
+
+    // prepare transforms
+    q->nfft       = 1 << liquid_nextpow2( (unsigned int)( 2 * q->s_len ) ); // NOTE: must be even
+    q->buf_time_0 = (float complex*) malloc(q->nfft * sizeof(float complex));
+    q->buf_freq_0 = (float complex*) malloc(q->nfft * sizeof(float complex));
+    q->buf_freq_1 = (float complex*) malloc(q->nfft * sizeof(float complex));
+    q->buf_time_1 = (float complex*) malloc(q->nfft * sizeof(float complex));
+
+    q->fft  = fft_create_plan(q->nfft, q->buf_time_0, q->buf_freq_0, LIQUID_FFT_FORWARD,  0);
+    q->ifft = fft_create_plan(q->nfft, q->buf_freq_1, q->buf_time_1, LIQUID_FFT_BACKWARD, 0);
+
+    // create frequency-domain template by taking nfft-point transform on 's', storing in 'S'
+    q->S = (float complex*) malloc(q->nfft * sizeof(float complex));
+    memset(q->buf_time_0, 0x00, q->nfft*sizeof(float complex));
+    memmove(q->buf_time_0, q->s, q->s_len*sizeof(float complex));
+    fft_execute(q->fft);
+    memmove(q->S, q->buf_freq_0, q->nfft*sizeof(float complex));
+
+    // reset state variables
+    q->counter        = q->nfft/2;
+    q->num_transforms = 0;
+    q->x2_sum_0       = 0.0f;
+    q->x2_sum_1       = 0.0f;
+    q->state          = QDETECTOR_STATE_SEEK;
+    q->frame_detected = 0;
+    memset(q->buf_time_0, 0x00, q->nfft*sizeof(float complex));
+    
+    // reset estimates
+    q->tau_hat   = 0.0f;
+    q->gamma_hat = 0.0f;
+    q->dphi_hat  = 0.0f;
+    q->phi_hat   = 0.0f;
+
+    qdetector_cccf_set_threshold(q,0.5f);
+    qdetector_cccf_set_range    (q,0.3f); // set initial range for higher detection
+
+    // return object
+    return q;
+}
+
+
+// create detector from sequence of symbols using internal linear interpolator
+//  _sequence       :   symbol sequence
+//  _sequence_len   :   length of symbol sequence
+//  _ftype          :   filter prototype (e.g. LIQUID_FIRFILT_RRC)
+//  _k              :   samples/symbol
+//  _m              :   filter delay
+//  _beta           :   excess bandwidth factor
+qdetector_cccf qdetector_cccf_create_linear(float complex * _sequence,
+                                            unsigned int    _sequence_len,
+                                            int             _ftype,
+                                            unsigned int    _k,
+                                            unsigned int    _m,
+                                            float           _beta)
+{
+    // validate input
+    if (_sequence_len == 0) {
+        fprintf(stderr,"error: qdetector_cccf_create_linear(), sequence length cannot be zero\n");
+        exit(1);
+    } else if (_k < 2 || _k > 80) {
+        fprintf(stderr,"error: qdetector_cccf_create_linear(), samples per symbol must be in [2,80]\n");
+        exit(1);
+    } else if (_m < 1 || _m > 100) {
+        fprintf(stderr,"error: qdetector_cccf_create_linear(), filter delay must be in [1,100]\n");
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: qdetector_cccf_create_linear(), excess bandwidth factor must be in [0,1]\n");
+        exit(1);
+    }
+    
+    // create time-domain template
+    unsigned int    s_len = _k * (_sequence_len + 2*_m);
+    float complex * s     = (float complex*) malloc(s_len * sizeof(float complex));
+    firinterp_crcf interp = firinterp_crcf_create_prototype(_ftype, _k, _m, _beta, 0);
+    unsigned int i;
+    for (i=0; i<_sequence_len + 2*_m; i++)
+        firinterp_crcf_execute(interp, i < _sequence_len ? _sequence[i] : 0, &s[_k*i]);
+    firinterp_crcf_destroy(interp);
+
+    // create main object
+    qdetector_cccf q = qdetector_cccf_create(s, s_len);
+
+    // free allocated temporary array
+    free(s);
+
+    // return object
+    return q;
+}
+
+// create detector from sequence of symbols using internal linear interpolator
+//  _sequence       :   bit sequence
+//  _sequence_len   :   length of bit sequence
+//  _k              :   samples/symbol
+//  _m              :   filter delay
+//  _beta           :   excess bandwidth factor
+qdetector_cccf qdetector_cccf_create_gmsk(unsigned char * _sequence,
+                                          unsigned int    _sequence_len,
+                                          unsigned int    _k,
+                                          unsigned int    _m,
+                                          float           _beta)
+{
+    // validate input
+    if (_sequence_len == 0) {
+        fprintf(stderr,"error: qdetector_cccf_create_gmsk(), sequence length cannot be zero\n");
+        exit(1);
+    } else if (_k < 2 || _k > 80) {
+        fprintf(stderr,"error: qdetector_cccf_create_gmsk(), samples per symbol must be in [2,80]\n");
+        exit(1);
+    } else if (_m < 1 || _m > 100) {
+        fprintf(stderr,"error: qdetector_cccf_create_gmsk(), filter delay must be in [1,100]\n");
+        exit(1);
+    } else if (_beta < 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: qdetector_cccf_create_gmsk(), excess bandwidth factor must be in [0,1]\n");
+        exit(1);
+    }
+    
+    // create time-domain template using GMSK modem
+    unsigned int    s_len = _k * (_sequence_len + 2*_m);
+    float complex * s     = (float complex*) malloc(s_len * sizeof(float complex));
+    gmskmod mod = gmskmod_create(_k, _m, _beta);
+    unsigned int i;
+    for (i=0; i<_sequence_len + 2*_m; i++)
+        gmskmod_modulate(mod, i < _sequence_len ? _sequence[i] : 0, &s[_k*i]);
+    gmskmod_destroy(mod);
+
+    // create main object
+    qdetector_cccf q = qdetector_cccf_create(s, s_len);
+
+    // free allocated temporary array
+    free(s);
+
+    // return object
+    return q;
+}
+
+void qdetector_cccf_destroy(qdetector_cccf _q)
+{
+    // free allocated arrays
+    free(_q->s         );
+    free(_q->S         );
+    free(_q->buf_time_0);
+    free(_q->buf_freq_0);
+    free(_q->buf_freq_1);
+    free(_q->buf_time_1);
+
+    // destroy objects
+    fft_destroy_plan(_q->fft);
+    fft_destroy_plan(_q->ifft);
+
+    // free main object memory
+    free(_q);
+}
+
+void qdetector_cccf_print(qdetector_cccf _q)
+{
+    printf("qdetector_cccf:\n");
+    printf("  template length (time):   %-u\n",   _q->s_len);
+    printf("  FFT size              :   %-u\n",   _q->nfft);
+    printf("  detection threshold   :   %6.4f\n", _q->threshold);
+    printf("  sum{ s^2 }            :   %.2f\n",  _q->s2_sum);
+}
+
+void qdetector_cccf_reset(qdetector_cccf _q)
+{
+}
+
+void * qdetector_cccf_execute(qdetector_cccf _q,
+                              float complex  _x)
+{
+    switch (_q->state) {
+    case QDETECTOR_STATE_SEEK:
+        // seek signal
+        qdetector_cccf_execute_seek(_q, _x);
+        break;
+
+    case QDETECTOR_STATE_ALIGN:
+        // align signal
+        qdetector_cccf_execute_align(_q, _x);
+        break;
+    }
+
+    // check if frame was detected
+    if (_q->frame_detected) {
+        // clear flag
+        _q->frame_detected = 0;
+
+        // return pointer to internal buffer of saved samples
+        return (void*)(_q->buf_time_1);
+    }
+
+    // frame not yet ready
+    return NULL;
+}
+
+// set detection threshold (should be between 0 and 1, good starting point is 0.5)
+void qdetector_cccf_set_threshold(qdetector_cccf _q,
+                                  float          _threshold)
+{
+    if (_threshold <= 0.0f || _threshold > 2.0f) {
+        fprintf(stderr,"warning: threshold (%12.4e) out of range; ignoring\n", _threshold);
+        return;
+    }
+
+    // set internal threshold value
+    _q->threshold = _threshold;
+}
+
+// set carrier offset search range
+void qdetector_cccf_set_range(qdetector_cccf _q,
+                              float          _dphi_max)
+{
+    if (_dphi_max < 0.0f || _dphi_max > 0.5f) {
+        fprintf(stderr,"warning: carrier offset search range (%12.4e) out of range; ignoring\n", _dphi_max);
+        return;
+    }
+
+    // set internal search range
+    _q->range = (int)(_dphi_max * _q->nfft / (2*M_PI));
+    _q->range = _q->range < 0 ? 0 : _q->range;
+    //printf("range: %d / %u\n", _q->range, _q->nfft);
+}
+
+// get sequence length
+unsigned int qdetector_cccf_get_seq_len(qdetector_cccf _q)
+{
+    return _q->s_len;
+}
+
+// pointer to sequence
+const void * qdetector_cccf_get_sequence(qdetector_cccf _q)
+{
+    return (const void*) _q->s;
+}
+
+// buffer length
+unsigned int qdetector_cccf_get_buf_len(qdetector_cccf _q)
+{
+    return _q->nfft;
+}
+
+// fractional timing offset estimate
+float qdetector_cccf_get_tau(qdetector_cccf _q)
+{
+    return _q->tau_hat;
+}
+
+// channel gain
+float qdetector_cccf_get_gamma(qdetector_cccf _q)
+{
+    return _q->gamma_hat;
+}
+
+// carrier frequency offset estimate
+float qdetector_cccf_get_dphi(qdetector_cccf _q)
+{
+    return _q->dphi_hat;
+}
+
+// carrier phase offset estimate
+float qdetector_cccf_get_phi(qdetector_cccf _q)
+{
+    return _q->phi_hat;
+}
+
+
+//
+// internal methods
+//
+
+// seek signal (initial detection)
+void qdetector_cccf_execute_seek(qdetector_cccf _q,
+                                 float complex  _x)
+{
+    // write sample to buffer and increment counter
+    _q->buf_time_0[_q->counter++] = _x;
+
+    // accumulate signal magnitude
+    _q->x2_sum_1 += crealf(_x)*crealf(_x) + cimagf(_x)*cimagf(_x);
+
+    if (_q->counter < _q->nfft)
+        return;
+    
+    // reset counter (last half of time buffer)
+    _q->counter = _q->nfft/2;
+
+    // run forward transform
+    fft_execute(_q->fft);
+
+    // compute scaling factor (TODO: use median rather than mean signal level)
+    float g0 = sqrtf(_q->x2_sum_0 + _q->x2_sum_1) * sqrtf((float)(_q->s_len) / (float)(_q->nfft));
+    float g = 1.0f / ( (float)(_q->nfft) * g0 * sqrtf(_q->s2_sum) );
+    
+    // sweep over carrier frequency offset range
+    int offset;
+    unsigned int i;
+    float        rxy_peak   = 0.0f;
+    unsigned int rxy_index  = 0;
+    int          rxy_offset = 0;
+    // NOTE: this offset may be coarse as a fine carrier estimate is computed later
+    for (offset=-_q->range; offset<=_q->range; offset++) {
+
+        // cross-multiply, aligning appropriately
+        for (i=0; i<_q->nfft; i++) {
+            // shifted index
+            unsigned int j = (i + _q->nfft - offset) % _q->nfft;
+
+            _q->buf_freq_1[i] = _q->buf_freq_0[i] * conjf(_q->S[j]);
+        }
+
+        // run inverse transform
+        fft_execute(_q->ifft);
+        
+        // scale output appropriately
+        liquid_vectorcf_mulscalar(_q->buf_time_1, _q->nfft, g, _q->buf_time_1);
+
+#if DEBUG_QDETECTOR
+        // debug output
+        char filename[64];
+        sprintf(filename,"qdetector_out_%u_%d.m", _q->num_transforms, offset+2);
+        FILE * fid = fopen(filename, "w");
+        fprintf(fid,"clear all; close all;\n");
+        fprintf(fid,"nfft = %u;\n", _q->nfft);
+        for (i=0; i<_q->nfft; i++)
+            fprintf(fid,"rxy(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(_q->buf_time_1[i]), cimagf(_q->buf_time_1[i]));
+        fprintf(fid,"figure;\n");
+        fprintf(fid,"t=[0:(nfft-1)];\n");
+        fprintf(fid,"plot(t,abs(rxy));\n");
+        fprintf(fid,"grid on;\n");
+        fprintf(fid,"axis([0 %u 0 1.5]);\n", _q->nfft);
+        fprintf(fid,"[v i] = max(abs(rxy));\n");
+        fprintf(fid,"title(sprintf('peak of %%12.8f at index %%u', v, i));\n");
+        fclose(fid);
+        printf("debug: %s\n", filename);
+#endif
+        // search for peak
+        // TODO: only search over range [-nfft/2, nfft/2)
+        for (i=0; i<_q->nfft; i++) {
+            float rxy_abs = cabsf(_q->buf_time_1[i]);
+            if (rxy_abs > rxy_peak) {
+                rxy_peak   = rxy_abs;
+                rxy_index  = i;
+                rxy_offset = offset;
+            }
+        }
+    }
+
+    // increment number of transforms (debugging)
+    _q->num_transforms++;
+
+    if (rxy_peak > _q->threshold && rxy_index < _q->nfft - _q->s_len) {
+#if DEBUG_QDETECTOR_PRINT
+        printf("*** frame detected! rxy = %12.8f, time index=%u, freq. offset=%d\n", rxy_peak, rxy_index, rxy_offset);
+#endif
+        // update state, reset counter, copy buffer appropriately
+        _q->state = QDETECTOR_STATE_ALIGN;
+        _q->offset = rxy_offset;
+        // TODO: check for edge case where rxy_index is zero (signal already aligned)
+
+        // copy last part of fft input buffer to front
+        memmove(_q->buf_time_0, _q->buf_time_0 + rxy_index, (_q->nfft - rxy_index)*sizeof(float complex));
+        _q->counter = _q->nfft - rxy_index;
+
+        return;
+    }
+#if DEBUG_QDETECTOR_PRINT
+    printf(" no detect, rxy = %12.8f, time index=%u, freq. offset=%d\n", rxy_peak, rxy_index, rxy_offset);
+#endif
+    
+    // copy last half of fft input buffer to front
+    memmove(_q->buf_time_0, _q->buf_time_0 + _q->nfft/2, (_q->nfft/2)*sizeof(float complex));
+
+    // swap accumulated signal levels
+    _q->x2_sum_0 = _q->x2_sum_1;
+    _q->x2_sum_1 = 0.0f;
+}
+
+// align signal in time, compute offset estimates
+void qdetector_cccf_execute_align(qdetector_cccf _q,
+                                  float complex  _x)
+{
+    // write sample to buffer and increment counter
+    _q->buf_time_0[_q->counter++] = _x;
+
+    if (_q->counter < _q->nfft)
+        return;
+
+    //printf("signal is aligned!\n");
+
+    // estimate timing offset
+    fft_execute(_q->fft);
+    // cross-multiply frequency-domain components, aligning appropriately with
+    // estimated FFT offset index due to carrier frequency offset in received signal
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++) {
+        // shifted index
+        unsigned int j = (i + _q->nfft - _q->offset) % _q->nfft;
+        _q->buf_freq_1[i] = _q->buf_freq_0[i] * conjf(_q->S[j]);
+    }
+    fft_execute(_q->ifft);
+    // time aligned to index 0
+    // NOTE: taking the sqrt removes bias in the timing estimate, but messes up gamma estimate
+    float yneg = cabsf(_q->buf_time_1[_q->nfft-1]);  yneg = sqrtf(yneg);
+    float y0   = cabsf(_q->buf_time_1[         0]);  y0   = sqrtf(y0  );
+    float ypos = cabsf(_q->buf_time_1[         1]);  ypos = sqrtf(ypos);
+    // compute timing offset estimate from quadratic polynomial fit
+    //  y = a x^2 + b x + c, [xneg = -1, x0 = 0, xpos = +1]
+    float a     =  0.5f*(ypos + yneg) - y0;
+    float b     =  0.5f*(ypos - yneg);
+    float c     =  y0;
+    _q->tau_hat = -b / (2.0f*a); //-0.5f*(ypos - yneg) / (ypos + yneg - 2*y0);
+    float g_hat   = (a*_q->tau_hat*_q->tau_hat + b*_q->tau_hat + c);
+    _q->gamma_hat = g_hat * g_hat / ((float)(_q->nfft) * _q->s2_sum); // g_hat^2 because of sqrt for yneg/y0/ypos
+
+    // copy buffer to preserve data integrity
+    memmove(_q->buf_time_1, _q->buf_time_0, _q->nfft*sizeof(float complex));
+
+    // estimate carrier frequency offset
+    for (i=0; i<_q->nfft; i++)
+        _q->buf_time_0[i] *= i < _q->s_len ? conjf(_q->s[i]) : 0.0f;
+    fft_execute(_q->fft);
+#if DEBUG_QDETECTOR
+    // debug output
+    char filename[64];
+    sprintf(filename,"qdetector_fft.m");
+    FILE * fid = fopen(filename, "w");
+    fprintf(fid,"clear all; close all;\n");
+    fprintf(fid,"nfft = %u;\n", _q->nfft);
+    for (i=0; i<_q->nfft; i++)
+        fprintf(fid,"V(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(_q->buf_freq_0[i]), cimagf(_q->buf_freq_0[i]));
+    fprintf(fid,"V = fftshift(V) / max(abs(V));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"f=[0:(nfft-1)] - nfft/2;\n");
+    fprintf(fid,"plot(f,abs(V),'-x');\n");
+    fprintf(fid,"grid on;\n");
+    fprintf(fid,"axis([-10 10 0 1.2]);\n");
+    fclose(fid);
+    printf("debug: %s\n", filename);
+#endif
+    // search for peak (NOTE: should be at: _q->offset)
+    // TODO: don't search for peak but just use internal offset
+    float        v0 = 0.0f;
+    unsigned int i0 = 0;
+    for (i=0; i<_q->nfft; i++) {
+        float v_abs = cabsf(_q->buf_freq_0[i]);
+        if (v_abs > v0) {
+            v0 = v_abs;
+            i0 = i;
+        }
+    }
+    // interpolate using quadratic polynomial for carrier frequency estimate
+    unsigned int ineg = (i0 + _q->nfft - 1)%_q->nfft;
+    unsigned int ipos = (i0            + 1)%_q->nfft;
+    float        vneg = cabsf(_q->buf_freq_0[ineg]);
+    float        vpos = cabsf(_q->buf_freq_0[ipos]);
+    a            =  0.5f*(vpos + vneg) - v0;
+    b            =  0.5f*(vpos - vneg);
+    //c            =  v0;
+    float idx    = -b / (2.0f*a); //-0.5f*(vpos - vneg) / (vpos + vneg - 2*v0);
+    float index  = (float)i0 + idx;
+    _q->dphi_hat = (i0 > _q->nfft/2 ? index-(float)_q->nfft : index) * 2*M_PI / (float)(_q->nfft);
+
+    // estimate carrier phase offset
+#if 0
+    // METHOD 1: linear interpolation of phase in FFT output buffer
+    float p0     = cargf(_q->buf_freq_0[ idx < 0 ? ineg : i0   ]);
+    float p1     = cargf(_q->buf_freq_0[ idx < 0 ? i0   : ipos ]);
+    float xp     = idx < 0 ? 1+idx : idx;
+    _q->phi_hat  = (p1-p0)*xp + p0;
+    //printf("v0 = %12.8f, v1 = %12.8f, xp = %12.8f\n", v0, v1, xp);
+#else
+    // METHOD 2: compute metric by de-rotating signal and measuring resulting phase
+    // NOTE: this is possibly more accurate than the above method but might also
+    //       be more computationally complex
+    float complex metric = 0;
+    for (i=0; i<_q->s_len; i++)
+        metric += _q->buf_time_0[i] * cexpf(-_Complex_I*_q->dphi_hat*i);
+    //printf("metric : %12.8f <%12.8f>\n", cabsf(metric), cargf(metric));
+    _q->phi_hat = cargf(metric);
+#endif
+
+#if DEBUG_QDETECTOR_PRINT
+    printf("  y[    -1] : %12.8f\n", yneg);
+    printf("  y[     0] : %12.8f\n", y0  );
+    printf("  y[    +1] : %12.8f\n", ypos);
+    printf("  tau-hat   : %12.8f\n", _q->tau_hat);
+    //printf("  g-hat:    : %12.8f\n", g_hat);
+    printf("  gamma-hat : %12.8f\n", _q->gamma_hat);
+    printf("  v[%4u-1] : %12.8f\n", i0,vneg);
+    printf("  v[%4u+0] : %12.8f\n", i0,v0  );
+    printf("  v[%4u+1] : %12.8f\n", i0,vpos);
+    printf("  dphi-hat  : %12.8f\n", _q->dphi_hat);
+    printf("  phi-hat   : %12.8f\n", _q->phi_hat);
+#endif
+
+    // set flag
+    _q->frame_detected = 1;
+
+    // reset state
+    // copy saved buffer state (last half of buf_time_1 to front half of buf_time_0)
+    memmove(_q->buf_time_0, _q->buf_time_1 + _q->nfft/2, (_q->nfft/2)*sizeof(float complex));
+    _q->state = QDETECTOR_STATE_SEEK;
+    _q->x2_sum_0 = liquid_sumsqcf(_q->buf_time_0, _q->nfft/2);
+    _q->x2_sum_1 = 0;
+    _q->counter = _q->nfft/2;
+}
+
diff --git a/src/framing/src/qpacketmodem.c b/src/framing/src/qpacketmodem.c
new file mode 100644
index 0000000..1332c3e
--- /dev/null
+++ b/src/framing/src/qpacketmodem.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2007 - 2016 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// qpacketmodem.c
+//
+// convenient modulator/demodulator and packet encoder/decoder combination
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+
+#include "liquid.internal.h"
+
+struct qpacketmodem_s {
+    // properties
+    modem           mod_payload;        // payload modulator/demodulator
+    packetizer      p;                  // packet encoder/decoder
+    unsigned int    bits_per_symbol;    // modulator bits/symbol
+    unsigned int    payload_dec_len;    // number of decoded payload bytes
+    unsigned char * payload_enc;        // payload data (encoded bytes)
+    unsigned char * payload_mod;        // payload symbols (modulator output, demod input)
+    unsigned int    payload_enc_len;    // number of encoded payload bytes
+    unsigned int    payload_bit_len;    // number of bits in encoded payload
+    unsigned int    payload_mod_len;    // number of symbols in encoded payload
+};
+
+// create packet encoder
+qpacketmodem qpacketmodem_create()
+{
+    // allocate memory for main object
+    qpacketmodem q = (qpacketmodem) malloc(sizeof(struct qpacketmodem_s));
+
+    // create payload modem (initially QPSK, overridden by properties)
+    q->mod_payload = modem_create(LIQUID_MODEM_QPSK);
+    q->bits_per_symbol = 2;
+    
+    // initial memory allocation for payload
+    q->payload_dec_len = 1;
+    q->p = packetizer_create(q->payload_dec_len,
+                             LIQUID_CRC_NONE,
+                             LIQUID_FEC_NONE,
+                             LIQUID_FEC_NONE);
+
+    // number of bytes in encoded payload
+    q->payload_enc_len = packetizer_get_enc_msg_len(q->p);
+    
+    // number of bits in encoded payload
+    q->payload_bit_len = 8*q->payload_enc_len;
+
+    // number of symbols in encoded payload
+    div_t d = div(q->payload_bit_len, q->bits_per_symbol);
+    q->payload_mod_len = d.quot + (d.rem ? 1 : 0);
+
+    // soft demodulator uses one byte to represent each soft bit
+    q->payload_enc = (unsigned char*) malloc(q->bits_per_symbol*q->payload_mod_len*sizeof(unsigned char));
+
+    // set symbol length appropriately
+    q->payload_mod_len = q->payload_enc_len * q->bits_per_symbol;   // for QPSK
+    q->payload_mod = (unsigned char*) malloc(q->payload_mod_len*sizeof(unsigned char));
+
+    // return pointer to main object
+    return q;
+}
+
+// destroy object, freeing all internal arrays
+void qpacketmodem_destroy(qpacketmodem _q)
+{
+    // free objects
+    packetizer_destroy(_q->p);
+    modem_destroy(_q->mod_payload);
+
+    // free arrays
+    free(_q->payload_enc);
+    free(_q->payload_mod);
+}
+
+// reset object
+void qpacketmodem_reset(qpacketmodem _q)
+{
+    modem_reset(_q->mod_payload);
+}
+
+// print object internals
+void qpacketmodem_print(qpacketmodem _q)
+{
+    printf("qpacketmodem:\n");
+    printf("  check             :   %s\n", crc_scheme_str[packetizer_get_crc(_q->p)][1]);
+    printf("  fec (inner)       :   %s\n", fec_scheme_str[packetizer_get_fec0(_q->p)][1]);
+    printf("  fec (outer)       :   %s\n", fec_scheme_str[packetizer_get_fec1(_q->p)][1]);
+    printf("  modulation scheme :   %s\n", modulation_types[modem_get_scheme(_q->mod_payload)].name);
+    printf("  payload dec len   :   %u\n", _q->payload_dec_len);
+    printf("  payload enc len   :   %u\n", _q->payload_enc_len);
+    printf("  payload bit len   :   %u\n", _q->payload_bit_len);
+    printf("  payload mod len   :   %u\n", _q->payload_mod_len);
+}
+
+//
+int qpacketmodem_configure(qpacketmodem _q,
+                           unsigned int _payload_len,
+                           crc_scheme   _check,
+                           fec_scheme   _fec0,
+                           fec_scheme   _fec1,
+                           int          _ms)
+{
+    // set new decoded message length
+    _q->payload_dec_len = _payload_len;
+
+    // recreate modem object and get new bits per symbol
+    _q->mod_payload = modem_recreate(_q->mod_payload, _ms);
+    _q->bits_per_symbol = modem_get_bps(_q->mod_payload);
+
+    // recreate packetizer object and compute new encoded payload length
+    _q->p = packetizer_recreate(_q->p, _q->payload_dec_len, _check, _fec0, _fec1);
+    _q->payload_enc_len = packetizer_get_enc_msg_len(_q->p);
+
+    // number of bits in encoded payload
+    _q->payload_bit_len = 8*_q->payload_enc_len;
+
+    // number of symbols in encoded payload
+    div_t d = div(_q->payload_bit_len, _q->bits_per_symbol);
+    _q->payload_mod_len = d.quot + (d.rem ? 1 : 0);
+
+    // encoded payload array (leave room for soft-decision decoding)
+    _q->payload_enc = (unsigned char*) realloc(_q->payload_enc,
+            _q->bits_per_symbol*_q->payload_mod_len*sizeof(unsigned char));
+
+    // reallocate memory for modem symbols
+    _q->payload_mod = (unsigned char*) realloc(_q->payload_mod,
+                                               _q->payload_mod_len*sizeof(unsigned char));
+
+    return 0;
+}
+
+// get length of encoded frame in symbols
+unsigned int qpacketmodem_get_frame_len(qpacketmodem _q)
+{
+    return _q->payload_mod_len;
+}
+
+// get unencoded/decoded payload length (bytes)
+unsigned int qpacketmodem_get_payload_len(qpacketmodem _q)
+{
+    // number of decoded payload bytes
+    return _q->payload_dec_len;
+}
+
+unsigned int qpacketmodem_get_crc(qpacketmodem _q)
+{
+    return packetizer_get_crc(_q->p);
+}
+
+unsigned int qpacketmodem_get_fec0(qpacketmodem _q)
+{
+    return packetizer_get_fec0(_q->p);
+}
+
+unsigned int qpacketmodem_get_fec1(qpacketmodem _q)
+{
+    return packetizer_get_fec1(_q->p);
+}
+
+unsigned int qpacketmodem_get_modscheme(qpacketmodem _q)
+{
+    return modem_get_scheme(_q->mod_payload);
+}
+
+// encode packet into un-modulated frame symbol indices
+//  _q          :   qpacketmodem object
+//  _payload    :   unencoded payload bytes
+//  _syms       :   encoded but un-modulated payload symbol indices
+void qpacketmodem_encode_syms(qpacketmodem          _q,
+                              const unsigned char * _payload,
+                              unsigned char *       _syms)
+{
+    // encode payload
+    packetizer_encode(_q->p, _payload, _q->payload_enc);
+
+    // clear internal payload
+    memset(_q->payload_mod, 0x00, _q->payload_mod_len);
+
+    // repack 8-bit payload bytes into 'bps'-bit payload symbols
+    unsigned int bps = _q->bits_per_symbol;
+    unsigned int num_written;
+    liquid_repack_bytes(_q->payload_enc,  8,  _q->payload_enc_len,
+                        _syms,           bps, _q->payload_mod_len,
+                        &num_written);
+    assert(num_written == _q->payload_mod_len);
+}
+
+// decode packet from demodulated frame symbol indices (hard-decision decoding)
+//  _q          :   qpacketmodem object
+//  _syms       :   received hard-decision symbol indices
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_syms(qpacketmodem    _q,
+                             unsigned char * _syms,
+                             unsigned char * _payload)
+{
+    // pack bytes into payload array
+    unsigned int bps = _q->bits_per_symbol;
+    unsigned int num_written;
+    liquid_repack_bytes(_syms,           bps, _q->payload_mod_len,
+                        _q->payload_enc,   8, _q->payload_mod_len, // NOTE: payload_enc allocation is actually payload_mod_len bytes
+                        &num_written);
+    //assert(num_written == _q->payload_enc_len); // NOTE: this will fail for bps in {3,5,6,7}
+
+    // decode payload
+    return packetizer_decode(_q->p, _q->payload_enc, _payload);
+
+}
+
+// decode packet from demodulated frame bits (soft-decision decoding)
+//  _q          :   qpacketmodem object
+//  _bits       :   received soft-decision bits
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_bits(qpacketmodem    _q,
+                             unsigned char * _bits,
+                             unsigned char * _payload)
+{
+    // decode payload (soft-decision)
+    return packetizer_decode_soft(_q->p, _bits, _payload);
+}
+
+// encode and modulate packet into modulated frame samples
+//  _q          :   qpacketmodem object
+//  _payload    :   unencoded payload bytes
+//  _frame      :   encoded/modulated payload symbols
+void qpacketmodem_encode(qpacketmodem          _q,
+                         const unsigned char * _payload,
+                         float complex *       _frame)
+{
+    // encode payload symbols into internal buffer
+    qpacketmodem_encode_syms(_q, _payload, _q->payload_mod);
+
+    // modulate symbols
+    unsigned int i;
+    for (i=0; i<_q->payload_mod_len; i++)
+        modem_modulate(_q->mod_payload, _q->payload_mod[i], &_frame[i]);
+}
+
+// decode packet from modulated frame samples, returning flag if CRC passed
+//  _q          :   qpacketmodem object
+//  _frame      :   encoded/modulated payload symbols
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode(qpacketmodem    _q,
+                        float complex * _frame,
+                        unsigned char * _payload)
+{
+    unsigned int i;
+
+    // demodulate and pack bytes into decoder input buffer
+    unsigned int sym;
+    //memset(_q->payload_enc, 0x00, _q->payload_enc_len*sizeof(unsigned char));
+    for (i=0; i<_q->payload_mod_len; i++) {
+        // demodulate symbol
+        modem_demodulate(_q->mod_payload, _frame[i], &sym);
+
+        // pack decoded symbol into array
+        liquid_pack_array(_q->payload_enc,
+                          _q->payload_enc_len,
+                          i * _q->bits_per_symbol,
+                          _q->bits_per_symbol,
+                          sym);
+    }
+
+    // decode payload, returning flag if decoded payload is valid
+    return packetizer_decode(_q->p, _q->payload_enc, _payload);
+}
+
+// decode packet from modulated frame samples, returning flag if CRC passed
+//  _q          :   qpacketmodem object
+//  _frame      :   encoded/modulated payload symbols
+//  _payload    :   recovered decoded payload bytes
+int qpacketmodem_decode_soft(qpacketmodem    _q,
+                             float complex * _frame,
+                             unsigned char * _payload)
+{
+    unsigned int i;
+
+    // demodulate and pack bytes into decoder input buffer
+    unsigned int sym;
+    //memset(_q->payload_enc, 0x00, _q->payload_enc_len*sizeof(unsigned char));
+    unsigned int n = 0;
+    for (i=0; i<_q->payload_mod_len; i++) {
+        // demodulate symbol
+        modem_demodulate_soft(_q->mod_payload, _frame[i], &sym, _q->payload_enc+n);
+        n += _q->bits_per_symbol;
+    }
+    //printf("received %u bits (expected %u)\n", n, _q->payload_mod_len * _q->bits_per_symbol);
+    assert( n == _q->payload_mod_len * _q->bits_per_symbol);
+
+    // decode payload, returning flag if decoded payload is valid
+    return packetizer_decode_soft(_q->p, _q->payload_enc, _payload);
+}
+
diff --git a/src/framing/src/qpilotgen.c b/src/framing/src/qpilotgen.c
new file mode 100644
index 0000000..a1539c1
--- /dev/null
+++ b/src/framing/src/qpilotgen.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// qpilotgen.c
+//
+// pilot injection
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+struct qpilotgen_s {
+    // properties
+    unsigned int    payload_len;    // number of samples in payload
+    unsigned int    pilot_spacing;  // spacing between pilot symbols
+    unsigned int    num_pilots;     // total number of pilot symbols
+    unsigned int    frame_len;      // total number of frame symbols
+    float complex * pilots;         // pilot sequence
+};
+
+// create packet encoder
+qpilotgen qpilotgen_create(unsigned int _payload_len,
+                           unsigned int _pilot_spacing)
+{
+    // validate input
+    if (_payload_len == 0) {
+        fprintf(stderr,"error: qpilotgen_create(), frame length must be at least 1 symbol\n");
+        exit(1);
+    } else if (_pilot_spacing < 2) {
+        fprintf(stderr,"error: qpilotgen_create(), pilot spacing must be at least 2 symbols\n");
+        exit(1);
+    }
+    unsigned int i;
+
+    // allocate memory for main object
+    qpilotgen q = (qpilotgen) malloc(sizeof(struct qpilotgen_s));
+
+    // set internal properties
+    q->payload_len   = _payload_len;
+    q->pilot_spacing = _pilot_spacing;
+
+    // derived values
+    div_t d = div(q->payload_len,(q->pilot_spacing - 1));
+    q->num_pilots = d.quot + (d.rem ? 1 : 0);
+    q->frame_len  = q->payload_len + q->num_pilots;
+
+    // allocate memory for pilots
+    q->pilots = (float complex*) malloc(q->num_pilots*sizeof(float complex));
+
+    // find appropriate sequence size
+    unsigned int m = liquid_nextpow2(q->num_pilots);
+
+    // generate pilot sequence
+    msequence seq = msequence_create_default(m);
+    for (i=0; i<q->num_pilots; i++) {
+        // generate symbol
+        unsigned int s = msequence_generate_symbol(seq, 2);
+
+        // save modulated symbol
+        float theta = (2 * M_PI * (float)s / 4.0f) + M_PI / 4.0f;
+        q->pilots[i] = cexpf(_Complex_I*theta);
+    }
+    msequence_destroy(seq);
+
+    // return pointer to main object
+    return q;
+}
+
+// recreate packet encoder
+qpilotgen qpilotgen_recreate(qpilotgen    _q,
+                             unsigned int _payload_len,
+                             unsigned int _pilot_spacing)
+{
+    // TODO: only re-generate objects as necessary
+
+    // destroy object
+    if (_q != NULL)
+        qpilotgen_destroy(_q);
+
+    // create new object
+    return qpilotgen_create(_payload_len, _pilot_spacing);
+}
+
+void qpilotgen_destroy(qpilotgen _q)
+{
+    // free arrays
+    free(_q->pilots);
+    
+    // free main object memory
+    free(_q);
+}
+
+void qpilotgen_reset(qpilotgen _q)
+{
+}
+
+void qpilotgen_print(qpilotgen _q)
+{
+    printf("qpilotgen:\n");
+    printf("  payload len   :   %u\n", _q->payload_len);
+    printf("  pilot spacing :   %u\n", _q->pilot_spacing);
+    printf("  num pilots    :   %u\n", _q->num_pilots);
+    printf("  frame len     :   %u\n", _q->frame_len);
+}
+
+// get length of frame in symbols
+unsigned int qpilotgen_get_frame_len(qpilotgen _q)
+{
+    return _q->frame_len;
+}
+
+// encode packet into modulated frame samples
+// TODO: include method with just symbol indices? would be useful for
+//       non-linear modulation types
+void qpilotgen_execute(qpilotgen       _q,
+                       float complex * _payload,
+                       float complex * _frame)
+{
+    unsigned int i;
+    unsigned int n = 0;
+    unsigned int p = 0;
+    for (i=0; i<_q->frame_len; i++) {
+        if ( (i % _q->pilot_spacing)==0 )
+            _frame[i] = _q->pilots[p++];
+        else
+            _frame[i] = _payload[n++];
+    }
+    //printf("n = %u (expected %u)\n", n, _q->payload_len);
+    //printf("p = %u (expected %u)\n", p, _q->num_pilots);
+    assert(n == _q->payload_len);
+    assert(p == _q->num_pilots);
+}
+
diff --git a/src/framing/src/qpilotsync.c b/src/framing/src/qpilotsync.c
new file mode 100644
index 0000000..15bac1d
--- /dev/null
+++ b/src/framing/src/qpilotsync.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// qpilotsync.c
+//
+// pilot injection
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <complex.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_QPILOTSYNC 0
+
+struct qpilotsync_s {
+    // properties
+    unsigned int    payload_len;    // number of samples in payload
+    unsigned int    pilot_spacing;  // spacing between pilot symbols
+    unsigned int    num_pilots;     // total number of pilot symbols
+    unsigned int    frame_len;      // total number of frame symbols
+    float complex * pilots;         // pilot sequence
+
+    unsigned int    nfft;           // FFT size
+    float complex * buf_time;       // FFT time buffer
+    float complex * buf_freq;       // FFT freq buffer
+    fftplan         fft;            // transform object
+
+    float           dphi_hat;       // carrier frequency offset estimate
+    float           phi_hat;        // carrier phase offset estimate
+    float           g_hat;          // gain correction estimate
+};
+
+// create packet encoder
+qpilotsync qpilotsync_create(unsigned int _payload_len,
+                             unsigned int _pilot_spacing)
+{
+    // validate input
+    if (_payload_len == 0) {
+        fprintf(stderr,"error: qpilotsync_create(), frame length must be at least 1 symbol\n");
+        exit(1);
+    } else if (_pilot_spacing < 2) {
+        fprintf(stderr,"error: qpilotsync_create(), pilot spacing must be at least 2 symbols\n");
+        exit(1);
+    }
+    unsigned int i;
+
+    // allocate memory for main object
+    qpilotsync q = (qpilotsync) malloc(sizeof(struct qpilotsync_s));
+
+    // set internal properties
+    q->payload_len   = _payload_len;
+    q->pilot_spacing = _pilot_spacing;
+
+    // derived values
+    div_t d = div(q->payload_len,(q->pilot_spacing - 1));
+    q->num_pilots = d.quot + (d.rem ? 1 : 0);
+    q->frame_len  = q->payload_len + q->num_pilots;
+
+    // allocate memory for pilots
+    q->pilots = (float complex*) malloc(q->num_pilots*sizeof(float complex));
+
+    // find appropriate sequence size
+    unsigned int m = liquid_nextpow2(q->num_pilots);
+
+    // generate pilot sequence
+    msequence seq = msequence_create_default(m);
+    for (i=0; i<q->num_pilots; i++) {
+        // generate symbol
+        unsigned int s = msequence_generate_symbol(seq, 2);
+
+        // save modulated symbol
+        float theta = (2 * M_PI * (float)s / 4.0f) + M_PI / 4.0f;
+        q->pilots[i] = cexpf(_Complex_I*theta);
+    }
+    msequence_destroy(seq);
+
+    // compute fft size and create transform objects
+    q->nfft = 1 << liquid_nextpow2(q->num_pilots + (q->num_pilots>>1));
+    q->buf_time = (float complex*) malloc(q->nfft*sizeof(float complex));
+    q->buf_freq = (float complex*) malloc(q->nfft*sizeof(float complex));
+    q->fft      = fft_create_plan(q->nfft, q->buf_time, q->buf_freq, LIQUID_FFT_FORWARD, 0);
+
+    // reset and return pointer to main object
+    qpilotsync_reset(q);
+    return q;
+}
+
+// recreate packet encoder
+qpilotsync qpilotsync_recreate(qpilotsync   _q,
+                               unsigned int _payload_len,
+                               unsigned int _pilot_spacing)
+{
+    // TODO: only re-generate objects as necessary
+
+    // destroy object
+    if (_q != NULL)
+        qpilotsync_destroy(_q);
+
+    // create new object
+    return qpilotsync_create(_payload_len, _pilot_spacing);
+}
+
+void qpilotsync_destroy(qpilotsync _q)
+{
+    // free arrays
+    free(_q->pilots);
+    free(_q->buf_time);
+    free(_q->buf_freq);
+
+    // destroy objects
+    fft_destroy_plan(_q->fft);
+    
+    // free main object memory
+    free(_q);
+}
+
+void qpilotsync_reset(qpilotsync _q)
+{
+    // clear FFT input buffer
+    unsigned int i;
+    for (i=0; i<_q->nfft; i++)
+        _q->buf_time[i] = 0.0f;
+    
+    // reset estimates
+    _q->dphi_hat = 0.0f;
+    _q->phi_hat  = 0.0f;
+    _q->g_hat    = 1.0f;
+}
+
+void qpilotsync_print(qpilotsync _q)
+{
+    printf("qpilotsync:\n");
+    printf("  payload len   :   %u\n", _q->payload_len);
+    printf("  pilot spacing :   %u\n", _q->pilot_spacing);
+    printf("  num pilots    :   %u\n", _q->num_pilots);
+    printf("  frame len     :   %u\n", _q->frame_len);
+    printf("  nfft          :   %u\n", _q->nfft);
+}
+
+// get length of frame in symbols
+unsigned int qpilotsync_get_frame_len(qpilotsync _q)
+{
+    return _q->frame_len;
+}
+
+// encode packet into modulated frame samples
+// TODO: include method with just symbol indices? would be useful for
+//       non-linear modulation types
+void qpilotsync_execute(qpilotsync      _q,
+                        float complex * _frame,
+                        float complex * _payload)
+{
+    unsigned int i;
+    unsigned int n = 0;
+    unsigned int p = 0;
+
+    // extract pilots and de-rotate with known sequence
+    for (i=0; i<_q->num_pilots; i++) {
+        _q->buf_time[i] = _frame[i*_q->pilot_spacing] * conjf(_q->pilots[i]);
+
+#if DEBUG_QPILOTSYNC
+        printf("(%8.4f,%8.4f) = (%8.4f,%8.4f) * conj(%8.4f,%8.4f)\n",
+            crealf(_q->buf_time[i]),
+            cimagf(_q->buf_time[i]),
+            crealf(_frame[i*_q->pilot_spacing]),
+            cimagf(_frame[i*_q->pilot_spacing]),
+            crealf(_q->pilots[i]),
+            cimagf(_q->pilots[i]));
+#endif
+    }
+
+    // compute frequency offset by computing transform and finding peak
+    fft_execute(_q->fft);
+    unsigned int i0 = 0;
+    float        y0 = 0;
+    for (i=0; i<_q->nfft; i++) {
+#if DEBUG_QPILOTSYNC
+        printf("X(%3u) = %12.8f + 1i*%12.8f; %% %12.8f\n",
+                i+1, crealf(_q->buf_freq[i]), cimagf(_q->buf_freq[i]), cabsf(_q->buf_freq[i]));
+#endif
+        if (i==0 || cabsf(_q->buf_freq[i]) > y0) {
+            i0 = i;
+            y0 = cabsf(_q->buf_freq[i]);
+        }
+    }
+
+    // interpolate and recover frequency
+    unsigned int ineg = (i0 + _q->nfft - 1) % _q->nfft;
+    unsigned int ipos = (i0 +            1) % _q->nfft;
+    float        ypos = cabsf(_q->buf_freq[ipos]);
+    float        yneg = cabsf(_q->buf_freq[ineg]);
+    float        a    =  0.5f*(ypos + yneg) - y0;
+    float        b    =  0.5f*(ypos - yneg);
+    //float        c    =  y0;
+    float        idx  = -b / (2.0f*a); //-0.5f*(ypos - yneg) / (ypos + yneg - 2*y0);
+    float index = (float)i0 + idx;
+    _q->dphi_hat = (i0 > _q->nfft/2 ? index-(float)_q->nfft : index) * 2*M_PI / (float)(_q->nfft * _q->pilot_spacing);
+#if DEBUG_QPILOTSYNC
+    printf("X[%3u] = %12.8f <%12.8f>\n", ineg, yneg, cargf(_q->buf_freq[ineg]));
+    printf("X[%3u] = %12.8f <%12.8f>\n", i0,   y0,   cargf(_q->buf_freq[i0]));
+    printf("X[%3u] = %12.8f <%12.8f>\n", ipos, ypos, cargf(_q->buf_freq[ipos]));
+    printf("yneg  = %12.8f;\n", yneg);
+    printf("ypos  = %12.8f;\n", ypos);
+    printf("y0    = %12.8f;\n", y0);
+    printf("interpolated peak at %12.8f (%u + %12.8f)\n", index, i0, idx);
+#endif
+
+    // estimate carrier phase offset
+#if 0
+    // METHOD 1: linear interpolation of phase in FFT output buffer
+    float v0 = cargf(_q->buf_freq[ idx < 0 ? ineg : i0   ]);
+    float v1 = cargf(_q->buf_freq[ idx < 0 ? i0   : ipos ]);
+    float xp = idx < 0 ? 1+idx : idx;
+    float phi_hat  = (v1-v0)*xp + v0;
+    //printf("v0 = %12.8f, v1 = %12.8f, xp = %12.8f\n", v0, v1, xp);
+
+    // channel gain: use quadratic interpolation of FFT amplitude to find peak
+    //               correlation output in the frequency domain
+    float g_hat = (a*idx*idx + b*idx + c) / (float)(_q->num_pilots);
+#else
+    // METHOD 2: compute metric by de-rotating pilots and measuring resulting phase
+    // NOTE: this is possibly more accurate than the above method but might also
+    //       be more computationally complex
+    float complex metric = 0;
+    for (i=0; i<_q->num_pilots; i++)
+        metric += _q->buf_time[i] * cexpf(-_Complex_I*_q->dphi_hat*i*(float)(_q->pilot_spacing));
+    //printf("metric : %12.8f <%12.8f>\n", cabsf(metric), cargf(metric));
+    _q->phi_hat = cargf(metric);
+    _q->g_hat   = cabsf(metric) / (float)(_q->num_pilots);
+#endif
+
+#if DEBUG_QPILOTSYNC
+    // print estimates of carrier frequency, phase, gain
+    printf("dphi-hat    :   %12.8f\n", _q->dphi_hat);
+    printf(" phi-hat    :   %12.8f\n",  _q->phi_hat);
+    printf("   g-hat    :   %12.8f\n",    _q->g_hat);
+#endif
+
+    // frequency correction
+    float g = 1.0f / _q->g_hat;
+
+    // recover frame symbols
+    for (i=0; i<_q->frame_len; i++) {
+        if ( (i % _q->pilot_spacing)==0 )
+            p++;
+        else
+            _payload[n++] = g * _frame[i] * cexpf(-_Complex_I*(_q->dphi_hat*i + _q->phi_hat));
+    }
+#if DEBUG_QPILOTSYNC
+    printf("n = %u (expected %u)\n", n, _q->payload_len);
+    printf("p = %u (expected %u)\n", p, _q->num_pilots);
+    assert(n == _q->payload_len);
+    assert(p == _q->num_pilots);
+#endif
+}
+
+// get estimates
+float qpilotsync_get_dphi(qpilotsync _q)
+{
+    return _q->dphi_hat;
+}
+
+float qpilotsync_get_phi(qpilotsync _q)
+{
+    return _q->phi_hat;
+}
+
+float qpilotsync_get_gain(qpilotsync _q)
+{
+    return _q->g_hat;
+}
+
+
diff --git a/src/framing/src/symstream.c b/src/framing/src/symstream.c
new file mode 100644
index 0000000..a7652e3
--- /dev/null
+++ b/src/framing/src/symstream.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Symbol streaming generator
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+// internal structure
+struct SYMSTREAM(_s) {
+    int             filter_type;    // filter type (e.g. LIQUID_RNYQUIST_RKAISER)
+    unsigned int    k;              // samples/symbol
+    unsigned int    m;              // filter semi-length
+    float           beta;           // filter excess bandwidth
+    int             mod_scheme;     // demodulator
+    MODEM()         mod;            // modulator
+    FIRINTERP()     interp;         // interpolator
+    TO *            buf;            // output buffer
+    unsigned int    buf_index;      // output buffer sample index
+};
+
+// create symstream object using default parameters
+SYMSTREAM() SYMSTREAM(_create)()
+{
+    return SYMSTREAM(_create_linear)(LIQUID_FIRFILT_ARKAISER,
+                                     2,     // samples/symbol
+                                     7,     // filter delay
+                                     0.3f,  // filter excess bandwidth
+                                     LIQUID_MODEM_QPSK);
+}
+
+// create symstream object with linear modulation
+//  _ftype  : filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _k      : samples per symbol
+//  _m      : filter delay (symbols)
+//  _beta   : filter excess bandwidth
+//  _ms     : modulation scheme (e.g. LIQUID_MODEM_QPSK)
+SYMSTREAM() SYMSTREAM(_create_linear)(int          _ftype,
+                                      unsigned int _k,
+                                      unsigned int _m,
+                                      float        _beta,
+                                      int          _ms)
+{
+    // validate input
+    if (_k < 2) {
+        fprintf(stderr,"error: symstream%s_create(), samples/symbol must be at least 2\n", EXTENSION);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: symstream%s_create(), filter delay must be greater than zero\n", EXTENSION);
+        exit(1);
+    } else if (_beta <= 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: symstream%s_create(), filter excess bandwidth must be in (0,1]\n", EXTENSION);
+        exit(1);
+    } else if (_ms == LIQUID_MODEM_UNKNOWN || _ms >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: symstream%s_create(), invalid modulation scheme\n", EXTENSION);
+        exit(1);
+    }
+
+    // allocate memory for main object
+    SYMSTREAM() q = (SYMSTREAM()) malloc( sizeof(struct SYMSTREAM(_s)) );
+
+    // set input parameters
+    q->filter_type = _ftype;
+    q->k           = _k;
+    q->m           = _m;
+    q->beta        = _beta;
+    q->mod_scheme  = _ms;
+
+    // modulator
+    q->mod = MODEM(_create)(q->mod_scheme);
+
+    // interpolator
+    q->interp = FIRINTERP(_create_prototype)(q->filter_type, q->k, q->m, q->beta, 0);
+
+    // sample buffer
+    q->buf = (TO*) malloc(q->k*sizeof(TO));
+
+    // reset and return main object
+    SYMSTREAM(_reset)(q);
+    return q;
+}
+
+// destroy symstream object, freeing all internal memory
+void SYMSTREAM(_destroy)(SYMSTREAM() _q)
+{
+    // destroy objects
+    MODEM    (_destroy)(_q->mod);
+    FIRINTERP(_destroy)(_q->interp);
+
+    free(_q->buf);
+
+    // free main object
+    free(_q);
+}
+
+// print symstream object's parameters
+void SYMSTREAM(_print)(SYMSTREAM() _q)
+{
+    printf("symstream_%s:\n", EXTENSION);
+}
+
+// reset symstream internal state
+void SYMSTREAM(_reset)(SYMSTREAM() _q)
+{
+    // reset objects and counter
+    MODEM(_reset)(_q->mod);
+    FIRINTERP(_reset)(_q->interp);
+    _q->buf_index = 0;
+}
+
+// fill buffer with samples
+void SYMSTREAM(_fill_buffer)(SYMSTREAM() _q)
+{
+    // generate random symbol
+    unsigned int sym = MODEM(_gen_rand_sym)(_q->mod);
+
+    // modulate
+    TO v;
+    MODEM(_modulate)(_q->mod, sym, &v);
+
+    // interpolate
+    FIRINTERP(_execute)(_q->interp, v, _q->buf);
+}
+
+// write block of samples to output buffer
+//  _q      : synchronizer object
+//  _buf    : output buffer [size: _buf_len x 1]
+//  _buf_len: output buffer size
+void SYMSTREAM(_write_samples)(SYMSTREAM()  _q,
+                               TO *         _buf,
+                               unsigned int _buf_len)
+{
+    unsigned int i;
+    for (i=0; i<_buf_len; i++) {
+        // check to see if buffer needs samples
+        if (_q->buf_index==0)
+            SYMSTREAM(_fill_buffer)(_q);
+
+        // write output sample from internal buffer
+        _buf[i] = _q->buf[_q->buf_index];
+
+        // increment internal index
+        _q->buf_index = (_q->buf_index + 1) % _q->k;
+    }
+}
+
diff --git a/src/framing/src/symstreamcf.c b/src/framing/src/symstreamcf.c
new file mode 100644
index 0000000..81ddc1f
--- /dev/null
+++ b/src/framing/src/symstreamcf.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION           "cf"
+
+#define TO                  float complex   // output type
+#define T                   float           // primitive type
+
+#define TO_COMPLEX          1
+#define T_COMPLEX           0
+
+// object references
+#define SYMSTREAM(name)     LIQUID_CONCAT(symstreamcf,name)
+#define MODEM(name)         LIQUID_CONCAT(modem,name)
+#define FIRINTERP(name)     LIQUID_CONCAT(firinterp_crcf,name)
+
+// source files
+#include "symstream.c"
+
diff --git a/src/framing/src/symtrack.c b/src/framing/src/symtrack.c
new file mode 100644
index 0000000..3218db4
--- /dev/null
+++ b/src/framing/src/symtrack.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Symbol tracker/synchronizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#define DEBUG_SYMTRACK           0
+#define DEBUG_SYMTRACK_PRINT     0
+#define DEBUG_SYMTRACK_FILENAME  "symtrack_internal_debug.m"
+#define DEBUG_BUFFER_LEN        (1024)
+
+//
+// forward declaration of internal methods
+//
+
+// internal structure
+struct SYMTRACK(_s) {
+    // parameters
+    int             filter_type;        // filter type (e.g. LIQUID_RNYQUIST_RKAISER)
+    unsigned int    k;                  // samples/symbol
+    unsigned int    m;                  // filter semi-length
+    float           beta;               // filter excess bandwidth
+    int             mod_scheme;         // demodulator
+
+    // automatic gain control
+    AGC()           agc;                // agc object
+    float           agc_bandwidth;      // agc bandwidth
+
+    // symbol timing recovery
+    SYMSYNC()       symsync;            // symbol timing recovery object
+    float           symsync_bandwidth;  // symsync loop bandwidth
+    TO              symsync_buf[8];     // symsync output buffer
+    unsigned int    symsync_index;      // symsync output sample index
+
+    // equalizer/decimator
+    EQLMS()         eq;                 // equalizer (LMS)
+    unsigned int    eq_len;             // equalizer length
+    float           eq_bandwidth;       // equalizer bandwidth
+
+    // nco/phase-locked loop
+    NCO()           nco;                // nco (carrier recovery)
+    float           pll_bandwidth;      // phase-locked loop bandwidth
+
+    // demodulator
+    MODEM()         demod;              // linear modem demodulator
+
+    // state and counters
+    unsigned int    num_syms_rx;        // number of symbols recovered
+};
+
+// create symtrack object with basic parameters
+//  _ftype  : filter type (e.g. LIQUID_RNYQUIST_RRC)
+//  _k      : samples per symbol
+//  _m      : filter delay (symbols)
+//  _beta   : filter excess bandwidth
+//  _ms     : modulation scheme (e.g. LIQUID_MODEM_QPSK)
+SYMTRACK() SYMTRACK(_create)(int          _ftype,
+                             unsigned int _k,
+                             unsigned int _m,
+                             float        _beta,
+                             int          _ms)
+{
+    // validate input
+    if (_m == 0) {
+        fprintf(stderr,"error: symtrack_%s_create(), filter delay must be greater than zero\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_beta <= 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: symtrack_%s_create(), filter excess bandwidth must be in (0,1]\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_ms == LIQUID_MODEM_UNKNOWN || _ms >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: symtrack_%s_create(), invalid modulation scheme\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // allocate memory for main object
+    SYMTRACK() q = (SYMTRACK()) malloc( sizeof(struct SYMTRACK(_s)) );
+
+    // set input parameters
+    q->filter_type = _ftype;
+    q->k           = _k;
+    q->m           = _m;
+    q->beta        = _beta;
+    q->mod_scheme  = _ms == LIQUID_MODEM_UNKNOWN ? LIQUID_MODEM_BPSK : _ms;
+
+    // create automatic gain control
+    q->agc = AGC(_create)();
+    
+    // create symbol synchronizer (output rate: 2 samples per symbol)
+    if (q->filter_type == LIQUID_FIRFILT_UNKNOWN)
+        q->symsync = SYMSYNC(_create_kaiser)(q->k, q->m, 0.9f, 16);
+    else
+        q->symsync = SYMSYNC(_create_rnyquist)(q->filter_type, q->k, q->m, q->beta, 16);
+    SYMSYNC(_set_output_rate)(q->symsync, 2);
+
+    // create equalizer as default low-pass filter with integer symbol delay (2 samples/symbol)
+    q->eq_len = 2 * 4 + 1;
+    q->eq = EQLMS(_create_lowpass)(q->eq_len,0.45f);
+
+    // nco and phase-locked loop
+    q->nco = NCO(_create)(LIQUID_VCO);
+
+    // demodulator
+    q->demod = MODEM(_create)(q->mod_scheme);
+
+    // set default bandwidth
+    SYMTRACK(_set_bandwidth)(q, 0.1f);
+
+    // reset and return main object
+    SYMTRACK(_reset)(q);
+    return q;
+}
+
+// create symtrack object using default parameters
+SYMTRACK() SYMTRACK(_create_default)()
+{
+    return SYMTRACK(_create)(LIQUID_FIRFILT_ARKAISER,
+                             2,     // samples/symbol
+                             7,     // filter delay
+                             0.3f,  // filter excess bandwidth
+                             LIQUID_MODEM_QPSK);
+}
+
+
+// destroy symtrack object, freeing all internal memory
+void SYMTRACK(_destroy)(SYMTRACK() _q)
+{
+    // destroy objects
+    AGC    (_destroy)(_q->agc);
+    SYMSYNC(_destroy)(_q->symsync);
+    EQLMS  (_destroy)(_q->eq);
+    NCO    (_destroy)(_q->nco);
+    MODEM  (_destroy)(_q->demod);
+
+    // free main object
+    free(_q);
+}
+
+// print symtrack object's parameters
+void SYMTRACK(_print)(SYMTRACK() _q)
+{
+    printf("symtrack_%s:\n", EXTENSION_FULL);
+}
+
+// reset symtrack internal state
+void SYMTRACK(_reset)(SYMTRACK() _q)
+{
+    // reset objects
+
+    // reset internal counters
+    _q->symsync_index = 0;
+    _q->num_syms_rx = 0;
+}
+
+// set symtrack modulation scheme
+void SYMTRACK(_set_modscheme)(SYMTRACK() _q,
+                              int        _ms)
+{
+    // validate input
+    if (_ms == LIQUID_MODEM_UNKNOWN || _ms >= LIQUID_MODEM_NUM_SCHEMES) {
+        fprintf(stderr,"error: symtrack_%s_set_modscheme(), invalid/unsupported modulation scheme\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // set internal modulation scheme
+    _q->mod_scheme = _ms;
+
+    // re-create modem
+    _q->demod = MODEM(_recreate)(_q->demod, _q->mod_scheme);
+}
+
+// set symtrack internal bandwidth
+void SYMTRACK(_set_bandwidth)(SYMTRACK() _q,
+                              float      _bw)
+{
+    // validate input
+    if (_bw < 0) {
+        fprintf(stderr,"error: symtrack_%s_set_bandwidth(), bandwidth must be in [0,1]\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // set bandwidths accordingly
+    // TODO: set bandwidths based on input bandwidth
+    float agc_bandwidth     = 0.02f;
+    float symsync_bandwidth = 0.001f;
+    float eq_bandwidth      = 0.02f;
+    float pll_bandwidth     = 0.001f;
+
+    // automatic gain control
+    AGC(_set_bandwidth)(_q->agc, agc_bandwidth);
+
+    // symbol timing recovery
+    SYMSYNC(_set_lf_bw)(_q->symsync, symsync_bandwidth);
+
+    // equalizer
+    EQLMS(_set_bw)(_q->eq, eq_bandwidth);
+    
+    // phase-locked loop
+    NCO(_pll_set_bandwidth)(_q->nco, pll_bandwidth);
+}
+
+// adjust internal nco by requested phase
+void SYMTRACK(_adjust_phase)(SYMTRACK() _q,
+                             T          _dphi)
+{
+    // adjust internal nco phase
+    NCO(_adjust_phase)(_q->nco, _dphi);
+}
+
+// execute synchronizer on single input sample
+//  _q      : synchronizer object
+//  _x      : input data sample
+//  _y      : output data array
+//  _ny     : number of samples written to output buffer
+void SYMTRACK(_execute)(SYMTRACK()     _q,
+                        TI             _x,
+                        TO *           _y,
+                        unsigned int * _ny)
+{
+    TO v;   // output sample
+    unsigned int i;
+    unsigned int num_outputs = 0;
+
+    // run sample through automatic gain control
+    AGC(_execute)(_q->agc, _x, &v);
+
+    // symbol synchronizer
+    unsigned int nw = 0;
+    SYMSYNC(_execute)(_q->symsync, &v, 1, _q->symsync_buf, &nw);
+
+    // process each output sample
+    for (i=0; i<nw; i++) {
+        // update phase-locked loop
+        NCO(_step)(_q->nco);
+        nco_crcf_mix_down(_q->nco, _q->symsync_buf[i], &v);
+
+        // equalizer/decimator
+        EQLMS(_push)(_q->eq, v);
+
+        // decimate result, noting that symsync outputs at exactly 2 samples/symbol
+        _q->symsync_index++;
+        if ( !(_q->symsync_index % 2) )
+            continue;
+
+        // increment number of symbols received
+        _q->num_syms_rx++;
+
+        // compute equalizer output
+        TO d_hat;
+        EQLMS(_execute)(_q->eq, &d_hat);
+
+        // demodulate result, apply phase correction
+        unsigned int sym_out;
+        MODEM(_demodulate)(_q->demod, d_hat, &sym_out);
+        float phase_error = MODEM(_get_demodulator_phase_error)(_q->demod);
+
+        // update equalizer independent of the signal: estimate error
+        // assuming constant modulus signal
+        // TODO: use decision-directed feedback when modulation scheme is known
+        // TODO: check lock conditions of previous object to determine when to run equalizer
+        if (_q->num_syms_rx > 200)
+            EQLMS(_step)(_q->eq, d_hat/cabsf(d_hat), d_hat);
+
+        // update pll
+        NCO(_pll_step)(_q->nco, phase_error);
+
+        // save result to output
+        _y[num_outputs++] = d_hat;
+    }
+
+#if DEBUG_SYMTRACK
+    printf("symsync wrote %u samples, %u outputs\n", nw, num_outputs);
+#endif
+
+    //
+    *_ny = num_outputs;
+}
+
+// execute synchronizer on input data array
+//  _q      : synchronizer object
+//  _x      : input data array
+//  _nx     : number of input samples
+//  _y      : output data array
+//  _ny     : number of samples written to output buffer
+void SYMTRACK(_execute_block)(SYMTRACK()     _q,
+                              TI *           _x,
+                              unsigned int   _nx,
+                              TO *           _y,
+                              unsigned int * _ny)
+{
+    //
+    unsigned int i;
+    unsigned int num_written = 0;
+
+    //
+    for (i=0; i<_nx; i++) {
+        unsigned int nw = 0;
+        SYMTRACK(_execute)(_q, _x[i], &_y[num_written], &nw);
+
+        num_written += nw;
+    }
+
+    //
+    *_ny = num_written;
+}
+
diff --git a/src/framing/src/symtrack_cccf.c b/src/framing/src/symtrack_cccf.c
new file mode 100644
index 0000000..c5480a6
--- /dev/null
+++ b/src/framing/src/symtrack_cccf.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Framing API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "cccf"
+
+#define PRINTVAL(x)         printf("%12.4e + j%12.4e", crealf(x), cimagf(x))
+
+#define T                   float
+#define TO                  float complex
+#define TC                  float complex
+#define TI                  float complex
+#define ABS(X)              cabsf(X)
+
+// object references
+#define SYMTRACK(name)      LIQUID_CONCAT(symtrack_cccf,name)
+#define AGC(name)           LIQUID_CONCAT(agc_crcf,name)
+#define SYMSYNC(name)       LIQUID_CONCAT(symsync_crcf,name)
+#define EQLMS(name)         LIQUID_CONCAT(eqlms_cccf,name)
+#define NCO(name)           LIQUID_CONCAT(nco_crcf,name)
+#define MODEM(name)         LIQUID_CONCAT(modem,name)
+
+#define TO_COMPLEX          1
+#define TC_COMPLEX          1
+#define TI_COMPLEX          1
+
+// source files
+#include "symtrack.c"
+
diff --git a/src/framing/tests/bpacketsync_autotest.c b/src/framing/tests/bpacketsync_autotest.c
new file mode 100644
index 0000000..334e12d
--- /dev/null
+++ b/src/framing/tests/bpacketsync_autotest.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+static int bpacketsync_autotest_callback(unsigned char *  _payload,
+                                         int              _payload_valid,
+                                         unsigned int     _payload_len,
+                                         framesyncstats_s _stats,
+                                         void *           _userdata)
+{
+    if (!_payload_valid)
+        return 0;
+
+    unsigned int * num_packets_found = (unsigned int *) _userdata;
+
+    *num_packets_found += 1;
+
+    return 0;
+}
+
+// 
+// AUTOTEST: bpacketsync
+//
+void autotest_bpacketsync()
+{
+    // options
+    unsigned int num_packets = 50;          // number of packets to encode
+    unsigned int dec_msg_len = 64;          // original data message length
+    crc_scheme check = LIQUID_CRC_32;       // data integrity check
+    fec_scheme fec0 = LIQUID_FEC_HAMMING74; // inner code
+    fec_scheme fec1 = LIQUID_FEC_NONE;      // outer code
+
+    // create packet generator
+    bpacketgen pg = bpacketgen_create(0, dec_msg_len, check, fec0, fec1);
+    if (liquid_autotest_verbose)
+        bpacketgen_print(pg);
+
+    // compute packet length
+    unsigned int enc_msg_len = bpacketgen_get_packet_len(pg);
+
+    // initialize arrays
+    unsigned char msg_org[dec_msg_len]; // original message
+    unsigned char msg_enc[enc_msg_len]; // encoded message
+
+    unsigned int num_packets_found=0;
+
+    // create packet synchronizer
+    bpacketsync ps = bpacketsync_create(0, bpacketsync_autotest_callback, (void*)&num_packets_found);
+
+    unsigned int i;
+    unsigned int n;
+    for (n=0; n<num_packets; n++) {
+        // initialize original data message
+        for (i=0; i<dec_msg_len; i++)
+            msg_org[i] = rand() % 256;
+
+        // encode packet
+        bpacketgen_encode(pg,msg_org,msg_enc);
+
+        // push packet through synchronizer
+        bpacketsync_execute(ps, msg_enc, enc_msg_len);
+    }
+
+    // count number of packets
+    if (liquid_autotest_verbose)
+        printf("found %u / %u packets\n", num_packets_found, num_packets);
+
+    CONTEND_EQUALITY( num_packets_found, num_packets );
+
+    // clean up allocated objects
+    bpacketgen_destroy(pg);
+    bpacketsync_destroy(ps);
+}
+
diff --git a/src/framing/tests/bsync_autotest.c b/src/framing/tests/bsync_autotest.c
new file mode 100644
index 0000000..bf24462
--- /dev/null
+++ b/src/framing/tests/bsync_autotest.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: bsync_rrrf/simple correlation
+//
+void autotest_bsync_rrrf_15()
+{
+    // generate sequence (15-bit msequence)
+    float h[15] = {
+         1.0,  1.0,  1.0,  1.0, 
+        -1.0,  1.0, -1.0,  1.0, 
+         1.0, -1.0, -1.0,  1.0, 
+        -1.0, -1.0, -1.0
+    };
+    float tol = 1e-3f;
+
+    // generate synchronizer
+    bsync_rrrf fs = bsync_rrrf_create(15,h);
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    float rxy;
+
+    // fill buffer with sequence
+    for (i=0; i<15; i++)
+        bsync_rrrf_correlate(fs,h[i],&rxy);
+
+    // correlation should be 1.0
+    CONTEND_DELTA( rxy, 1.0f, tol );
+
+    // all other cross-correlations should be exactly -1/15
+    for (i=0; i<14; i++) {
+        bsync_rrrf_correlate(fs,h[i],&rxy);
+        CONTEND_DELTA( rxy, -1.0f/15.0f, tol );
+    }
+
+    // clean it up
+    bsync_rrrf_destroy(fs);
+}
+
+
+// 
+// AUTOTEST: bsync_crcf/simple correlation
+//
+void autotest_bsync_crcf_15()
+{
+    // generate sequence (15-bit msequence)
+    float h[15] = {
+         1.0,  1.0,  1.0,  1.0, 
+        -1.0,  1.0, -1.0,  1.0, 
+         1.0, -1.0, -1.0,  1.0, 
+        -1.0, -1.0, -1.0
+    };
+    float tol = 1e-3f;
+
+    // generate synchronizer
+    bsync_crcf fs = bsync_crcf_create(15,h);
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    float complex rxy;
+
+    // fill buffer with sequence
+    for (i=0; i<15; i++)
+        bsync_crcf_correlate(fs,h[i],&rxy);
+
+    // correlation should be 1.0  + j*(-1/15)
+    CONTEND_DELTA( crealf(rxy),  1.0f,       tol );
+    CONTEND_DELTA( cimagf(rxy), -1.0f/15.0f, tol );
+
+    // all other cross-correlations should be exactly -1/15
+    for (i=0; i<14; i++) {
+        bsync_crcf_correlate(fs,h[i],&rxy);
+        CONTEND_DELTA( crealf(rxy), -1.0f/15.0f, tol );
+        CONTEND_DELTA( cimagf(rxy), -1.0f/15.0f, tol );
+    }
+
+    // clean it up
+    bsync_crcf_destroy(fs);
+}
+
+// 
+// AUTOTEST: bsync_crcf/simple correlation with phase
+//           offset
+//
+void xautotest_bsync_crcf_phase_15()
+{
+    // generate sequence (15-bit msequence)
+    float h[15] = {
+         1.0,  1.0,  1.0,  1.0, 
+        -1.0,  1.0, -1.0,  1.0, 
+         1.0, -1.0, -1.0,  1.0, 
+        -1.0, -1.0, -1.0
+    };
+    float tol = 1e-3f;
+    float theta = 0.3f;
+
+    // generate synchronizer
+    bsync_crcf fs = bsync_crcf_create(15,h);
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    float complex rxy;
+
+    // fill buffer with sequence
+    for (i=0; i<15; i++)
+        bsync_crcf_correlate(fs,h[i]*cexpf(_Complex_I*theta),&rxy);
+
+    // correlation should be 1.0
+    CONTEND_DELTA( crealf(rxy), 1.0f*cosf(theta), tol );
+    CONTEND_DELTA( cimagf(rxy), 1.0f*sinf(theta), tol );
+
+    // all other cross-correlations should be exactly -1/15
+    for (i=0; i<14; i++) {
+        bsync_crcf_correlate(fs,h[i]*cexpf(_Complex_I*theta),&rxy);
+        CONTEND_DELTA( crealf(rxy), -1.0f/15.0f*cosf(theta), tol );
+        CONTEND_DELTA( cimagf(rxy), -1.0f/15.0f*sinf(theta), tol );
+    }
+
+    // clean it up
+    bsync_crcf_destroy(fs);
+}
+
diff --git a/src/framing/tests/detector_autotest.c b/src/framing/tests/detector_autotest.c
new file mode 100644
index 0000000..319c518
--- /dev/null
+++ b/src/framing/tests/detector_autotest.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _n      :   sequence length
+//  _dt     :   fractional sample offset
+//  _dphi   :   carrier frequency offset
+//  _gamma  :   gain
+void detector_cccf_runtest(unsigned int _n,
+                           float        _dt,
+                           float        _dphi);
+
+//
+// AUTOTESTS
+//
+
+void autotest_detector_cccf_n64()   { detector_cccf_runtest(  64, 0.2f, 0.01f); }
+void autotest_detector_cccf_n83()   { detector_cccf_runtest(  83, 0.2f, 0.01f); }
+
+void autotest_detector_cccf_n128()  { detector_cccf_runtest( 128, 0.2f, 0.01f); }
+void autotest_detector_cccf_n167()  { detector_cccf_runtest( 167, 0.2f, 0.01f); }
+
+void autotest_detector_cccf_n256()  { detector_cccf_runtest( 256, 0.2f, 0.01f); }
+void autotest_detector_cccf_n335()  { detector_cccf_runtest( 335, 0.2f, 0.01f); }
+
+void autotest_detector_cccf_n512()  { detector_cccf_runtest( 512, 0.2f, 0.01f); }
+void autotest_detector_cccf_n671()  { detector_cccf_runtest( 671, 0.2f, 0.01f); }
+
+void autotest_detector_cccf_n1024() { detector_cccf_runtest(1024, 0.2f, 0.01f); }
+void autotest_detector_cccf_n1341() { detector_cccf_runtest(1341, 0.2f, 0.01f); }
+
+// autotest helper function
+//  _n      :   sequence length
+//  _dt     :   fractional sample offset
+//  _dphi   :   carrier frequency offset
+void detector_cccf_runtest(unsigned int _n,
+                           float        _dt,
+                           float        _dphi)
+{
+    // TODO: validate input
+
+    unsigned int i;
+
+    // fixed values
+    float noise_floor = -80.0f;     // noise floor [dB]
+    float SNRdB       =  30.0f;     // signal-to-noise ratio [dB]
+    unsigned int m    =  11;        // resampling filter semi-length
+    float threshold   =  0.3f;      // detection threshold
+
+    // derived values
+    unsigned int num_samples = _n + 2*m + 1;
+    float nstd = powf(10.0f, noise_floor/20.0f);
+    float gamma = powf(10.0f, (SNRdB + noise_floor)/20.0f);
+    float delay = (float)(_n + m) + _dt;    // expected delay
+
+    // arrays
+    float complex s[_n];            // synchronization pattern (samples)
+    float complex x[num_samples];   // resampled signal with noise and offsets
+
+    // generate synchronization pattern (two samples per symbol)
+    unsigned int n2 = (_n - (_n%2)) / 2;    // n2 = floor(n/2)
+    unsigned int mm = liquid_nextpow2(n2);  // mm = ceil( log2(n2) )
+    msequence ms = msequence_create_default(mm);
+    float complex v = 0.0f;
+    for (i=0; i<_n; i++) {
+        if ( (i%2)==0 )
+            v = msequence_advance(ms) ? 1.0f : -1.0f;
+        s[i] = v;
+    }
+    msequence_destroy(ms);
+
+    // create fractional sample interpolator
+    firfilt_crcf finterp = firfilt_crcf_create_kaiser(2*m+1, 0.45f, 40.0f, _dt);
+
+    // generate sequence
+    for (i=0; i<num_samples; i++) {
+        // add fractional sample timing offset
+        if (i < _n) firfilt_crcf_push(finterp, s[i]);
+        else        firfilt_crcf_push(finterp, 0.0f);
+
+        // compute output
+        firfilt_crcf_execute(finterp, &x[i]);
+
+        // add channel gain
+        x[i] *= gamma;
+
+        // add carrier offset
+        x[i] *= cexpf(_Complex_I*_dphi*i);
+
+        // add noise
+        x[i] += nstd * ( randnf() + _Complex_I*randnf() ) * M_SQRT1_2;
+    }
+    
+    // destroy fractional sample interpolator
+    firfilt_crcf_destroy(finterp);
+
+    // create detector
+    detector_cccf sync = detector_cccf_create(s, _n, threshold, 2*_dphi);
+    
+    // push signal through detector
+    float tau_hat   = 0.0f;     // fractional sample offset estimate
+    float dphi_hat  = 0.0f;     // carrier offset estimate
+    float gamma_hat = 1.0f;     // signal level estimate (linear)
+    float delay_hat = 0.0f;     // total delay offset estimate
+    int signal_detected = 0;    // signal detected flag
+    for (i=0; i<num_samples; i++) {
+        
+        // correlate
+        int detected = detector_cccf_correlate(sync, x[i], &tau_hat, &dphi_hat, &gamma_hat);
+
+        if (detected) {
+            signal_detected = 1;
+            delay_hat = (float)i + (float)tau_hat;
+            if (liquid_autotest_verbose) {
+                printf("****** preamble found, tau_hat=%8.6f, dphi_hat=%8.6f, gamma_hat=%8.6f\n",
+                        tau_hat, dphi_hat, gamma_hat);
+            }
+        }
+    }
+    
+    // destroy objects
+    detector_cccf_destroy(sync);
+
+    // 
+    // run tests
+    //
+    
+    // convert to dB
+    gamma     = 20*log10f(gamma);
+    gamma_hat = 20*log10f(gamma_hat);
+
+    if (liquid_autotest_verbose) {
+        printf("detector autotest [%3u]: signal detected? %s\n", _n, signal_detected ? "yes" : "no");
+        printf("    dphi    :   estimate = %12.6f (expected %12.6f)\n", dphi_hat,  _dphi);
+        printf("    delay   :   estimate = %12.6f (expected %12.6f)\n", delay_hat, delay);
+        printf("    gamma   :   estimate = %12.6f (expected %12.6f)\n", gamma_hat, gamma);
+    }
+
+    // ensure signal was detected
+    CONTEND_EXPRESSION( signal_detected );
+
+    // check carrier offset estimate
+    CONTEND_DELTA( dphi_hat, _dphi, 0.01f );
+    
+    // check delay estimate
+    CONTEND_DELTA( delay_hat, delay, 0.2f );
+    
+    // check signal level estimate
+    CONTEND_DELTA( gamma_hat, gamma, 2.0f );
+}
+
+
diff --git a/src/framing/tests/flexframesync_autotest.c b/src/framing/tests/flexframesync_autotest.c
new file mode 100644
index 0000000..588915a
--- /dev/null
+++ b/src/framing/tests/flexframesync_autotest.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST : test simple recovery of frame in noise
+//
+void autotest_flexframesync()
+{
+    unsigned int i;
+
+    unsigned int _payload_len = 400;
+    int _ms    = LIQUID_MODEM_QPSK;
+    int _fec0  = LIQUID_FEC_NONE;
+    int _fec1  = LIQUID_FEC_NONE;
+    int _check = LIQUID_CRC_32;
+
+    // create flexframegen object
+    flexframegenprops_s fgprops;
+    flexframegenprops_init_default(&fgprops);
+    fgprops.mod_scheme  = _ms;
+    fgprops.check       = _check;
+    fgprops.fec0        = _fec0;
+    fgprops.fec1        = _fec1;
+    flexframegen fg = flexframegen_create(&fgprops);
+
+    // create flexframesync object
+    flexframesync fs = flexframesync_create(NULL,NULL);
+    
+    // initialize header and payload
+    unsigned char header[14] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+    unsigned char payload[_payload_len];
+    for (i=0; i<_payload_len; i++)
+        payload[i] = rand() & 0xff;
+    
+    // assemble the frame
+    flexframegen_assemble(fg, header, payload, _payload_len);
+    if (liquid_autotest_verbose)
+        flexframegen_print(fg);
+
+    // generate the frame
+    int frame_complete = 0;
+    float complex buf[2];
+    while (!frame_complete) {
+        // write samples to buffer
+        frame_complete = flexframegen_write_samples(fg, buf, 2);
+
+        // run through frame synchronizer
+        flexframesync_execute(fs, buf, 2);
+    }
+
+    // get frame data statistics
+    framedatastats_s stats = flexframesync_get_framedatastats(fs);
+    if (liquid_autotest_verbose)
+        flexframesync_print(fs);
+
+    // check to see that frame was recovered
+    CONTEND_EQUALITY( stats.num_frames_detected, 1 );
+    CONTEND_EQUALITY( stats.num_headers_valid,   1 );
+    CONTEND_EQUALITY( stats.num_payloads_valid,  1 );
+    CONTEND_EQUALITY( stats.num_bytes_received,  _payload_len );
+
+    // destroy objects
+    flexframegen_destroy(fg);
+    flexframesync_destroy(fs);
+}
+
diff --git a/src/framing/tests/framesync64_autotest.c b/src/framing/tests/framesync64_autotest.c
new file mode 100644
index 0000000..f5e7916
--- /dev/null
+++ b/src/framing/tests/framesync64_autotest.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+static int callback(unsigned char *  _header,
+                    int              _header_valid,
+                    unsigned char *  _payload,
+                    unsigned int     _payload_len,
+                    int              _payload_valid,
+                    framesyncstats_s _stats,
+                    void *           _userdata)
+{
+    //printf("callback invoked, payload valid: %s\n", _payload_valid ? "yes" : "no");
+    int * frame_recovered = (int*) _userdata;
+
+    if (_header_valid && _payload_valid)
+        *frame_recovered = 1;
+    else
+        *frame_recovered = 0;
+    
+    return 0;
+}
+
+// 
+// AUTOTEST : test simple recovery of frame in noise
+//
+void autotest_framesync64()
+{
+    unsigned int i;
+
+    framegen64 fg = framegen64_create();
+
+    // frame data
+    unsigned char header[8] = {0, 1, 2, 3, 4, 5, 6, 7};
+    unsigned char payload[64];
+    // initialize payload
+    for (i=0; i<64; i++)
+        payload[i] = rand() & 0xff;
+    
+    // create framesync64 object
+    int frame_recovered = 0;
+    framesync64 fs = framesync64_create(callback,(void*)&frame_recovered);
+    
+    if (liquid_autotest_verbose) {
+        framesync64_print(fs);
+        framegen64_print(fg);
+    }
+
+    // generate the frame
+    unsigned int frame_len = LIQUID_FRAME64_LEN;
+    float complex frame[frame_len];
+    framegen64_execute(fg, header, payload, frame);
+
+    // add some noise
+    for (i=0; i<frame_len; i++)
+        frame[i] += 0.01f*(randnf() + _Complex_I*randnf()) * M_SQRT1_2;
+
+    // try to find the frame
+    framesync64_execute(fs, frame, frame_len);
+
+    // check to see that frame was recovered
+    CONTEND_EQUALITY( frame_recovered, 1 );
+
+    // destroy objects
+    framegen64_destroy(fg);
+    framesync64_destroy(fs);
+}
+
diff --git a/src/framing/tests/qdetector_cccf_autotest.c b/src/framing/tests/qdetector_cccf_autotest.c
new file mode 100644
index 0000000..53a4090
--- /dev/null
+++ b/src/framing/tests/qdetector_cccf_autotest.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper functions
+//  _sequence_len   :   sequence length
+void qdetector_cccf_runtest_linear(unsigned int _sequence_len);
+void qdetector_cccf_runtest_gmsk  (unsigned int _sequence_len);
+
+//
+// AUTOTESTS
+//
+
+// linear tests
+void autotest_qdetector_cccf_linear_n64()   { qdetector_cccf_runtest_linear(  64); }
+void autotest_qdetector_cccf_linear_n83()   { qdetector_cccf_runtest_linear(  83); }
+void autotest_qdetector_cccf_linear_n128()  { qdetector_cccf_runtest_linear( 128); }
+void autotest_qdetector_cccf_linear_n167()  { qdetector_cccf_runtest_linear( 167); }
+void autotest_qdetector_cccf_linear_n256()  { qdetector_cccf_runtest_linear( 256); }
+void autotest_qdetector_cccf_linear_n335()  { qdetector_cccf_runtest_linear( 335); }
+void autotest_qdetector_cccf_linear_n512()  { qdetector_cccf_runtest_linear( 512); }
+void autotest_qdetector_cccf_linear_n671()  { qdetector_cccf_runtest_linear( 671); }
+void autotest_qdetector_cccf_linear_n1024() { qdetector_cccf_runtest_linear(1024); }
+void autotest_qdetector_cccf_linear_n1341() { qdetector_cccf_runtest_linear(1341); }
+
+// gmsk tests
+void autotest_qdetector_cccf_gmsk_n64()     { qdetector_cccf_runtest_gmsk  (  64); }
+void autotest_qdetector_cccf_gmsk_n83()     { qdetector_cccf_runtest_gmsk  (  83); }
+void autotest_qdetector_cccf_gmsk_n128()    { qdetector_cccf_runtest_gmsk  ( 128); }
+void autotest_qdetector_cccf_gmsk_n167()    { qdetector_cccf_runtest_gmsk  ( 167); }
+void autotest_qdetector_cccf_gmsk_n256()    { qdetector_cccf_runtest_gmsk  ( 256); }
+void autotest_qdetector_cccf_gmsk_n335()    { qdetector_cccf_runtest_gmsk  ( 335); }
+void autotest_qdetector_cccf_gmsk_n512()    { qdetector_cccf_runtest_gmsk  ( 512); }
+void autotest_qdetector_cccf_gmsk_n671()    { qdetector_cccf_runtest_gmsk  ( 671); }
+void autotest_qdetector_cccf_gmsk_n1024()   { qdetector_cccf_runtest_gmsk  (1024); }
+void autotest_qdetector_cccf_gmsk_n1341()   { qdetector_cccf_runtest_gmsk  (1341); }
+
+// autotest helper function
+//  _sequence_len   :   sequence length
+void qdetector_cccf_runtest_linear(unsigned int _sequence_len)
+{
+    unsigned int k     =     2;     // samples per symbol
+    unsigned int m     =     7;     // filter delay [symbols]
+    float        beta  =  0.3f;     // excess bandwidth factor
+    int          ftype = LIQUID_FIRFILT_ARKAISER; // filter type
+    float        gamma =  1.0f;     // channel gain
+    float        tau   = -0.3f;     // fractional sample timing offset
+    float        dphi  = -0.000f;   // carrier frequency offset (zero for now)
+    float        phi   =  0.5f;     // carrier phase offset
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_symbols = 8*_sequence_len + 2*m;
+    unsigned int num_samples = k * num_symbols;
+
+    // arrays
+    float complex x[num_samples];   // transmitted signal
+    float complex y[num_samples];   // received signal
+
+    // generate synchronization sequence (QPSK symbols)
+    float complex sequence[_sequence_len];
+    for (i=0; i<_sequence_len; i++) {
+        sequence[i] = (rand() % 2 ? 1.0f : -1.0f) * M_SQRT1_2 +
+                      (rand() % 2 ? 1.0f : -1.0f) * M_SQRT1_2 * _Complex_I;
+    }
+
+    // generate transmitted signal
+    firinterp_crcf interp = firinterp_crcf_create_prototype(ftype, k, m, beta, -tau);
+    unsigned int n = 0;
+    for (i=0; i<num_symbols; i++) {
+        // original sequence, then random symbols
+        float complex sym = i < _sequence_len ? sequence[i] : sequence[rand()%_sequence_len];
+
+        // interpolate
+        firinterp_crcf_execute(interp, sym, &x[n]);
+        n += k;
+    }
+    firinterp_crcf_destroy(interp);
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i] = x[i];
+
+        // channel gain
+        y[i] *= gamma;
+
+        // carrier offset
+        y[i] *= cexpf(_Complex_I*(dphi*i + phi));
+    }
+
+    // estimates
+    float tau_hat   = 0.0f;
+    float gamma_hat = 0.0f;
+    float dphi_hat  = 0.0f;
+    float phi_hat   = 0.0f;
+    int   frame_detected = 0;
+    int   false_positive = 0;
+
+    // create detector
+    qdetector_cccf q = qdetector_cccf_create_linear(sequence, _sequence_len, ftype, k, m, beta);
+    if (liquid_autotest_verbose)
+        qdetector_cccf_print(q);
+
+    unsigned int buf_len = qdetector_cccf_get_buf_len(q);
+
+    // try to detect frame
+    float complex * v = NULL;
+    for (i=0; i<num_samples; i++) {
+        if (frame_detected)
+            break;
+
+        v = qdetector_cccf_execute(q,y[i]);
+
+        if (v != NULL) {
+            frame_detected = 1;
+
+            // get statistics
+            tau_hat   = qdetector_cccf_get_tau(q);
+            gamma_hat = qdetector_cccf_get_gamma(q);
+            dphi_hat  = qdetector_cccf_get_dphi(q);
+            phi_hat   = qdetector_cccf_get_phi(q);
+            break;
+        }
+    }
+    unsigned int sample_index = i;
+
+    // destroy objects
+    qdetector_cccf_destroy(q);
+
+    if (liquid_autotest_verbose) {
+        // print results
+        printf("\n");
+        printf("frame detected  :   %s\n", frame_detected ? "yes" : "no");
+        printf("  sample index  : %8u, actual=%8u (error=%8d)\n", sample_index, buf_len, (int)sample_index - (int)buf_len);
+        printf("  gamma hat     : %8.3f, actual=%8.3f (error=%8.3f)\n",            gamma_hat, gamma, gamma_hat - gamma);
+        printf("  tau hat       : %8.3f, actual=%8.3f (error=%8.3f) samples\n",    tau_hat,   tau,   tau_hat   - tau  );
+        printf("  dphi hat      : %8.5f, actual=%8.5f (error=%8.5f) rad/sample\n", dphi_hat,  dphi,  dphi_hat  - dphi );
+        printf("  phi hat       : %8.5f, actual=%8.5f (error=%8.5f) radians\n",    phi_hat,   phi,   phi_hat   - phi  );
+        printf("\n");
+    }
+
+    if (false_positive)
+        AUTOTEST_FAIL("false positive detected");
+    else if (!frame_detected)
+        AUTOTEST_FAIL("frame not detected");
+    else {
+        // check signal level estimate
+        CONTEND_DELTA( gamma_hat, gamma, 0.05f );
+
+        // check timing offset estimate
+        CONTEND_DELTA( tau_hat, tau, 0.05f );
+
+        // check carrier frequency offset estimate
+        CONTEND_DELTA( dphi_hat, dphi, 0.01f );
+
+        // check carrier phase offset estimate
+        CONTEND_DELTA( phi_hat, phi, 0.1f );
+    }
+}
+
+
+// autotest helper function
+//  _sequence_len   :   sequence length
+void qdetector_cccf_runtest_gmsk(unsigned int _sequence_len)
+{
+    unsigned int k     =     2;     // samples per symbol
+    unsigned int m     =     7;     // filter delay [symbols]
+    float        beta  =  0.3f;     // excess bandwidth factor
+    float        gamma =  1.0f;     // channel gain
+    float        tau   =  0.0f;     // fractional sample timing offset
+    float        dphi  = -0.000f;   // carrier frequency offset (zero for now)
+    float        phi   =  0.5f;     // carrier phase offset
+
+    unsigned int i;
+
+    // derived values
+    unsigned int num_symbols = 8*_sequence_len + 2*m;
+    unsigned int num_samples = k * num_symbols;
+
+    // arrays
+    float complex x[num_samples];   // transmitted signal
+    float complex y[num_samples];   // received signal
+
+    // generate synchronization sequence (QPSK symbols)
+    unsigned char sequence[_sequence_len];
+    for (i=0; i<_sequence_len; i++)
+        sequence[i] = rand() & 0x01;
+
+    // generate transmitted signal (gmsk)
+    gmskmod mod = gmskmod_create(k, m, beta);
+    unsigned int n = 0;
+    for (i=0; i<num_symbols; i++) {
+        // original sequence, then random symbols
+        unsigned char bit = i < _sequence_len ? sequence[i] : rand() & 0x01;
+
+        // modulate
+        gmskmod_modulate(mod, bit, &x[n]);
+        n += k;
+    }
+    gmskmod_destroy(mod);
+
+    // add channel impairments
+    for (i=0; i<num_samples; i++) {
+        y[i] = x[i];
+
+        // channel gain
+        y[i] *= gamma;
+
+        // carrier offset
+        y[i] *= cexpf(_Complex_I*(dphi*i + phi));
+    }
+
+    // estimates
+    float tau_hat   = 0.0f;
+    float gamma_hat = 0.0f;
+    float dphi_hat  = 0.0f;
+    float phi_hat   = 0.0f;
+    int   frame_detected = 0;
+    int   false_positive = 0;
+
+    // create detector
+    qdetector_cccf q = qdetector_cccf_create_gmsk(sequence, _sequence_len, k, m, beta);
+    if (liquid_autotest_verbose)
+        qdetector_cccf_print(q);
+
+    unsigned int buf_len = qdetector_cccf_get_buf_len(q);
+
+    // try to detect frame
+    float complex * v = NULL;
+    for (i=0; i<num_samples; i++) {
+        if (frame_detected)
+            break;
+
+        v = qdetector_cccf_execute(q,y[i]);
+
+        if (v != NULL) {
+            frame_detected = 1;
+
+            // get statistics
+            tau_hat   = qdetector_cccf_get_tau(q);
+            gamma_hat = qdetector_cccf_get_gamma(q);
+            dphi_hat  = qdetector_cccf_get_dphi(q);
+            phi_hat   = qdetector_cccf_get_phi(q);
+            break;
+        }
+    }
+    unsigned int sample_index = i;
+
+    // destroy objects
+    qdetector_cccf_destroy(q);
+
+    if (liquid_autotest_verbose) {
+        // print results
+        printf("\n");
+        printf("frame detected  :   %s\n", frame_detected ? "yes" : "no");
+        printf("  sample index  : %8u, actual=%8u (error=%8d)\n", sample_index, buf_len, (int)sample_index - (int)buf_len);
+        printf("  gamma hat     : %8.3f, actual=%8.3f (error=%8.3f)\n",            gamma_hat, gamma, gamma_hat - gamma);
+        printf("  tau hat       : %8.3f, actual=%8.3f (error=%8.3f) samples\n",    tau_hat,   tau,   tau_hat   - tau  );
+        printf("  dphi hat      : %8.5f, actual=%8.5f (error=%8.5f) rad/sample\n", dphi_hat,  dphi,  dphi_hat  - dphi );
+        printf("  phi hat       : %8.5f, actual=%8.5f (error=%8.5f) radians\n",    phi_hat,   phi,   phi_hat   - phi  );
+        printf("\n");
+    }
+
+    if (false_positive)
+        AUTOTEST_FAIL("false positive detected");
+    else if (!frame_detected)
+        AUTOTEST_FAIL("frame not detected");
+    else {
+        // check signal level estimate
+        // TODO: check discrepency with short sequences
+        //CONTEND_DELTA( gamma_hat, gamma, 0.05f );
+
+        // check timing offset estimate
+        CONTEND_DELTA( tau_hat, tau, 0.05f );
+
+        // check carrier frequency offset estimate
+        CONTEND_DELTA( dphi_hat, dphi, 0.01f );
+
+        // check carrier phase offset estimate
+        CONTEND_DELTA( phi_hat, phi, 0.1f );
+    }
+}
+
+
diff --git a/src/framing/tests/qpacketmodem_autotest.c b/src/framing/tests/qpacketmodem_autotest.c
new file mode 100644
index 0000000..be78a63
--- /dev/null
+++ b/src/framing/tests/qpacketmodem_autotest.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST : test simple recovery of frame
+//
+void qpacketmodem_modulated(unsigned int _payload_len,
+                            int          _check,
+                            int          _fec0,
+                            int          _fec1,
+                            int          _ms)
+{
+    // derived values
+    unsigned int i;
+
+    // create and configure packet encoder/decoder object
+    qpacketmodem q = qpacketmodem_create();
+    qpacketmodem_configure(q, _payload_len, _check, _fec0, _fec1, _ms);
+    if (liquid_autotest_verbose)
+        qpacketmodem_print(q);
+
+    // initialize payload
+    unsigned char payload_tx[_payload_len];
+    unsigned char payload_rx[_payload_len];
+
+    // initialize payload
+    for (i=0; i<_payload_len; i++) {
+        payload_tx[i] = rand() & 0xff;
+        payload_rx[i] = rand() & 0xff;
+    }
+
+    // get frame length
+    unsigned int frame_len = qpacketmodem_get_frame_len(q);
+
+    // allocate memory for frame samples
+    float complex frame[frame_len];
+
+    // encode frame
+    qpacketmodem_encode(q, payload_tx, frame);
+
+    // decode frame
+    int crc_pass = qpacketmodem_decode_soft(q, frame, payload_rx);
+
+    // destroy object
+    qpacketmodem_destroy(q);
+
+    // check to see that frame was recovered
+    CONTEND_EQUALITY( crc_pass, 1 );
+    CONTEND_SAME_DATA( payload_tx, payload_rx, _payload_len );
+}
+
+void autotest_qpacketmodem_bpsk()   { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QPSK);    }
+void autotest_qpacketmodem_qpsk()   { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QPSK);    }
+void autotest_qpacketmodem_psk8()   { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_PSK8);    }
+void autotest_qpacketmodem_qam16()  { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM16);   }
+void autotest_qpacketmodem_sqam32() { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_SQAM32);  }
+void autotest_qpacketmodem_qam64()  { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM64);   }
+void autotest_qpacketmodem_sqam128(){ qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_SQAM128); }
+void autotest_qpacketmodem_qam256() { qpacketmodem_modulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM256);  }
+
+// 
+// AUTOTEST : test un-modulated frame symbols (hard-decision demod)
+//
+void qpacketmodem_unmodulated(unsigned int _payload_len,
+                              int          _check,
+                              int          _fec0,
+                              int          _fec1,
+                              int          _ms)
+{
+    // derived values
+    unsigned int i;
+
+    // create and configure packet encoder/decoder object
+    qpacketmodem q = qpacketmodem_create();
+    qpacketmodem_configure(q, _payload_len, _check, _fec0, _fec1, _ms);
+    if (liquid_autotest_verbose)
+        qpacketmodem_print(q);
+
+    // initialize payload
+    unsigned char payload_tx[_payload_len];
+    unsigned char payload_rx[_payload_len];
+
+    // initialize payload
+    for (i=0; i<_payload_len; i++) {
+        payload_tx[i] = rand() & 0xff;
+        payload_rx[i] = rand() & 0xff;
+    }
+
+    // get frame length (symbols)
+    unsigned int frame_len = qpacketmodem_get_frame_len(q);
+
+    // allocate memory for frame samples
+    unsigned char frame_syms[frame_len];
+
+    // encode frame symbols
+    qpacketmodem_encode_syms(q, payload_tx, frame_syms);
+
+    // decode frame symbols
+    int crc_pass = qpacketmodem_decode_syms(q, frame_syms, payload_rx);
+
+    // destroy object
+    qpacketmodem_destroy(q);
+
+    // check to see that frame was recovered
+    CONTEND_EQUALITY( crc_pass, 1 );
+    CONTEND_SAME_DATA( payload_tx, payload_rx, _payload_len );
+}
+
+void autotest_qpacketmodem_unmod_bpsk()   { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QPSK);    }
+void autotest_qpacketmodem_unmod_qpsk()   { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QPSK);    }
+void autotest_qpacketmodem_unmod_psk8()   { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_PSK8);    }
+void autotest_qpacketmodem_unmod_qam16()  { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM16);   }
+void autotest_qpacketmodem_unmod_sqam32() { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_SQAM32);  }
+void autotest_qpacketmodem_unmod_qam64()  { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM64);   }
+void autotest_qpacketmodem_unmod_sqam128(){ qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_SQAM128); }
+void autotest_qpacketmodem_unmod_qam256() { qpacketmodem_unmodulated(400,LIQUID_CRC_32,LIQUID_FEC_NONE,LIQUID_FEC_NONE, LIQUID_MODEM_QAM256);  }
+
diff --git a/src/framing/tests/qpilotsync_autotest.c b/src/framing/tests/qpilotsync_autotest.c
new file mode 100644
index 0000000..10d52b7
--- /dev/null
+++ b/src/framing/tests/qpilotsync_autotest.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+#define DEBUG_QPILOTSYNC_AUTOTEST 0
+
+// 
+// AUTOTEST : test simple recovery of frame in noise
+//
+//  _ms             :   modulation scheme (e.g. LIQUID_MODEM_QPSK)
+//  _payload_len    :   payload length [symbols]
+//  _pilot_spacing  :   spacing between pilot symbols
+//  _dphi           :   carrier frequency offset
+//  _phi            :   carrier phase offset
+//  _gamma          :   channel gain
+//  _SNRdB          :   signal-to-noise ratio [dB]
+void qpilotsync_test(modulation_scheme _ms,
+                     unsigned int      _payload_len,
+                     unsigned int      _pilot_spacing,
+                     float             _dphi,
+                     float             _phi,
+                     float             _gamma,
+                     float             _SNRdB)
+{
+    unsigned int i;
+    // derived values
+    float nstd = powf(10.0f, -_SNRdB/20.0f);
+
+    // create pilot generator and synchronizer objects
+    qpilotgen  pg = qpilotgen_create( _payload_len, _pilot_spacing);
+    qpilotsync ps = qpilotsync_create(_payload_len, _pilot_spacing);
+
+    // get frame length
+    unsigned int frame_len = qpilotgen_get_frame_len(pg);
+
+    // allocate arrays
+    unsigned char payload_sym_tx[_payload_len]; // transmitted payload symbols
+    float complex payload_tx    [_payload_len]; // transmitted payload samples
+    float complex frame_tx      [frame_len];    // transmitted frame samples
+    float complex frame_rx      [frame_len];    // received frame samples
+    float complex payload_rx    [_payload_len]; // received payload samples
+    unsigned char payload_sym_rx[_payload_len]; // received payload symbols
+
+    // create modem objects for payload
+    modem mod = modem_create(_ms);
+    modem dem = modem_create(_ms);
+
+    // assemble payload symbols
+    for (i=0; i<_payload_len; i++) {
+        // generate random symbol
+        payload_sym_tx[i] = modem_gen_rand_sym(mod);
+
+        // modulate
+        modem_modulate(mod, payload_sym_tx[i], &payload_tx[i]);
+    }
+
+    // assemble frame
+    qpilotgen_execute(pg, payload_tx, frame_tx);
+
+    // add channel impairments
+    for (i=0; i<frame_len; i++) {
+        // add carrier offset
+        frame_rx[i]  = frame_tx[i] * cexpf(_Complex_I*_dphi*i + _Complex_I*_phi);
+
+        // add noise
+        frame_rx[i] += nstd*(randnf() + _Complex_I*randnf())*M_SQRT1_2;
+
+        // apply channel gain
+        frame_rx[i] *= _gamma;
+    }
+
+    // recieve frame
+    qpilotsync_execute(ps, frame_rx, payload_rx);
+
+    // demodulate
+    for (i=0; i<_payload_len; i++) {
+        unsigned int sym_demod;
+        modem_demodulate(dem, payload_rx[i], &sym_demod);
+        payload_sym_rx[i] = (unsigned char)sym_demod;
+    }
+
+    // count errors
+    unsigned int bit_errors = 0;
+    for (i=0; i<_payload_len; i++)
+        bit_errors += count_bit_errors(payload_sym_rx[i], payload_sym_tx[i]);
+
+    // get estimates
+    float dphi_hat  = qpilotsync_get_dphi(ps);
+    float phi_hat   = qpilotsync_get_phi (ps);
+    float gamma_hat = qpilotsync_get_gain(ps);
+
+    if (liquid_autotest_verbose) {
+        qpilotgen_print(pg);
+        printf("  received bit errors : %u / %u\n", bit_errors, _payload_len * modem_get_bps(mod));
+        printf("  dphi (carrier freq.): %12.8ff (expected %12.8f, error=%12.8f)\n", dphi_hat, _dphi, _dphi-dphi_hat);
+        printf("  phi  (carrier phase): %12.8ff (expected %12.8f, error=%12.8f)\n",  phi_hat,  _phi, _phi-phi_hat);
+        printf("  gamma (channel gain): %12.8ff (expected %12.8f, error=%12.8f)\n", gamma_hat, _gamma, _gamma-gamma_hat);
+    }
+    
+    // check to see that frame was recovered
+    CONTEND_DELTA   (   dphi_hat,  _dphi, 0.010f );
+    CONTEND_DELTA   (    phi_hat,   _phi, 0.087f );  // 0.087 radians is about 5 degrees
+    CONTEND_DELTA   (  gamma_hat, _gamma, 0.010f );
+    CONTEND_EQUALITY( bit_errors, 0 );
+    
+    // destroy allocated objects
+    qpilotgen_destroy(pg);
+    qpilotsync_destroy(ps);
+    modem_destroy(mod);
+    modem_destroy(dem);
+
+#if DEBUG_QPILOTSYNC_AUTOTEST
+    // write symbols to output file for plotting
+    char filename[256];
+    sprintf(filename,"qpilotsync_autotest_%u_%u_debug.m", _payload_len, _pilot_spacing);
+    FILE * fid = fopen(filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: could not open '%s' for writing\n", filename);
+        return;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", filename);
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"payload_len = %u;\n", _payload_len);
+    fprintf(fid,"frame_len   = %u;\n", frame_len);
+    fprintf(fid,"frame_tx   = zeros(1,frame_len);\n");
+    fprintf(fid,"payload_rx = zeros(1,payload_len);\n");
+    for (i=0; i<frame_len; i++)
+        fprintf(fid,"frame_rx(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(frame_rx[i]), cimagf(frame_rx[i]));
+    for (i=0; i<_payload_len; i++)
+        fprintf(fid,"payload_rx(%6u) = %12.4e + 1i*%12.4e;\n", i+1, crealf(payload_rx[i]), cimagf(payload_rx[i]));
+    fprintf(fid,"figure('Color','white','position',[100 100 950 400]);\n");
+    fprintf(fid,"subplot(1,2,1);\n");
+    fprintf(fid,"  plot(real(frame_rx),  imag(frame_rx),  'x','MarkerSize',3);\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('real');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  title('received');\n");
+    fprintf(fid,"subplot(1,2,2);\n");
+    fprintf(fid,"  plot(real(payload_rx),imag(payload_rx),'x','MarkerSize',3);\n");
+    fprintf(fid,"  axis([-1 1 -1 1]*1.5);\n");
+    fprintf(fid,"  axis square;\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('real');\n");
+    fprintf(fid,"  ylabel('imag');\n");
+    fprintf(fid,"  title('recovered');\n");
+
+    fclose(fid);
+    printf("results written to '%s'\n", filename);
+#endif
+}
+
+void autotest_qpilotsync_100_16() { qpilotsync_test(LIQUID_MODEM_QPSK, 100, 16, 0.07f, 1.2f, 0.7f, 40.0f); }
+void autotest_qpilotsync_200_20() { qpilotsync_test(LIQUID_MODEM_QPSK, 200, 20, 0.07f, 1.2f, 0.7f, 40.0f); }
+void autotest_qpilotsync_300_24() { qpilotsync_test(LIQUID_MODEM_QPSK, 300, 24, 0.07f, 1.2f, 0.7f, 40.0f); }
+void autotest_qpilotsync_400_28() { qpilotsync_test(LIQUID_MODEM_QPSK, 400, 28, 0.07f, 1.2f, 0.7f, 40.0f); }
+void autotest_qpilotsync_500_32() { qpilotsync_test(LIQUID_MODEM_QPSK, 500, 32, 0.07f, 1.2f, 0.7f, 40.0f); }
+
diff --git a/src/libliquid.c b/src/libliquid.c
new file mode 100644
index 0000000..a678534
--- /dev/null
+++ b/src/libliquid.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Run-time library version numbers
+//
+
+#include "liquid.h"
+
+const char liquid_version[] = LIQUID_VERSION;
+
+const char * liquid_libversion(void)
+{
+    return LIQUID_VERSION;
+}
+
+int liquid_libversion_number(void)
+{
+    return LIQUID_VERSION_NUMBER;
+}
+
diff --git a/src/math/bench/polyfit_benchmark.c b/src/math/bench/polyfit_benchmark.c
new file mode 100644
index 0000000..21e4036
--- /dev/null
+++ b/src/math/bench/polyfit_benchmark.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void polyf_fit_bench(struct rusage *_start,
+                     struct rusage *_finish,
+                     unsigned long int *_num_iterations,
+                     unsigned int _Q,
+                     unsigned int _N)
+{
+    // normalize number of iterations
+    // time ~ 0.2953 + 0.03381 * _N
+    *_num_iterations /= 0.2953 + 0.03381 * _N;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float p[_Q+1];
+
+    float x[_N];
+    float y[_N];
+    unsigned int i;
+    for (i=0; i<_N; i++) {
+        x[i] = randnf();
+        y[i] = randnf();
+    }
+    
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        polyf_fit(x,y,_N, p,_Q+1);
+        polyf_fit(x,y,_N, p,_Q+1);
+        polyf_fit(x,y,_N, p,_Q+1);
+        polyf_fit(x,y,_N, p,_Q+1);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define POLYF_FIT_BENCHMARK_API(Q,N)    \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ polyf_fit_bench(_start, _finish, _num_iterations, Q, N); }
+
+void benchmark_polyf_fit_q3_n8      POLYF_FIT_BENCHMARK_API(3, 8)
+void benchmark_polyf_fit_q3_n16     POLYF_FIT_BENCHMARK_API(3, 16)
+void benchmark_polyf_fit_q3_n32     POLYF_FIT_BENCHMARK_API(3, 32)
+void benchmark_polyf_fit_q3_n64     POLYF_FIT_BENCHMARK_API(3, 64)
+void benchmark_polyf_fit_q3_n128    POLYF_FIT_BENCHMARK_API(3, 128)
+
diff --git a/src/math/src/math.bessel.c b/src/math/src/math.bessel.c
new file mode 100644
index 0000000..9d90517
--- /dev/null
+++ b/src/math/src/math.bessel.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Bessel Functions
+//
+// liquid_lnbesselif    :   log modified Bessel function of the first kind
+// liquid_besselif      :   modified Bessel function of the first kind
+// liquid_besseli0f     :   modified Bessel function of the first kind (order 0)
+// liquid_besseljf      :   Bessel function of the first kind
+// liquid_besselj0f     :   Bessel function of the first kind (order 0)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// log(I_v(z)) : log Modified Bessel function of the first kind
+#define NUM_BESSELI_ITERATIONS 64
+float liquid_lnbesselif(float _nu,
+                        float _z)
+{
+    // TODO : validate input
+    // TODO : compute low-signal approximation to avoid log(0)
+
+#if 0
+    // high-signal approximation: _z >> _nu
+    //     I_v(z) ~ exp(z) / sqrt(2*pi*z)
+    //  ln I_v(z) ~ z - 0.5*ln(2*pi*z)
+    if ( _nu > 0.0f && logf(_z/_nu) > 8.0f )
+        return _z - 0.5f*logf(2*M_PI*_z);
+#endif
+
+    float t0 = _nu*logf(0.5f*_z);
+    float t1 = 0.0f;
+    float t2 = 0.0f;
+    float t3 = 0.0f;
+    float y = 0.0f;
+
+    unsigned int k;
+    for (k=0; k<NUM_BESSELI_ITERATIONS; k++) {
+        // compute log( (z^2/4)^k )
+        t1 = 2.0f * k * logf(0.5f*_z);
+
+        // compute: log( k! * Gamma(nu + k +1) )
+        t2 = liquid_lngammaf((float)k + 1.0f);
+        t3 = liquid_lngammaf(_nu + (float)k + 1.0f);
+
+        // accumulate y
+        y += expf( t1 - t2 - t3 );
+    }
+
+    return t0 + logf(y);
+}
+
+// I_v(z) : Modified Bessel function of the first kind
+float liquid_besselif(float _nu,
+                      float _z)
+{
+    return expf( liquid_lnbesselif(_nu, _z) );
+}
+
+// I_0(z) : Modified bessel function of the first kind (order zero)
+#define NUM_BESSELI0_ITERATIONS 32
+float liquid_besseli0f(float _z)
+{
+    // TODO : use better low-signal approximation
+    if (_z == 0.0f)
+        return 1.0f;
+
+    unsigned int k;
+    float t, y=0.0f;
+    for (k=0; k<NUM_BESSELI0_ITERATIONS; k++) {
+#if 0
+        t = powf(_z/2, (float)k) / tgamma((float)k+1);
+        y += t*t;
+#else
+        t = k * logf(0.5f*_z) - liquid_lngammaf((float)k + 1.0f);
+        y += expf(2*t);
+#endif
+    }
+    return y;
+}
+
+// J_v(z) : Bessel function of the first kind
+#define NUM_BESSELJ_ITERATIONS 128
+float liquid_besseljf(float _nu,
+                      float _z)
+{
+    // TODO : validate input
+
+    float J = 0.0f;
+
+    float abs_nu = fabsf(_nu);
+
+    unsigned int k;
+    for (k=0; k<NUM_BESSELJ_ITERATIONS; k++) {
+        // compute: (2k + |nu|)
+        float t0 = (2.0f*k + abs_nu);
+
+        // compute: (2k + |nu|)*log(z)
+        float t1 = t0 * logf(_z);
+
+        // compute: (2k + |nu|)*log(2)
+        float t2 = t0 * logf(2.0f);
+
+        // compute: log(Gamma(k+1))
+        float t3 = liquid_lngammaf((float)k + 1.0f);
+
+        // compute: log(Gamma(|nu|+k+1))
+        float t4 = liquid_lngammaf(abs_nu + (float)k + 1.0f);
+
+        // accumulate J
+        if ( (k%2) == 0) J += expf(t1 - t2 - t3 - t4);
+        else             J -= expf(t1 - t2 - t3 - t4);
+    }
+
+    return J;
+}
+
+// J_0(z) : Bessel function of the first kind (order zero)
+#define NUM_BESSELJ0_ITERATIONS 16
+float liquid_besselj0f(float _z)
+{
+    // large signal approximation, see
+    // Gross, F. B "New Approximations to J0 and J1 Bessel Functions,"
+    //   IEEE Trans. on Antennas and Propagation, vol. 43, no. 8,
+    //   August, 1995
+    if (fabsf(_z) > 10.0f)
+        return sqrtf(2/(M_PI*fabsf(_z)))*cosf(fabsf(_z)-M_PI/4);
+
+    unsigned int k;
+    float t, y=0.0f;
+    for (k=0; k<NUM_BESSELJ0_ITERATIONS; k++) {
+        t = powf(_z/2, (float)k) / tgamma((float)k+1);
+        y += (k%2) ? -t*t : t*t;
+    }
+    return y;
+}
+
diff --git a/src/math/src/math.c b/src/math/src/math.c
new file mode 100644
index 0000000..2035916
--- /dev/null
+++ b/src/math/src/math.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Useful mathematical formulae
+//
+// References:
+//  [Helstrom:1960] Helstrom, C. W., Statistical Theory of Signal
+//      Detection. New York: Pergamon Press, 1960
+//  [Helstrom:1992] Helstrom, C. W. "Computing the Generalized Marcum Q-
+//      Function," IEEE Transactions on Information Theory, vol. 38, no. 4,
+//      July, 1992.
+//  [Proakis:2001] Proakis, J. Digital Communications. New York:
+//      McGraw-Hill, 2001
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+//                    infty
+// Q(z) = 1/sqrt(2 pi) int { exp(-u^2/2) du }
+//                      z
+//
+// Q(z) = (1/2)*(1 - erf(z/sqrt(2)))
+float liquid_Qf(float _z)
+{
+    return 0.5f * (1.0f - erff(_z*M_SQRT1_2));
+}
+
+// Marcum Q-function
+// TODO : check this computation
+// [Helstrom:1960], [Proakis:2001], [Helstrom:1992]
+#define NUM_MARCUMQ_ITERATIONS 16
+float liquid_MarcumQf(int _M,
+                      float _alpha,
+                      float _beta)
+{
+#if 0
+    // expand as:
+    //                               infty
+    // Q_M(a,b) = exp(-(a^2+b^2)/2) * sum { (a/b)^k I_k(a*b) }
+    //                               k=1-M
+    return 0.0f
+#else
+
+    // use approximation [Helstrom:1992] (Eq. 25)
+    // Q_M(a,b) ~ erfc(x),
+    //   x = (b-a-M)/sigma^2,
+    //   sigma = M + 2a
+
+    // compute sigma
+    float sigma = (float)(_M) + 2.0f*_alpha;
+
+    // compute x
+    float x = (_beta - _alpha - (float)_M) / (sigma*sigma);
+
+    // return erfc(x)
+    return erfcf(x);
+#endif
+}
+
+// Marcum Q-function (M=1)
+// TODO : check this computation
+// [Helstrom:1960], [Proakis:2001]
+#define NUM_MARCUMQ1_ITERATIONS 64
+float liquid_MarcumQ1f(float _alpha,
+                       float _beta)
+{
+#if 1
+    // expand as:                    infty
+    // Q_1(a,b) = exp(-(a^2+b^2)/2) * sum { (a/b)^k I_k(a*b) }
+    //                                k=0
+
+    float t0 = expf( -0.5f*(_alpha*_alpha + _beta*_beta) );
+    float t1 = 1.0f;
+
+    float a_div_b = _alpha / _beta;
+    float a_mul_b = _alpha * _beta;
+
+    float y = 0.0f;
+    unsigned int k;
+    for (k=0; k<NUM_MARCUMQ1_ITERATIONS; k++) {
+        // accumulate y
+        y += t1 * liquid_besselif((float)k, a_mul_b);
+
+        // update t1
+        t1 *= a_div_b;
+    }
+
+    return t0 * y;
+#else
+    
+    // call generalized Marcum-Q function with M=1
+    return liquid_MarcumQf(1, _alpha, _beta);
+#endif
+}
+
+// compute sinc(x) = sin(pi*x) / (pi*x)
+float sincf(float _x) {
+    // _x ~ 0 approximation
+    //if (fabsf(_x) < 0.01f)
+    //    return expf(-lngammaf(1+_x) - lngammaf(1-_x));
+
+    // _x ~ 0 approximation
+    // from : http://mathworld.wolfram.com/SincFunction.html
+    // sinc(z) = \prod_{k=1}^{\infty} { cos(\pi z / 2^k) }
+    if (fabsf(_x) < 0.01f)
+        return cosf(M_PI*_x/2.0f)*cosf(M_PI*_x/4.0f)*cosf(M_PI*_x/8.0f);
+
+    return sinf(M_PI*_x)/(M_PI*_x);
+}
+
+// next power of 2 : y = ceil(log2(_x))
+unsigned int liquid_nextpow2(unsigned int _x)
+{
+    if (_x == 0) {
+        fprintf(stderr,"error: liquid_nextpow2(), input must be greater than zero\n");
+        exit(1);
+    }
+
+    _x--;
+    unsigned int n=0;
+    while (_x > 0) {
+        _x >>= 1;
+        n++;
+    }
+    return n;
+}
+
+// (n choose k) = n! / ( k! (n-k)! )
+float liquid_nchoosek(unsigned int _n, unsigned int _k)
+{
+    // 
+    if (_k > _n) {
+        fprintf(stderr,"error: liquid_nchoosek(), _k cannot exceed _n\n");
+        exit(1);
+    } else if (_k == 0 || _k == _n) {
+        return 1;
+    }
+
+    // take advantage of symmetry and take larger value
+    if (_k < _n/2)
+        _k = _n - _k;
+
+    // use lngamma() function when _n is large
+    if (_n > 12) {
+        double t0 = lgamma((double)_n + 1.0f);
+        double t1 = lgamma((double)_n - (double)_k + 1.0f);
+        double t2 = lgamma((double)_k + 1.0f);
+
+        return round(exp( t0 - t1 - t2 ));
+    }
+
+    // old method
+    float rnum=1, rden=1;
+    unsigned int i;
+    for (i=_n; i>_k; i--)
+        rnum *= i;
+    for (i=1; i<=_n-_k; i++)
+        rden *= i;
+    return rnum / rden;
+}
+
diff --git a/src/math/src/math.complex.c b/src/math/src/math.complex.c
new file mode 100644
index 0000000..fc4aecf
--- /dev/null
+++ b/src/math/src/math.complex.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex math functions (trig, log, exp, etc.)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// complex square root
+float complex liquid_csqrtf(float complex _z)
+{
+    float r = cabsf(_z);            // magnitude of _z
+    float a = crealf(_z);           // real component of _z
+
+    float re = sqrtf(0.5f*(r+a));   // real component of return value
+    float im = sqrtf(0.5f*(r-a));   // imag component of return value
+
+    // return value, retaining sign of imaginary component
+    return cimagf(_z) > 0 ? re + _Complex_I*im :
+                            re - _Complex_I*im;
+}
+
+// complex exponent
+float complex liquid_cexpf(float complex _z)
+{
+    float r = expf( crealf(_z) );
+    float re = cosf( cimagf(_z) );
+    float im = sinf( cimagf(_z) );
+
+    return r * ( re + _Complex_I*im );
+}
+
+// complex logarithm
+float complex liquid_clogf(float complex _z)
+{
+    return logf(cabsf(_z)) + _Complex_I*cargf(_z);
+}
+
+// complex arcsin
+float complex liquid_casinf(float complex _z)
+{
+    return 0.5f*M_PI - liquid_cacosf(_z);
+}
+
+// complex arccos
+float complex liquid_cacosf(float complex _z)
+{
+    // return based on quadrant
+    int sign_i = crealf(_z) > 0;
+    int sign_q = cimagf(_z) > 0;
+
+    if (sign_i == sign_q) {
+        return -_Complex_I*liquid_clogf(_z + liquid_csqrtf(_z*_z - 1.0f));
+    } else {
+        return -_Complex_I*liquid_clogf(_z - liquid_csqrtf(_z*_z - 1.0f));
+    }
+
+    // should never get to this state
+    return 0.0f;
+}
+
+// complex arctan
+float complex liquid_catanf(float complex _z)
+{
+    float complex t0 = 1.0f - _Complex_I*_z;
+    float complex t1 = 1.0f + _Complex_I*_z;
+
+    return 0.5f*_Complex_I*liquid_clogf( t0 / t1 );
+}
+
+// approximation to cargf() but faster
+float liquid_cargf_approx(float complex _x)
+{
+    float theta;
+    float xi = crealf(_x);
+    float xq = cimagf(_x);
+
+    if (xi == 0.0f) {
+        if (xq == 0.0f)
+            return 0.0f;
+        return xq > 0.0f ? M_PI_2 : -M_PI_2;
+    } else {
+        theta = xq / fabsf(xi);
+    }
+
+    if (theta >  M_PI_2)
+        theta =  M_PI_2;
+    else if (theta < -M_PI_2)
+        theta = -M_PI_2;
+    return theta;
+}
+
diff --git a/src/math/src/math.gamma.c b/src/math/src/math.gamma.c
new file mode 100644
index 0000000..def2c09
--- /dev/null
+++ b/src/math/src/math.gamma.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// gamma functions
+//
+// liquid_lngammaf()        :   log( Gamma(z) )
+// liquid_gammaf()          :   Gamma(z)
+// liquid_lnlowergammaf()   :   log( gamma(z,a) ), lower incomplete
+// liquid_lnuppergammaf()   :   log( Gamma(z,a) ), upper incomplete
+// liquid_lowergammaf()     :   gamma(z,a), lower incomplete
+// liquid_uppergammaf()     :   Gamma(z,a), upper incomplete
+// liquid_factorialf()      :   z!
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define NUM_LNGAMMA_ITERATIONS (256)
+#define EULER_GAMMA            (0.57721566490153286)
+float liquid_lngammaf(float _z)
+{
+    float g;
+    if (_z < 0) {
+        fprintf(stderr,"error: liquid_lngammaf(), undefined for z <= 0\n");
+        exit(1);
+    } else if (_z < 10.0f) {
+#if 0
+        g = -EULER_GAMMA*_z - logf(_z);
+        unsigned int k;
+        for (k=1; k<NUM_LNGAMMA_ITERATIONS; k++) {
+            float t0 = _z / (float)k;
+            float t1 = logf(1.0f + t0);
+
+            g += t0 - t1;
+        }
+#else
+        // Use recursive formula:
+        //    gamma(z+1) = z * gamma(z)
+        // therefore:
+        //    log(Gamma(z)) = log(gamma(z+1)) - ln(z)
+        return liquid_lngammaf(_z + 1.0f) - logf(_z);
+#endif
+    } else {
+        // high value approximation
+        g = 0.5*( logf(2*M_PI)-log(_z) );
+        g += _z*( logf(_z+(1/(12.0f*_z-0.1f/_z)))-1);
+    }
+    return g;
+}
+
+float liquid_gammaf(float _z)
+{
+    if (_z < 0) {
+        // use identities
+        //  (1) gamma(z)*gamma(-z) = -pi / (z*sin(pi*z))
+        //  (2) z*gamma(z) = gamma(1+z)
+        //
+        // therefore:
+        //  gamma(z) = pi / ( gamma(1-z) * sin(pi*z) )
+        float t0 = liquid_gammaf(1.0 - _z);
+        float t1 = sinf(M_PI*_z);
+        if (t0==0 || t1==0)
+            fprintf(stderr,"warning: liquid_gammaf(), divide by zero\n");
+        return M_PI / (t0 * t1);
+    } else {
+        return expf( liquid_lngammaf(_z) );
+    }
+}
+
+// ln( gamma(z,alpha) ) : lower incomplete gamma function
+#define LOWERGAMMA_MIN_ITERATIONS 50    // minimum number of iterations
+#define LOWERGAMMA_MAX_ITERATIONS 1000  // maximum number of iterations
+float liquid_lnlowergammaf(float _z, float _alpha)
+{
+    float t0 = _z * logf(_alpha);
+    float t1 = liquid_lngammaf(_z);
+    float t2 = -_alpha;
+    float t3 = 0.0f;
+
+    unsigned int k = 0;
+    float log_alpha = logf(_alpha);
+    float tprime = 0.0f;
+    float tmax = 0.0f;
+    float t = 0.0f;
+    for (k=0; k<LOWERGAMMA_MAX_ITERATIONS; k++) {
+        // retain previous value for t
+        tprime = t;
+
+        // compute log( alpha^k / Gamma(_z + k + 1) )
+        //         = k*log(alpha) - lnGamma(_z + k + 1)
+        t = k*log_alpha - liquid_lngammaf(_z + (float)k + 1.0f);
+
+        // accumulate e^t
+        t3 += expf(t);
+
+        // check premature exit criteria
+        if (k==0 || t > tmax)
+            tmax = t;
+
+        // conditions:
+        //  1. minimum number of iterations met
+        //  2. surpassed inflection point: k*log(alpha) - log(Gamma(z+k+1))
+        //     has an inverted parabolic shape
+        //  3. sufficiently beyond peak
+        if ( k > LOWERGAMMA_MIN_ITERATIONS && tprime > t && (tmax-t) > 20.0f)
+            break;
+    }
+
+    return t0 + t1 + t2 + logf(t3);
+}
+
+// ln( Gamma(z,alpha) ) : upper incomplete gamma function
+float liquid_lnuppergammaf(float _z, float _alpha)
+{
+    return logf( liquid_gammaf(_z) - liquid_lowergammaf(_z,_alpha) );
+}
+
+
+// gamma(z,alpha) : lower incomplete gamma function
+float liquid_lowergammaf(float _z, float _alpha)
+{
+    return expf( liquid_lnlowergammaf(_z,_alpha) );
+}
+
+// Gamma(z,alpha) : upper incomplete gamma function
+float liquid_uppergammaf(float _z, float _alpha)
+{
+    return expf( liquid_lnuppergammaf(_z,_alpha) );
+}
+
+// compute _n!
+float liquid_factorialf(unsigned int _n) {
+    return fabsf(liquid_gammaf((float)(_n+1)));
+}
+
diff --git a/src/math/src/math.trig.c b/src/math/src/math.trig.c
new file mode 100644
index 0000000..59820a4
--- /dev/null
+++ b/src/math/src/math.trig.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Useful mathematical formulae (trig)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#if 0
+#define LIQUID_SINF_POLYORD (4)
+static float liquid_sinf_poly[LIQUID_SINF_POLYORD] = {
+  -0.113791698328739f,
+  -0.069825754521815f,
+   1.026821728423492f,
+   0.000000000000000f
+};
+#endif
+
+void liquid_sincosf(float _x,
+                    float * _sinf,
+                    float * _cosf)
+{
+    * _sinf = sinf(_x);
+    * _cosf = cosf(_x);
+}
+
+float liquid_sinf(float _x)
+{
+    float s, c;
+    liquid_sincosf(_x,&s,&c);
+    return s;
+}
+
+float liquid_cosf(float _x)
+{
+    float s, c;
+    liquid_sincosf(_x,&s,&c);
+    return c;
+}
+
+float liquid_tanf(float _x)
+{
+    float s, c;
+    liquid_sincosf(_x,&s,&c);
+    return s/c;
+}
+
+float liquid_expf(float _x)
+{
+    return expf(_x);
+}
+
+float liquid_logf(float _x)
+{
+    return logf(_x);
+}
+
diff --git a/src/math/src/modular_arithmetic.c b/src/math/src/modular_arithmetic.c
new file mode 100644
index 0000000..4f60bc6
--- /dev/null
+++ b/src/math/src/modular_arithmetic.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Modular arithmetic and related integer mathematics
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.h"
+
+// determine if number is prime (slow, simple method)
+int liquid_is_prime(unsigned int _n)
+{
+    if (_n < 4) return 1;
+
+    unsigned int i;
+    for (i=2; i<_n; i++) {
+        if ( (_n % i) == 0)
+            return 0;
+    }
+
+    return 1;
+}
+
+// compute number's prime factors
+//  _n          :   number to factor
+//  _factors    :   pre-allocated array of factors [size: LIQUID_MAX_FACTORS x 1]
+//  _num_factors:   number of factors found, sorted ascending
+void liquid_factor(unsigned int   _n,
+                   unsigned int * _factors,
+                   unsigned int * _num_factors)
+{
+    unsigned int k;
+    unsigned int n = _n;
+    unsigned int num_factors = 0;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n % k) == 0) {
+                // k factors _n; append to list
+                _factors[num_factors++] = k;
+                n /= k;
+                break;
+            }
+        }
+    } while (n > 1 && num_factors < LIQUID_MAX_FACTORS);
+
+    if (n > 1 && num_factors == LIQUID_MAX_FACTORS) {
+        fprintf(stderr,"error, liquid_factor(), could not factor %u in %u numbers\n", _n, LIQUID_MAX_FACTORS);
+        exit(1);
+    }
+
+    *_num_factors = num_factors;
+}
+
+// compute number's unique prime factors
+//  _n          :   number to factor
+//  _factors    :   pre-allocated array of factors [size: LIQUID_MAX_FACTORS x 1]
+//  _num_factors:   number of unique factors found, sorted ascending
+void liquid_unique_factor(unsigned int   _n,
+                          unsigned int * _factors,
+                          unsigned int * _num_factors)
+{
+    unsigned int k;
+    unsigned int n = _n;
+    unsigned int num_factors = 0;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n % k) == 0) {
+                // k factors _n; append to list
+                _factors[num_factors] = k;
+                n /= k;
+                
+                if (num_factors == 0)
+                    num_factors++;
+                else if (_factors[num_factors-1] != k)
+                    num_factors++;
+
+                break;
+            }
+        }
+    } while (n > 1 && num_factors < LIQUID_MAX_FACTORS);
+
+    if (n > 1 && num_factors == LIQUID_MAX_FACTORS) {
+        fprintf(stderr,"error, liquid_unqiue_factor(), could not factor %u in %u numbers\n", _n, LIQUID_MAX_FACTORS);
+        exit(1);
+    }
+
+    *_num_factors = num_factors;
+}
+
+// find smallest primitive root of _n
+unsigned int liquid_primitive_root(unsigned int _n)
+{
+    // TODO : flesh out this function
+    return 1;
+}
+
+// find smallest primitive root of _n (assuming _n is prime)
+unsigned int liquid_primitive_root_prime(unsigned int _n)
+{
+    // find unique factors of _n-1
+    unsigned int unique_factors[LIQUID_MAX_FACTORS];
+    unsigned int num_unique_factors = 0;
+    unsigned int n = _n-1;
+    unsigned int k;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n%k)==0 ) {
+                // k is a factor of (_n-1)
+                n /= k;
+
+                // add element to end of table
+                unique_factors[num_unique_factors] = k;
+
+                // increment counter only if element is unique
+                if (num_unique_factors == 0)
+                    num_unique_factors++;
+                else if (unique_factors[num_unique_factors-1] != k)
+                    num_unique_factors++;
+                break;
+            }
+        }
+    } while (n > 1 && num_unique_factors < LIQUID_MAX_FACTORS);
+
+#if 0
+    // print unique factors
+    printf("found %u unique factors of n-1 = %u\n", num_unique_factors, _n-1);
+    for (k=0; k<num_unique_factors; k++)
+        printf("  %3u\n", unique_factors[k]);
+#endif
+
+    // search for minimum integer for which
+    //   g^( (_n-1)/m ) != 1 (mod _n)
+    // for all unique roots 'm'
+    unsigned int g;
+    for (g=2; g<_n; g++) {
+        int is_root = 1;
+        for (k=0; k<num_unique_factors; k++) {
+            unsigned int e = (_n-1) / unique_factors[k];
+            if ( liquid_modpow(g,e,_n) == 1 ) {
+                // not a root
+                is_root = 0;
+                break;
+            }
+        }
+
+        if (is_root) {
+            //printf("  %u is a primitive root!\n", g);
+            break;
+        }
+    }
+
+    return g;
+}
+
+// compute c = base^exp (mod n)
+unsigned int liquid_modpow(unsigned int _base,
+                           unsigned int _exp,
+                           unsigned int _n)
+{
+    unsigned int c = 1;
+    unsigned int i;
+    for (i=0; i<_exp; i++)
+        c = (c * _base) % _n;
+
+    return c;
+}
+
+// Euler's totient function
+unsigned int liquid_totient(unsigned int _n)
+{
+    // find unique prime factors
+    unsigned int t = _n;    // totient
+    unsigned int n = _n;
+    unsigned int p = 0;     // last unique prime factor
+    unsigned int k;
+    do {
+        for (k=2; k<=n; k++) {
+            if ( (n%k)==0 ) {
+                n /= k;
+
+                if (p != k) {
+                    // factor is unique
+                    t *= k-1;
+                    t /= k;
+                }
+
+                p = k;
+                break;
+            }
+        }
+    } while (n > 1);
+
+    return t;
+}
+
diff --git a/src/math/src/poly.c b/src/math/src/poly.c
new file mode 100644
index 0000000..dce4038
--- /dev/null
+++ b/src/math/src/poly.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Floating-point polynomials (double precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrix, name)
+#define POLY(name)      LIQUID_CONCAT(poly,   name)
+#define POLY_NAME       "poly"
+#define T               double
+#define TC              double complex
+
+#define T_COMPLEX       1
+#define TI_COMPLEX      1
+
+#define T_ABS(X)        fabs(X)
+#define TC_ABS(X)       cabs(X)
+
+#include "poly.common.c"
+#include "poly.expand.c"
+#include "poly.findroots.c"
+#include "poly.lagrange.c"
+
diff --git a/src/math/src/poly.common.c b/src/math/src/poly.common.c
new file mode 100644
index 0000000..6cdb454
--- /dev/null
+++ b/src/math/src/poly.common.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// common polynomial routines
+//  polyval
+//  polyfit
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+T POLY(_val)(T * _p, unsigned int _k, T _x)
+{
+    unsigned int i;
+    T xk = 1;
+    T y = 0.0f;
+    for (i=0; i<_k; i++) {
+        y += _p[i]*xk;
+        xk *= _x;
+    }
+    return y;
+}
+
+void POLY(_fit)(T * _x,
+                T * _y,
+                unsigned int _n,
+                T * _p,
+                unsigned int _k)
+{
+
+    // ...
+    T X[_n*_k];
+    unsigned int r,c;
+    T v;
+    for (r=0; r<_n; r++) {
+        v = 1;
+        for (c=0; c<_k; c++) {
+            matrix_access(X,_n,_k,r,c) = v;
+            v *= _x[r];
+        }
+    }
+
+    // compute transpose of X
+    T Xt[_k*_n];
+    memmove(Xt,X,_k*_n*sizeof(T));
+    MATRIX(_trans)(Xt,_n,_k);
+
+    // compute [X']*y
+    T Xty[_k];
+    MATRIX(_mul)(Xt, _k, _n,
+                 _y, _n, 1,
+                 Xty,_k, 1);
+
+    // compute [X']*X
+    T X2[_k*_k];
+    MATRIX(_mul)(Xt, _k, _n,
+                 X,  _n, _k,
+                 X2, _k, _k);
+
+    // compute inv([X']*X)
+    T G[_k*_k];
+    memmove(G,X2,_k*_k*sizeof(T));
+    MATRIX(_inv)(G,_k,_k);
+
+    // compute coefficients
+    MATRIX(_mul)(G,  _k, _k,
+                 Xty,_k, 1,
+                 _p, _k, 1);
+}
+
diff --git a/src/math/src/poly.expand.c b/src/math/src/poly.expand.c
new file mode 100644
index 0000000..b58ddf4
--- /dev/null
+++ b/src/math/src/poly.expand.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// polynomial expansion methods
+//
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// expands the polynomial:
+//  P_n(x) = (1+x)^n
+// as
+//  P_n(x) = p[0] + p[1]*x + p[2]*x^2 + ... + p[n]x^n
+// NOTE: _p has order n=m+k (array is length n+1)
+void POLY(_expandbinomial)(unsigned int _n,
+                           T * _c)
+{
+    // no roots; return zero
+    if (_n == 0) {
+        _c[0] = 0.;
+        return;
+    }
+
+    int i, j;
+    // initialize coefficients array to [1,0,0,....0]
+    for (i=0; i<=_n; i++)
+        _c[i] = (i==0) ? 1 : 0;
+
+    // iterative polynomial multiplication
+    for (i=0; i<_n; i++) {
+        for (j=i+1; j>0; j--)
+            _c[j] = _c[j] + _c[j-1];
+    }
+    // assert(_c[0]==1.0f);
+}
+
+// expands the polynomial:
+//  P_n(x) = (1+x)^m * (1-x)^k
+// as
+//  P_n(x) = p[0] + p[1]*x + p[2]*x^2 + ... + p[n]x^n
+// NOTE: _p has order n=m+k (array is length n+1)
+void POLY(_expandbinomial_pm)(unsigned int _m,
+                              unsigned int _k,
+                              T * _c)
+{
+    unsigned int n = _m + _k;
+
+    // no roots; return zero
+    if (n == 0) {
+        _c[0] = 0.;
+        return;
+    }
+
+    int i, j;
+    // initialize coefficients array to [1,0,0,....0]
+    for (i=0; i<=n; i++)
+        _c[i] = (i==0) ? 1 : 0;
+
+    // iterative polynomial multiplication (1+x)
+    for (i=0; i<_m; i++) {
+        for (j=i+1; j>0; j--)
+            _c[j] = _c[j] + _c[j-1];
+    }
+
+    // iterative polynomial multiplication (1-x)
+    for (i=_m; i<n; i++) {
+        for (j=i+1; j>0; j--)
+            _c[j] = _c[j] - _c[j-1];
+    }
+    // assert(_c[0]==1.0f);
+}
+
+#if 0
+// expands the polynomial:
+//  (1+x*a[0])*(1+x*a[1]) * ... * (1+x*a[n-1])
+// as
+//  c[0] + c[1]*x + c[2]*x^2 + ... + c[n]*x^n
+void POLY(_expandbinomial)(T * _a,
+                           unsigned int _n,
+                           T * _c)
+{
+    // no roots; return zero
+    if (_n == 0) {
+        _c[0] = 0.;
+        return;
+    }
+
+    int i, j;
+    // initialize coefficients array to [1,0,0,....0]
+    for (i=0; i<=_n; i++)
+        _c[i] = (i==0) ? 1 : 0;
+
+    // iterative polynomial multiplication
+    for (i=0; i<_n; i++) {
+        for (j=i+1; j>0; j--)
+            _c[j] = _a[i]*_c[j] + _c[j-1];
+
+        _c[j] *= _a[i];
+    }
+
+    // flip values
+    unsigned int r = (_n+1) % 2;
+    unsigned int L = (_n+1-r)/2;
+    T tmp;
+    for (i=0; i<L; i++) {
+        tmp = _c[i];
+        _c[i] = _c[_n-i];
+        _c[_n-i] = tmp;
+    }
+
+    // assert(_c[0]==1.0f);
+}
+#endif
+
+
+// expands the polynomial:
+//  P_n(x) = (x-r[0]) * (x-r[1]) * ... * (x-r[n-1])
+// as
+//  P_n(x) = c[0] + c[1]*x + ... + c[n]*x^n
+// where r[0],r[1],...,r[n-1] are the roots of P_n(x)
+//
+// c has order _n (array is length _n+1)
+void POLY(_expandroots)(T * _a,
+                        unsigned int _n,
+                        T * _c)
+{
+    // no roots; return zero
+    if (_n == 0) {
+        _c[0] = 0.;
+        return;
+    }
+
+    int i, j;
+    // initialize coefficients array to [1,0,0,....0]
+    for (i=0; i<=_n; i++)
+        _c[i] = (i==0) ? 1 : 0;
+
+    // iterative polynomial multiplication
+    for (i=0; i<_n; i++) {
+        for (j=i+1; j>0; j--)
+            _c[j] = -_a[i]*_c[j] + _c[j-1];
+
+        _c[j] *= -_a[i];
+    }
+
+    // assert(c[_n]==1.0f)
+}
+
+// expands the polynomial:
+//  P_n(x) =
+//    (x*b[0]-a[0]) * (x*b[1]-a[1]) * ... * (x*b[n-1]-a[n-1])
+// as
+//  P_n(x) = c[0] + c[1]*x + ... + c[n]*x^n
+//
+// c has order _n (array is length _n+1)
+void POLY(_expandroots2)(T * _a,
+                         T * _b,
+                         unsigned int _n,
+                         T * _c)
+{
+    unsigned int i;
+
+    // factor _b[i] from each root : (x*b - a) = (x - a/b)*b
+    T g = 1.0;
+    T r[_n];
+    for (i=0; i<_n; i++) {
+        g *= -_b[i];
+        r[i] = _a[i] / _b[i];
+    }
+
+    // expand new root set
+    POLY(_expandroots)(r, _n, _c);
+
+    // multiply by gain
+    for (i=0; i<_n+1; i++)
+        _c[i] *= g;
+}
+
+// expands the multiplication of two polynomials
+//
+//  (a[0] + a[1]*x + a[2]*x^2 + ...) * (b[0] + b[1]*x + b[]*x^2 + ...2 + ...)
+// as
+//  c[0] + c[1]*x + c[2]*x^2 + ... + c[n]*x^n
+//
+// where order(c)  = order(a)  + order(b) + 1
+//    :: length(c) = length(a) + length(b) - 1
+//
+//  _a          :   1st polynomial coefficients (length is _order_a+1)
+//  _order_a    :   1st polynomial order
+//  _b          :   2nd polynomial coefficients (length is _order_b+1)
+//  _order_b    :   2nd polynomial order
+//  _c          :   output polynomial coefficients (length is _order_a + _order_b + 1)
+void POLY(_mul)(T * _a,
+                unsigned int _order_a,
+                T * _b,
+                unsigned int _order_b,
+                T * _c)
+{
+    unsigned int na = _order_a + 1;
+    unsigned int nb = _order_b + 1;
+    unsigned int nc = na + nb - 1;
+    unsigned int i;
+    for (i=0; i<nc; i++)
+        _c[i] = 0.0f;
+
+    unsigned int j;
+    for (i=0; i<na; i++) {
+        for (j=0; j<nb; j++) {
+            _c[i+j] += _a[i]*_b[j];
+        }
+    }
+}
+
diff --git a/src/math/src/poly.findroots.c b/src/math/src/poly.findroots.c
new file mode 100644
index 0000000..e000ee8
--- /dev/null
+++ b/src/math/src/poly.findroots.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// polynomial methods
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+// debug polynomial root-finding methods?
+#define LIQUID_POLY_FINDROOTS_DEBUG     0
+
+// 
+// forward declaration of internal methods
+//
+
+// iterate over Bairstow's method, finding quadratic factor x^2 + u*x + v
+void POLY(_findroots_bairstow_recursion)(T *          _p,
+                                         unsigned int _k,
+                                         T *          _p1,
+                                         T *          _u,
+                                         T *          _v);
+
+
+// finds the complex roots of the polynomial
+//  _p      :   polynomial array, ascending powers [size: _k x 1]
+//  _k      :   polynomials length (poly order = _k - 1)
+//  _roots  :   resulting complex roots [size: _k-1 x 1]
+void POLY(_findroots)(T *          _p,
+                      unsigned int _k,
+                      TC *         _roots)
+{
+    // find roots of polynomial using Bairstow's method (more
+    // accurate and reliable than Durand-Kerner)
+    POLY(_findroots_bairstow)(_p,_k,_roots);
+}
+
+// finds the complex roots of the polynomial using the Durand-Kerner method
+//  _p      :   polynomial array, ascending powers [size: _k x 1]
+//  _k      :   polynomials length (poly order = _k - 1)
+//  _roots  :   resulting complex roots [size: _k-1 x 1]
+void POLY(_findroots_durandkerner)(T *          _p,
+                                   unsigned int _k,
+                                   TC *         _roots)
+{
+    if (_k < 2) {
+        fprintf(stderr,"%s_findroots_durandkerner(), order must be greater than 0\n", POLY_NAME);
+        exit(1);
+    } else if (_p[_k-1] != 1) {
+        fprintf(stderr,"%s_findroots_durandkerner(), _p[_k-1] must be equal to 1\n", POLY_NAME);
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int num_roots = _k-1;
+    T r0[num_roots];
+    T r1[num_roots];
+
+    // find intial magnitude
+    float g     = 0.0f;
+    float gmax  = 0.0f;
+    for (i=0; i<_k; i++) {
+        g = T_ABS(_p[i]);
+        if (i==0 || g > gmax)
+            gmax = g;
+    }
+
+    // initialize roots
+    T t0 = 0.9f * (1 + gmax) * cexpf(_Complex_I*1.1526f);
+    T t  = 1.0f;
+    for (i=0; i<num_roots; i++) {
+        r0[i] = t;
+        t *= t0;
+    }
+
+    unsigned int max_num_iterations = 50;
+    int continue_iterating = 1;
+    unsigned int j, k;
+    T f;
+    T fp;
+    //for (i=0; i<num_iterations; i++) {
+    i = 0;
+    while (continue_iterating) {
+#if LIQUID_POLY_FINDROOTS_DEBUG
+        printf("%s_findroots(), i=%3u :\n", POLY_NAME, i);
+        for (j=0; j<num_roots; j++)
+            printf("  r[%3u] = %12.8f + j*%12.8f\n", j, crealf(r0[j]), cimagf(r0[j]));
+#endif
+        for (j=0; j<num_roots; j++) {
+            f = POLY(_val)(_p,_k,r0[j]);
+            fp = 1;
+            for (k=0; k<num_roots; k++) {
+                if (k==j) continue;
+                fp *= r0[j] - r0[k];
+            }
+            r1[j] = r0[j] - f / fp;
+        }
+
+        // stop iterating if roots have settled
+        float delta=0.0f;
+        T e;
+        for (j=0; j<num_roots; j++) {
+            e = r0[j] - r1[j];
+            delta += crealf(e*conjf(e));
+        }
+        delta /= num_roots * gmax;
+#if LIQUID_POLY_FINDROOTS_DEBUG
+        printf("delta[%3u] = %12.4e\n", i, delta);
+#endif
+
+        if (delta < 1e-6f || i == max_num_iterations)
+            continue_iterating = 0;
+
+        memmove(r0, r1, num_roots*sizeof(T));
+        i++;
+    }
+
+    for (i=0; i<_k; i++)
+        _roots[i] = r1[i];
+}
+
+// finds the complex roots of the polynomial using Bairstow's method
+//  _p      :   polynomial array, ascending powers [size: _k x 1]
+//  _k      :   polynomials length (poly order = _k - 1)
+//  _roots  :   resulting complex roots [size: _k-1 x 1]
+void POLY(_findroots_bairstow)(T *          _p,
+                               unsigned int _k,
+                               TC *         _roots)
+{
+    T p0[_k];       // buffer 0
+    T p1[_k];       // buffer 1
+    T * p   = NULL; // input polynomial
+    T * pr  = NULL; // output (reduced) polynomial
+
+    unsigned int i;
+    unsigned int k=0;   // output roots counter
+    memmove(p0, _p, _k*sizeof(T));
+
+    T u, v;
+
+    unsigned int n = _k;        // input counter (decrementer)
+    unsigned int r = _k % 2;    // polynomial length odd? (order even?)
+    unsigned int L = (_k-r)/2;  // semi-length
+    for (i=0; i<L-1+r; i++) {
+        // set polynomial and reduced polynomial buffer pointers
+        p  = (i % 2) == 0 ? p0 : p1;
+        pr = (i % 2) == 0 ? p1 : p0;
+
+        // initial estimates for u, v
+        // TODO : ensure no division by zero
+        if (p[n-1] == 0) {
+            fprintf(stderr,"warning: poly_findroots_bairstow(), irreducible polynomial");
+            p[n-1] = 1e-12;
+        }
+        u = p[n-2] / p[n-1];
+        v = p[n-3] / p[n-1];
+
+        // compute factor using Bairstow's recursion
+        POLY(_findroots_bairstow_recursion)(p,n,pr,&u,&v);
+
+        // compute complex roots of x^2 + u*x + v
+        TC r0 = 0.5f*(-u + csqrtf(u*u - 4.0*v));
+        TC r1 = 0.5f*(-u - csqrtf(u*u - 4.0*v));
+
+        // append result to output
+        _roots[k++] = r0;
+        _roots[k++] = r1;
+
+#if LIQUID_POLY_FINDROOTS_DEBUG
+        // print debugging info
+        unsigned int j;
+        printf("initial polynomial:\n");
+        for (j=0; j<n; j++)
+            printf("  p[%3u]  = %12.8f + j*%12.8f\n", j, crealf(p[j]), cimagf(p[j]));
+
+        printf("polynomial factor: x^2 + u*x + v\n");
+        printf("  u : %12.8f + j*%12.8f\n", crealf(u), cimagf(u));
+        printf("  v : %12.8f + j*%12.8f\n", crealf(v), cimagf(v));
+
+        printf("roots:\n");
+        printf("  r0 : %12.8f + j*%12.8f\n", crealf(r0), cimagf(r0));
+        printf("  r1 : %12.8f + j*%12.8f\n", crealf(r1), cimagf(r1));
+
+        printf("reduced polynomial:\n");
+        for (j=0; j<n-2; j++)
+            printf("  pr[%3u] = %12.8f + j*%12.8f\n", j, crealf(pr[j]), cimagf(pr[j]));
+#endif
+
+        // decrement new (reduced) polynomial size by 2
+        n -= 2;
+    }
+
+    if (r==0) {
+#if LIQUID_POLY_FINDROOTS_DEBUG
+        assert(n==2);
+#endif
+        _roots[k++] = -pr[0]/pr[1];
+    }
+}
+
+// iterate over Bairstow's method, finding quadratic factor x^2 + u*x + v
+//  _p      :   polynomial array, ascending powers [size: _k x 1]
+//  _k      :   polynomials length (poly order = _k - 1)
+//  _p1     :   reduced polynomial (output) [size: _k-2 x 1]
+//  _u      :   input: initial estimate for u; output: resulting u
+//  _v      :   input: initial estimate for v; output: resulting v
+void POLY(_findroots_bairstow_recursion)(T *          _p,
+                                         unsigned int _k,
+                                         T *          _p1,
+                                         T *          _u,
+                                         T *          _v)
+{
+    // validate length
+    if (_k < 3) {
+        fprintf(stderr,"findroots_bairstow_recursion(), invalid polynomial length: %u\n", _k);
+        exit(1);
+    }
+
+    // initial estimates for u, v
+    T u = *_u;
+    T v = *_v;
+    
+    unsigned int n = _k-1;
+    T c,d,g,h;
+    T q;
+    T du, dv;
+
+    // reduced polynomials
+    T b[_k];
+    T f[_k];
+    b[n] = b[n-1] = 0;
+    f[n] = f[n-1] = 0;
+
+    int i;
+    unsigned int k=0;
+    unsigned int max_num_iterations=50;
+    int continue_iterating = 1;
+
+    while (continue_iterating) {
+        // update reduced polynomial coefficients
+        for (i=n-2; i>=0; i--) {
+            b[i] = _p[i+2] - u*b[i+1] - v*b[i+2];
+            f[i] =  b[i+2] - u*f[i+1] - v*f[i+2];
+        }
+        c = _p[1] - u*b[0] - v*b[1];
+        g =  b[1] - u*f[0] - v*f[1];
+        d = _p[0] - v*b[0];
+        h =  b[0] - v*f[0];
+
+        // compute scaling factor
+        q  = 1/(v*g*g + h*(h-u*g));
+
+        // compute u, v steps
+        du = - q*(-h*c   + g*d);
+        dv = - q*(-g*v*c + (g*u-h)*d);
+
+#if LIQUID_POLY_FINDROOTS_DEBUG
+        // print debugging info
+        printf("bairstow [%u] :\n", k);
+        printf("  u     : %12.4e + j*%12.4e\n", crealf(u), cimagf(u));
+        printf("  v     : %12.4e + j*%12.4e\n", crealf(v), cimagf(v));
+        printf("  b     : \n");
+        for (i=0; i<n-2; i++)
+            printf("      %12.4e + j*%12.4e\n", crealf(b[i]), cimagf(b[i]));
+        printf("  fb    : \n");
+        for (i=0; i<n-2; i++)
+            printf("      %12.4e + j*%12.4e\n", crealf(f[i]), cimagf(f[i]));
+        printf("  c     : %12.4e + j*%12.4e\n", crealf(c), cimagf(c));
+        printf("  g     : %12.4e + j*%12.4e\n", crealf(g), cimagf(g));
+        printf("  d     : %12.4e + j*%12.4e\n", crealf(d), cimagf(d));
+        printf("  h     : %12.4e + j*%12.4e\n", crealf(h), cimagf(h));
+        printf("  q     : %12.4e + j*%12.4e\n", crealf(q), cimagf(q));
+        printf("  du    : %12.4e + j*%12.4e\n", crealf(du), cimagf(du));
+        printf("  dv    : %12.4e + j*%12.4e\n", crealf(dv), cimagf(dv));
+
+        printf("  step : %12.4e + j*%12.4e\n", crealf(du+dv), cimagf(du+dv));
+#endif
+
+        // adjust u, v
+        if (isnan(T_ABS(du)) || isnan(T_ABS(dv))) {
+            u *= 0.5f;
+            v *= 0.5f;
+        } else {
+            u += du;
+            v += dv;
+        }
+
+        // increment iteration counter
+        k++;
+
+        // exit conditions
+        if (T_ABS(du+dv) < 1e-6f || k == max_num_iterations)
+            continue_iterating = 0;
+    }
+
+    // set resulting reduced polynomial
+    for (i=0; i<_k-2; i++)
+        _p1[i] = b[i];
+
+    // set output pairs
+    *_u = u;
+    *_v = v;
+
+}
+
diff --git a/src/math/src/poly.lagrange.c b/src/math/src/poly.lagrange.c
new file mode 100644
index 0000000..12e7ac0
--- /dev/null
+++ b/src/math/src/poly.lagrange.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Lagrange polynomial methods
+//
+
+#include <stdio.h>
+#include <string.h>
+
+// Lagrange polynomial exact fit (order _n-1)
+void POLY(_fit_lagrange)(T * _x,
+                         T * _y,
+                         unsigned int _n,
+                         T * _p)
+{
+    unsigned int k=_n-1;    // polynomial order
+
+    // clear polynomial coefficients array
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        _p[i] = 0.;
+
+    // compute roots, gain
+    T roots[k];     // polynomial roots
+    T c[_n];        // expanded polynomial
+    T g;            // gain
+    unsigned int j;
+    unsigned int n;
+    for (i=0; i<_n; i++) {
+        n=0;
+        g=1.0f;
+        for (j=0; j<_n; j++) {
+            if (j!=i) {
+                roots[n++] = - _x[j];
+                g *= (_x[i] - _x[j]);
+            }
+        }
+        g = _y[i] / g;
+
+        // expand roots
+        POLY(_expandroots)(roots, k, c);
+
+        for (j=0; j<_n; j++) {
+            _p[j] += g * c[j];
+        }
+
+#if 0
+        // debug/print
+        printf("****** %3u : **********************\n", i);
+        printf("  g : %12.8f\n", crealf(g));
+        printf("  roots:\n");
+        for (j=0; j<k; j++)
+            printf("  r[%3u] = %12.8f\n", j, crealf(roots[j]));
+        printf("  expanded roots:\n");
+        for (j=0; j<_n; j++)
+            printf("  c[%3u] = %16.6f > %16.6f\n", j, crealf(c[j]), crealf(g*c[j]));
+#endif
+    }
+
+}
+
+// Lagrange polynomial interpolation
+T POLY(_interp_lagrange)(T * _x,
+                         T * _y,
+                         unsigned int _n,
+                         T   _x0)
+{
+    T y0 = 0.0;     // set output to zero
+    T g;            // accumulator
+    unsigned int i, j;
+    for (i=0; i<_n; i++) {
+        g=1.0f;
+        for (j=0; j<_n; j++) {
+            if (j!=i) {
+                g *= (_x0 - _x[j])/(_x[i] - _x[j]);
+            }
+        }
+        y0 += _y[i] * g;
+    }
+
+    return y0;
+}
+// Lagrange polynomial fit (barycentric form)
+void POLY(_fit_lagrange_barycentric)(T * _x,
+                                     unsigned int _n,
+                                     T * _w)
+{
+    // compute barycentric weights (slow method)
+    unsigned int j, k;
+    for (j=0; j<_n; j++) {
+        _w[j] = 1.;
+        for (k=0; k<_n; k++) {
+            if (j==k)   continue;
+            else        _w[j] *= (_x[j] - _x[k]);
+        }
+
+        _w[j] = 1. / _w[j];
+    }
+
+    // normalize by _w[0]
+    T w0 = _w[0];
+    for (j=0; j<_n; j++)
+        _w[j] /= w0;
+}
+
+// Lagrange polynomial interpolation (barycentric form)
+T POLY(_val_lagrange_barycentric)(T * _x,
+                                  T * _y,
+                                  T * _w,
+                                  T   _x0,
+                                  unsigned int _n)
+{
+    T t0 = 0.;  // numerator sum
+    T t1 = 0.;  // denominator sum
+    T g;        // _x0 - _x[j]
+
+    // exact fit tolerance
+    float tol = 1e-6f;
+
+    unsigned int j;
+    for (j=0; j<_n; j++) {
+        g = _x0 - _x[j];
+
+        // test for "exact" fit
+        if (T_ABS(g) < tol)
+            return _y[j];
+
+        t0 += _w[j] * _y[j] / g;
+        t1 += _w[j] / g;
+    }
+    return t0 / t1;
+}
+
diff --git a/src/math/src/polyc.c b/src/math/src/polyc.c
new file mode 100644
index 0000000..4a7f48d
--- /dev/null
+++ b/src/math/src/polyc.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex floating-point polynomials (double precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixc, name)
+#define POLY(name)      LIQUID_CONCAT(polyc,   name)
+#define POLY_NAME       "polyc"
+#define T               double complex
+#define TC              double complex
+
+#define T_COMPLEX       1
+#define TI_COMPLEX      1
+
+#define T_ABS(X)        cabs(X)
+#define TC_ABS(X)       cabs(X)
+
+#include "poly.common.c"
+#include "poly.expand.c"
+#include "poly.findroots.c"
+#include "poly.lagrange.c"
+
diff --git a/src/math/src/polycf.c b/src/math/src/polycf.c
new file mode 100644
index 0000000..f866ab5
--- /dev/null
+++ b/src/math/src/polycf.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex floating-point polynomials (single precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixcf, name)
+#define POLY(name)      LIQUID_CONCAT(polycf,   name)
+#define POLY_NAME       "polycf"
+#define T               float complex
+#define TC              float complex
+
+#define T_COMPLEX       1
+#define TI_COMPLEX      1
+
+#define T_ABS(X)        cabsf(X)
+#define TC_ABS(X)       cabsf(X)
+
+#include "poly.common.c"
+#include "poly.expand.c"
+#include "poly.findroots.c"
+#include "poly.lagrange.c"
+
diff --git a/src/math/src/polyf.c b/src/math/src/polyf.c
new file mode 100644
index 0000000..61affa3
--- /dev/null
+++ b/src/math/src/polyf.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Floating-point polynomials (single precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixf, name)
+#define POLY(name)      LIQUID_CONCAT(polyf,   name)
+#define POLY_NAME       "polyf"
+#define T               float
+#define TC              float complex
+
+#define T_COMPLEX       1
+#define TI_COMPLEX      1
+
+#define T_ABS(X)        fabsf(X)
+#define TC_ABS(X)       cabsf(X)
+
+#include "poly.common.c"
+#include "poly.expand.c"
+#include "poly.findroots.c"
+#include "poly.lagrange.c"
+
diff --git a/src/math/src/windows.c b/src/math/src/windows.c
new file mode 100644
index 0000000..2da5241
--- /dev/null
+++ b/src/math/src/windows.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Windowing functions
+//
+// References:
+//  [Kaiser:1980] James F. Kaiser and Ronald W. Schafer, "On
+//      the Use of I0-Sinh Window for Spectrum Analysis,"
+//      IEEE Transactions on Acoustics, Speech, and Signal
+//      Processing, vol. ASSP-28, no. 1, pp. 105--107,
+//      February, 1980.
+//  [harris:1978] frederic j. harris, "On the Use of Windows for Harmonic
+//      Analysis with the Discrete Fourier Transform," Proceedings of the
+//      IEEE, vol. 66, no. 1, January, 1978.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+const char * liquid_window_str[LIQUID_WINDOW_NUM_FUNCTIONS][2] = {
+    // short name,  long name
+    {"unknown",         "unknown"                   },
+    {"hamming",         "Hamming"                   },
+    {"hann",            "Hann"                      },
+    {"blackmanharris",  "Blackman-harris (4-term)"  },
+    {"blackmanharris7", "Blackman-harris (7-term)"  },
+    {"kaiser",          "Kaiser-Bessel"             },
+    {"flattop",         "flat top"                  },
+    {"triangular",      "triangular"                },
+    {"rcostaper",       "raised-cosine taper"       },
+    {"kbd",             "Kaiser-Bessel derived"     },
+};
+
+// Print compact list of existing and available windowing functions
+void liquid_print_windows()
+{
+    unsigned int i;
+    unsigned int len = 10;
+
+    // print all available MOD schemes
+    printf("          ");
+    for (i=0; i<LIQUID_WINDOW_NUM_FUNCTIONS; i++) {
+        printf("%s", liquid_window_str[i][0]);
+
+        if (i != LIQUID_WINDOW_NUM_FUNCTIONS-1)
+            printf(", ");
+
+        len += strlen(liquid_window_str[i][0]);
+        if (len > 48 && i != LIQUID_WINDOW_NUM_FUNCTIONS-1) {
+            len = 10;
+            printf("\n          ");
+        }
+    }
+    printf("\n");
+}
+
+// returns modulation_scheme based on input string
+liquid_window_type liquid_getopt_str2window(const char * _str)
+{
+    // compare each string to short name
+    unsigned int i;
+    for (i=0; i<LIQUID_WINDOW_NUM_FUNCTIONS; i++) {
+        if (strcmp(_str,liquid_window_str[i][0])==0) {
+            return i;
+        }
+    }
+
+    fprintf(stderr,"warning: liquid_getopt_str2window(), unknown/unsupported window scheme : %s\n", _str);
+    return LIQUID_WINDOW_UNKNOWN;
+}
+
+// Kaiser-Bessel derived window
+float liquid_kbd(unsigned int _n,
+                 unsigned int _N,
+                 float _beta)
+{
+    // TODO add reference
+
+    // validate input
+    if (_n >= _N) {
+        fprintf(stderr,"error: liquid_kbd(), index exceeds maximum\n");
+        exit(1);
+    } else if (_N == 0) {
+        fprintf(stderr,"error: liquid_kbd(), window length must be greater than zero\n");
+        exit(1);
+    } else if ( _N % 2 ) {
+        fprintf(stderr,"error: liquid_kbd(), window length must be odd\n");
+        exit(1);
+    }
+
+    unsigned int M = _N / 2;
+    if (_n >= M)
+        return liquid_kbd(_N-_n-1,_N,_beta);
+
+    float w0 = 0.0f;
+    float w1 = 0.0f;
+    float w;
+    unsigned int i;
+    for (i=0; i<=M; i++) {
+        // compute Kaiser window
+        w = kaiser(i,M+1,_beta,0.0f);
+
+        // accumulate window sums
+        w1 += w;
+        if (i <= _n) w0 += w;
+    }
+    //printf("%12.8f / %12.8f = %12.8f\n", w0, w1, w0/w1);
+
+    return sqrtf(w0 / w1);
+}
+
+
+// Kaiser-Bessel derived window (full window function)
+void liquid_kbd_window(unsigned int _n,
+                       float _beta,
+                       float * _w)
+{
+    unsigned int i;
+    // TODO add reference
+
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_kbd_window(), window length must be greater than zero\n");
+        exit(1);
+    } else if ( _n % 2 ) {
+        fprintf(stderr,"error: liquid_kbd_window(), window length must be odd\n");
+        exit(1);
+    } else if ( _beta < 0.0f ) {
+        fprintf(stderr,"error: liquid_kbd_window(), _beta must be positive\n");
+        exit(1);
+    }
+
+    // compute half length
+    unsigned int M = _n / 2;
+
+    // generate regular Kaiser window, length M+1
+    float w_kaiser[M+1];
+    for (i=0; i<=M; i++)
+        w_kaiser[i] = kaiser(i,M+1,_beta,0.0f);
+
+    // compute sum(wk[])
+    float w_sum = 0.0f;
+    for (i=0; i<=M; i++)
+        w_sum += w_kaiser[i];
+
+    // accumulate window
+    float w_acc = 0.0f;
+    for (i=0; i<M; i++) {
+        w_acc += w_kaiser[i];
+        _w[i] = sqrtf(w_acc / w_sum);
+    }
+
+    // window has even symmetry; flip around index M
+    for (i=0; i<M; i++)
+        _w[_n-i-1] = _w[i];
+}
+
+
+// Kaiser window [Kaiser:1980]
+//  _n      :   sample index
+//  _N      :   window length (samples)
+//  _beta   :   window taper parameter
+//  _mu     :   fractional sample offset
+float kaiser(unsigned int _n,
+             unsigned int _N,
+             float        _beta,
+             float        _mu)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: kaiser(), sample index must not exceed window length\n");
+        exit(1);
+    } else if (_beta < 0) {
+        fprintf(stderr,"error: kaiser(), beta must be greater than or equal to zero\n");
+        exit(1);
+    } else if (_mu < -0.5 || _mu > 0.5) {
+        fprintf(stderr,"error: kaiser(), fractional sample offset must be in [-0.5,0.5]\n");
+        exit(1);
+    }
+
+    float t = (float)_n - (float)(_N-1)/2 + _mu;
+    float r = 2.0f*t/(float)(_N);
+    float a = liquid_besseli0f(_beta*sqrtf(1-r*r));
+    float b = liquid_besseli0f(_beta);
+    return a / b;
+}
+
+// Hamming window
+float hamming(unsigned int _n,
+              unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: hamming(), sample index must not exceed window length\n");
+        exit(1);
+    }
+
+    // TODO add reference
+    return 0.53836 - 0.46164*cosf( (2*M_PI*(float)_n) / ((float)(_N-1)) );
+}
+
+// Hann window
+float hann(unsigned int _n,
+           unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: hann(), sample index must not exceed window length\n");
+        exit(1);
+    }
+
+    // TODO test this function
+    // TODO add reference
+    return 0.5f - 0.5f*cosf( (2*M_PI*(float)_n) / ((float)(_N-1)) );
+}
+
+// Blackman-harris window [harris:1978]
+float blackmanharris(unsigned int _n,
+                     unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: blackmanharris(), sample index must not exceed window length\n");
+        exit(1);
+    }
+
+    // TODO test this function
+    // TODO add reference
+    float a0 = 0.35875f;
+    float a1 = 0.48829f;
+    float a2 = 0.14128f;
+    float a3 = 0.01168f;
+    float t = 2*M_PI*(float)_n / ((float)(_N-1));
+
+    return a0 - a1*cosf(t) + a2*cosf(2*t) - a3*cosf(3*t);
+}
+
+// 7th-order Blackman-harris window
+float blackmanharris7(unsigned int _n, unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: blackmanharris7(), sample index must not exceed window length\n");
+        exit(1);
+    }
+
+	float a0 = 0.27105f;
+	float a1 = 0.43329f;
+	float a2 = 0.21812f;
+	float a3 = 0.06592f;
+	float a4 = 0.01081f;
+	float a5 = 0.00077f;
+	float a6 = 0.00001f;
+	float t = 2*M_PI*(float)_n / ((float)(_N-1));
+
+	return a0 - a1*cosf(  t) + a2*cosf(2*t) - a3*cosf(3*t)
+			  + a4*cosf(4*t) - a5*cosf(5*t) + a6*cosf(6*t);
+}
+
+// Flat-top window
+float flattop(unsigned int _n, unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: flattop(), sample index must not exceed window length\n");
+        exit(1);
+    }
+
+	float a0 = 1.000f;
+	float a1 = 1.930f;
+	float a2 = 1.290f;
+	float a3 = 0.388f;
+	float a4 = 0.028f;
+	float t = 2*M_PI*(float)_n / ((float)(_N-1));
+
+	return a0 - a1*cosf(t) + a2*cosf(2*t) - a3*cosf(3*t) + a4*cosf(4*t);
+}
+
+// Triangular window
+float triangular(unsigned int _n,
+                 unsigned int _N,
+                 unsigned int _L)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: triangular(), sample index must not exceed window length\n");
+        exit(1);
+    } else if (_L != _N-1 && _L != _N && _L != _N+1) {
+        fprintf(stderr,"error: triangular(), sub-length must be in _N+{-1,0,1}\n");
+        exit(1);
+    } else if (_L == 0) {
+        fprintf(stderr,"error: triangular(), sub-length must be greater than zero\n");
+        exit(1);
+    }
+
+	float _num   = (float)_n - (float)((_N-1)/2.0f);
+	float _denom = ((float)_L)/2.0f;
+	return 1.0 - fabsf(_num / _denom);
+}
+
+// raised-cosine tapering window
+//  _n      :   window index
+//  _t      :   taper length
+//  _N      :   full window length
+float liquid_rcostaper_windowf(unsigned int _n,
+                               unsigned int _t,
+                               unsigned int _N)
+{
+    // validate input
+    if (_n > _N) {
+        fprintf(stderr,"error: liquid_rcostaper_windowf(), sample index must not exceed window length\n");
+        exit(1);
+    } else if (_t > _N/2) {
+        fprintf(stderr,"error: liquid_rcostaper_windowf(), taper length cannot exceed half window length\n");
+        exit(1);
+    }
+
+    // reverse time for ramp-down section
+    if (_n > _N - _t - 1)
+        _n = _N - _n - 1;
+
+    // return ramp or flat component
+    return (_n < _t) ? 0.5f - 0.5f*cosf(M_PI*((float)_n + 0.5f) / (float)_t) : 1.0f;
+}
+
+
diff --git a/src/math/tests/kbd_autotest.c b/src/math/tests/kbd_autotest.c
new file mode 100644
index 0000000..4772528
--- /dev/null
+++ b/src/math/tests/kbd_autotest.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: Kaiser-Bessel derived window
+//
+void liquid_kbd_window_test(unsigned int _n,
+                            float _beta)
+{
+    unsigned int i;
+    float tol = 1e-3f;
+
+    // compute window
+    float w[_n];
+    liquid_kbd_window(_n,_beta,w);
+
+    // square window
+    float w2[_n];
+    for (i=0; i<_n; i++)
+        w2[i] = w[i]*w[i];
+
+    // ensure w[i]^2 + w[i+M]^2 == 1
+    unsigned int M = _n/2;
+    for (i=0; i<M; i++)
+        CONTEND_DELTA(w2[i]+w2[(i+M)%_n], 1.0f, tol);
+
+    // ensure sum(w[i]^2) == _n/2
+    float sum=0.0f;
+    for (i=0; i<_n; i++)
+        sum += w2[i];
+    CONTEND_DELTA(sum, 0.5f*_n, tol);
+}
+
+void autotest_kbd_n16() { liquid_kbd_window_test(16, 10.0f); }
+void autotest_kbd_n32() { liquid_kbd_window_test(32, 20.0f); }
+void autotest_kbd_n48() { liquid_kbd_window_test(48, 12.0f); }
+
diff --git a/src/math/tests/math_autotest.c b/src/math/tests/math_autotest.c
new file mode 100644
index 0000000..5243a84
--- /dev/null
+++ b/src/math/tests/math_autotest.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+
+// 
+// AUTOTEST: Q function
+//
+void autotest_Q()
+{
+    float tol = 1e-6f;
+    CONTEND_DELTA(liquid_Qf(-4.0f), 0.999968329f, tol);
+    CONTEND_DELTA(liquid_Qf(-3.0f), 0.998650102f, tol);
+    CONTEND_DELTA(liquid_Qf(-2.0f), 0.977249868f, tol);
+    CONTEND_DELTA(liquid_Qf(-1.0f), 0.841344746f, tol);
+    CONTEND_DELTA(liquid_Qf( 0.0f), 0.5f,         tol);
+    CONTEND_DELTA(liquid_Qf( 1.0f), 0.158655254f, tol);
+    CONTEND_DELTA(liquid_Qf( 2.0f), 0.022750132f, tol);
+    CONTEND_DELTA(liquid_Qf( 3.0f), 0.001349898f, tol);
+    CONTEND_DELTA(liquid_Qf( 4.0f), 0.000031671f, tol);
+}
+
+// 
+// AUTOTEST: sincf
+//
+void autotest_sincf()
+{
+    float tol = 1e-3f;
+    CONTEND_DELTA(sincf(0.0f), 1.0f, tol);
+}
+
+// 
+// AUTOTEST: nextpow2
+//
+void autotest_nextpow2()
+{
+    CONTEND_EQUALITY(liquid_nextpow2(1),    0);
+
+    CONTEND_EQUALITY(liquid_nextpow2(2),    1);
+
+    CONTEND_EQUALITY(liquid_nextpow2(3),    2);
+    CONTEND_EQUALITY(liquid_nextpow2(4),    2);
+
+    CONTEND_EQUALITY(liquid_nextpow2(5),    3);
+    CONTEND_EQUALITY(liquid_nextpow2(6),    3);
+    CONTEND_EQUALITY(liquid_nextpow2(7),    3);
+    CONTEND_EQUALITY(liquid_nextpow2(8),    3);
+
+    CONTEND_EQUALITY(liquid_nextpow2(9),    4);
+    CONTEND_EQUALITY(liquid_nextpow2(10),   4);
+    CONTEND_EQUALITY(liquid_nextpow2(11),   4);
+    CONTEND_EQUALITY(liquid_nextpow2(12),   4);
+    CONTEND_EQUALITY(liquid_nextpow2(13),   4);
+    CONTEND_EQUALITY(liquid_nextpow2(14),   4);
+    CONTEND_EQUALITY(liquid_nextpow2(15),   4);
+
+    CONTEND_EQUALITY(liquid_nextpow2(67),   7);
+    CONTEND_EQUALITY(liquid_nextpow2(179),  8);
+    CONTEND_EQUALITY(liquid_nextpow2(888),  10);
+}
+
diff --git a/src/math/tests/math_bessel_autotest.c b/src/math/tests/math_bessel_autotest.c
new file mode 100644
index 0000000..8ab2605
--- /dev/null
+++ b/src/math/tests/math_bessel_autotest.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: log Modified Bessel function of the first kind
+//
+void autotest_lnbesselif()
+{
+    float tol = 1e-5f;
+
+    // test lnbesselif(nu,z) with various values for nu and z
+    CONTEND_DELTA( liquid_lnbesselif( 0.0f, 0.1f),    0.00249843923387607,tol );
+    CONTEND_DELTA( liquid_lnbesselif( 0.1f, 7.1f),    5.21933724549090,   tol );
+    CONTEND_DELTA( liquid_lnbesselif( 0.3f, 2.1f),    0.853008130814754,  tol );
+    CONTEND_DELTA( liquid_lnbesselif( 0.9f, 9.3f),    7.23414120004177,   tol );
+    CONTEND_DELTA( liquid_lnbesselif( 1.0f, 0.1f),   -2.99448253386220,   tol );
+    CONTEND_DELTA( liquid_lnbesselif( 1.7f, 0.01f),  -9.44195081753909,   tol );
+    CONTEND_DELTA( liquid_lnbesselif( 1.8f, 1e-3f), -14.1983271298778,    tol );
+    CONTEND_DELTA( liquid_lnbesselif( 1.9f, 8.7f),    6.49469148684252,   tol );
+    CONTEND_DELTA( liquid_lnbesselif( 4.9f, 0.01f), -30.5795429642925,    tol );
+    CONTEND_DELTA( liquid_lnbesselif( 7.4f, 9.3f),    4.33486237261960,   tol );
+
+    // test large values of nu
+    CONTEND_DELTA( liquid_lnbesselif( 20.0f,  3.0f),  -34.1194307343208, tol);
+    CONTEND_DELTA( liquid_lnbesselif( 30.0f,  3.0f),  -62.4217845317278, tol);
+#if 0
+    CONTEND_DELTA( liquid_lnbesselif( 35.0f,  3.0f),  -77.8824494916507, tol);
+    CONTEND_DELTA( liquid_lnbesselif( 38.0f,  3.0f),  -87.5028737258841, tol);
+    CONTEND_DELTA( liquid_lnbesselif( 39.0f,  3.0f),  -90.7624095618186, tol);
+    CONTEND_DELTA( liquid_lnbesselif( 40.0f,  3.0f),  -94.0471931331690, tol);
+    CONTEND_DELTA( liquid_lnbesselif( 80.0f,  3.0f), -241.208142562073,  tol);
+    CONTEND_DELTA( liquid_lnbesselif( 140.0f, 3.0f), -498.439222461430,  tol);
+#endif
+}
+
+// 
+// AUTOTEST: Modified Bessel function of the first kind
+//
+void autotest_besselif()
+{
+    float tol = 1e-3f;
+
+    // check case when nu=0
+    CONTEND_DELTA(liquid_besselif(0.0f,0.0f), 1.0f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,0.1f), 1.00250156293410f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,0.2f), 1.01002502779515f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,0.5f), 1.06348337074132f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,1.0f), 1.26606587775201f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,2.0f), 2.27958530233607f, tol);
+    CONTEND_DELTA(liquid_besselif(0.0f,3.0f), 4.88079258586503f, tol);
+
+    // check case when nu=0.5
+    CONTEND_DELTA(liquid_besselif(0.5f,0.0f), 0.000000000000000, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,0.1f), 0.252733984600132, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,0.2f), 0.359208417583362, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,0.5f), 0.587993086790417, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,1.0f), 0.937674888245489, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,2.0f), 2.046236863089057, tol);
+    CONTEND_DELTA(liquid_besselif(0.5f,3.0f), 4.614822903407577, tol);
+
+    // check case when nu=1.3
+    CONTEND_DELTA(liquid_besselif(1.3f,0.0f), 0.000000000000000, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,0.1f), 0.017465030873157, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,0.2f), 0.043144293848607, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,0.5f), 0.145248507279042, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,1.0f), 0.387392350983796, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,2.0f), 1.290819215135879, tol);
+    CONTEND_DELTA(liquid_besselif(1.3f,3.0f), 3.450680420553085, tol);
+}
+
+// 
+// AUTOTEST: Modified Bessel function of the first kind
+//
+void autotest_besseli0f()
+{
+    float tol = 1e-3f;
+    CONTEND_DELTA(liquid_besseli0f(0.0f), 1.0f, tol);
+    CONTEND_DELTA(liquid_besseli0f(0.1f), 1.00250156293410f, tol);
+    CONTEND_DELTA(liquid_besseli0f(0.2f), 1.01002502779515f, tol);
+    CONTEND_DELTA(liquid_besseli0f(0.5f), 1.06348337074132f, tol);
+    CONTEND_DELTA(liquid_besseli0f(1.0f), 1.26606587775201f, tol);
+    CONTEND_DELTA(liquid_besseli0f(2.0f), 2.27958530233607f, tol);
+    CONTEND_DELTA(liquid_besseli0f(3.0f), 4.88079258586503f, tol);
+}
+
+// 
+// AUTOTEST: Bessel function of the first kind
+//
+void autotest_besseljf()
+{
+    float tol = 1e-3f;
+
+    // check case when nu=0
+    CONTEND_DELTA(liquid_besseljf(0.0f,0.0f),  1.000000000000000, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,0.1f),  0.997501562066040, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,0.2f),  0.990024972239576, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,0.5f),  0.938469807240813, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,1.0f),  0.765197686557967, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,2.0f),  0.223890779141236, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,3.0f), -0.260051954901934, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,4.0f), -0.397149809863847, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,6.0f),  0.150645257250997, tol);
+    CONTEND_DELTA(liquid_besseljf(0.0f,8.0f),  0.171650807137554, tol);
+
+    // check case when nu=0.5
+    CONTEND_DELTA(liquid_besseljf(0.5f,0.0f),  0.000000000000000, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,0.1f),  0.251892940326001, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,0.2f),  0.354450744211402, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,0.5f),  0.540973789934529, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,1.0f),  0.671396707141804, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,2.0f),  0.513016136561828, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,3.0f),  0.065008182877376, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,4.0f), -0.301920513291637, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,6.0f), -0.091015409523068, tol);
+    CONTEND_DELTA(liquid_besseljf(0.5f,8.0f),  0.279092808570990, tol);
+
+    // check case when nu=1.7
+    CONTEND_DELTA(liquid_besseljf(1.7f,0.0f),  0.000000000000000, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,0.1f),  0.003971976455203, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,0.2f),  0.012869169735073, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,0.5f),  0.059920175825578, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,1.0f),  0.181417665056645, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,2.0f),  0.437811462130677, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,3.0f),  0.494432522734784, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,4.0f),  0.268439400467270, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,6.0f), -0.308175744215833, tol);
+    CONTEND_DELTA(liquid_besseljf(1.7f,8.0f), -0.001102600927987, tol);
+}
+
+// 
+// AUTOTEST: Bessel function of the first kind
+//
+void autotest_besselj0f()
+{
+    float tol = 1e-3f;
+    CONTEND_DELTA(liquid_besselj0f(0.0f),  1.0f, tol);
+    CONTEND_DELTA(liquid_besselj0f(0.1f),  0.997501562066040f, tol);
+    CONTEND_DELTA(liquid_besselj0f(0.2f),  0.990024972239576f, tol);
+    CONTEND_DELTA(liquid_besselj0f(0.5f),  0.938469807240813f, tol);
+    CONTEND_DELTA(liquid_besselj0f(1.0f),  0.765197686557967f, tol);
+    CONTEND_DELTA(liquid_besselj0f(2.0f),  0.223890779141236f, tol);
+    CONTEND_DELTA(liquid_besselj0f(2.5f), -0.048383776468199f, tol);
+    CONTEND_DELTA(liquid_besselj0f(3.0f), -0.260051954901934f, tol);
+    CONTEND_DELTA(liquid_besselj0f(3.5f), -0.380127739987263f, tol);
+    CONTEND_DELTA(liquid_besselj0f(4.0f), -0.397149809863848f, tol);
+    CONTEND_DELTA(liquid_besselj0f(4.5f), -0.320542508985121f, tol);
+}
+
diff --git a/src/math/tests/math_complex_autotest.c b/src/math/tests/math_complex_autotest.c
new file mode 100644
index 0000000..fab623f
--- /dev/null
+++ b/src/math/tests/math_complex_autotest.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST: cexpf
+//
+void autotest_cexpf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+    float complex z[32] = {
+        2.721502e+00+_Complex_I* -8.449366e-01,   2.264794e+00+_Complex_I*  2.387520e+00,
+        3.293179e+00+_Complex_I* -2.419589e+00,  -1.318218e+00+_Complex_I*  2.145837e+00,
+       -1.777802e+00+_Complex_I*  4.317598e-01,  -1.808236e-01+_Complex_I*  1.030967e+00,
+       -1.081724e+00+_Complex_I*  1.072073e-01,   3.617838e+00+_Complex_I*  3.329561e+00,
+        1.085694e+00+_Complex_I*  1.738376e+00,  -2.867179e+00+_Complex_I*  8.557510e-01,
+       -3.869596e+00+_Complex_I* -2.056906e+00,  -2.902147e+00+_Complex_I*  2.433414e+00,
+       -2.746567e+00+_Complex_I* -7.924449e-01,  -2.961677e+00+_Complex_I* -3.129529e+00,
+        3.991396e+00+_Complex_I* -2.253945e+00,   1.034594e-01+_Complex_I*  2.712898e+00,
+        9.011188e-01+_Complex_I* -1.631747e+00,   1.100418e+00+_Complex_I*  1.942973e-01,
+       -5.133605e-02+_Complex_I*  3.782200e+00,  -1.659866e+00+_Complex_I*  2.170862e+00,
+        2.139597e-01+_Complex_I*  2.159311e+00,  -7.981710e-01+_Complex_I*  3.132236e+00,
+       -1.733482e+00+_Complex_I* -1.180333e+00,   2.461796e+00+_Complex_I*  3.352212e+00,
+       -3.441958e+00+_Complex_I*  3.594616e+00,   2.079630e-01+_Complex_I* -3.311553e+00,
+       -2.462289e+00+_Complex_I*  1.305815e+00,   3.121861e+00+_Complex_I* -1.208857e+00,
+       -3.486629e+00+_Complex_I* -3.839816e+00,  -3.383861e-01+_Complex_I* -3.495233e+00,
+       -2.093760e+00+_Complex_I*  3.765073e+00,   3.217665e+00+_Complex_I*  2.807358e+00 };
+
+    float complex test[32] = {
+        1.009152e+01+_Complex_I* -1.137087e+01,  -7.018747e+00+_Complex_I*  6.592232e+00,
+       -2.020926e+01+_Complex_I* -1.779666e+01,  -1.455457e-01+_Complex_I*  2.245718e-01,
+        1.534993e-01+_Complex_I*  7.072523e-02,   4.289666e-01+_Complex_I*  7.159020e-01,
+        3.370642e-01+_Complex_I*  3.627482e-02,  -3.660068e+01+_Complex_I* -6.961947e+00,
+       -4.939656e-01+_Complex_I*  2.920008e+00,   3.727975e-02+_Complex_I*  4.293222e-02,
+       -9.748754e-03+_Complex_I* -1.844954e-02,  -4.170312e-02+_Complex_I*  3.571317e-02,
+        4.503850e-02+_Complex_I* -4.567777e-02,  -5.172835e-02+_Complex_I* -6.240385e-04,
+       -3.416917e+01+_Complex_I* -4.198295e+01,  -1.008646e+00+_Complex_I*  4.609940e-01,
+       -1.499894e-01+_Complex_I* -2.457784e+00,   2.948871e+00+_Complex_I*  5.802783e-01,
+       -7.616135e-01+_Complex_I* -5.677744e-01,  -1.073852e-01+_Complex_I*  1.569425e-01,
+       -6.875640e-01+_Complex_I*  1.030203e+00,  -4.501318e-01+_Complex_I*  4.212064e-03,
+        6.724286e-02+_Complex_I* -1.633708e-01,  -1.146673e+01+_Complex_I* -2.451473e+00,
+       -2.877386e-02+_Complex_I* -1.400682e-02,  -1.213428e+00+_Complex_I*  2.082440e-01,
+        2.232348e-02+_Complex_I*  8.226451e-02,   8.033771e+00+_Complex_I* -2.121861e+01,
+       -2.344211e-02+_Complex_I*  1.967391e-02,  -6.688032e-01+_Complex_I*  2.468952e-01,
+       -1.000387e-01+_Complex_I* -7.194542e-02,  -2.358796e+01+_Complex_I*  8.191224e+00 };
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_cexpf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
+
+
+// 
+// AUTOTEST: clogf
+//
+void autotest_clogf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+    float complex z[32] = {
+        2.7215e+00+_Complex_I* -8.4494e-01,   2.2648e+00+_Complex_I*  2.3875e+00,
+        3.2932e+00+_Complex_I* -2.4196e+00,  -1.3182e+00+_Complex_I*  2.1458e+00,
+       -1.7778e+00+_Complex_I*  4.3176e-01,  -1.8082e-01+_Complex_I*  1.0310e+00,
+       -1.0817e+00+_Complex_I*  1.0721e-01,   3.6178e+00+_Complex_I*  3.3296e+00,
+        1.0857e+00+_Complex_I*  1.7384e+00,  -2.8672e+00+_Complex_I*  8.5575e-01,
+       -3.8696e+00+_Complex_I* -2.0569e+00,  -2.9021e+00+_Complex_I*  2.4334e+00,
+       -2.7466e+00+_Complex_I* -7.9244e-01,  -2.9617e+00+_Complex_I* -3.1295e+00,
+        3.9914e+00+_Complex_I* -2.2539e+00,   1.0346e-01+_Complex_I*  2.7129e+00,
+        9.0112e-01+_Complex_I* -1.6317e+00,   1.1004e+00+_Complex_I*  1.9430e-01,
+       -5.1336e-02+_Complex_I*  3.7822e+00,  -1.6599e+00+_Complex_I*  2.1709e+00,
+        2.1396e-01+_Complex_I*  2.1593e+00,  -7.9817e-01+_Complex_I*  3.1322e+00,
+       -1.7335e+00+_Complex_I* -1.1803e+00,   2.4618e+00+_Complex_I*  3.3522e+00,
+       -3.4420e+00+_Complex_I*  3.5946e+00,   2.0796e-01+_Complex_I* -3.3116e+00,
+       -2.4623e+00+_Complex_I*  1.3058e+00,   3.1219e+00+_Complex_I* -1.2089e+00,
+       -3.4866e+00+_Complex_I* -3.8398e+00,  -3.3839e-01+_Complex_I* -3.4952e+00,
+       -2.0938e+00+_Complex_I*  3.7651e+00,   3.2177e+00+_Complex_I*  2.8074e+00};
+
+    float complex test[32] = {
+        1.0472e+00+_Complex_I* -3.0103e-01,   1.1911e+00+_Complex_I*  8.1177e-01,
+        1.4077e+00+_Complex_I* -6.3365e-01,   9.2362e-01+_Complex_I*  2.1217e+00,
+        6.0403e-01+_Complex_I*  2.9033e+00,   4.5647e-02+_Complex_I*  1.7444e+00,
+        8.3443e-02+_Complex_I*  3.0428e+00,   1.5927e+00+_Complex_I*  7.4393e-01,
+        7.1762e-01+_Complex_I*  1.0125e+00,   1.0960e+00+_Complex_I*  2.8515e+00,
+        1.4776e+00+_Complex_I* -2.6530e+00,   1.3317e+00+_Complex_I*  2.4438e+00,
+        1.0503e+00+_Complex_I* -2.8607e+00,   1.4607e+00+_Complex_I* -2.3286e+00,
+        1.5225e+00+_Complex_I* -5.1406e-01,   9.9874e-01+_Complex_I*  1.5327e+00,
+        6.2274e-01+_Complex_I* -1.0662e+00,   1.1104e-01+_Complex_I*  1.7477e-01,
+        1.3304e+00+_Complex_I*  1.5844e+00,   1.0053e+00+_Complex_I*  2.2236e+00,
+        7.7467e-01+_Complex_I*  1.4720e+00,   1.1732e+00+_Complex_I*  1.8203e+00,
+        7.4059e-01+_Complex_I* -2.5438e+00,   1.4253e+00+_Complex_I*  9.3737e-01,
+        1.6048e+00+_Complex_I*  2.3345e+00,   1.1994e+00+_Complex_I* -1.5081e+00,
+        1.0250e+00+_Complex_I*  2.6540e+00,   1.2083e+00+_Complex_I* -3.6944e-01,
+        1.6461e+00+_Complex_I* -2.3080e+00,   1.2561e+00+_Complex_I* -1.6673e+00,
+        1.4605e+00+_Complex_I*  2.0783e+00,   1.4517e+00+_Complex_I*  7.1740e-01};
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_clogf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
+
+// 
+// AUTOTEST: csqrtf
+//
+void autotest_csqrtf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+    float complex z[32] = {
+        1.3608e+00+_Complex_I* -4.2247e-01,   1.1324e+00+_Complex_I*  1.1938e+00,
+        1.6466e+00+_Complex_I* -1.2098e+00,  -6.5911e-01+_Complex_I*  1.0729e+00,
+       -8.8890e-01+_Complex_I*  2.1588e-01,  -9.0412e-02+_Complex_I*  5.1548e-01,
+       -5.4086e-01+_Complex_I*  5.3604e-02,   1.8089e+00+_Complex_I*  1.6648e+00,
+        5.4285e-01+_Complex_I*  8.6919e-01,  -1.4336e+00+_Complex_I*  4.2788e-01,
+       -1.9348e+00+_Complex_I* -1.0285e+00,  -1.4511e+00+_Complex_I*  1.2167e+00,
+       -1.3733e+00+_Complex_I* -3.9622e-01,  -1.4808e+00+_Complex_I* -1.5648e+00,
+        1.9957e+00+_Complex_I* -1.1270e+00,   5.1730e-02+_Complex_I*  1.3564e+00,
+        4.5056e-01+_Complex_I* -8.1587e-01,   5.5021e-01+_Complex_I*  9.7149e-02,
+       -2.5668e-02+_Complex_I*  1.8911e+00,  -8.2993e-01+_Complex_I*  1.0854e+00,
+        1.0698e-01+_Complex_I*  1.0797e+00,  -3.9909e-01+_Complex_I*  1.5661e+00,
+       -8.6674e-01+_Complex_I* -5.9017e-01,   1.2309e+00+_Complex_I*  1.6761e+00,
+       -1.7210e+00+_Complex_I*  1.7973e+00,   1.0398e-01+_Complex_I* -1.6558e+00,
+       -1.2311e+00+_Complex_I*  6.5291e-01,   1.5609e+00+_Complex_I* -6.0443e-01,
+       -1.7433e+00+_Complex_I* -1.9199e+00,  -1.6919e-01+_Complex_I* -1.7476e+00,
+       -1.0469e+00+_Complex_I*  1.8825e+00,   1.6088e+00+_Complex_I*  1.4037e+00};
+
+    float complex test[32] = {
+        1.1802e+00+_Complex_I* -1.7899e-01,   1.1785e+00+_Complex_I*  5.0647e-01,
+        1.3583e+00+_Complex_I* -4.4534e-01,   5.4776e-01+_Complex_I*  9.7936e-01,
+        1.1366e-01+_Complex_I*  9.4964e-01,   4.6526e-01+_Complex_I*  5.5397e-01,
+        3.6399e-02+_Complex_I*  7.3633e-01,   1.4607e+00+_Complex_I*  5.6986e-01,
+        8.8533e-01+_Complex_I*  4.9088e-01,   1.7676e-01+_Complex_I*  1.2103e+00,
+        3.5802e-01+_Complex_I* -1.4363e+00,   4.7042e-01+_Complex_I*  1.2932e+00,
+        1.6736e-01+_Complex_I* -1.1838e+00,   5.8032e-01+_Complex_I* -1.3482e+00,
+        1.4642e+00+_Complex_I* -3.8485e-01,   8.3939e-01+_Complex_I*  8.0799e-01,
+        8.3144e-01+_Complex_I* -4.9064e-01,   7.4462e-01+_Complex_I*  6.5233e-02,
+        9.6582e-01+_Complex_I*  9.7902e-01,   5.1789e-01+_Complex_I*  1.0479e+00,
+        7.7199e-01+_Complex_I*  6.9927e-01,   7.8009e-01+_Complex_I*  1.0038e+00,
+        3.0154e-01+_Complex_I* -9.7860e-01,   1.2866e+00+_Complex_I*  6.5140e-01,
+        6.1944e-01+_Complex_I*  1.4508e+00,   9.3889e-01+_Complex_I* -8.8178e-01,
+        2.8497e-01+_Complex_I*  1.1456e+00,   1.2718e+00+_Complex_I* -2.3763e-01,
+        6.5191e-01+_Complex_I* -1.4725e+00,   8.9067e-01+_Complex_I* -9.8107e-01,
+        7.4403e-01+_Complex_I*  1.2651e+00,   1.3682e+00+_Complex_I*  5.1297e-01};
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_csqrtf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
+
+// 
+// AUTOTEST: casinf
+//
+void autotest_casinf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+
+    float complex z[32] = {
+        1.3608e+00+_Complex_I* -4.2247e-01,   1.1324e+00+_Complex_I*  1.1938e+00,
+        1.6466e+00+_Complex_I* -1.2098e+00,  -6.5911e-01+_Complex_I*  1.0729e+00,
+       -8.8890e-01+_Complex_I*  2.1588e-01,  -9.0412e-02+_Complex_I*  5.1548e-01,
+       -5.4086e-01+_Complex_I*  5.3604e-02,   1.8089e+00+_Complex_I*  1.6648e+00,
+        5.4285e-01+_Complex_I*  8.6919e-01,  -1.4336e+00+_Complex_I*  4.2788e-01,
+       -1.9348e+00+_Complex_I* -1.0285e+00,  -1.4511e+00+_Complex_I*  1.2167e+00,
+       -1.3733e+00+_Complex_I* -3.9622e-01,  -1.4808e+00+_Complex_I* -1.5648e+00,
+        1.9957e+00+_Complex_I* -1.1270e+00,   5.1730e-02+_Complex_I*  1.3564e+00,
+        4.5056e-01+_Complex_I* -8.1587e-01,   5.5021e-01+_Complex_I*  9.7149e-02,
+       -2.5668e-02+_Complex_I*  1.8911e+00,  -8.2993e-01+_Complex_I*  1.0854e+00,
+        1.0698e-01+_Complex_I*  1.0797e+00,  -3.9909e-01+_Complex_I*  1.5661e+00,
+       -8.6674e-01+_Complex_I* -5.9017e-01,   1.2309e+00+_Complex_I*  1.6761e+00,
+       -1.7210e+00+_Complex_I*  1.7973e+00,   1.0398e-01+_Complex_I* -1.6558e+00,
+       -1.2311e+00+_Complex_I*  6.5291e-01,   1.5609e+00+_Complex_I* -6.0443e-01,
+       -1.7433e+00+_Complex_I* -1.9199e+00,  -1.6919e-01+_Complex_I* -1.7476e+00,
+       -1.0469e+00+_Complex_I*  1.8825e+00,   1.6088e+00+_Complex_I*  1.4037e+00 };
+
+    float complex test[32] = {
+        1.1716e+00+_Complex_I* -9.4147e-01,   6.7048e-01+_Complex_I*  1.2078e+00,
+        8.7747e-01+_Complex_I* -1.3947e+00,  -4.3898e-01+_Complex_I*  1.0065e+00,
+       -9.7768e-01+_Complex_I*  3.7722e-01,  -8.0395e-02+_Complex_I*  4.9650e-01,
+       -5.7016e-01+_Complex_I*  6.3633e-02,   7.8546e-01+_Complex_I*  1.5918e+00,
+        4.0539e-01+_Complex_I*  8.4256e-01,  -1.1968e+00+_Complex_I*  9.9741e-01,
+       -1.0352e+00+_Complex_I* -1.4505e+00,  -8.0295e-01+_Complex_I*  1.3267e+00,
+       -1.1968e+00+_Complex_I* -9.3995e-01,  -7.0503e-01+_Complex_I* -1.4678e+00,
+        1.0130e+00+_Complex_I* -1.4999e+00,   3.0692e-02+_Complex_I*  1.1128e+00,
+        3.4725e-01+_Complex_I* -7.8464e-01,   5.7823e-01+_Complex_I*  1.1575e-01,
+       -1.1998e-02+_Complex_I*  1.3939e+00,  -5.4040e-01+_Complex_I*  1.0574e+00,
+        7.2656e-02+_Complex_I*  9.3853e-01,  -2.1290e-01+_Complex_I*  1.2502e+00,
+       -7.4285e-01+_Complex_I* -7.3366e-01,   5.8143e-01+_Complex_I*  1.4462e+00,
+       -7.2379e-01+_Complex_I*  1.6089e+00,   5.3725e-02+_Complex_I* -1.2794e+00,
+       -9.5454e-01+_Complex_I*  9.7013e-01,   1.1275e+00+_Complex_I* -1.1433e+00,
+       -7.0078e-01+_Complex_I* -1.6516e+00,  -8.3905e-02+_Complex_I* -1.3278e+00,
+       -4.6555e-01+_Complex_I*  1.4904e+00,   7.9838e-01+_Complex_I*  1.4487e+00 };
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_casinf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
+
+// 
+// AUTOTEST: cacosf
+//
+void autotest_cacosf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+
+    float complex z[32] = {
+        1.3608e+00+_Complex_I* -4.2247e-01,   1.1324e+00+_Complex_I*  1.1938e+00,
+        1.6466e+00+_Complex_I* -1.2098e+00,  -6.5911e-01+_Complex_I*  1.0729e+00,
+       -8.8890e-01+_Complex_I*  2.1588e-01,  -9.0412e-02+_Complex_I*  5.1548e-01,
+       -5.4086e-01+_Complex_I*  5.3604e-02,   1.8089e+00+_Complex_I*  1.6648e+00,
+        5.4285e-01+_Complex_I*  8.6919e-01,  -1.4336e+00+_Complex_I*  4.2788e-01,
+       -1.9348e+00+_Complex_I* -1.0285e+00,  -1.4511e+00+_Complex_I*  1.2167e+00,
+       -1.3733e+00+_Complex_I* -3.9622e-01,  -1.4808e+00+_Complex_I* -1.5648e+00,
+        1.9957e+00+_Complex_I* -1.1270e+00,   5.1730e-02+_Complex_I*  1.3564e+00,
+        4.5056e-01+_Complex_I* -8.1587e-01,   5.5021e-01+_Complex_I*  9.7149e-02,
+       -2.5668e-02+_Complex_I*  1.8911e+00,  -8.2993e-01+_Complex_I*  1.0854e+00,
+        1.0698e-01+_Complex_I*  1.0797e+00,  -3.9909e-01+_Complex_I*  1.5661e+00,
+       -8.6674e-01+_Complex_I* -5.9017e-01,   1.2309e+00+_Complex_I*  1.6761e+00,
+       -1.7210e+00+_Complex_I*  1.7973e+00,   1.0398e-01+_Complex_I* -1.6558e+00,
+       -1.2311e+00+_Complex_I*  6.5291e-01,   1.5609e+00+_Complex_I* -6.0443e-01,
+       -1.7433e+00+_Complex_I* -1.9199e+00,  -1.6919e-01+_Complex_I* -1.7476e+00,
+       -1.0469e+00+_Complex_I*  1.8825e+00,   1.6088e+00+_Complex_I*  1.4037e+00,
+      };
+
+    float complex test[32] = {
+        3.9923e-01+_Complex_I*  9.4147e-01,   9.0032e-01+_Complex_I* -1.2078e+00,
+        6.9333e-01+_Complex_I*  1.3947e+00,   2.0098e+00+_Complex_I* -1.0065e+00,
+        2.5485e+00+_Complex_I* -3.7722e-01,   1.6512e+00+_Complex_I* -4.9650e-01,
+        2.1410e+00+_Complex_I* -6.3633e-02,   7.8534e-01+_Complex_I* -1.5918e+00,
+        1.1654e+00+_Complex_I* -8.4256e-01,   2.7676e+00+_Complex_I* -9.9741e-01,
+        2.6060e+00+_Complex_I*  1.4505e+00,   2.3737e+00+_Complex_I* -1.3267e+00,
+        2.7676e+00+_Complex_I*  9.3995e-01,   2.2758e+00+_Complex_I*  1.4678e+00,
+        5.5780e-01+_Complex_I*  1.4999e+00,   1.5401e+00+_Complex_I* -1.1128e+00,
+        1.2235e+00+_Complex_I*  7.8464e-01,   9.9257e-01+_Complex_I* -1.1575e-01,
+        1.5828e+00+_Complex_I* -1.3939e+00,   2.1112e+00+_Complex_I* -1.0574e+00,
+        1.4981e+00+_Complex_I* -9.3853e-01,   1.7837e+00+_Complex_I* -1.2502e+00,
+        2.3137e+00+_Complex_I*  7.3366e-01,   9.8936e-01+_Complex_I* -1.4462e+00,
+        2.2946e+00+_Complex_I* -1.6089e+00,   1.5171e+00+_Complex_I*  1.2794e+00,
+        2.5253e+00+_Complex_I* -9.7013e-01,   4.4330e-01+_Complex_I*  1.1433e+00,
+        2.2716e+00+_Complex_I*  1.6516e+00,   1.6547e+00+_Complex_I*  1.3278e+00,
+        2.0363e+00+_Complex_I* -1.4904e+00,   7.7241e-01+_Complex_I* -1.4487e+00};
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_cacosf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
+
+// 
+// AUTOTEST: catanf
+//
+void autotest_catanf()
+{
+    float tol = 1e-3f;
+
+    unsigned int n = 32;
+
+    float complex z[32] = {
+        1.3608e+00+_Complex_I* -4.2247e-01,   1.1324e+00+_Complex_I*  1.1938e+00,
+        1.6466e+00+_Complex_I* -1.2098e+00,  -6.5911e-01+_Complex_I*  1.0729e+00,
+       -8.8890e-01+_Complex_I*  2.1588e-01,  -9.0412e-02+_Complex_I*  5.1548e-01,
+       -5.4086e-01+_Complex_I*  5.3604e-02,   1.8089e+00+_Complex_I*  1.6648e+00,
+        5.4285e-01+_Complex_I*  8.6919e-01,  -1.4336e+00+_Complex_I*  4.2788e-01,
+       -1.9348e+00+_Complex_I* -1.0285e+00,  -1.4511e+00+_Complex_I*  1.2167e+00,
+       -1.3733e+00+_Complex_I* -3.9622e-01,  -1.4808e+00+_Complex_I* -1.5648e+00,
+        1.9957e+00+_Complex_I* -1.1270e+00,   5.1730e-02+_Complex_I*  1.3564e+00,
+        4.5056e-01+_Complex_I* -8.1587e-01,   5.5021e-01+_Complex_I*  9.7149e-02,
+       -2.5668e-02+_Complex_I*  1.8911e+00,  -8.2993e-01+_Complex_I*  1.0854e+00,
+        1.0698e-01+_Complex_I*  1.0797e+00,  -3.9909e-01+_Complex_I*  1.5661e+00,
+       -8.6674e-01+_Complex_I* -5.9017e-01,   1.2309e+00+_Complex_I*  1.6761e+00,
+       -1.7210e+00+_Complex_I*  1.7973e+00,   1.0398e-01+_Complex_I* -1.6558e+00,
+       -1.2311e+00+_Complex_I*  6.5291e-01,   1.5609e+00+_Complex_I* -6.0443e-01,
+       -1.7433e+00+_Complex_I* -1.9199e+00,  -1.6919e-01+_Complex_I* -1.7476e+00,
+       -1.0469e+00+_Complex_I*  1.8825e+00,   1.6088e+00+_Complex_I*  1.4037e+00 };
+
+    float complex test[32] = {
+        9.6632e-01+_Complex_I* -1.4321e-01,   1.1084e+00+_Complex_I*  3.8248e-01,
+        1.1690e+00+_Complex_I* -2.5348e-01,  -9.9442e-01+_Complex_I*  5.9395e-01,
+       -7.3961e-01+_Complex_I*  1.1977e-01,  -1.2203e-01+_Complex_I*  5.6250e-01,
+       -4.9673e-01+_Complex_I*  4.1475e-02,   1.2597e+00+_Complex_I*  2.5677e-01,
+        8.0849e-01+_Complex_I*  6.2435e-01,  -9.8924e-01+_Complex_I*  1.3534e-01,
+       -1.1736e+00+_Complex_I* -1.8533e-01,  -1.1493e+00+_Complex_I*  2.9550e-01,
+       -9.6685e-01+_Complex_I* -1.3328e-01,  -1.2294e+00+_Complex_I* -3.1261e-01,
+        1.1940e+00+_Complex_I* -1.8871e-01,   1.5097e+00+_Complex_I*  9.3927e-01,
+        7.1303e-01+_Complex_I* -6.7324e-01,   5.0608e-01+_Complex_I*  7.4585e-02,
+       -1.5608e+00+_Complex_I*  5.8828e-01,  -1.0261e+00+_Complex_I*  4.9481e-01,
+        1.1311e+00+_Complex_I*  1.3740e+00,  -1.3409e+00+_Complex_I*  6.6079e-01,
+       -8.1408e-01+_Complex_I* -3.1801e-01,   1.2521e+00+_Complex_I*  3.7037e-01,
+       -1.2781e+00+_Complex_I*  2.7452e-01,   1.5117e+00+_Complex_I* -6.9351e-01,
+       -9.6809e-01+_Complex_I*  2.3851e-01,   1.0471e+00+_Complex_I* -1.6469e-01,
+       -1.2973e+00+_Complex_I* -2.7269e-01,  -1.4903e+00+_Complex_I* -6.3926e-01,
+       -1.3098e+00+_Complex_I*  4.0318e-01,   1.2032e+00+_Complex_I*  2.7803e-01 };
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float complex t = liquid_catanf(z[i]);
+
+        CONTEND_DELTA(crealf(t), crealf(test[i]), tol);
+        CONTEND_DELTA(cimagf(t), cimagf(test[i]), tol);
+    }
+}
diff --git a/src/math/tests/math_gamma_autotest.c b/src/math/tests/math_gamma_autotest.c
new file mode 100644
index 0000000..331105e
--- /dev/null
+++ b/src/math/tests/math_gamma_autotest.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: Gamma
+//
+void autotest_gamma()
+{
+    // error tolerance
+    float tol = 1e-5f;
+
+    // test vectors
+    float v[12][2] = {
+        {0.0001f, 9999.42288323161f     },
+        {0.001f,   999.423772484595f    },
+        {0.01f,     99.4325851191505f   },
+        {0.1f,       9.51350769866873f  },
+        {0.2f,       4.59084371199880f  },
+        {0.5f,       1.77245385090552f  },
+        {1.5f,       0.886226925452758f },
+        {2.5f,       1.329340388179140f },
+        {3.2f,       2.42396547993537f  },
+        {4.1f,       6.81262286301667f  },
+        {5.3f,      38.0779764499523f   },
+        {12.0f, 39916800.0000000f       }};
+
+    unsigned int i;
+    for (i=0; i<12; i++) {
+        // extract test vector
+        float z = v[i][0];
+        float g = v[i][1];
+
+        // compute gamma
+        float gamma = liquid_gammaf(z);
+
+        // compute relative error
+        float error = fabsf(gamma-g) / fabsf(g);
+
+        // print results
+        if (liquid_autotest_verbose)
+            printf("  gamma(%12.4e) = %12.4e (expected %12.4e) %12.4e\n", z, gamma, g, error);
+
+        // run test
+        CONTEND_LESS_THAN(error, tol);
+    }
+}
+
+// 
+// AUTOTEST: lngamma
+//
+void autotest_lngamma()
+{
+    // error tolerance
+    float tol = 1e-4f;
+
+    // test vectors
+    CONTEND_DELTA( liquid_lngammaf(1.00000000000000e-05), 1.15129196928958e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.47910838816821e-05), 1.11214774616959e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.18776162394955e-05), 1.07300339056431e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(3.23593656929628e-05), 1.03385883900717e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(4.78630092322638e-05), 9.94713997633970e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(7.07945784384137e-05), 9.55568727630758e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.04712854805090e-04), 9.16422823723390e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.54881661891248e-04), 8.77275982391398e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.29086765276777e-04), 8.38127754918765e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(3.38844156139203e-04), 7.98977478095072e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(5.01187233627273e-04), 7.59824172030200e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(7.41310241300918e-04), 7.20666389700363e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.09647819614319e-03), 6.81501995916639e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.62181009735893e-03), 6.42327843686104e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.39883291901949e-03), 6.03139302698778e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(3.54813389233576e-03), 5.63929577576287e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(5.24807460249773e-03), 5.24688733606614e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(7.76247116628692e-03), 4.85402329836329e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.14815362149688e-02), 4.46049557831785e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.69824365246175e-02), 4.06600834803635e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.51188643150958e-02), 3.67014984368726e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(3.71535229097173e-02), 3.27236635981559e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(5.49540873857625e-02), 2.87195653880158e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(8.12830516164099e-02), 2.46812982675138e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.20226443461741e-01), 2.06022544058646e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.77827941003892e-01), 1.64828757901128e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.63026799189538e-01), 1.23436563201614e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(3.89045144994281e-01), 8.25181176502332e-01, tol );
+    CONTEND_DELTA( liquid_lngammaf(5.75439937337158e-01), 4.37193579132034e-01, tol );
+    CONTEND_DELTA( liquid_lngammaf(8.51138038202378e-01), 1.05623142071343e-01, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.25892541179417e+00),-1.00254418080515e-01, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.86208713666287e+00),-5.19895823734552e-02, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.75422870333817e+00), 4.78681466346387e-01, tol );
+    CONTEND_DELTA( liquid_lngammaf(4.07380277804113e+00), 1.88523210546678e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(6.02559586074358e+00), 4.83122059829788e+00, tol );
+    CONTEND_DELTA( liquid_lngammaf(8.91250938133746e+00), 1.04177681572532e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.31825673855641e+01), 2.04497048921129e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.94984459975805e+01), 3.78565107279246e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(2.88403150312661e+01), 6.73552537656878e+01, tol );
+    CONTEND_DELTA( liquid_lngammaf(4.26579518801593e+01), 1.16490742768456e+02, tol );
+    CONTEND_DELTA( liquid_lngammaf(6.30957344480194e+01), 1.97262133863497e+02, tol );
+    CONTEND_DELTA( liquid_lngammaf(9.33254300796992e+01), 3.28659150940827e+02, tol );
+    CONTEND_DELTA( liquid_lngammaf(1.38038426460289e+02), 5.40606126998515e+02, tol );
+
+    // test very large numbers
+    CONTEND_DELTA( liquid_lngammaf(140), 550.278651724286, tol);
+    CONTEND_DELTA( liquid_lngammaf(150), 600.009470555327, tol);
+    CONTEND_DELTA( liquid_lngammaf(160), 650.409682895655, tol);
+    CONTEND_DELTA( liquid_lngammaf(170), 701.437263808737, tol);
+}
+
+// 
+// AUTOTEST: upper incomplete Gamma
+//
+void autotest_uppergamma()
+{
+    float tol = 1e-3f;
+
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.001f), 1.04649f,  tol);
+
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.01f),  1.04646f,  tol);
+
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.1f),   1.04295f,  tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.2f),   1.03231f,  tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.3f),   1.01540f,  tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.4f),   0.993237f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.5f),   0.966782f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.6f),   0.936925f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.7f),   0.904451f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.8f),   0.870053f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 0.9f),   0.834330f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 1.0f),   0.797796f, tol);
+
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 2.0f),   0.455589f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 3.0f),   0.229469f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 4.0f),   0.107786f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 5.0f),   0.0484292f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 6.0f),   0.0211006f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 7.0f),   0.00898852f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 8.0f),   0.00376348f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 9.0f),   0.00155445f, tol);
+    CONTEND_DELTA(liquid_uppergammaf(2.1f, 10.0f),  0.000635002f, tol);
+}
+
+// 
+// AUTOTEST: Factorial
+//
+void autotest_factorial()
+{
+    float tol = 1e-3f;
+    CONTEND_DELTA(liquid_factorialf(0), 1,   tol);
+    CONTEND_DELTA(liquid_factorialf(1), 1,   tol);
+    CONTEND_DELTA(liquid_factorialf(2), 2,   tol);
+    CONTEND_DELTA(liquid_factorialf(3), 6,   tol);
+    CONTEND_DELTA(liquid_factorialf(4), 24,  tol);
+    CONTEND_DELTA(liquid_factorialf(5), 120, tol);
+    CONTEND_DELTA(liquid_factorialf(6), 720, tol);
+}
+
+// 
+// AUTOTEST: nchoosek
+//
+void autotest_nchoosek()
+{
+    float tol = 1e-3f;
+
+    // nchoosek(6, k)
+    CONTEND_DELTA(liquid_nchoosek(6,    0),      1,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    1),      6,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    2),     15,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    3),     20,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    4),     15,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    5),      6,     tol);
+    CONTEND_DELTA(liquid_nchoosek(6,    6),      1,     tol);
+
+    // nchoosek(7, k)
+    CONTEND_DELTA(liquid_nchoosek(7,    0),      1,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    1),      7,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    2),     21,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    3),     35,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    4),     35,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    5),     21,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    6),      7,     tol);
+    CONTEND_DELTA(liquid_nchoosek(7,    7),      1,     tol);
+
+    // test very large numbers
+    CONTEND_DELTA(liquid_nchoosek(124,  5),     225150024,  5000);
+}
+
diff --git a/src/math/tests/polynomial_autotest.c b/src/math/tests/polynomial_autotest.c
new file mode 100644
index 0000000..57569ed
--- /dev/null
+++ b/src/math/tests/polynomial_autotest.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: poly_fit 3rd order polynomial, critically sampled
+//
+void autotest_polyf_fit_q3n3()
+{
+    unsigned int Q=2;   // polynomial order
+    unsigned int n=3;   // input vector size
+
+    float x[3] = {-1.0f, 0.0f, 1.0f};
+    float y[3] = { 1.0f, 0.0f, 1.0f};
+    float p[3];
+    float p_test[3] = {0.0f, 0.0f, 1.0f};
+    float tol = 1e-3f;
+
+    unsigned int k=Q+1;
+    polyf_fit(x,y,n, p,k);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        for (i=0; i<3; i++)
+            printf("%3u : %12.8f > %12.8f\n", i, x[i], y[i]);
+        for (i=0; i<3; i++)
+            printf("p[%3u] = %12.8f\n", i, p[i]);
+    }
+    
+    CONTEND_DELTA(p[0], p_test[0], tol);
+    CONTEND_DELTA(p[1], p_test[1], tol);
+    CONTEND_DELTA(p[2], p_test[2], tol);
+    //CONTEND_DELTA(p[3], p_test[3], tol);
+}
+
+#if 0
+// 
+// AUTOTEST: poly_expandbinomial
+//
+void xautotest_polyf_expandbinomial_4()
+{
+    float a[4] = { 3, 2, -5, 1 };
+    float c[5];
+    float c_test[5] = { 1, 1, -19, -49, -30 };
+    float tol = 1e-3f;
+
+    polyf_expandbinomial(a,4,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        printf("c[5] = ");
+        for (i=0; i<5; i++)
+            printf("%8.2f", c[i]);
+        printf("\n");
+    }
+    
+    CONTEND_DELTA(c[0], c_test[0], tol);
+    CONTEND_DELTA(c[1], c_test[1], tol);
+    CONTEND_DELTA(c[2], c_test[2], tol);
+    CONTEND_DELTA(c[3], c_test[3], tol);
+    CONTEND_DELTA(c[4], c_test[4], tol);
+}
+#endif
+
+// 
+// AUTOTEST: poly_expandroots
+//
+void autotest_polyf_expandroots_4()
+{
+    float roots[5] = { -2, -1, -4, 5, 3 };
+    float c[6];
+    float c_test[6] = { 120, 146, 1, -27, -1, 1 };
+    float tol = 1e-3f;
+
+    polyf_expandroots(roots,5,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        printf("c[6] = ");
+        for (i=0; i<6; i++)
+            printf("%8.2f", c[i]);
+        printf("\n");
+    }
+    
+    CONTEND_DELTA(c[0], c_test[0], tol);
+    CONTEND_DELTA(c[1], c_test[1], tol);
+    CONTEND_DELTA(c[2], c_test[2], tol);
+    CONTEND_DELTA(c[3], c_test[3], tol);
+    CONTEND_DELTA(c[4], c_test[4], tol);
+    CONTEND_DELTA(c[5], c_test[5], tol);
+}
+
+
+// 
+// AUTOTEST: poly_expandroots
+//
+void autotest_polyf_expandroots_11()
+{
+    float roots[11] = { -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11 };
+    float c[12];
+    float c_test[12] = {39916800,
+                        120543840,
+                        150917976,
+                        105258076,
+                        45995730,
+                        13339535,
+                        2637558,
+                        357423,
+                        32670,
+                        1925,
+                        66,
+                        1};
+    float tol = 1e-6f;
+
+    polyf_expandroots(roots,11,c);
+
+    unsigned int i;
+    for (i=0; i<12; i++) {
+        if (liquid_autotest_verbose)
+            printf("  c[%3u] : %16.8e (expected %16.8e)\n", i, c[i], c_test[i]);
+
+        CONTEND_DELTA(c[i], c_test[i], fabsf(tol*c_test[i]));
+    }
+}
+
+// 
+// AUTOTEST: polycf_expandroots
+//
+void autotest_polycf_expandroots_4()
+{
+    // expand complex roots on conjugate pair
+    float theta = 1.7f;
+    float complex a[2] = { -cexpf(_Complex_I*theta), -cexpf(-_Complex_I*theta) };
+    float complex c[3];
+    float complex c_test[3] = { 1, 2*cosf(theta), 1 };
+    float tol = 1e-3f;
+
+    polycf_expandroots(a,2,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        for (i=0; i<3; i++)
+            printf("c[%3u] = %12.8f + j*%12.8f\n", i, crealf(c[i]), cimagf(c[i]));
+    }
+    
+    CONTEND_DELTA(crealf(c[0]), crealf(c_test[0]), tol);
+    CONTEND_DELTA(cimagf(c[0]), cimagf(c_test[0]), tol);
+
+    CONTEND_DELTA(crealf(c[1]), crealf(c_test[1]), tol);
+    CONTEND_DELTA(cimagf(c[1]), cimagf(c_test[1]), tol);
+
+    CONTEND_DELTA(crealf(c[2]), crealf(c_test[2]), tol);
+    CONTEND_DELTA(cimagf(c[2]), cimagf(c_test[2]), tol);
+
+}
+
+// 
+// AUTOTEST: poly_expandroots2
+//
+// expand (2*x-5)*(3*x+2)*(-1*x+3)
+//
+void autotest_polyf_expandroots2_3()
+{
+    unsigned int n=3;
+    float a[3] = {  2,  3, -1 };
+    float b[3] = {  5, -2, -3 };
+    float c[4];
+    float c_test[4] = { -6, 29, -23, -30 };
+    float tol = 1e-3f;
+
+    polyf_expandroots2(a,b,n,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        printf("c[%u] = ", n+1);
+        for (i=0; i<n+1; i++)
+            printf("%8.2f", c[i]);
+        printf("\n");
+    }
+    
+    CONTEND_DELTA(c[0], c_test[0], tol);
+    CONTEND_DELTA(c[1], c_test[1], tol);
+    CONTEND_DELTA(c[2], c_test[2], tol);
+    CONTEND_DELTA(c[3], c_test[3], tol);
+}
+
+
+// 
+// AUTOTEST: polyf_mul
+//
+void autotest_polyf_mul_2_3()
+{
+    float a[3] = {  2, -4,  3 };
+    float b[4] = { -9,  3, -2,  5};
+    float c[6];
+    float c_test[6] = { -18, 42, -43, 27, -26, 15 };
+    float tol = 1e-3f;
+
+    polyf_mul(a,2,b,3,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        printf("c[6] = ");
+        for (i=0; i<6; i++)
+            printf("%8.2f", c[i]);
+        printf("\n");
+    }
+    
+    CONTEND_DELTA(c[0], c_test[0], tol);
+    CONTEND_DELTA(c[1], c_test[1], tol);
+    CONTEND_DELTA(c[2], c_test[2], tol);
+    CONTEND_DELTA(c[3], c_test[3], tol);
+    CONTEND_DELTA(c[4], c_test[4], tol);
+    CONTEND_DELTA(c[5], c_test[5], tol);
+}
+
+// 
+// AUTOTEST: poly_expandbinomial
+//
+void autotest_poly_expandbinomial_n6()
+{
+    unsigned int n=6;
+    float c[7];
+    float c_test[7] = {1, 6, 15, 20, 15, 6, 1};
+
+    polyf_expandbinomial(n,c);
+
+    if (liquid_autotest_verbose) {
+        unsigned int i;
+        printf("c[%2u] = ", n+1);
+        for (i=0; i<=n; i++)
+            printf("%6.1f", c[i]);
+        printf("\n");
+    }
+    
+    CONTEND_SAME_DATA(c,c_test,sizeof(c));
+}
+
+
+// 
+// AUTOTEST: poly_binomial_expand_pm
+//
+void autotest_poly_binomial_expand_pm_m6_k1()
+{
+    unsigned int m=5;
+    unsigned int k=1;
+    unsigned int n = m+k;
+    float c[7];
+    float c_test[7] = {1,  4,  5,  0, -5, -4, -1};
+
+    polyf_expandbinomial_pm(m,k,c);
+
+    unsigned int i;
+    if (liquid_autotest_verbose) {
+        printf("c[%u] = ", m+1);
+        for (i=0; i<=n; i++)
+            printf("%6.1f", c[i]);
+        printf("\n");
+    }
+
+    for (i=0; i<=n; i++)
+        CONTEND_DELTA(c[i], c_test[i], 1e-3f);
+}
+
+// 
+// AUTOTEST: poly_expandbinomial_pm
+//
+void autotest_poly_expandbinomial_pm_m5_k2()
+{
+    unsigned int m=5;
+    unsigned int k=2;
+    unsigned int n = m+k;
+    float c[8];
+    float c_test[8] = {  1.0f,  3.0f,  1.0f, -5.0f,
+                        -5.0f,  1.0f,  3.0f,  1.0f};
+
+    polyf_expandbinomial_pm(m,k,c);
+
+    unsigned int i;
+    if (liquid_autotest_verbose) {
+        printf("c[%u] = ", n+1);
+        for (i=0; i<=n; i++)
+            printf("%6.2f", c[i]);
+        printf("\n");
+    }
+
+    for (i=0; i<=n; i++)
+        CONTEND_DELTA(c[i], c_test[i], 1e-3f);
+}
+
+// 
+// AUTOTEST: polyf_findroots
+//
+void autotest_polyf_findroots()
+{
+    float tol=1e-6f;
+
+    float p[6] = {6,11,-33,-33,11,6};
+    float complex roots[5];
+    float complex rtest[5] = {-3,2,-1,0.5,-1./3.};
+
+    polyf_findroots(p,6,roots);
+
+    unsigned int i;
+    if (liquid_autotest_verbose) {
+        printf("poly:\n");
+        for (i=0; i<6; i++)
+            printf("  p[%3u] = %12.8f + j*%12.8f\n", i, crealf(p[i]), cimagf(p[i]));
+
+        printf("roots:\n");
+        for (i=0; i<5; i++)
+            printf("  r[%3u] = %12.8f + j*%12.8f\n", i, crealf(roots[i]), cimagf(roots[i]));
+    }
+
+    int rtest_used[5];
+    memset(rtest_used, 0, sizeof(rtest_used));
+
+    unsigned int j,k=0;
+    for (i=0; i<5; i++) {
+        for (j=0; j<5; j++) {
+            // check to see if this root has been used already
+            if (rtest_used[j]) continue;
+
+            // check to see if roots match within relative tolerance
+            if (cabsf(roots[i]-rtest[j]) < tol) {
+                rtest_used[j] = 1;
+                CONTEND_DELTA(crealf(roots[i]), crealf(rtest[j]), tol);
+                CONTEND_DELTA(cimagf(roots[i]), cimagf(rtest[j]), tol);
+                k++;
+                continue;
+            }
+        }
+    }
+    CONTEND_EQUALITY(k,5);
+}
+
+
+// 
+// AUTOTEST: polycf_findroots (random roots)
+//
+void xautotest_polycf_findroots_rand()
+{
+    unsigned int n=5;
+    float tol=1e-4f;
+
+    float complex p[n];
+    float complex roots[n-1];
+
+    float complex p_hat[n];
+
+    unsigned int i;
+    for (i=0; i<n; i++)
+        p[i] = i == n-1 ? 1 : 3.0f * randnf();
+
+    polycf_findroots(p,n,roots);
+
+    float complex roots_hat[n-1];
+    // convert form...
+    for (i=0; i<n-1; i++)
+        roots_hat[i] = -roots[i];
+
+    polycf_expandroots(roots_hat,n-1,p_hat);
+
+    if (liquid_autotest_verbose) {
+        printf("poly:\n");
+        for (i=0; i<n; i++)
+            printf("  p[%3u] = %12.8f + j*%12.8f\n", i, crealf(p[i]), cimagf(p[i]));
+
+        printf("roots:\n");
+        for (i=0; i<n-1; i++)
+            printf("  r[%3u] = %12.8f + j*%12.8f\n", i, crealf(roots[i]), cimagf(roots[i]));
+
+        printf("poly (expanded roots):\n");
+        for (i=0; i<n; i++)
+            printf("  p[%3u] = %12.8f + j*%12.8f\n", i, crealf(p_hat[i]), cimagf(p_hat[i]));
+    }
+
+    for (i=0; i<n; i++) {
+        CONTEND_DELTA(crealf(p[i]), crealf(p_hat[i]), tol);
+        CONTEND_DELTA(cimagf(p[i]), cimagf(p_hat[i]), tol);
+    }
+}
+
diff --git a/src/matrix/bench/matrixf_inv_benchmark.c b/src/matrix/bench/matrixf_inv_benchmark.c
new file mode 100644
index 0000000..54205e7
--- /dev/null
+++ b/src/matrix/bench/matrixf_inv_benchmark.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void matrixf_inv_bench(struct rusage *_start,
+                       struct rusage *_finish,
+                       unsigned long int *_num_iterations,
+                       unsigned int _n)
+{
+    // normalize number of iterations
+    // time ~ _n ^ 2
+    *_num_iterations /= _n * _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float x[_n*_n];
+    unsigned int i;
+    for (i=0; i<_n*_n; i++)
+        x[i] = randnf();
+    
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        matrixf_inv(x,_n,_n);
+        matrixf_inv(x,_n,_n);
+        matrixf_inv(x,_n,_n);
+        matrixf_inv(x,_n,_n);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define MATRIXF_INV_BENCHMARK_API(N)    \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ matrixf_inv_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_matrixf_inv_n2      MATRIXF_INV_BENCHMARK_API(2)
+void benchmark_matrixf_inv_n4      MATRIXF_INV_BENCHMARK_API(4)
+void benchmark_matrixf_inv_n8      MATRIXF_INV_BENCHMARK_API(8)
+void benchmark_matrixf_inv_n16     MATRIXF_INV_BENCHMARK_API(16)
+void benchmark_matrixf_inv_n32     MATRIXF_INV_BENCHMARK_API(32)
+void benchmark_matrixf_inv_n64     MATRIXF_INV_BENCHMARK_API(64)
+
diff --git a/src/matrix/bench/matrixf_linsolve_benchmark.c b/src/matrix/bench/matrixf_linsolve_benchmark.c
new file mode 100644
index 0000000..35fa13a
--- /dev/null
+++ b/src/matrix/bench/matrixf_linsolve_benchmark.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void matrixf_linsolve_bench(struct rusage *     _start,
+                            struct rusage *     _finish,
+                            unsigned long int * _num_iterations,
+                            unsigned int        _n)
+{
+    // normalize number of iterations
+    // time ~ _n ^ 2
+    *_num_iterations /= _n * _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    unsigned long int i;
+
+    float A[_n*_n];
+    float b[_n];
+    float x[_n];
+    for (i=0; i<_n*_n; i++)
+        A[i] = randnf();
+    for (i=0; i<_n; i++)
+        b[i] = randnf();
+    
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        matrixf_linsolve(A,_n,b,x,NULL);
+        matrixf_linsolve(A,_n,b,x,NULL);
+        matrixf_linsolve(A,_n,b,x,NULL);
+        matrixf_linsolve(A,_n,b,x,NULL);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define MATRIXF_LINSOLVE_BENCHMARK_API(N)   \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ matrixf_linsolve_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_matrixf_linsolve_n2      MATRIXF_LINSOLVE_BENCHMARK_API(2)
+void benchmark_matrixf_linsolve_n4      MATRIXF_LINSOLVE_BENCHMARK_API(4)
+void benchmark_matrixf_linsolve_n8      MATRIXF_LINSOLVE_BENCHMARK_API(8)
+void benchmark_matrixf_linsolve_n16     MATRIXF_LINSOLVE_BENCHMARK_API(16)
+void benchmark_matrixf_linsolve_n32     MATRIXF_LINSOLVE_BENCHMARK_API(32)
+void benchmark_matrixf_linsolve_n64     MATRIXF_LINSOLVE_BENCHMARK_API(64)
+
diff --git a/src/matrix/bench/matrixf_mul_benchmark.c b/src/matrix/bench/matrixf_mul_benchmark.c
new file mode 100644
index 0000000..27ef718
--- /dev/null
+++ b/src/matrix/bench/matrixf_mul_benchmark.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void matrixf_mul_bench(struct rusage *_start,
+                       struct rusage *_finish,
+                       unsigned long int *_num_iterations,
+                       unsigned int _n)
+{
+    // normalize number of iterations
+    // time ~ _n ^ 2
+    *_num_iterations /= _n * _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    float a[_n*_n];
+    float b[_n*_n];
+    float c[_n*_n];
+    unsigned int i;
+    for (i=0; i<_n*_n; i++) {
+        a[i] = randnf();
+        b[i] = randnf();
+    }
+    
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        matrixf_mul(a,_n,_n,  b,_n,_n,  c,_n,_n);
+        matrixf_mul(a,_n,_n,  b,_n,_n,  c,_n,_n);
+        matrixf_mul(a,_n,_n,  b,_n,_n,  c,_n,_n);
+        matrixf_mul(a,_n,_n,  b,_n,_n,  c,_n,_n);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+#define MATRIXF_MUL_BENCHMARK_API(N)    \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ matrixf_mul_bench(_start, _finish, _num_iterations, N); }
+
+void benchmark_matrixf_mul_n2      MATRIXF_MUL_BENCHMARK_API(2)
+void benchmark_matrixf_mul_n4      MATRIXF_MUL_BENCHMARK_API(4)
+void benchmark_matrixf_mul_n8      MATRIXF_MUL_BENCHMARK_API(8)
+void benchmark_matrixf_mul_n16     MATRIXF_MUL_BENCHMARK_API(16)
+void benchmark_matrixf_mul_n32     MATRIXF_MUL_BENCHMARK_API(32)
+void benchmark_matrixf_mul_n64     MATRIXF_MUL_BENCHMARK_API(64)
+
diff --git a/src/matrix/bench/smatrixf_mul_benchmark.c b/src/matrix/bench/smatrixf_mul_benchmark.c
new file mode 100644
index 0000000..e19372b
--- /dev/null
+++ b/src/matrix/bench/smatrixf_mul_benchmark.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <sys/resource.h>
+
+#include "liquid.h"
+
+// Helper function to keep code base small
+void smatrixf_mul_bench(struct rusage *     _start,
+                        struct rusage *     _finish,
+                        unsigned long int * _num_iterations,
+                        unsigned int        _n)
+{
+    // normalize number of iterations
+    // time ~ _n ^ 3
+    *_num_iterations /= _n * _n * _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    unsigned long int i;
+
+    // generate random matrices
+    smatrixf a = smatrixf_create(_n, _n);
+    smatrixf b = smatrixf_create(_n, _n);
+    smatrixf c = smatrixf_create(_n, _n);
+
+    // number of random non-zero entries
+    unsigned int nnz = _n / 20 < 4 ? 4 : _n / 20;
+
+    // initialize _a
+    for (i=0; i<nnz; i++) {
+        unsigned int row = rand() % _n;
+        unsigned int col = rand() % _n;
+        float value      = randf();
+        smatrixf_set(a, row, col, value);
+    }
+    
+    // initialize _b
+    for (i=0; i<nnz; i++) {
+        unsigned int row = rand() % _n;
+        unsigned int col = rand() % _n;
+        float value      = randf();
+        smatrixf_set(b, row, col, value);
+    }
+
+    // initialize c with first multiplication
+    smatrixf_mul(a,b,c);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        smatrixf_mul(a,b,c);
+        smatrixf_mul(a,b,c);
+        smatrixf_mul(a,b,c);
+        smatrixf_mul(a,b,c);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // free smatrix objects
+    smatrixf_destroy(a);
+    smatrixf_destroy(b);
+    smatrixf_destroy(c);
+}
+
+#define SMATRIXF_MUL_BENCHMARK_API(M)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ smatrixf_mul_bench(_start, _finish, _num_iterations, M); }
+
+void benchmark_smatrixf_mul_n32     SMATRIXF_MUL_BENCHMARK_API( 32)
+void benchmark_smatrixf_mul_n64     SMATRIXF_MUL_BENCHMARK_API( 64)
+void benchmark_smatrixf_mul_n128    SMATRIXF_MUL_BENCHMARK_API(128)
+void benchmark_smatrixf_mul_n256    SMATRIXF_MUL_BENCHMARK_API(256)
+void benchmark_smatrixf_mul_n512    SMATRIXF_MUL_BENCHMARK_API(512)
+
diff --git a/src/matrix/src/matrix.base.c b/src/matrix/src/matrix.base.c
new file mode 100644
index 0000000..126c410
--- /dev/null
+++ b/src/matrix/src/matrix.base.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix method base definitions
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+void MATRIX(_print)(T * _X,
+                    unsigned int _R,
+                    unsigned int _C)
+{
+    printf("matrix [%u x %u] : \n", _R, _C);
+    unsigned int r,c;
+    for (r=0; r<_R; r++) {
+        for (c=0; c<_C; c++) {
+            MATRIX_PRINT_ELEMENT(_X,_R,_C,r,c);
+        }
+        printf("\n");
+    }
+}
+
+// initialize square matrix to the identity matrix
+void MATRIX(_eye)(T * _x, unsigned int _n)
+{
+    unsigned int r,c,k=0;
+    for (r=0; r<_n; r++) {
+        for (c=0; c<_n; c++) {
+            _x[k++] = r==c ? 1. : 0.;
+        }
+    }
+}
+
+// initialize matrix to ones
+void MATRIX(_ones)(T * _x,
+                   unsigned int _r,
+                   unsigned int _c)
+{
+    unsigned int k;
+    for (k=0; k<_r*_c; k++)
+        _x[k] = 1.;
+}
+
+// initialize matrix to zeros
+void MATRIX(_zeros)(T * _x,
+                    unsigned int _r,
+                    unsigned int _c)
+{
+    unsigned int k;
+    for (k=0; k<_r*_c; k++)
+        _x[k] = 0.;
+}
+
diff --git a/src/matrix/src/matrix.c b/src/matrix/src/matrix.c
new file mode 100644
index 0000000..a0d58cc
--- /dev/null
+++ b/src/matrix/src/matrix.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Floating-point matrix (double precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrix, name)
+#define MATRIX_NAME     "matrix"
+
+#define T               double          // general type
+#define TP              double          // primitive type
+#define T_COMPLEX       0               // is type complex?
+
+#define T_ABS(X)        fabs(X)
+#define TP_ABS(X)       fabs(X)
+
+#define MATRIX_PRINT_ELEMENT(X,R,C,r,c) \
+    printf("%12.8f", matrix_access(X,R,C,r,c));
+
+#include "matrix.base.c"
+#include "matrix.cgsolve.c"
+#include "matrix.chol.c"
+#include "matrix.gramschmidt.c"
+#include "matrix.inv.c"
+#include "matrix.linsolve.c"
+#include "matrix.ludecomp.c"
+#include "matrix.qrdecomp.c"
+#include "matrix.math.c"
+
diff --git a/src/matrix/src/matrix.cgsolve.c b/src/matrix/src/matrix.cgsolve.c
new file mode 100644
index 0000000..23baf7d
--- /dev/null
+++ b/src/matrix/src/matrix.cgsolve.c
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Solve linear system of equations using conjugate gradient method
+//
+// References:
+//  [Schewchuk:1994] Jonathon Richard Shewchuk, "An Introduction to
+//      the Conjugate Gradient Method Without the Agonizing Pain,"
+//      Manuscript, August, 1994.
+//
+
+#include <math.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_CGSOLVE 0
+
+// solve linear system of equations using conjugate gradient method
+//  _A      :   symmetric positive definite matrix [size: _n x _n]
+//  _n      :   system dimension
+//  _b      :   equality [size: _n x 1]
+//  _x      :   solution estimate [size: _n x 1]
+//  _opts   :   options (ignored for now)
+void MATRIX(_cgsolve)(T *          _A,
+                      unsigned int _n,
+                      T *          _b,
+                      T *          _x,
+                      void *       _opts)
+{
+    // validate input
+    if (_n == 0) {
+        fprintf(stderr,"error: matrix_cgsolve(), system dimension cannot be zero\n");
+        exit(1);
+    }
+
+    // options
+    unsigned int max_iterations = 4*_n; // maximum number of iterations
+    double tol = 1e-6;                  // error tolerance
+
+    unsigned int j;
+
+    // TODO : check options
+    //  1. set initial _x0
+    //  2. max number of iterations
+    //  3. residual tolerance
+
+    // allocate memory for arrays
+    T x0[_n], x1[_n];   // iterative vector x (solution estimate)
+    T d0[_n], d1[_n];   // iterative vector d
+    T r0[_n], r1[_n];   // iterative vector r (step direction)
+    T q[_n];            // A * d0
+    T Ax1[_n];          // A * x1
+
+    // scalars
+    T delta_init;       // b^T * b0
+    T delta0;           // r0^T * r0
+    T delta1;           // r1^T * r1
+    T gamma;            // d0^T * q
+    T alpha;
+    T beta;
+    double res;         // residual
+    double res_opt=0.0; // residual of best solution
+
+    // initialize x0 to {0, 0, ... 0}
+    for (j=0; j<_n; j++)
+        x0[j] = 0.0;
+
+    // d0 = b - A*x0 (assume x0 = {0, 0, 0, ...0})
+    for (j=0; j<_n; j++)
+        d0[j] = _b[j];
+
+    // r0 = d0
+    memmove(r0, d0, _n*sizeof(T));
+
+    // delta_init = b^T * b
+    MATRIX(_transpose_mul)(_b, _n, 1, &delta_init);
+
+    // delta0 = r0^T * r0
+    MATRIX(_transpose_mul)(r0, _n, 1, &delta0);
+
+    // save best solution
+    memmove(_x, x0, _n*sizeof(T));
+    unsigned int i=0;   // iteration counter
+    while ( (i < max_iterations) && (creal(delta0) > tol*tol*creal(delta_init)) ) {
+#if DEBUG_CGSOLVE
+        printf("*********** %4u / %4u (max) **************\n", i, max_iterations);
+        printf("  comparing %12.4e > %12.4e\n", creal(delta0), tol*tol*creal(delta_init));
+#endif
+
+        // q = A*d0
+        MATRIX(_mul)(_A, _n, _n,
+                     d0, _n,  1,
+                     q,  _n,  1);
+
+        // gamma = d0^T * q
+        gamma = 0.0;
+        for (j=0; j<_n; j++) {
+            T prod = conj(d0[j]) * q[j];
+            gamma += prod;
+        }
+
+        // step size: alpha = (r0^T * r0) / (d0^T * A * d0)
+        //                  = delta0 / gamma
+        alpha = delta0 / gamma;
+#if DEBUG_CGSOLVE
+        printf("  alpha  = %12.8f\n", crealf(alpha));
+        printf("  delta0 = %12.8f\n", crealf(delta0));
+#endif
+
+        // update x
+        for (j=0; j<_n; j++)
+            x1[j] = x0[j] + alpha*d0[j];
+
+#if DEBUG_CGSOLVE
+        printf("  x:\n");
+        MATRIX(_print)(x1, _n, 1);
+#endif
+
+        // update r
+        if ( ((i+1)%50) == 0) {
+            // peridically re-compute: r = b - A*x1
+            MATRIX(_mul)(_A,  _n, _n,
+                         x1,  _n,  1,
+                         Ax1, _n, 1);
+            for (j=0; j<_n; j++)
+                r1[j] = _b[j] - Ax1[j];
+        } else {
+            for (j=0; j<_n; j++)
+                r1[j] = r0[j] - alpha*q[j];
+        }
+
+        // delta1 = r1^T * r1
+        MATRIX(_transpose_mul)(r1, _n, 1, &delta1);
+
+        // update beta
+        beta = delta1 / delta0;
+
+        // d1 = r + beta*d0
+        for (j=0; j<_n; j++)
+            d1[j] = r1[j] + beta*d0[j];
+
+        // compute residual
+        res = sqrt( T_ABS(delta1) / T_ABS(delta_init) );
+        if (i==0 || res < res_opt) {
+            // save best solution
+            res_opt = res;
+            memmove(_x, x1, _n*sizeof(T));
+        }
+#if DEBUG_CGSOLVE
+        printf("  res    = %12.4e\n", res);
+#endif
+
+        // copy old x, d, r, delta
+        memmove(x0, x1, _n*sizeof(T));
+        memmove(d0, d1, _n*sizeof(T));
+        memmove(r0, r1, _n*sizeof(T));
+        delta0 = delta1;
+
+        // increment counter
+        i++;
+    }
+}
diff --git a/src/matrix/src/matrix.chol.c b/src/matrix/src/matrix.chol.c
new file mode 100644
index 0000000..611cc9c
--- /dev/null
+++ b/src/matrix/src/matrix.chol.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix Cholesky decomposition method definitions
+//
+
+#include <math.h>
+#include "liquid.internal.h"
+
+#define DEBUG_MATRIX_CHOL 0
+
+// Compute Cholesky decomposition of a symmetric/Hermitian positive-
+// definite matrix as A = L * L^T
+//  _A      :   input square matrix [size: _n x _n]
+//  _n      :   input matrix dimension
+//  _L      :   output lower-triangular matrix
+void MATRIX(_chol)(T *          _A,
+                   unsigned int _n,
+                   T *          _L)
+{
+    // reset L
+    unsigned int i;
+    for (i=0; i<_n*_n; i++)
+        _L[i] = 0.0;
+
+    unsigned int j;
+    unsigned int k;
+    T  A_jj;
+    T  L_jj;
+    T  L_ik;
+    T  L_jk;
+    TP t0;
+    T  t1;
+    for (j=0; j<_n; j++) {
+        // assert that A_jj is real, positive
+        A_jj = matrix_access(_A,_n,_n,j,j);
+        if ( creal(A_jj) < 0.0 ) {
+            fprintf(stderr,"warning: matrix_chol(), matrix is not positive definite (real{A[%u,%u]} = %12.4e < 0)\n",j,j,creal(A_jj));
+            return;
+        }
+#if T_COMPLEX
+        if ( fabs(cimag(A_jj)) > 0.0 ) {
+            fprintf(stderr,"warning: matrix_chol(), matrix is not positive definite (|imag{A[%u,%u]}| = %12.4e > 0)\n",j,j,fabs(cimag(A_jj)));
+            return;
+        }
+#endif
+
+        // compute L_jj and store it in output matrix
+        t0 = 0.0;
+        for (k=0; k<j; k++) {
+            L_jk = matrix_access(_L,_n,_n,j,k);
+#if T_COMPLEX
+            t0 += creal( L_jk * conj(L_jk) );
+#else
+            t0 += L_jk * L_jk;
+#endif
+        }
+        // test to ensure A_jj > t0
+        if ( creal(A_jj) < t0 ) {
+            fprintf(stderr,"warning: matrix_chol(), matrix is not positive definite (real{A[%u,%u]} = %12.4e < %12.4e)\n",j,j,creal(A_jj),t0);
+            return;
+        }
+        L_jj = sqrt( A_jj - t0 );
+        matrix_access(_L,_n,_n,j,j) = L_jj;
+
+        for (i=j+1; i<_n; i++) {
+            t1 = matrix_access(_A,_n,_n,i,j);
+            for (k=0; k<j; k++) {
+                L_ik = matrix_access(_L,_n,_n,i,k);
+                L_jk = matrix_access(_L,_n,_n,j,k);
+#if T_COMPLEX
+                t1 -= L_ik * conj(L_jk);
+#else
+                t1 -= L_ik * L_jk;
+#endif
+            }
+            // TODO : store inverse of L_jj to reduce number of divisions
+            matrix_access(_L,_n,_n,i,j) = t1 / L_jj;
+        }
+    }
+}
+
diff --git a/src/matrix/src/matrix.gramschmidt.c b/src/matrix/src/matrix.gramschmidt.c
new file mode 100644
index 0000000..014135c
--- /dev/null
+++ b/src/matrix/src/matrix.gramschmidt.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix Gram-Schmidt Orthonormalization
+//
+
+#include <math.h>
+#include "liquid.internal.h"
+
+#define DEBUG_MATRIX_GRAMSCHMIDT 0
+
+// compute projection of _u onto _v, store in _e
+void MATRIX(_proj)(T * _u,
+                   T * _v,
+                   unsigned int _n,
+                   T * _e)
+{
+    // compute dot-product between _u and _v
+    unsigned int i;
+    T uv = 0.;
+    T uu = 0.;
+    for (i=0; i<_n; i++) {
+        uv += _u[i] * _v[i];
+        uu += _u[i] * _u[i];
+    }
+
+    // TODO : check magnitude of _uu
+    T g = uv / uu;
+    for (i=0; i<_n; i++)
+        _e[i] = _u[i] * g;
+}
+
+// Orthnormalization using the Gram-Schmidt algorithm
+void MATRIX(_gramschmidt)(T *          _x,
+                          unsigned int _rx,
+                          unsigned int _cx,
+                          T *          _v)
+{
+    // validate input
+    if (_rx == 0 || _cx == 0) {
+        fprintf(stderr,"error: matrix_gramschmidt(), input matrix cannot have zero-length dimensions\n");
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    // copy _x to _u
+    memmove(_v, _x, _rx * _cx * sizeof(T));
+
+    unsigned int n = _rx;   // dimensionality of each vector
+    T proj_ij[n];
+    for (j=0; j<_cx; j++) {
+        for (i=0; i<j; i++) {
+            // v_j  <-  v_j - proj(v_i, v_j)
+
+#if DEBUG_MATRIX_GRAMSCHMIDT
+            printf("computing proj(v_%u, v_%u)\n", i, j);
+#endif
+
+            // compute proj(v_i, v_j)
+            T vij = 0.;     // dotprod(v_i, v_j)
+            T vii = 0.;     // dotprod(v_i, v_i)
+            T ti;
+            T tj;
+            for (k=0; k<n; k++) {
+                ti = matrix_access(_v, _rx, _cx, k, i);
+                tj = matrix_access(_v, _rx, _cx, k, j);
+
+                T prodij = ti * conj(tj);
+                vij += prodij;
+
+                T prodii = ti * conj(ti);
+                vii += prodii;
+            }
+            // TODO : vii should be 1.0 from normalization step below
+            T g = vij / vii;
+
+            // complete projection
+            for (k=0; k<n; k++)
+                proj_ij[k] = matrix_access(_v, _rx, _cx, k, i) * g;
+
+            // subtract projection from v_j
+            for (k=0; k<n; k++)
+                matrix_access(_v, _rx, _cx, k, j) -= proj_ij[k];
+        }
+
+        // normalize v_j
+        T vjj = 0.;     // dotprod(v_j, v_j)
+        T tj  = 0.;
+        for (k=0; k<n; k++) {
+            tj = matrix_access(_v, _rx, _cx, k, j);
+            T prodjj = tj * conj(tj);
+            vjj += prodjj;
+        }
+        // TODO : check magnitude of vjj
+        T g = 1. / sqrt( creal(vjj) );
+        for (k=0; k<n; k++)
+            matrix_access(_v, _rx, _cx, k, j) *= g;
+
+#if DEBUG_MATRIX_GRAMSCHMIDT
+        MATRIX(_print)(_v, _rx, _cx);
+#endif
+    }
+}
+
diff --git a/src/matrix/src/matrix.inv.c b/src/matrix/src/matrix.inv.c
new file mode 100644
index 0000000..5ad96fd
--- /dev/null
+++ b/src/matrix/src/matrix.inv.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix inverse method definitions
+//
+
+#include "liquid.internal.h"
+
+void MATRIX(_inv)(T * _X, unsigned int _XR, unsigned int _XC)
+{
+    // ensure lengths are valid
+    if (_XR != _XC ) {
+        fprintf(stderr, "error: matrix_inv(), invalid dimensions\n");
+        exit(1);
+    }
+
+    // X:
+    //  x11 x12 ... x1n
+    //  x21 x22 ... x2n
+    //  ...
+    //  xn1 xn2 ... xnn
+
+    // allocate temporary memory
+    T x[2*_XR*_XC];
+    unsigned int xr = _XR;
+    unsigned int xc = _XC*2;
+
+    // x:
+    //  x11 x12 ... x1n 1   0   ... 0
+    //  x21 x22 ... x2n 0   1   ... 0
+    //  ...
+    //  xn1 xn2 ... xnn 0   0   ... 1
+    unsigned int r,c;
+    for (r=0; r<_XR; r++) {
+        // copy matrix elements
+        for (c=0; c<_XC; c++)
+            matrix_access(x,xr,xc,r,c) = matrix_access(_X,_XR,_XC,r,c);
+
+        // append identity matrix
+        for (c=0; c<_XC; c++)
+            matrix_access(x,xr,xc,r,_XC+c) = (r==c) ? 1 : 0;
+    }
+
+    // perform Gauss-Jordan elimination on x
+    // x:
+    //  1   0   ... 0   y11 y12 ... y1n
+    //  0   1   ... 0   y21 y22 ... y2n
+    //  ...
+    //  0   0   ... 1   yn1 yn2 ... ynn
+    MATRIX(_gjelim)(x,xr,xc);
+
+    // copy result from right half of x
+    for (r=0; r<_XR; r++) {
+        for (c=0; c<_XC; c++)
+            matrix_access(_X,_XR,_XC,r,c) = matrix_access(x,xr,xc,r,_XC+c);
+    }
+}
+
+// Gauss-Jordan elmination
+void MATRIX(_gjelim)(T * _X, unsigned int _XR, unsigned int _XC)
+{
+    unsigned int r, c;
+
+    // choose pivot rows based on maximum element along column
+    float v;
+    float v_max=0.;
+    unsigned int r_opt=0;
+    unsigned int r_hat;
+    for (r=0; r<_XR; r++) {
+
+        // check values along this column and find the maximum
+        for (r_hat=r; r_hat<_XR; r_hat++) {
+            v = T_ABS( matrix_access(_X,_XR,_XC,r_hat,r) );
+            // swap rows if necessary
+            if (v > v_max || r_hat==r) {
+                r_opt = r_hat;
+                v_max = v;
+            }
+        }
+
+        // if the maximum is zero, matrix is singular
+        if (v_max == 0.0f) {
+            fprintf(stderr,"warning: matrix_gjelim(), matrix singular to machine precision\n");
+        }
+
+        // if row does not match column (e.g. maximum value does not
+        // lie on the diagonal) swap the rows
+        if (r != r_opt) {
+            MATRIX(_swaprows)(_X,_XR,_XC,r,r_opt);
+        }
+
+        // pivot on the diagonal element
+        MATRIX(_pivot)(_X,_XR,_XC,r,r);
+    }
+
+    // scale by diagonal
+    T g;
+    for (r=0; r<_XR; r++) {
+        g = 1 / matrix_access(_X,_XR,_XC,r,r);
+        for (c=0; c<_XC; c++)
+            matrix_access(_X,_XR,_XC,r,c) *= g;
+    }
+}
+
+// pivot on element _r, _c
+void MATRIX(_pivot)(T * _X, unsigned int _XR, unsigned int _XC, unsigned int _r, unsigned int _c)
+{
+    T v = matrix_access(_X,_XR,_XC,_r,_c);
+    if (v==0) {
+        fprintf(stderr, "warning: matrix_pivot(), pivoting on zero\n");
+        return;
+    }
+    unsigned int r,c;
+
+    // pivot using back-substitution
+    T g;    // multiplier
+    for (r=0; r<_XR; r++) {
+
+        // skip over pivot row
+        if (r == _r)
+            continue;
+
+        // compute multiplier
+        g = matrix_access(_X,_XR,_XC,r,_c) / v;
+
+        // back-substitution
+        for (c=0; c<_XC; c++) {
+            matrix_access(_X,_XR,_XC,r,c) = g*matrix_access(_X,_XR,_XC,_r,c) -
+                                              matrix_access(_X,_XR,_XC, r,c);
+        }
+    }
+}
+
+void MATRIX(_swaprows)(T * _X, unsigned int _XR, unsigned int _XC, unsigned int _r1, unsigned int _r2)
+{
+    if (_r1 == _r2)
+        return;
+
+    unsigned int c;
+    T v_tmp;
+    for (c=0; c<_XC; c++) {
+        v_tmp = matrix_access(_X,_XR,_XC,_r1,c);
+        matrix_access(_X,_XR,_XC,_r1,c) = matrix_access(_X,_XR,_XC,_r2,c);
+        matrix_access(_X,_XR,_XC,_r2,c) = v_tmp;
+    }
+}
diff --git a/src/matrix/src/matrix.linsolve.c b/src/matrix/src/matrix.linsolve.c
new file mode 100644
index 0000000..de0de47
--- /dev/null
+++ b/src/matrix/src/matrix.linsolve.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Solve linear system of equations
+//
+
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// solve linear system of n equations: Ax = b
+//  _A      :   system matrix [size: _n x _n]
+//  _n      :   system size
+//  _b      :   equality vector [size: _n x 1]
+//  _x      :   solution vector [size: _n x 1]
+//  _opts   :   options (ignored for now)
+void MATRIX(_linsolve)(T *          _A,
+                       unsigned int _n,
+                       T *          _b,
+                       T *          _x,
+                       void *       _opts)
+{
+#if 0
+    T A_inv[_n*_n];
+    memmove(A_inv, _A, _n*_n*sizeof(T));
+    MATRIX(_inv)(A_inv,_n,_n);
+
+    MATRIX(_mul)(A_inv, _n, _n,
+                 _b,    _n,  1,
+                 _x,    _n,  1);
+#else
+    unsigned int r;
+    unsigned int c;
+
+    // compute augmented matrix M [size: _n x _n+1] as
+    // T:
+    //  A11 A12 A13 ... A1n b1
+    //  A21 A22 A23 ... A2n b2
+    //  A31 A32 A33 ... A3n b3
+    //  ...
+    //  An1 An2 An3 ... Ann bn
+    T M[_n*_n + _n];    // allocate array
+    unsigned int m=0;   // output matrix index counter
+    unsigned int a=0;   // input matrix index counter
+    for (r=0; r<_n; r++) {
+        for (c=0; c<_n; c++)
+            M[m++] = _A[a++];
+
+        M[m++] = _b[r];
+    }
+
+    // run Gauss-Jordan elimination on M
+    // T:
+    //  1   0   0   ... 0   x1
+    //  0   1   0   ... 0   x2
+    //  0   0   1   ... 0   x3
+    //  ...
+    //  0   0   0   ... 1   xn
+    MATRIX(_gjelim)(M, _n, _n+1);
+
+    // copy result from right-most column of M
+    for (r=0; r<_n; r++)
+        _x[r] = M[(_n+1)*(r+1)-1];
+#endif
+}
+
diff --git a/src/matrix/src/matrix.ludecomp.c b/src/matrix/src/matrix.ludecomp.c
new file mode 100644
index 0000000..e940d95
--- /dev/null
+++ b/src/matrix/src/matrix.ludecomp.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix L/U decomposition method definitions
+//
+
+#include "liquid.internal.h"
+
+// L/U/P decomposition, Crout's method
+void MATRIX(_ludecomp_crout)(T * _x,
+                             unsigned int _rx,
+                             unsigned int _cx,
+                             T * _L,
+                             T * _U,
+                             T * _P)
+{
+    // validate input
+    if (_rx != _cx) {
+        fprintf(stderr,"error: matrix_ludecomp_crout(), input matrix not square\n");
+        exit(-1);
+    }
+    unsigned int n = _rx;
+
+    // reset L, U
+    unsigned int i;
+    for (i=0; i<n*n; i++) {
+        _L[i] = 0.0;
+        _U[i] = 0.0;
+        _P[i] = 0.0;
+    }
+
+    unsigned int j,k,t;
+    T L_ik, U_kj;
+    for (k=0; k<n; k++) {
+        for (i=k; i<n; i++) {
+            L_ik = matrix_access(_x,n,n,i,k);
+            for (t=0; t<k; t++) {
+                L_ik -= matrix_access(_L,n,n,i,t)*
+                        matrix_access(_U,n,n,t,k);
+            }
+            matrix_access(_L,n,n,i,k) = L_ik;
+        }
+
+        for (j=k; j<n; j++) {
+            // set upper triangular matrix to unity on diagonal
+            if (j==k) {
+                matrix_access(_U,n,n,k,j) = 1.0f;
+                continue;
+            }
+
+            U_kj = matrix_access(_x,n,n,k,j);
+            for (t=0; t<k; t++) {
+                U_kj -= matrix_access(_L,n,n,k,t)*
+                        matrix_access(_U,n,n,t,j);
+            }
+            U_kj /= matrix_access(_L,n,n,k,k);
+            matrix_access(_U,n,n,k,j) = U_kj;
+        }
+    }
+
+    // set output permutation matrix to identity matrix
+    MATRIX(_eye)(_P,n);
+}
+
+// L/U/P decomposition, Doolittle's method
+void MATRIX(_ludecomp_doolittle)(T * _x,
+                                 unsigned int _rx,
+                                 unsigned int _cx,
+                                 T * _L,
+                                 T * _U,
+                                 T * _P)
+{
+    // validate input
+    if (_rx != _cx) {
+        fprintf(stderr,"error: matrix_ludecomp_doolittle(), input matrix not square\n");
+        exit(-1);
+    }
+    unsigned int n = _rx;
+
+    // reset L, U
+    unsigned int i;
+    for (i=0; i<n*n; i++) {
+        _L[i] = 0.0;
+        _U[i] = 0.0;
+        _P[i] = 0.0;
+    }
+
+    unsigned int j,k,t;
+    T U_kj, L_ik;
+    for (k=0; k<n; k++) {
+        // compute upper triangular matrix
+        for (j=k; j<n; j++) {
+            U_kj = matrix_access(_x,n,n,k,j);
+            for (t=0; t<k; t++) {
+                U_kj -= matrix_access(_L,n,n,k,t)*
+                        matrix_access(_U,n,n,t,j);
+            }
+            matrix_access(_U,n,n,k,j) = U_kj;
+        }
+
+        // compute lower triangular matrix
+        for (i=k; i<n; i++) {
+            // set lower triangular matrix to unity on diagonal
+            if (i==k) {
+                matrix_access(_L,n,n,i,k) = 1.0f;
+                continue;
+            }
+
+            L_ik = matrix_access(_x,n,n,i,k);
+            for (t=0; t<k; t++) {
+                L_ik -= matrix_access(_L,n,n,i,t)*
+                        matrix_access(_U,n,n,t,k);
+            }
+            L_ik /= matrix_access(_U,n,n,k,k);
+            matrix_access(_L,n,n,i,k) = L_ik;
+        }
+    }
+
+    // set output permutation matrix to identity matrix
+    MATRIX(_eye)(_P,n);
+}
+
diff --git a/src/matrix/src/matrix.math.c b/src/matrix/src/matrix.math.c
new file mode 100644
index 0000000..293c0e1
--- /dev/null
+++ b/src/matrix/src/matrix.math.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix method definitions
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+// add elements of two matrices
+//  _X      :   1st input matrix [size: _R x _C]
+//  _Y      :   2nd input matrix [size: _R x _C]
+//  _Z      :   output matrix [size: _R x _C]
+//  _R      :   number of rows
+//  _C      :   number of columns
+void MATRIX(_add)(T * _X,
+                  T * _Y,
+                  T * _Z,
+                  unsigned int _R,
+                  unsigned int _C)
+{
+    unsigned int i;
+    for (i=0; i<(_R*_C); i++)
+        _Z[i] = _X[i] + _Y[i];
+}
+
+// subtract elements of two matrices
+//  _X      :   1st input matrix [size: _R x _C]
+//  _Y      :   2nd input matrix [size: _R x _C]
+//  _Z      :   output matrix [size: _R x _C]
+//  _R      :   number of rows
+//  _C      :   number of columns
+void MATRIX(_sub)(T * _X,
+                  T * _Y,
+                  T * _Z,
+                  unsigned int _R,
+                  unsigned int _C)
+{
+    unsigned int i;
+    for (i=0; i<(_R*_C); i++)
+        _Z[i] = _X[i] - _Y[i];
+}
+
+// point-wise multiplication
+//  _X      :   1st input matrix [size: _R x _C]
+//  _Y      :   2nd input matrix [size: _R x _C]
+//  _Z      :   output matrix [size: _R x _C]
+//  _R      :   number of rows
+//  _C      :   number of columns
+void MATRIX(_pmul)(T * _X,
+                   T * _Y,
+                   T * _Z,
+                   unsigned int _R,
+                   unsigned int _C)
+{
+    unsigned int i;
+    for (i=0; i<(_R*_C); i++)
+        _Z[i] = _X[i] * _Y[i];
+}
+
+// point-wise division
+//  _X      :   1st input matrix [size: _R x _C]
+//  _Y      :   2nd input matrix [size: _R x _C]
+//  _Z      :   output matrix [size: _R x _C]
+//  _R      :   number of rows
+//  _C      :   number of columns
+void MATRIX(_pdiv)(T * _X,
+                   T * _Y,
+                   T * _Z,
+                   unsigned int _R,
+                   unsigned int _C)
+{
+    unsigned int i;
+    for (i=0; i<(_R*_C); i++)
+        _Z[i] = _X[i] / _Y[i];
+}
+
+
+// multiply two matrices together
+void MATRIX(_mul)(T * _X, unsigned int _XR, unsigned int _XC,
+                  T * _Y, unsigned int _YR, unsigned int _YC,
+                  T * _Z, unsigned int _ZR, unsigned int _ZC)
+{
+    // ensure lengths are valid
+    if (_ZR != _XR || _ZC != _YC || _XC != _YR ) {
+        fprintf(stderr,"error: matrix_mul(), invalid dimensions\n");
+        exit(1);
+    }
+
+    unsigned int r, c, i;
+    for (r=0; r<_ZR; r++) {
+        for (c=0; c<_ZC; c++) {
+            // z(i,j) = dotprod( x(i,:), y(:,j) )
+            T sum=0.0f;
+            for (i=0; i<_XC; i++) {
+                sum += matrix_access(_X,_XR,_XC,r,i) *
+                       matrix_access(_Y,_YR,_YC,i,c);
+            }
+            matrix_access(_Z,_ZR,_ZC,r,c) = sum;
+#ifdef DEBUG
+            printf("z(%u,%u) = ", r, c);
+            MATRIX_PRINT_ELEMENT(_Z,_ZR,_ZC,r,c);
+            printf("\n");
+#endif
+        }
+    }
+}
+
+// augment matrices x and y:
+//  z = [x | y]
+void MATRIX(_aug)(T * _x, unsigned int _rx, unsigned int _cx,
+                  T * _y, unsigned int _ry, unsigned int _cy,
+                  T * _z, unsigned int _rz, unsigned int _cz)
+{
+    // ensure lengths are valid
+    if (_rz != _rx || _rz != _ry || _rx != _ry || _cz != _cx + _cy) {
+        fprintf(stderr,"error: matrix_aug(), invalid dimensions\n");
+        exit(1);
+    }
+
+    // TODO: improve speed with memmove
+    unsigned int r, c, n;
+    for (r=0; r<_rz; r++) {
+        n=0;
+        for (c=0; c<_cx; c++)
+            matrix_access(_z,_rz,_cz,r,n++) = matrix_access(_x,_rx,_cx,r,c);
+        for (c=0; c<_cy; c++)
+            matrix_access(_z,_rz,_cz,r,n++) = matrix_access(_y,_ry,_cy,r,c);
+    }
+}
+
+// solve set of linear equations
+void MATRIX(_div)(T * _X,
+                  T * _Y,
+                  T * _Z,
+                  unsigned int _n)
+{
+    // compute inv(_Y)
+    T Y_inv[_n*_n];
+    memmove(Y_inv, _Y, _n*_n*sizeof(T));
+    MATRIX(_inv)(Y_inv,_n,_n);
+
+    // _Z = _X * inv(_Y)
+    MATRIX(_mul)(_X,    _n, _n,
+                 Y_inv, _n, _n,
+                 _Z,    _n, _n);
+}
+
+// matrix determinant (2 x 2)
+T MATRIX(_det2x2)(T * _X,
+                  unsigned int _r,
+                  unsigned int _c)
+{
+    // validate input
+    if (_r != 2 || _c != 2) {
+        fprintf(stderr,"error: matrix_det2x2(), invalid dimensions\n");
+        exit(1);
+    }
+    return _X[0]*_X[3] - _X[1]*_X[2];
+}
+
+// matrix determinant (n x n)
+T MATRIX(_det)(T * _X,
+               unsigned int _r,
+               unsigned int _c)
+{
+    // validate input
+    if (_r != _c) {
+        fprintf(stderr,"error: matrix_det(), matrix must be square\n");
+        exit(1);
+    }
+    unsigned int n = _r;
+    if (n==2) return MATRIX(_det2x2)(_X,2,2);
+
+    // compute L/U decomposition (Doolittle's method)
+    T L[n*n]; // lower
+    T U[n*n]; // upper
+    T P[n*n]; // permutation
+    MATRIX(_ludecomp_doolittle)(_X,n,n,L,U,P);
+
+    // evaluate along the diagonal of U
+    T det = 1.0;
+    unsigned int i;
+    for (i=0; i<n; i++)
+        det *= matrix_access(U,n,n,i,i);
+
+    return det;
+}
+
+// compute matrix transpose
+void MATRIX(_trans)(T * _X,
+                    unsigned int _XR,
+                    unsigned int _XC)
+{
+    // compute Hermitian transpose
+    MATRIX(_hermitian)(_X,_XR,_XC);
+    
+    // conjugate elements
+    unsigned int i;
+    for (i=0; i<_XR*_XC; i++)
+        _X[i] = conj(_X[i]);
+}
+
+// compute matrix Hermitian transpose
+void MATRIX(_hermitian)(T * _X,
+                        unsigned int _XR,
+                        unsigned int _XC)
+{
+    T y[_XR*_XC];
+    memmove(y,_X,_XR*_XC*sizeof(T));
+
+    unsigned int r,c;
+    for (r=0; r<_XR; r++) {
+        for (c=0; c<_XC; c++) {
+            matrix_access(_X,_XC,_XR,c,r) = matrix_access(y,_XR,_XC,r,c);
+        }
+    }
+}
+
+// compute x*x' on m x n matrix, result: m x m
+void MATRIX(_mul_transpose)(T * _x,
+                            unsigned int _m,
+                            unsigned int _n,
+                            T * _xxT)
+{
+    unsigned int r;
+    unsigned int c;
+    unsigned int i;
+
+    // clear _xxT
+    for (i=0; i<_m*_m; i++)
+        _xxT[i] = 0.0f;
+
+    // 
+    T sum = 0;
+    for (r=0; r<_m; r++) {
+
+        for (c=0; c<_m; c++) {
+            sum = 0.0f;
+
+            for (i=0; i<_n; i++) {
+                T prod =        matrix_access(_x,_m,_n,r,i) *
+                         conjf( matrix_access(_x,_m,_n,c,i) );
+                sum += prod;
+            }
+
+            matrix_access(_xxT,_m,_m,r,c) = sum;
+        }
+    }
+}
+
+
+// compute x'*x on m x n matrix, result: n x n
+void MATRIX(_transpose_mul)(T * _x,
+                            unsigned int _m,
+                            unsigned int _n,
+                            T * _xTx)
+{
+    unsigned int r;
+    unsigned int c;
+    unsigned int i;
+
+    // clear _xTx
+    for (i=0; i<_n*_n; i++)
+        _xTx[i] = 0.0f;
+
+    // 
+    T sum = 0;
+    for (r=0; r<_n; r++) {
+
+        for (c=0; c<_n; c++) {
+            sum = 0.0f;
+
+            for (i=0; i<_m; i++) {
+                T prod = conjf( matrix_access(_x,_m,_n,i,r) ) *
+                                matrix_access(_x,_m,_n,i,c);
+                sum += prod;
+            }
+
+            matrix_access(_xTx,_n,_n,r,c) = sum;
+        }
+    }
+}
+
+
+// compute x*x.' on m x n matrix, result: m x m
+void MATRIX(_mul_hermitian)(T * _x,
+                            unsigned int _m,
+                            unsigned int _n,
+                            T * _xxH)
+{
+    unsigned int r;
+    unsigned int c;
+    unsigned int i;
+
+    // clear _xxH
+    for (i=0; i<_m*_m; i++)
+        _xxH[i] = 0.0f;
+
+    // 
+    T sum = 0;
+    for (r=0; r<_m; r++) {
+
+        for (c=0; c<_m; c++) {
+            sum = 0.0f;
+
+            for (i=0; i<_n; i++) {
+                sum += matrix_access(_x,_m,_n,r,i) *
+                       matrix_access(_x,_m,_n,c,i);
+            }
+
+            matrix_access(_xxH,_m,_m,r,c) = sum;
+        }
+    }
+}
+
+
+// compute x.'*x on m x n matrix, result: n x n
+void MATRIX(_hermitian_mul)(T * _x,
+                            unsigned int _m,
+                            unsigned int _n,
+                            T * _xHx)
+{
+    unsigned int r;
+    unsigned int c;
+    unsigned int i;
+
+    // clear _xHx
+    for (i=0; i<_n*_n; i++)
+        _xHx[i] = 0.0f;
+
+    // 
+    T sum = 0;
+    for (r=0; r<_n; r++) {
+
+        for (c=0; c<_n; c++) {
+            sum = 0.0f;
+
+            for (i=0; i<_m; i++) {
+                sum += matrix_access(_x,_m,_n,i,r) *
+                       matrix_access(_x,_m,_n,i,c);
+            }
+
+            matrix_access(_xHx,_n,_n,r,c) = sum;
+        }
+    }
+}
+
diff --git a/src/matrix/src/matrix.qrdecomp.c b/src/matrix/src/matrix.qrdecomp.c
new file mode 100644
index 0000000..72851cb
--- /dev/null
+++ b/src/matrix/src/matrix.qrdecomp.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Matrix Q/R decomposition method definitions
+//
+
+#include <math.h>
+#include "liquid.internal.h"
+
+#define DEBUG_MATRIX_QRDECOMP 1
+
+// Q/R decomposition using the Gram-Schmidt algorithm
+void MATRIX(_qrdecomp_gramschmidt)(T *          _x,
+                                   unsigned int _rx,
+                                   unsigned int _cx,
+                                   T *          _Q,
+                                   T *          _R)
+{
+    // validate input
+    if (_rx != _cx) {
+        fprintf(stderr,"error: matrix_qrdecomp_gramschmidt(), input matrix not square\n");
+        exit(-1);
+    }
+    unsigned int n = _rx;
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    // generate and initialize matrices
+    T e[n*n];   // normalized...
+    for (i=0; i<n*n; i++)
+        e[i] = 0.0f;
+
+    for (k=0; k<n; k++) {
+        // e(i,k) <- _x(i,k)
+        for (i=0; i<n; i++)
+            matrix_access(e,n,n,i,k) = matrix_access(_x,n,n,i,k);
+
+        // subtract...
+        for (i=0; i<k; i++) {
+            // compute dot product _x(:,k) * e(:,i)
+            T g = 0;
+            for (j=0; j<n; j++) {
+                T prod = matrix_access(_x,n,n,j,k) * conj( matrix_access(e,n,n,j,i) );
+                g += prod;
+            }
+            //printf("  i=%2u, g = %12.4e\n", i, crealf(g));
+            for (j=0; j<n; j++)
+                matrix_access(e,n,n,j,k) -= matrix_access(e,n,n,j,i) * g;
+        }
+
+        // compute e_k = e_k / |e_k|
+        float ek = 0.0f;
+        T ak;
+        TP ak2;
+        for (i=0; i<n; i++) {
+            ak  = matrix_access(e,n,n,i,k);
+            ak2 = T_ABS(ak);
+            ak2 = ak2 * ak2;
+            ek += ak2;
+        }
+        ek = sqrtf(ek);
+
+        // normalize e
+        for (i=0; i<n; i++)
+            matrix_access(e,n,n,i,k) /= ek;
+    }
+
+    // move Q
+    memmove(_Q, e, n*n*sizeof(T));
+
+    // compute R
+    // j : row
+    // k : column
+    for (j=0; j<n; j++) {
+        for (k=0; k<n; k++) {
+            if (k < j) {
+                matrix_access(_R,n,n,j,k) = 0.0f;
+            } else {
+                // compute dot product between and Q(:,j) and _x(:,k)
+                T g = 0;
+                for (i=0; i<n; i++) {
+                    T prod = conj( matrix_access(_Q,n,n,i,j) ) * matrix_access(_x,n,n,i,k);
+                    g += prod;
+                }
+                matrix_access(_R,n,n,j,k) = g;
+            }
+        }
+    }
+}
+
diff --git a/src/matrix/src/matrixc.c b/src/matrix/src/matrixc.c
new file mode 100644
index 0000000..6cf50af
--- /dev/null
+++ b/src/matrix/src/matrixc.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex floating-point matrix (double precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixc, name)
+#define MATRIX_NAME     "matrixc"
+
+#define T               double complex  // general type
+#define TP              double          // primitive type
+#define T_COMPLEX       1               // is type complex?
+
+#define T_ABS(X)        cabs(X)
+#define TP_ABS(X)       fabs(X)
+
+#define MATRIX_PRINT_ELEMENT(X,R,C,r,c)     \
+    printf("%7.2f+j%6.2f ",                 \
+        crealf(matrix_access(X,R,C,r,c)),   \
+        cimagf(matrix_access(X,R,C,r,c)));
+
+#include "matrix.base.c"
+#include "matrix.cgsolve.c"
+#include "matrix.chol.c"
+#include "matrix.gramschmidt.c"
+#include "matrix.inv.c"
+#include "matrix.linsolve.c"
+#include "matrix.ludecomp.c"
+#include "matrix.qrdecomp.c"
+#include "matrix.math.c"
+
diff --git a/src/matrix/src/matrixcf.c b/src/matrix/src/matrixcf.c
new file mode 100644
index 0000000..74ad430
--- /dev/null
+++ b/src/matrix/src/matrixcf.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Complex floating-point matrix (single precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixcf, name)
+#define MATRIX_NAME     "matrixcf"
+
+#define T               float complex   // general type
+#define TP              float           // primitive type
+#define T_COMPLEX       1               // is type complex?
+
+#define T_ABS(X)        cabsf(X)
+#define TP_ABS(X)       fabsf(X)
+
+#define MATRIX_PRINT_ELEMENT(X,R,C,r,c)     \
+    printf("%7.2f+j%6.2f ",                 \
+        crealf(matrix_access(X,R,C,r,c)),   \
+        cimagf(matrix_access(X,R,C,r,c)));
+
+#include "matrix.base.c"
+#include "matrix.cgsolve.c"
+#include "matrix.chol.c"
+#include "matrix.gramschmidt.c"
+#include "matrix.inv.c"
+#include "matrix.linsolve.c"
+#include "matrix.ludecomp.c"
+#include "matrix.qrdecomp.c"
+#include "matrix.math.c"
+
diff --git a/src/matrix/src/matrixf.c b/src/matrix/src/matrixf.c
new file mode 100644
index 0000000..0c3c207
--- /dev/null
+++ b/src/matrix/src/matrixf.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Floating-point matrix (single precision)
+// 
+
+#include "liquid.internal.h"
+
+#define MATRIX(name)    LIQUID_CONCAT(matrixf, name)
+#define MATRIX_NAME     "matrixf"
+
+#define T               float           // general type
+#define TP              float           // primitive type
+#define T_COMPLEX       0               // is type complex?
+
+#define T_ABS(X)        fabsf(X)
+#define TP_ABS(X)       fabsf(X)
+
+#define MATRIX_PRINT_ELEMENT(X,R,C,r,c) \
+    printf("%12.7f", matrix_access(X,R,C,r,c));
+
+#include "matrix.base.c"
+#include "matrix.cgsolve.c"
+#include "matrix.chol.c"
+#include "matrix.gramschmidt.c"
+#include "matrix.inv.c"
+#include "matrix.linsolve.c"
+#include "matrix.ludecomp.c"
+#include "matrix.qrdecomp.c"
+#include "matrix.math.c"
+
diff --git a/src/matrix/src/smatrix.c b/src/matrix/src/smatrix.c
new file mode 100644
index 0000000..c43ac2e
--- /dev/null
+++ b/src/matrix/src/smatrix.c
@@ -0,0 +1,606 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// sparse matrices
+//
+
+// sparse matrix structure
+// example: the following floating-point sparse matrix is represented
+//          with the corresponding values:
+//    [ 0   0   0   0   0 ]
+//    [ 0 2.3   0   0   0 ]
+//    [ 0   0   0   0 1.2 ]
+//    [ 0   0   0   0   0 ]
+//    [ 0 3.4   0 4.4   0 ]
+//    [ 0   0   0   0   0 ]
+//
+//  M (rows)        :   6
+//  N (cols)        :   5
+//  mlist           :   { {}, {1},   {4}, {},  {1,3}, {} }
+//  nlist           :   { {}, {1,4}, {},  {4}, {2} }
+//  mvals           :   { {}, {2.3}, {1.2}, {}, {3.4,4.4}, {} }
+//  nvals           :   { {}, {2.3, 3.4}, {}, {4.4}, {1.2} }
+//  num_mlist       :   { 0, 1, 1, 0, 2, 0 }
+//  num_nlist       :   { 0, 2, 0, 1, 1 }
+//  max_num_mlist   :   2
+//  max_num_nlist   :   2
+//
+// NOTE: while this particular example does not show a particular
+//       improvement in memory use, such a case can be made for
+//       extremely large matrices which only have a few non-zero
+//       entries.
+//
+struct SMATRIX(_s) {
+    unsigned int M;                 // number of rows
+    unsigned int N;                 // number of columns
+    unsigned short int ** mlist;    // list of non-zero col indices in each row
+    unsigned short int ** nlist;    // list of non-zero row indices in each col
+    T ** mvals;                     // list of non-zero values in each row
+    T ** nvals;                     // list of non-zero values in each col
+    unsigned int * num_mlist;       // weight of each row, m
+    unsigned int * num_nlist;       // weight of each row, n
+    unsigned int max_num_mlist;     // maximum of num_mlist
+    unsigned int max_num_nlist;     // maximum of num_nlist
+};
+
+// create _M x _N matrix, initialized with zeros
+SMATRIX() SMATRIX(_create)(unsigned int _M,
+                           unsigned int _N)
+{
+    // create object and allocate memory
+    SMATRIX() q = (SMATRIX()) malloc(sizeof(struct SMATRIX(_s)));
+    q->M = _M;
+    q->N = _N;
+
+    unsigned int i;
+    unsigned int j;
+
+    // initialize size of each pointer list
+    q->num_mlist = (unsigned int*)malloc( q->M*sizeof(unsigned int) );
+    q->num_nlist = (unsigned int*)malloc( q->N*sizeof(unsigned int) );
+    for (i=0; i<q->M; i++) q->num_mlist[i] = 0;
+    for (j=0; j<q->N; j++) q->num_nlist[j] = 0;
+
+    // initialize lists
+    q->mlist = (unsigned short int **) malloc( q->M*sizeof(unsigned short int *) );
+    q->nlist = (unsigned short int **) malloc( q->N*sizeof(unsigned short int *) );
+    for (i=0; i<q->M; i++)
+        q->mlist[i] = (unsigned short int *) malloc( q->num_mlist[i]*sizeof(unsigned short int) );
+    for (j=0; j<q->N; j++)
+        q->nlist[j] = (unsigned short int *) malloc( q->num_nlist[j]*sizeof(unsigned short int) );
+
+    // initialize values
+    q->mvals = (T **) malloc( q->M*sizeof(T*) );
+    q->nvals = (T **) malloc( q->N*sizeof(T*) );
+    for (i=0; i<q->M; i++)
+        q->mvals[i] = (T *) malloc( q->num_mlist[i]*sizeof(T) );
+    for (j=0; j<q->N; j++)
+        q->nvals[j] = (T *) malloc( q->num_nlist[j]*sizeof(T) );
+
+    // set maximum list size
+    q->max_num_mlist = 0;
+    q->max_num_nlist = 0;
+
+    // return main object
+    return q;
+}
+
+// create _M x _N matrix, initialized on array
+SMATRIX() SMATRIX(_create_array)(T *          _v,
+                                 unsigned int _m,
+                                 unsigned int _n)
+{
+    // create object and allocate memory
+    SMATRIX() q = SMATRIX(_create)(_m,_n);
+
+    // initialize elements
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_m; i++) {
+        for (j=0; j<_n; j++) {
+            if (_v[i*_n + j] != 0)
+                SMATRIX(_set)(q,i,j,_v[i*_n + j]);
+        }
+    }
+
+    // return main object
+    return q;
+}
+
+// destroy object
+void SMATRIX(_destroy)(SMATRIX() _q)
+{
+    unsigned int i;
+    unsigned int j;
+
+    // free internal memory
+    free(_q->num_mlist);
+    free(_q->num_nlist);
+
+    // free lists
+    for (i=0; i<_q->M; i++) free(_q->mlist[i]);
+    for (j=0; j<_q->N; j++) free(_q->nlist[j]);
+    free(_q->mlist);
+    free(_q->nlist);
+
+    // free values
+    for (i=0; i<_q->M; i++) free(_q->mvals[i]);
+    for (j=0; j<_q->N; j++) free(_q->nvals[j]);
+    free(_q->mvals);
+    free(_q->nvals);
+
+    // free main object memory
+    free(_q);
+}
+
+// print compact form
+void SMATRIX(_print)(SMATRIX() _q)
+{
+    printf("dims : %u %u\n", _q->M, _q->N);
+    printf("max  : %u %u\n", _q->max_num_mlist, _q->max_num_nlist);
+    unsigned int i;
+    unsigned int j;
+    printf("rows :");
+    for (i=0; i<_q->M; i++) printf(" %u", _q->num_mlist[i]);
+    printf("\n");
+    printf("cols :");
+    for (j=0; j<_q->N; j++) printf(" %u", _q->num_nlist[j]);
+    printf("\n");
+
+    // print mlist
+    printf("row indices:\n");
+    for (i=0; i<_q->M; i++) {
+        if (_q->num_mlist[i] == 0)
+            continue;
+        printf("  %3u :", i);
+        for (j=0; j<_q->num_mlist[i]; j++)
+            printf(" %u", _q->mlist[i][j]);
+        printf("\n");
+    }
+
+    // print nlist
+    printf("column indices:\n");
+    for (j=0; j<_q->N; j++) {
+        if (_q->num_nlist[j] == 0)
+            continue;
+        printf("  %3u :", j);
+        for (i=0; i<_q->num_nlist[j]; i++)
+            printf(" %u", _q->nlist[j][i]);
+        printf("\n");
+    }
+#if SMATRIX_FLOAT
+    // print mvals
+    printf("row values:\n");
+    for (i=0; i<_q->M; i++) {
+        printf("  %3u :", i);
+        for (j=0; j<_q->num_mlist[i]; j++)
+            printf(" %6.2f", _q->mvals[i][j]);
+        printf("\n");
+    }
+
+    // print nvals
+    printf("column values:\n");
+    for (j=0; j<_q->N; j++) {
+        printf("  %3u :", j);
+        for (i=0; i<_q->num_nlist[j]; i++)
+            printf(" %6.2f", _q->nvals[j][i]);
+        printf("\n");
+    }
+#endif
+}
+
+// print expanded form
+void SMATRIX(_print_expanded)(SMATRIX() _q)
+{
+    unsigned int i;
+    unsigned int j;
+    unsigned int t;
+
+    // print in expanded 'regular' form
+    for (i=0; i<_q->M; i++) {
+        // reset counter
+        t = 0;
+        for (j=0; j<_q->N; j++) {
+            if (t == _q->num_mlist[i]) {
+                PRINTVAL_ZERO();
+            } else if (_q->mlist[i][t] == j) {
+                PRINTVAL(_q->mvals[i][t]);
+                t++;
+            } else {
+                PRINTVAL_ZERO();
+            }
+        }
+        printf("\n");
+    }
+}
+
+// get matrix dimensions
+void SMATRIX(_size)(SMATRIX()      _q,
+                    unsigned int * _m,
+                    unsigned int * _n)
+{
+    *_m = _q->M;
+    *_n = _q->N;
+}
+
+// zero all values, retaining memory allocation
+void SMATRIX(_clear)(SMATRIX() _q)
+{
+    unsigned int i;
+    unsigned int j;
+    
+    // clear row entries
+    for (i=0; i<_q->M; i++) {
+        for (j=0; j<_q->num_mlist[i]; j++) {
+            _q->mvals[i][j] = 0;
+        }
+    }
+
+    // clear colum entries
+    for (j=0; j<_q->N; j++) {
+        for (i=0; i<_q->num_nlist[j]; i++) {
+            _q->nvals[j][i] = 0;
+        }
+    }
+}
+
+// zero all values, clearing memory
+void SMATRIX(_reset)(SMATRIX() _q)
+{
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<_q->M; i++) _q->num_mlist[i] = 0;
+    for (j=0; j<_q->N; j++) _q->num_nlist[j] = 0;
+
+    _q->max_num_mlist = 0;
+    _q->max_num_nlist = 0;
+}
+
+// determine if element is set
+int SMATRIX(_isset)(SMATRIX()    _q,
+                    unsigned int _m,
+                    unsigned int _n)
+{
+    // validate input
+    if (_m >= _q->M || _n >= _q->N) {
+        fprintf(stderr,"error: SMATRIX(_isset)(%u,%u), index exceeds matrix dimension (%u,%u)\n",
+                _m, _n, _q->M, _q->N);
+        exit(1);
+    }
+
+    unsigned int j;
+    for (j=0; j<_q->num_mlist[_m]; j++) {
+        if (_q->mlist[_m][j] == _n)
+            return 1;
+    }
+    return 0;
+}
+
+// insert element at index
+void SMATRIX(_insert)(SMATRIX()    _q,
+                      unsigned int _m,
+                      unsigned int _n,
+                      T            _v)
+{
+    // validate input
+    if (_m >= _q->M || _n >= _q->N) {
+        fprintf(stderr,"error: SMATRIX(_insert)(%u,%u), index exceeds matrix dimension (%u,%u)\n",
+                _m, _n, _q->M, _q->N);
+        exit(1);
+    }
+
+    // check to see if element is already set
+    if (SMATRIX(_isset)(_q,_m,_n)) {
+        // simply set the value and return
+        printf("SMATRIX(_insert), value already set...\n");
+        SMATRIX(_set)(_q, _m, _n, _v);
+        return;
+    }
+
+    // increment list sizes
+    _q->num_mlist[_m]++;
+    _q->num_nlist[_n]++;
+
+    // reallocate indices lists at this index
+    _q->mlist[_m] = (unsigned short int*) realloc(_q->mlist[_m], _q->num_mlist[_m]*sizeof(unsigned short int));
+    _q->nlist[_n] = (unsigned short int*) realloc(_q->nlist[_n], _q->num_nlist[_n]*sizeof(unsigned short int));
+    
+    // reallocate values lists at this index
+    _q->mvals[_m] = (T*) realloc(_q->mvals[_m], _q->num_mlist[_m]*sizeof(T));
+    _q->nvals[_n] = (T*) realloc(_q->nvals[_n], _q->num_nlist[_n]*sizeof(T));
+
+    // find index within list to insert new value
+    unsigned int mindex = smatrix_indexsearch(_q->mlist[_m], _q->num_mlist[_m]-1, _n);
+    unsigned int nindex = smatrix_indexsearch(_q->nlist[_n], _q->num_nlist[_n]-1, _m);
+    //printf("inserting value (n=%3u) at m=%3u, index=%3u\n", _n, _m, mindex);
+    //printf("inserting value (m=%3u) at n=%3u, index=%3u\n", _m, _n, nindex);
+
+    // insert indices to appropriate place in list
+    memmove(&_q->mlist[_m][mindex+1], &_q->mlist[_m][mindex], (_q->num_mlist[_m]-mindex-1)*sizeof(unsigned short int));
+    memmove(&_q->nlist[_n][nindex+1], &_q->nlist[_n][nindex], (_q->num_nlist[_n]-nindex-1)*sizeof(unsigned short int));
+    _q->mlist[_m][mindex] = _n;
+    _q->nlist[_n][nindex] = _m;
+
+    // insert values to appropriate place in list
+    memmove(&_q->mvals[_m][mindex+1], &_q->mvals[_m][mindex], (_q->num_mlist[_m]-mindex-1)*sizeof(T));
+    memmove(&_q->nvals[_n][nindex+1], &_q->nvals[_n][nindex], (_q->num_nlist[_n]-nindex-1)*sizeof(T));
+    _q->mvals[_m][mindex] = _v;
+    _q->nvals[_n][nindex] = _v;
+
+    // update maximum
+    if (_q->num_mlist[_m] > _q->max_num_mlist) _q->max_num_mlist = _q->num_mlist[_m];
+    if (_q->num_nlist[_n] > _q->max_num_nlist) _q->max_num_nlist = _q->num_nlist[_n];
+}
+
+// delete element at index
+void SMATRIX(_delete)(SMATRIX()    _q,
+                      unsigned int _m,
+                      unsigned int _n)
+{
+    // validate input
+    if (_m > _q->M || _n > _q->N) {
+        fprintf(stderr,"error: SMATRIX(_delete)(%u,%u), index exceeds matrix dimension (%u,%u)\n",
+                _m, _n, _q->M, _q->N);
+        exit(1);
+    }
+
+    // check to see if element is already not set
+    if (!SMATRIX(_isset)(_q,_m,_n))
+        return;
+
+    // remove value from mlist (shift left)
+    unsigned int i;
+    unsigned int j;
+    unsigned int t=0;
+    for (j=0; j<_q->num_mlist[_m]; j++) {
+        if (_q->mlist[_m][j] == _n)
+            t = j;
+    }
+    for (j=t; j<_q->num_mlist[_m]-1; j++)
+        _q->mlist[_m][j] = _q->mlist[_m][j+1];
+
+    // remove value from nlist (shift left)
+    t = 0;
+    for (i=0; i<_q->num_nlist[_n]; i++) {
+        if (_q->nlist[_n][i] == _m)
+            t = i;
+    }
+    for (i=t; i<_q->num_nlist[_n]-1; i++)
+        _q->nlist[_n][i] = _q->nlist[_n][i+1];
+
+    // reduce sizes
+    _q->num_mlist[_m]--;
+    _q->num_nlist[_n]--;
+
+    // reallocate
+    _q->mlist[_m] = (unsigned short int*) realloc(_q->mlist[_m], _q->num_mlist[_m]*sizeof(unsigned short int));
+    _q->nlist[_n] = (unsigned short int*) realloc(_q->nlist[_n], _q->num_nlist[_n]*sizeof(unsigned short int));
+
+    // reset maxima
+    if (_q->max_num_mlist == _q->num_mlist[_m]+1)
+        SMATRIX(_reset_max_mlist)(_q);
+
+    if (_q->max_num_nlist == _q->num_nlist[_n]+1)
+        SMATRIX(_reset_max_nlist)(_q);
+}
+
+// set element value at index
+void SMATRIX(_set)(SMATRIX()    _q,
+                   unsigned int _m,
+                   unsigned int _n,
+                   T            _v)
+{
+    // validate input
+    if (_m >= _q->M || _n >= _q->N) {
+        fprintf(stderr,"error: SMATRIX(_set)(%u,%u), index exceeds matrix dimension (%u,%u)\n",
+                _m, _n, _q->M, _q->N);
+        exit(1);
+    }
+
+    // insert new element if not already allocated
+    if (!SMATRIX(_isset)(_q,_m,_n)) {
+        SMATRIX(_insert)(_q,_m,_n,_v);
+        return;
+    }
+
+    // set value
+    unsigned int i;
+    unsigned int j;
+    for (j=0; j<_q->num_mlist[_m]; j++) {
+        if (_q->mlist[_m][j] == _n)
+            _q->mvals[_m][j] = _v;
+    }
+
+    for (i=0; i<_q->num_nlist[_n]; i++) {
+        if (_q->nlist[_n][i] == _m)
+            _q->nvals[_n][i] = _v;
+    }
+}
+
+
+// get element value at index (return zero if not set)
+T SMATRIX(_get)(SMATRIX()    _q,
+                unsigned int _m,
+                unsigned int _n)
+{
+    // validate input
+    if (_m >= _q->M || _n >= _q->N) {
+        fprintf(stderr,"error: SMATRIX(_get)(%u,%u), index exceeds matrix dimension (%u,%u)\n",
+                _m, _n, _q->M, _q->N);
+        exit(1);
+    }
+
+    unsigned int j;
+    for (j=0; j<_q->num_mlist[_m]; j++) {
+        if (_q->mlist[_m][j] == _n)
+            return _q->mvals[_m][j];
+    }
+    return 0;
+}
+
+// initialize to identity matrix
+void SMATRIX(_eye)(SMATRIX() _q)
+{
+    // reset all elements
+    SMATRIX(_reset)(_q);
+
+    // set values along diagonal
+    unsigned int i;
+    unsigned int dmin = _q->M < _q->N ? _q->M : _q->N;
+    for (i=0; i<dmin; i++)
+        SMATRIX(_set)(_q, i, i, 1);
+}
+
+// multiply two sparse matrices
+void SMATRIX(_mul)(SMATRIX() _a,
+                   SMATRIX() _b,
+                   SMATRIX() _c)
+{
+    // validate input
+    if (_c->M != _a->M || _c->N != _b->N || _a->N != _b->M) {
+        fprintf(stderr,"error: SMATRIX(_mul)(), invalid dimensions\n");
+        exit(1);
+    }
+
+    // clear output matrix (retain memory allocation)
+    SMATRIX(_clear)(_c);
+
+    unsigned int r; // output row
+    unsigned int c; // output column
+    
+    unsigned int i;
+    unsigned int j;
+
+    for (r=0; r<_c->M; r++) {
+        
+        // find number of non-zero entries in row 'r' of matrix '_a'
+        unsigned int nnz_a_row = _a->num_mlist[r];
+
+        // if this number is zero, there will not be any non-zero
+        // entries in the corresponding row of the output matrix '_c'
+        if (nnz_a_row == 0)
+            continue;
+        
+        for (c=0; c<_c->N; c++) {
+
+            // find number of non-zero entries in column 'c' of matrix '_b'
+            unsigned int nnz_b_col = _b->num_nlist[c];
+
+            T p = 0;
+            int set_value = 0;
+
+            // find common elements between non-zero elements in
+            // row 'r' of matrix '_a' and col 'c' of matrix '_b'
+            i=0;    // reset array index for rows of '_a'
+            j=0;    // reset array index for cols of '_b'
+            while (i < nnz_a_row && j < nnz_b_col) {
+                // 
+                unsigned int ca = _a->mlist[r][i];
+                unsigned int rb = _b->nlist[c][j];
+                if (ca == rb) {
+                    // match found between _a[r,ca] and _b[rb,c]
+                    p += _a->mvals[r][i] * _b->nvals[c][j];
+                    set_value = 1;
+                    i++;
+                    j++;
+                } else if (ca < rb)
+                    i++;    // increment index for '_a'
+                else
+                    j++;    // increment index for '_b'
+
+            }
+
+            // set value if any multiplications have been made
+            if (set_value) {
+#if SMATRIX_BOOL
+                // set result modulo 2
+                SMATRIX(_set)(_c, r, c, p % 2);
+#else
+                SMATRIX(_set)(_c, r, c, p);
+#endif
+            }
+        }
+    }
+}
+
+// multiply by vector
+//  _q  :   sparse matrix
+//  _x  :   input vector [size: _N x 1]
+//  _y  :   output vector [size: _M x 1]
+void SMATRIX(_vmul)(SMATRIX() _q,
+                    T *       _x,
+                    T *       _y)
+{
+    unsigned int i;
+    unsigned int j;
+    
+    // initialize to zero
+    for (i=0; i<_q->M; i++)
+        _y[i] = 0;
+
+    for (i=0; i<_q->M; i++) {
+
+        // running total
+        T p = 0;
+
+        // only compute multiplications on non-zero entries
+        for (j=0; j<_q->num_mlist[i]; j++)
+            p += _q->mvals[i][j] * _x[ _q->mlist[i][j] ];
+
+        // set output value appropriately
+#if SMATRIX_BOOL
+        _y[i] = p % 2;
+#else
+        _y[i] = p;
+#endif
+    }
+}
+
+
+// 
+// internal methods
+//
+
+// find maximum mlist length
+void SMATRIX(_reset_max_mlist)(SMATRIX() _q)
+{
+    unsigned int i;
+
+    _q->max_num_mlist = 0;
+    for (i=0; i<_q->M; i++) {
+        if (_q->num_mlist[i] > _q->max_num_mlist)
+            _q->max_num_mlist = _q->num_mlist[i];
+    }
+}
+
+// find maximum nlist length
+void SMATRIX(_reset_max_nlist)(SMATRIX() _q)
+{
+    unsigned int j;
+
+    _q->max_num_nlist = 0;
+    for (j=0; j<_q->N; j++) {
+        if (_q->num_nlist[j] > _q->max_num_nlist)
+            _q->max_num_nlist = _q->num_nlist[j];
+    }
+}
diff --git a/src/matrix/src/smatrix.common.c b/src/matrix/src/smatrix.common.c
new file mode 100644
index 0000000..63314ec
--- /dev/null
+++ b/src/matrix/src/smatrix.common.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// sparse matrices: common methods
+//
+
+// search for index placement in list
+unsigned short int smatrix_indexsearch(unsigned short int * _list,
+                                       unsigned int         _num_elements,
+                                       unsigned short int   _value)
+{
+    // TODO: use bisection method
+    
+    unsigned int i;
+    for (i=0; i<_num_elements; i++) {
+        if (_list[i] > _value)
+            break;
+    }
+
+    //
+    return i;
+}
+
diff --git a/src/matrix/src/smatrixb.c b/src/matrix/src/smatrixb.c
new file mode 100644
index 0000000..70a454d
--- /dev/null
+++ b/src/matrix/src/smatrixb.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sparse matrix API: boolean
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+// name-mangling macro
+#define SMATRIX(name)       LIQUID_CONCAT(smatrixb,name)
+
+// primitive type
+#define T                   unsigned char
+
+// category (float/int/bool)
+#define SMATRIX_FLOAT       0
+#define SMATRIX_INT         0
+#define SMATRIX_BOOL        1
+
+// print macros
+#define PRINTVAL_ZERO()     printf(" .");
+#define PRINTVAL(V)         printf(" %1u", V);
+
+// source files
+#include "smatrix.c"
+
+// 
+// smatrix cross methods
+//
+
+// multiply sparse binary matrix by floating-point matrix
+//  _q  :   sparse matrix [size: A->M x A->N]
+//  _x  :   input vector  [size:  mx  x  nx ]
+//  _y  :   output vector [size:  my  x  ny ]
+void smatrixb_mulf(smatrixb     _A,
+                   float *      _x,
+                   unsigned int _mx,
+                   unsigned int _nx,
+                   float *      _y,
+                   unsigned int _my,
+                   unsigned int _ny)
+{
+    // ensure lengths are valid
+    if (_my != _A->M || _ny != _nx || _A->N != _mx ) {
+        fprintf(stderr,"error: matrix_mul(), invalid dimensions\n");
+        exit(1);
+    }
+    unsigned int i;
+    unsigned int j;
+
+    // clear output matrix
+    for (i=0; i<_my*_ny; i++)
+        _y[i] = 0.0f;
+
+    //
+    for (i=0; i<_A->M; i++) {
+        // find non-zero column entries in this row
+        unsigned int p;
+        for (p=0; p<_A->num_mlist[i]; p++) {
+            for (j=0; j<_ny; j++) {
+                //_y(i,j) += _x( _A->mlist[i][p], j);
+                _y[i*_ny + j] += _x[ _A->mlist[i][p]*_nx + j];
+            }
+        }
+    }
+}
+
+// multiply sparse binary matrix by floating-point vector
+//  _q  :   sparse matrix
+//  _x  :   input vector [size: _N x 1]
+//  _y  :   output vector [size: _M x 1]
+void smatrixb_vmulf(smatrixb _q,
+                    float *  _x,
+                    float *  _y)
+{
+    unsigned int i;
+    unsigned int j;
+    
+    for (i=0; i<_q->M; i++) {
+
+        // reset total
+        _y[i] = 0.0f;
+
+        // only accumulate values on non-zero entries
+        for (j=0; j<_q->num_mlist[i]; j++)
+            _y[i] += _x[ _q->mlist[i][j] ];
+    }
+}
+
diff --git a/src/matrix/src/smatrixf.c b/src/matrix/src/smatrixf.c
new file mode 100644
index 0000000..e8a03ca
--- /dev/null
+++ b/src/matrix/src/smatrixf.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sparse matrix API: float
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+// macro
+#define SMATRIX(name)       LIQUID_CONCAT(smatrixf,name)
+
+// primitive type
+#define T                   float
+
+// category (float/int/bool)
+#define SMATRIX_FLOAT       1
+#define SMATRIX_INT         0
+#define SMATRIX_BOOL        0
+
+// print macros
+#define PRINTVAL_ZERO()     printf(" %6s", ".");
+#define PRINTVAL(V)         printf(" %6.2f", V);
+
+// source files
+#include "smatrix.c"
diff --git a/src/matrix/src/smatrixi.c b/src/matrix/src/smatrixi.c
new file mode 100644
index 0000000..03f08e9
--- /dev/null
+++ b/src/matrix/src/smatrixi.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// sparse matrix API: short signed integer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "liquid.internal.h"
+
+// name-mangling macro
+#define SMATRIX(name)       LIQUID_CONCAT(smatrixi,name)
+
+// primitive type
+#define T                   short int
+
+// category (float/int/bool)
+#define SMATRIX_FLOAT       0
+#define SMATRIX_INT         1
+#define SMATRIX_BOOL        0
+
+// print macros
+#define PRINTVAL_ZERO()     printf(" .");
+#define PRINTVAL(V)         printf(" %3d", V);
+
+// source files
+#include "smatrix.c"
+
diff --git a/src/matrix/tests/data/README.md b/src/matrix/tests/data/README.md
new file mode 100644
index 0000000..6d89720
--- /dev/null
+++ b/src/matrix/tests/data/README.md
@@ -0,0 +1,7 @@
+
+matrix test data
+================
+
+This directory contains all of the data files for testing matrix 
+operations within liquid-dsp.
+
diff --git a/src/matrix/tests/data/matrixcf_data_add.c b/src/matrix/tests/data/matrixcf_data_add.c
new file mode 100644
index 0000000..e9f6e26
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_add.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix addition
+//
+
+#include <complex.h>
+
+// matrixcf_data_add_x [size: 5 x 4]
+float complex matrixcf_data_add_x[] = {
+    1.366575479507 +  -1.463535666466*_Complex_I /* ( 0, 0) */,
+    1.982354640961 +   0.090445250273*_Complex_I /* ( 0, 1) */,
+    0.913504719734 +  -0.689249753952*_Complex_I /* ( 0, 2) */,
+    0.671039104462 +  -0.184951126575*_Complex_I /* ( 0, 3) */,
+    0.264611721039 +   0.204716682434*_Complex_I /* ( 1, 0) */,
+    0.995791137218 +  -0.192152589560*_Complex_I /* ( 1, 1) */,
+   -1.983934879303 +  -0.306251227856*_Complex_I /* ( 1, 2) */,
+   -0.375840932131 +  -0.751154422760*_Complex_I /* ( 1, 3) */,
+    0.245937779546 +  -0.470900952816*_Complex_I /* ( 2, 0) */,
+   -0.343152791262 +   1.205224752426*_Complex_I /* ( 2, 1) */,
+   -0.143777698278 +  -0.457689523697*_Complex_I /* ( 2, 2) */,
+   -1.387172579765 +   0.225333094597*_Complex_I /* ( 2, 3) */,
+    0.781192481518 +  -1.102205991745*_Complex_I /* ( 3, 0) */,
+    1.444583415985 +   0.660028517246*_Complex_I /* ( 3, 1) */,
+    0.043851174414 +   0.049496211112*_Complex_I /* ( 3, 2) */,
+    0.399744838476 +  -1.435891628265*_Complex_I /* ( 3, 3) */,
+   -0.488068610430 +  -0.211131110787*_Complex_I /* ( 4, 0) */,
+    0.573721885681 +   0.016210200265*_Complex_I /* ( 4, 1) */,
+   -1.381630182266 +  -0.026558181271*_Complex_I /* ( 4, 2) */,
+   -0.176615595818 +  -0.414863616228*_Complex_I /* ( 4, 3) */};
+
+// matrixcf_data_add_y [size: 5 x 4]
+float complex matrixcf_data_add_y[] = {
+   -0.345586329699 +   0.240964725614*_Complex_I /* ( 0, 0) */,
+   -0.025732314214 +   1.212726473808*_Complex_I /* ( 0, 1) */,
+    0.907316803932 +   1.614625453949*_Complex_I /* ( 0, 2) */,
+   -1.277831077576 +  -0.193561494350*_Complex_I /* ( 0, 3) */,
+   -0.566267848015 +  -0.477513909340*_Complex_I /* ( 1, 0) */,
+   -0.220387980342 +   0.152425482869*_Complex_I /* ( 1, 1) */,
+    1.062286615372 +  -1.132043361664*_Complex_I /* ( 1, 2) */,
+    0.560977220535 +  -0.981467425823*_Complex_I /* ( 1, 3) */,
+    0.025816366076 +  -0.697246491909*_Complex_I /* ( 2, 0) */,
+    0.658244788647 +  -1.403432965279*_Complex_I /* ( 2, 1) */,
+   -1.326129436493 +  -0.529069602489*_Complex_I /* ( 2, 2) */,
+   -1.535833358765 +  -1.406206607819*_Complex_I /* ( 2, 3) */,
+    0.101660177112 +  -1.552777647972*_Complex_I /* ( 3, 0) */,
+    1.834807038307 +   0.647780478001*_Complex_I /* ( 3, 1) */,
+   -0.620168924332 +  -0.103246472776*_Complex_I /* ( 3, 2) */,
+   -0.063054643571 +  -0.756766498089*_Complex_I /* ( 3, 3) */,
+    0.280569136143 +   0.560459613800*_Complex_I /* ( 4, 0) */,
+   -0.069713011384 +  -0.971132814884*_Complex_I /* ( 4, 1) */,
+    0.225165605545 +  -1.117488980293*_Complex_I /* ( 4, 2) */,
+   -0.290932357311 +   0.302335798740*_Complex_I /* ( 4, 3) */};
+
+// matrixcf_data_add_z [size: 5 x 4]
+float complex matrixcf_data_add_z[] = {
+    1.020989149809 +  -1.222570940852*_Complex_I /* ( 0, 0) */,
+    1.956622326747 +   1.303171724081*_Complex_I /* ( 0, 1) */,
+    1.820821523666 +   0.925375699997*_Complex_I /* ( 0, 2) */,
+   -0.606791973114 +  -0.378512620926*_Complex_I /* ( 0, 3) */,
+   -0.301656126976 +  -0.272797226906*_Complex_I /* ( 1, 0) */,
+    0.775403156877 +  -0.039727106690*_Complex_I /* ( 1, 1) */,
+   -0.921648263931 +  -1.438294589520*_Complex_I /* ( 1, 2) */,
+    0.185136288404 +  -1.732621848583*_Complex_I /* ( 1, 3) */,
+    0.271754145622 +  -1.168147444725*_Complex_I /* ( 2, 0) */,
+    0.315091997385 +  -0.198208212852*_Complex_I /* ( 2, 1) */,
+   -1.469907134771 +  -0.986759126186*_Complex_I /* ( 2, 2) */,
+   -2.923005938530 +  -1.180873513222*_Complex_I /* ( 2, 3) */,
+    0.882852658629 +  -2.654983639717*_Complex_I /* ( 3, 0) */,
+    3.279390454292 +   1.307808995247*_Complex_I /* ( 3, 1) */,
+   -0.576317749918 +  -0.053750261664*_Complex_I /* ( 3, 2) */,
+    0.336690194905 +  -2.192658126354*_Complex_I /* ( 3, 3) */,
+   -0.207499474287 +   0.349328503013*_Complex_I /* ( 4, 0) */,
+    0.504008874297 +  -0.954922614619*_Complex_I /* ( 4, 1) */,
+   -1.156464576721 +  -1.144047161564*_Complex_I /* ( 4, 2) */,
+   -0.467547953129 +  -0.112527817488*_Complex_I /* ( 4, 3) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_aug.c b/src/matrix/tests/data/matrixcf_data_aug.c
new file mode 100644
index 0000000..7abfc4d
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_aug.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix augmentation
+//
+
+#include <complex.h>
+
+// matrixcf_data_aug_x [size: 5 x 4]
+float complex matrixcf_data_aug_x[] = {
+   -1.383545994759 +   0.803655147552*_Complex_I /* ( 0, 0) */,
+   -0.918114125729 +  -1.194809913635*_Complex_I /* ( 0, 1) */,
+    0.090901032090 +   0.484884619713*_Complex_I /* ( 0, 2) */,
+    0.109402157366 +   1.450437188148*_Complex_I /* ( 0, 3) */,
+   -2.269510746002 +  -0.606436431408*_Complex_I /* ( 1, 0) */,
+   -0.195189133286 +   0.416639328003*_Complex_I /* ( 1, 1) */,
+    1.940145850182 +   0.895506143570*_Complex_I /* ( 1, 2) */,
+   -0.784153759480 +  -0.345893263817*_Complex_I /* ( 1, 3) */,
+    0.652509629726 +   0.994532823563*_Complex_I /* ( 2, 0) */,
+   -2.253150939941 +   0.327611356974*_Complex_I /* ( 2, 1) */,
+    1.012208938599 +  -0.677044689655*_Complex_I /* ( 2, 2) */,
+   -0.700399398804 +  -0.330108255148*_Complex_I /* ( 2, 3) */,
+   -1.175772666931 +   0.248428389430*_Complex_I /* ( 3, 0) */,
+    0.412228941917 +  -2.519471645355*_Complex_I /* ( 3, 1) */,
+   -1.667356371880 +  -1.187105178833*_Complex_I /* ( 3, 2) */,
+    1.243350982666 +  -0.736937880516*_Complex_I /* ( 3, 3) */,
+    0.033468723297 +   0.131351217628*_Complex_I /* ( 4, 0) */,
+   -0.617851972580 +   1.434038400650*_Complex_I /* ( 4, 1) */,
+   -1.009798288345 +   0.758803665638*_Complex_I /* ( 4, 2) */,
+    1.450994849205 +  -0.595933079720*_Complex_I /* ( 4, 3) */};
+
+// matrixcf_data_aug_y [size: 5 x 3]
+float complex matrixcf_data_aug_y[] = {
+    0.301848381758 +   0.353115469217*_Complex_I /* ( 0, 0) */,
+    0.703616917133 +   0.044240720570*_Complex_I /* ( 0, 1) */,
+    0.268176555634 +   1.071476221085*_Complex_I /* ( 0, 2) */,
+   -0.717849135399 +  -0.764326214790*_Complex_I /* ( 1, 0) */,
+   -0.108926303685 +  -0.315297245979*_Complex_I /* ( 1, 1) */,
+    0.357895255089 +  -1.419853448868*_Complex_I /* ( 1, 2) */,
+   -0.831380963326 +   1.003911018372*_Complex_I /* ( 2, 0) */,
+   -0.361211270094 +  -0.926369905472*_Complex_I /* ( 2, 1) */,
+    2.307183980942 +  -0.432167291641*_Complex_I /* ( 2, 2) */,
+   -0.694230437279 +  -1.021739125252*_Complex_I /* ( 3, 0) */,
+    0.412434548140 +  -1.840429663658*_Complex_I /* ( 3, 1) */,
+    0.342358648777 +  -1.084336757660*_Complex_I /* ( 3, 2) */,
+   -0.314995974302 +  -0.811702668667*_Complex_I /* ( 4, 0) */,
+    0.912520587444 +  -2.686280250549*_Complex_I /* ( 4, 1) */,
+    0.204153224826 +  -0.616621196270*_Complex_I /* ( 4, 2) */};
+
+// matrixcf_data_aug_z [size: 5 x 7]
+float complex matrixcf_data_aug_z[] = {
+   -1.383545994759 +   0.803655147552*_Complex_I /* ( 0, 0) */,
+   -0.918114125729 +  -1.194809913635*_Complex_I /* ( 0, 1) */,
+    0.090901032090 +   0.484884619713*_Complex_I /* ( 0, 2) */,
+    0.109402157366 +   1.450437188148*_Complex_I /* ( 0, 3) */,
+    0.301848381758 +   0.353115469217*_Complex_I /* ( 0, 4) */,
+    0.703616917133 +   0.044240720570*_Complex_I /* ( 0, 5) */,
+    0.268176555634 +   1.071476221085*_Complex_I /* ( 0, 6) */,
+   -2.269510746002 +  -0.606436431408*_Complex_I /* ( 1, 0) */,
+   -0.195189133286 +   0.416639328003*_Complex_I /* ( 1, 1) */,
+    1.940145850182 +   0.895506143570*_Complex_I /* ( 1, 2) */,
+   -0.784153759480 +  -0.345893263817*_Complex_I /* ( 1, 3) */,
+   -0.717849135399 +  -0.764326214790*_Complex_I /* ( 1, 4) */,
+   -0.108926303685 +  -0.315297245979*_Complex_I /* ( 1, 5) */,
+    0.357895255089 +  -1.419853448868*_Complex_I /* ( 1, 6) */,
+    0.652509629726 +   0.994532823563*_Complex_I /* ( 2, 0) */,
+   -2.253150939941 +   0.327611356974*_Complex_I /* ( 2, 1) */,
+    1.012208938599 +  -0.677044689655*_Complex_I /* ( 2, 2) */,
+   -0.700399398804 +  -0.330108255148*_Complex_I /* ( 2, 3) */,
+   -0.831380963326 +   1.003911018372*_Complex_I /* ( 2, 4) */,
+   -0.361211270094 +  -0.926369905472*_Complex_I /* ( 2, 5) */,
+    2.307183980942 +  -0.432167291641*_Complex_I /* ( 2, 6) */,
+   -1.175772666931 +   0.248428389430*_Complex_I /* ( 3, 0) */,
+    0.412228941917 +  -2.519471645355*_Complex_I /* ( 3, 1) */,
+   -1.667356371880 +  -1.187105178833*_Complex_I /* ( 3, 2) */,
+    1.243350982666 +  -0.736937880516*_Complex_I /* ( 3, 3) */,
+   -0.694230437279 +  -1.021739125252*_Complex_I /* ( 3, 4) */,
+    0.412434548140 +  -1.840429663658*_Complex_I /* ( 3, 5) */,
+    0.342358648777 +  -1.084336757660*_Complex_I /* ( 3, 6) */,
+    0.033468723297 +   0.131351217628*_Complex_I /* ( 4, 0) */,
+   -0.617851972580 +   1.434038400650*_Complex_I /* ( 4, 1) */,
+   -1.009798288345 +   0.758803665638*_Complex_I /* ( 4, 2) */,
+    1.450994849205 +  -0.595933079720*_Complex_I /* ( 4, 3) */,
+   -0.314995974302 +  -0.811702668667*_Complex_I /* ( 4, 4) */,
+    0.912520587444 +  -2.686280250549*_Complex_I /* ( 4, 5) */,
+    0.204153224826 +  -0.616621196270*_Complex_I /* ( 4, 6) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_chol.c b/src/matrix/tests/data/matrixcf_data_chol.c
new file mode 100644
index 0000000..5617a30
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_chol.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing Cholesky decomposition
+//
+
+#include <complex.h>
+
+// matrixcf_data_chol_L [size: 4 x 4]
+float complex matrixcf_data_chol_L[] = {
+    1.010000000000 +   0.000000000000*_Complex_I /* ( 0, 0) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 0, 1) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 0, 2) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 0, 3) */,
+   -1.420000000000 +   0.250000000000*_Complex_I /* ( 1, 0) */,
+    0.500000000000 +   0.000000000000*_Complex_I /* ( 1, 1) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 1, 2) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 1, 3) */,
+    0.320000000000 +  -1.230000000000*_Complex_I /* ( 2, 0) */,
+    2.010000000000 +   0.780000000000*_Complex_I /* ( 2, 1) */,
+    0.300000000000 +   0.000000000000*_Complex_I /* ( 2, 2) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 2, 3) */,
+   -1.020000000000 +   1.020000000000*_Complex_I /* ( 3, 0) */,
+   -0.320000000000 +  -0.030000000000*_Complex_I /* ( 3, 1) */,
+   -1.650000000000 +   2.010000000000*_Complex_I /* ( 3, 2) */,
+    1.070000000000 +   0.000000000000*_Complex_I /* ( 3, 3) */};
+
+// matrixcf_data_chol_A [size: 4 x 4]
+float complex matrixcf_data_chol_A[] = {
+    1.020100000000 +   0.000000000000*_Complex_I /* ( 0, 0) */,
+   -1.434200000000 +  -0.252500000000*_Complex_I /* ( 0, 1) */,
+    0.323200000000 +   1.242300000000*_Complex_I /* ( 0, 2) */,
+   -1.030200000000 +  -1.030200000000*_Complex_I /* ( 0, 3) */,
+   -1.434200000000 +   0.252500000000*_Complex_I /* ( 1, 0) */,
+    2.328900000000 +   0.000000000000*_Complex_I /* ( 1, 1) */,
+    0.243100000000 +  -2.056600000000*_Complex_I /* ( 1, 2) */,
+    1.543400000000 +   1.208400000000*_Complex_I /* ( 1, 3) */,
+    0.323200000000 +  -1.242300000000*_Complex_I /* ( 2, 0) */,
+    0.243100000000 +   2.056600000000*_Complex_I /* ( 2, 1) */,
+    6.353800000000 +   0.000000000000*_Complex_I /* ( 2, 2) */,
+   -2.742600000000 +   0.135900000000*_Complex_I /* ( 2, 3) */,
+   -1.030200000000 +   1.030200000000*_Complex_I /* ( 3, 0) */,
+    1.543400000000 +  -1.208400000000*_Complex_I /* ( 3, 1) */,
+   -2.742600000000 +  -0.135900000000*_Complex_I /* ( 3, 2) */,
+   10.091600000000 +   0.000000000000*_Complex_I /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_inv.c b/src/matrix/tests/data/matrixcf_data_inv.c
new file mode 100644
index 0000000..5b01826
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_inv.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix inversion
+//
+
+#include <complex.h>
+
+// matrixcf_data_inv_x [size: 5 x 5]
+float complex matrixcf_data_inv_x[] = {
+   -0.911099433899 +  -0.436777323484*_Complex_I /* ( 0, 0) */,
+    0.598295390606 +  -0.283340752125*_Complex_I /* ( 0, 1) */,
+   -0.264758616686 +  -0.421906232834*_Complex_I /* ( 0, 2) */,
+   -0.066837862134 +  -0.934806823730*_Complex_I /* ( 0, 3) */,
+    0.393610686064 +  -1.011345505714*_Complex_I /* ( 0, 4) */,
+   -0.543692529202 +  -1.426580429077*_Complex_I /* ( 1, 0) */,
+   -1.006833553314 +   0.448534607887*_Complex_I /* ( 1, 1) */,
+    0.048818156123 +  -0.540948212147*_Complex_I /* ( 1, 2) */,
+    0.180871278048 +   0.331172674894*_Complex_I /* ( 1, 3) */,
+   -1.100448012352 +   1.841731786728*_Complex_I /* ( 1, 4) */,
+    2.341797351837 +  -1.200128436089*_Complex_I /* ( 2, 0) */,
+    0.239693909883 +   0.206349417567*_Complex_I /* ( 2, 1) */,
+   -0.815828502178 +  -0.349400132895*_Complex_I /* ( 2, 2) */,
+    1.213637232780 +   0.298941820860*_Complex_I /* ( 2, 3) */,
+   -1.522765398026 +   1.651986479759*_Complex_I /* ( 2, 4) */,
+    1.481738448143 +   0.055169839412*_Complex_I /* ( 3, 0) */,
+   -1.241538286209 +  -0.077680915594*_Complex_I /* ( 3, 1) */,
+    1.046607017517 +  -0.843883395195*_Complex_I /* ( 3, 2) */,
+   -1.564810752869 +   1.346152186394*_Complex_I /* ( 3, 3) */,
+    0.786287426949 +  -1.010108113289*_Complex_I /* ( 3, 4) */,
+    1.234361886978 +  -1.305809140205*_Complex_I /* ( 4, 0) */,
+    0.053748749197 +   0.403882414103*_Complex_I /* ( 4, 1) */,
+   -0.081336200237 +  -0.462558329105*_Complex_I /* ( 4, 2) */,
+   -1.370563983917 +  -0.284755766392*_Complex_I /* ( 4, 3) */,
+    0.200873896480 +  -0.036809749901*_Complex_I /* ( 4, 4) */};
+
+// matrixcf_data_inv_y [size: 5 x 5]
+float complex matrixcf_data_inv_y[] = {
+   -0.127852678827 +  -0.009178191835*_Complex_I /* ( 0, 0) */,
+   -0.199905444866 +   0.033789259175*_Complex_I /* ( 0, 1) */,
+    0.168465876479 +  -0.059607902071*_Complex_I /* ( 0, 2) */,
+    0.087700609092 +  -0.030597427908*_Complex_I /* ( 0, 3) */,
+    0.084793376582 +   0.131223765916*_Complex_I /* ( 0, 4) */,
+    0.209779356201 +   0.642123753363*_Complex_I /* ( 1, 0) */,
+   -0.045651767577 +  -0.019599459364*_Complex_I /* ( 1, 1) */,
+    0.137284052424 +   0.504637287094*_Complex_I /* ( 1, 2) */,
+   -0.333643348460 +   0.455368743084*_Complex_I /* ( 1, 3) */,
+    0.244939020151 +  -0.609710193351*_Complex_I /* ( 1, 4) */,
+   -0.114524820581 +   0.963012925652*_Complex_I /* ( 2, 0) */,
+    0.303499486096 +   0.348121666797*_Complex_I /* ( 2, 1) */,
+   -0.327372880299 +   0.397314645420*_Complex_I /* ( 2, 2) */,
+   -0.231096370464 +   0.372958732742*_Complex_I /* ( 2, 3) */,
+    0.089363987094 +  -0.240520272187*_Complex_I /* ( 2, 4) */,
+    0.072169240922 +   0.159456098576*_Complex_I /* ( 3, 0) */,
+   -0.064066539188 +   0.069570707500*_Complex_I /* ( 3, 1) */,
+    0.090335627717 +  -0.121329478735*_Complex_I /* ( 3, 2) */,
+    0.053196220990 +  -0.158230982223*_Complex_I /* ( 3, 3) */,
+   -0.413653285108 +   0.167815066469*_Complex_I /* ( 3, 4) */,
+    0.089194647874 +  -0.035492413461*_Complex_I /* ( 4, 0) */,
+   -0.192303472410 +  -0.221655891788*_Complex_I /* ( 4, 1) */,
+    0.111730542618 +  -0.221903756183*_Complex_I /* ( 4, 2) */,
+    0.303835472120 +  -0.022543572811*_Complex_I /* ( 4, 3) */,
+   -0.167008031325 +   0.051911194273*_Complex_I /* ( 4, 4) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_linsolve.c b/src/matrix/tests/data/matrixcf_data_linsolve.c
new file mode 100644
index 0000000..b8fa723
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_linsolve.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing linear solver
+//
+
+#include <complex.h>
+
+// matrixcf_data_linsolve_A [size: 5 x 5]
+float complex matrixcf_data_linsolve_A[] = {
+   -0.482884645462 +  -0.221723198891*_Complex_I /* ( 0, 0) */,
+   -0.387645065784 +   0.086682170630*_Complex_I /* ( 0, 1) */,
+    1.580931067467 +   0.883717715740*_Complex_I /* ( 0, 2) */,
+    1.570333838463 +   1.783135294914*_Complex_I /* ( 0, 3) */,
+   -1.081483244896 +  -0.691094517708*_Complex_I /* ( 0, 4) */,
+    0.248138338327 +  -0.250954031944*_Complex_I /* ( 1, 0) */,
+    0.790891706944 +  -0.313775628805*_Complex_I /* ( 1, 1) */,
+   -0.146090522408 +  -1.320674061775*_Complex_I /* ( 1, 2) */,
+    0.672296106815 +   1.346951484680*_Complex_I /* ( 1, 3) */,
+   -0.352442741394 +   0.056975554675*_Complex_I /* ( 1, 4) */,
+    0.707973957062 +  -0.069402769208*_Complex_I /* ( 2, 0) */,
+   -0.894841134548 +  -1.854133605957*_Complex_I /* ( 2, 1) */,
+    0.397095054388 +  -0.924011290073*_Complex_I /* ( 2, 2) */,
+    0.054669041187 +   0.017023870721*_Complex_I /* ( 2, 3) */,
+    0.515784740448 +  -0.455956429243*_Complex_I /* ( 2, 4) */,
+    0.570774257183 +   0.538610219955*_Complex_I /* ( 3, 0) */,
+   -0.389531791210 +   0.200702637434*_Complex_I /* ( 3, 1) */,
+    0.159817531705 +   1.283960223198*_Complex_I /* ( 3, 2) */,
+    1.571215510368 +   0.574963092804*_Complex_I /* ( 3, 3) */,
+   -2.452192783356 +  -0.583715677261*_Complex_I /* ( 3, 4) */,
+   -0.603657603264 +   0.617622077465*_Complex_I /* ( 4, 0) */,
+    0.935181498528 +   0.949800848961*_Complex_I /* ( 4, 1) */,
+    0.043205004185 +   1.351160168648*_Complex_I /* ( 4, 2) */,
+    0.674502849579 +   0.340750336647*_Complex_I /* ( 4, 3) */,
+   -0.241452947259 +   1.540177464485*_Complex_I /* ( 4, 4) */};
+
+// matrixcf_data_linsolve_x [size: 5 x 1]
+float complex matrixcf_data_linsolve_x[] = {
+   -0.686784207821 +   0.516409814358*_Complex_I /* ( 0, 0) */,
+    0.725918948650 +  -0.725804686546*_Complex_I /* ( 1, 0) */,
+    0.048043362796 +   1.415739893913*_Complex_I /* ( 2, 0) */,
+    1.184294700623 +  -1.108955144882*_Complex_I /* ( 3, 0) */,
+    1.000079274178 +   0.117630988359*_Complex_I /* ( 4, 0) */};
+
+// matrixcf_data_linsolve_b [size: 5 x 1]
+float complex matrixcf_data_linsolve_b[] = {
+    1.889372086452 +   2.079795053851*_Complex_I /* ( 0, 0) */,
+    4.099006087145 +   0.093571115573*_Complex_I /* ( 1, 0) */,
+   -0.465385431770 +  -0.201195243205*_Complex_I /* ( 2, 0) */,
+   -2.502649126311 +  -1.292489487343*_Complex_I /* ( 3, 0) */,
+    0.307098947642 +   0.568345470088*_Complex_I /* ( 4, 0) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_ludecomp.c b/src/matrix/tests/data/matrixcf_data_ludecomp.c
new file mode 100644
index 0000000..eec9915
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_ludecomp.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing L/U decomposition
+//
+
+#include <complex.h>
+
+// matrixcf_data_ludecomp_A [size: 8 x 8]
+float complex matrixcf_data_ludecomp_A[] = {
+    0.455808967352 +   0.239869371057*_Complex_I /* ( 0, 0) */,
+    1.076113820076 +   0.303303003311*_Complex_I /* ( 0, 1) */,
+   -1.174549579620 +  -1.593330740929*_Complex_I /* ( 0, 2) */,
+    1.428434848785 +  -2.108702898026*_Complex_I /* ( 0, 3) */,
+    1.944794058800 +   1.039716124535*_Complex_I /* ( 0, 4) */,
+   -0.003220892511 +  -1.070197224617*_Complex_I /* ( 0, 5) */,
+    2.282850980759 +   0.567153334618*_Complex_I /* ( 0, 6) */,
+    0.677789986134 +  -0.110934779048*_Complex_I /* ( 0, 7) */,
+   -0.541479706764 +   0.508462309837*_Complex_I /* ( 1, 0) */,
+    0.659551382065 +   0.341979026794*_Complex_I /* ( 1, 1) */,
+    0.422295093536 +  -0.748002707958*_Complex_I /* ( 1, 2) */,
+    0.991572380066 +  -1.739566326141*_Complex_I /* ( 1, 3) */,
+   -0.973251938820 +  -0.314995020628*_Complex_I /* ( 1, 4) */,
+   -0.348222613335 +   1.216362476349*_Complex_I /* ( 1, 5) */,
+   -0.444941103458 +  -0.435953140259*_Complex_I /* ( 1, 6) */,
+    0.664277911186 +   0.398205667734*_Complex_I /* ( 1, 7) */,
+    1.578197240829 +  -0.245545297861*_Complex_I /* ( 2, 0) */,
+   -0.734657287598 +  -0.314642846584*_Complex_I /* ( 2, 1) */,
+   -0.185400843620 +   0.411517560482*_Complex_I /* ( 2, 2) */,
+   -0.141458645463 +   2.540255069733*_Complex_I /* ( 2, 3) */,
+   -1.887707233429 +   1.261052608490*_Complex_I /* ( 2, 4) */,
+    1.356706976891 +  -0.073478087783*_Complex_I /* ( 2, 5) */,
+    0.382849365473 +   1.176013708115*_Complex_I /* ( 2, 6) */,
+    0.088731415570 +  -0.000313452416*_Complex_I /* ( 2, 7) */,
+    0.694614350796 +   0.107012517750*_Complex_I /* ( 3, 0) */,
+   -0.541421890259 +  -1.525843501091*_Complex_I /* ( 3, 1) */,
+    1.210077285767 +  -0.249905958772*_Complex_I /* ( 3, 2) */,
+    0.051122765988 +   0.576834678650*_Complex_I /* ( 3, 3) */,
+    2.360952138901 +  -0.439353585243*_Complex_I /* ( 3, 4) */,
+    0.927220702171 +   0.293185442686*_Complex_I /* ( 3, 5) */,
+   -0.235832184553 +  -0.484229415655*_Complex_I /* ( 3, 6) */,
+   -1.589996099472 +   0.180045768619*_Complex_I /* ( 3, 7) */,
+    1.345695137978 +  -0.080361045897*_Complex_I /* ( 4, 0) */,
+   -0.245824366808 +  -1.841626405716*_Complex_I /* ( 4, 1) */,
+    0.978698849678 +   1.369340777397*_Complex_I /* ( 4, 2) */,
+   -1.106017351151 +  -1.615537166595*_Complex_I /* ( 4, 3) */,
+    0.627505123615 +   1.024900913239*_Complex_I /* ( 4, 4) */,
+    1.808397769928 +  -0.614134788513*_Complex_I /* ( 4, 5) */,
+   -0.322292149067 +  -0.765307128429*_Complex_I /* ( 4, 6) */,
+   -0.674273192883 +   0.044275555760*_Complex_I /* ( 4, 7) */,
+   -2.861634492874 +   2.582857608795*_Complex_I /* ( 5, 0) */,
+   -1.920535564423 +  -0.081001155078*_Complex_I /* ( 5, 1) */,
+   -1.339942932129 +  -0.246527969837*_Complex_I /* ( 5, 2) */,
+    0.540911912918 +   0.283990591764*_Complex_I /* ( 5, 3) */,
+   -0.800716042519 +   0.764756917953*_Complex_I /* ( 5, 4) */,
+    1.206449866295 +   0.518103539944*_Complex_I /* ( 5, 5) */,
+   -0.377558648586 +   0.065486297011*_Complex_I /* ( 5, 6) */,
+   -1.090067625046 +   0.741791069508*_Complex_I /* ( 5, 7) */,
+   -1.424072742462 +   0.091005645692*_Complex_I /* ( 6, 0) */,
+    0.340615779161 +   1.995890378952*_Complex_I /* ( 6, 1) */,
+   -0.395366579294 +   0.685165762901*_Complex_I /* ( 6, 2) */,
+    0.367168039083 +  -1.265154719353*_Complex_I /* ( 6, 3) */,
+    0.716018438339 +   1.003421306610*_Complex_I /* ( 6, 4) */,
+   -0.648339152336 +   2.441966056824*_Complex_I /* ( 6, 5) */,
+    0.788251757622 +   1.254729628563*_Complex_I /* ( 6, 6) */,
+   -0.776828289032 +  -0.615517139435*_Complex_I /* ( 6, 7) */,
+    1.112848401070 +  -0.297139286995*_Complex_I /* ( 7, 0) */,
+    0.366721868515 +   0.650049626827*_Complex_I /* ( 7, 1) */,
+    0.072020366788 +  -0.518339037895*_Complex_I /* ( 7, 2) */,
+    1.033115744591 +  -0.196805760264*_Complex_I /* ( 7, 3) */,
+   -1.083071947098 +  -1.565491795540*_Complex_I /* ( 7, 4) */,
+    1.409144878387 +   0.992799341679*_Complex_I /* ( 7, 5) */,
+    0.387732833624 +  -1.445696353912*_Complex_I /* ( 7, 6) */,
+   -0.528750956059 +  -1.205648779869*_Complex_I /* ( 7, 7) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_mul.c b/src/matrix/tests/data/matrixcf_data_mul.c
new file mode 100644
index 0000000..9ecc45e
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_mul.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix multiplication
+//
+
+#include <complex.h>
+
+// matrixcf_data_mul_x [size: 5 x 4]
+float complex matrixcf_data_mul_x[] = {
+    1.131277322769 +  -2.908640623093*_Complex_I /* ( 0, 0) */,
+    0.847201466560 +  -1.637244105339*_Complex_I /* ( 0, 1) */,
+   -2.173580169678 +   0.096817605197*_Complex_I /* ( 0, 2) */,
+    0.792498826981 +  -0.358158409595*_Complex_I /* ( 0, 3) */,
+   -0.243082717061 +   0.824095964432*_Complex_I /* ( 1, 0) */,
+   -1.889652967453 +   0.283876717091*_Complex_I /* ( 1, 1) */,
+    0.044418141246 +  -0.882465064526*_Complex_I /* ( 1, 2) */,
+    0.515216410160 +  -0.366532146931*_Complex_I /* ( 1, 3) */,
+    0.579283773899 +   1.173513293266*_Complex_I /* ( 2, 0) */,
+    0.059265002608 +  -0.497733235359*_Complex_I /* ( 2, 1) */,
+   -0.877321839333 +   0.404732406139*_Complex_I /* ( 2, 2) */,
+   -0.794282734394 +   0.456011295319*_Complex_I /* ( 2, 3) */,
+   -1.174414634705 +  -1.358565688133*_Complex_I /* ( 3, 0) */,
+   -0.418152034283 +   1.380919337273*_Complex_I /* ( 3, 1) */,
+   -0.747197151184 +   1.584241986275*_Complex_I /* ( 3, 2) */,
+   -0.522293865681 +  -0.573823392391*_Complex_I /* ( 3, 3) */,
+   -1.866284489632 +  -0.199214607477*_Complex_I /* ( 4, 0) */,
+   -0.453905433416 +   0.452787637711*_Complex_I /* ( 4, 1) */,
+    1.989426016808 +  -1.771166682243*_Complex_I /* ( 4, 2) */,
+    2.234328985214 +   0.855401337147*_Complex_I /* ( 4, 3) */};
+
+// matrixcf_data_mul_y [size: 4 x 3]
+float complex matrixcf_data_mul_y[] = {
+    0.122429788113 +  -1.041572093964*_Complex_I /* ( 0, 0) */,
+   -1.123313307762 +  -1.396123170853*_Complex_I /* ( 0, 1) */,
+   -0.318034142256 +  -0.537796914577*_Complex_I /* ( 0, 2) */,
+    0.096901215613 +  -0.035752061754*_Complex_I /* ( 1, 0) */,
+    0.423960685730 +  -0.379842221737*_Complex_I /* ( 1, 1) */,
+   -0.184147700667 +   0.022100308910*_Complex_I /* ( 1, 2) */,
+    0.189968794584 +   0.919595599174*_Complex_I /* ( 2, 0) */,
+    0.621766507626 +  -0.634516119957*_Complex_I /* ( 2, 1) */,
+    0.605251312256 +   1.410223841667*_Complex_I /* ( 2, 2) */,
+    0.427330523729 +   0.042397715151*_Complex_I /* ( 3, 0) */,
+    0.204851210117 +   0.611065924168*_Complex_I /* ( 3, 1) */,
+    0.562124013901 +   0.047597970814*_Complex_I /* ( 3, 2) */};
+
+// matrixcf_data_mul_z [size: 5 x 3]
+float complex matrixcf_data_mul_z[] = {
+   -3.015598273252 +  -3.823225604286*_Complex_I /* ( 0, 0) */,
+   -6.503138041472 +   2.522251659946*_Complex_I /* ( 0, 1) */,
+   -3.033435877267 +  -2.533375977709*_Complex_I /* ( 0, 2) */,
+    1.711291176504 +   0.187568584413*_Complex_I /* ( 1, 0) */,
+    0.527484730969 +  -0.085346610822*_Complex_I /* ( 1, 1) */,
+    2.440625470928 +  -0.878385559540*_Complex_I /* ( 1, 2) */,
+    0.383559143593 +  -1.078745633782*_Complex_I /* ( 2, 0) */,
+    0.093675017974 +  -1.944126015771*_Complex_I /* ( 2, 1) */,
+   -1.122987739839 +  -1.365514815630*_Complex_I /* ( 2, 2) */,
+   -3.347645581625 +   0.552152171890*_Complex_I /* ( 3, 0) */,
+    0.554058303745 +   4.932442551750*_Complex_I /* ( 3, 1) */,
+   -3.263304464031 +   0.357861697730*_Complex_I /* ( 3, 2) */,
+    2.461434774758 +   3.932854324787*_Complex_I /* ( 4, 0) */,
+    1.845966920717 +   2.370697350446*_Complex_I /* ( 4, 1) */,
+    5.477082880684 +   3.294354034834*_Complex_I /* ( 4, 2) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_qrdecomp.c b/src/matrix/tests/data/matrixcf_data_qrdecomp.c
new file mode 100644
index 0000000..f7c9874
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_qrdecomp.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing Q/R decomposition
+//
+
+#include <complex.h>
+
+// matrixcf_data_qrdecomp_A [size: 4 x 4]
+float complex matrixcf_data_qrdecomp_A[] = {
+    2.114020000000 +  -0.576040000000*_Complex_I /* ( 0, 0) */,
+    0.417500000000 +   1.008330000000*_Complex_I /* ( 0, 1) */,
+   -0.962640000000 +  -3.621960000000*_Complex_I /* ( 0, 2) */,
+   -0.206790000000 +  -1.026680000000*_Complex_I /* ( 0, 3) */,
+    0.008540000000 +   1.616260000000*_Complex_I /* ( 1, 0) */,
+    0.846950000000 +  -0.327360000000*_Complex_I /* ( 1, 1) */,
+   -1.018620000000 +  -1.107860000000*_Complex_I /* ( 1, 2) */,
+   -1.788770000000 +   1.844560000000*_Complex_I /* ( 1, 3) */,
+   -2.979010000000 +  -1.303840000000*_Complex_I /* ( 2, 0) */,
+    0.522890000000 +   1.891100000000*_Complex_I /* ( 2, 1) */,
+    1.325760000000 +  -0.367370000000*_Complex_I /* ( 2, 2) */,
+    0.047170000000 +   0.206280000000*_Complex_I /* ( 2, 3) */,
+    0.289700000000 +   0.642470000000*_Complex_I /* ( 3, 0) */,
+   -0.559160000000 +   0.683020000000*_Complex_I /* ( 3, 1) */,
+    1.406150000000 +   0.623980000000*_Complex_I /* ( 3, 2) */,
+   -0.127670000000 +  -0.539970000000*_Complex_I /* ( 3, 3) */};
+
+// matrixcf_data_qrdecomp_Q [size: 4 x 4]
+float complex matrixcf_data_qrdecomp_Q[] = {
+    0.491706158979 +  -0.133982845866*_Complex_I /* ( 0, 0) */,
+    0.429660711419 +   0.559833033911*_Complex_I /* ( 0, 1) */,
+   -0.309333641162 +  -0.278321211351*_Complex_I /* ( 0, 2) */,
+    0.215207397547 +  -0.150957196713*_Complex_I /* ( 0, 3) */,
+    0.001986343837 +   0.375930689639*_Complex_I /* ( 1, 0) */,
+    0.242768204454 +   0.009257007128*_Complex_I /* ( 1, 1) */,
+   -0.422306122793 +  -0.032511505165*_Complex_I /* ( 1, 2) */,
+   -0.503566009661 +   0.605534385769*_Complex_I /* ( 1, 3) */,
+   -0.692896739226 +  -0.303263998601*_Complex_I /* ( 2, 0) */,
+    0.054111560749 +   0.468071856237*_Complex_I /* ( 2, 1) */,
+   -0.082147488614 +   0.069653107384*_Complex_I /* ( 2, 2) */,
+    0.279669645547 +   0.340721083028*_Complex_I /* ( 2, 3) */,
+    0.067382179098 +   0.149433995875*_Complex_I /* ( 3, 0) */,
+   -0.270466351267 +   0.384428384950*_Complex_I /* ( 3, 1) */,
+   -0.285071449427 +   0.744704670261*_Complex_I /* ( 3, 2) */,
+   -0.173581995183 +  -0.293616086507*_Complex_I /* ( 3, 3) */};
+
+// matrixcf_data_qrdecomp_R [size: 4 x 4]
+float complex matrixcf_data_qrdecomp_R[] = {
+    4.299356356224 +   0.000000000000*_Complex_I /* ( 0, 0) */,
+   -0.922616273377 +  -0.789487259898*_Complex_I /* ( 0, 1) */,
+   -1.025768821795 +  -1.040664085433*_Complex_I /* ( 0, 2) */,
+    0.541217397816 +  -0.002345615451*_Complex_I /* ( 0, 3) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 1, 0) */,
+    2.273733268802 +   0.000000000000*_Complex_I /* ( 1, 1) */,
+   -2.939502710322 +  -2.626579524510*_Complex_I /* ( 1, 2) */,
+   -1.154743344912 +   0.323209860623*_Complex_I /* ( 1, 3) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 2, 0) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 2, 1) */,
+    1.701364174878 +   0.000000000000*_Complex_I /* ( 2, 2) */,
+    0.689923063328 +  -0.348316412767*_Complex_I /* ( 2, 3) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 3, 0) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 3, 1) */,
+    0.000000000000 +   0.000000000000*_Complex_I /* ( 3, 2) */,
+    2.392371328442 +   0.000000000000*_Complex_I /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/data/matrixcf_data_transmul.c b/src/matrix/tests/data/matrixcf_data_transmul.c
new file mode 100644
index 0000000..95fc080
--- /dev/null
+++ b/src/matrix/tests/data/matrixcf_data_transmul.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing multiply/transpose
+//
+
+#include <complex.h>
+
+// matrixcf_data_transmul_x [size: 5 x 4]
+float complex matrixcf_data_transmul_x[] = {
+    0.897770464420 +  -1.137341141701*_Complex_I /* ( 0, 0) */,
+    0.816529691219 +  -0.575469911098*_Complex_I /* ( 0, 1) */,
+    2.407611131668 +   0.901603281498*_Complex_I /* ( 0, 2) */,
+   -1.024818181992 +  -1.785745739937*_Complex_I /* ( 0, 3) */,
+    1.494256496429 +  -0.826167643070*_Complex_I /* ( 1, 0) */,
+   -0.908512234688 +   0.119766108692*_Complex_I /* ( 1, 1) */,
+   -0.215938329697 +  -2.537411689758*_Complex_I /* ( 1, 2) */,
+   -1.348789930344 +  -0.935531198978*_Complex_I /* ( 1, 3) */,
+   -0.398543357849 +   0.101190350950*_Complex_I /* ( 2, 0) */,
+   -0.083604514599 +   1.493514776230*_Complex_I /* ( 2, 1) */,
+    0.477280050516 +  -0.074863225222*_Complex_I /* ( 2, 2) */,
+   -0.283995777369 +   0.336168438196*_Complex_I /* ( 2, 3) */,
+   -0.030109925196 +  -1.602186083794*_Complex_I /* ( 3, 0) */,
+    2.220442056656 +  -0.208865001798*_Complex_I /* ( 3, 1) */,
+    1.889614224434 +  -0.896111547947*_Complex_I /* ( 3, 2) */,
+   -0.317830920219 +   0.215485602617*_Complex_I /* ( 3, 3) */,
+   -0.945744097233 +  -0.822628259659*_Complex_I /* ( 4, 0) */,
+   -0.238264903426 +   0.054408840835*_Complex_I /* ( 4, 1) */,
+    0.532425582409 +   0.438958346844*_Complex_I /* ( 4, 2) */,
+    0.089408367872 +  -1.864659070969*_Complex_I /* ( 4, 3) */};
+
+// matrixcf_data_transmul_xxT [size: 5 x 5]
+float complex matrixcf_data_transmul_xxT[] = {
+   13.946043026996 +   0.000000000000*_Complex_I /* ( 0, 0) */,
+    1.715635857916 +   6.831514803023*_Complex_I /* ( 0, 1) */,
+   -0.628286275869 +   0.653261344190*_Complex_I /* ( 0, 2) */,
+    7.410888277177 +   5.014956782605*_Complex_I /* ( 0, 3) */,
+    4.776506544166 +  -0.740548606324*_Complex_I /* ( 0, 4) */,
+    1.715635857916 +  -6.831514803023*_Complex_I /* ( 1, 0) */,
+   12.934634198959 +   0.000000000000*_Complex_I /* ( 1, 1) */,
+   -0.268847669275 +   1.016808442009*_Complex_I /* ( 1, 2) */,
+    1.329226044140 +  -1.905118394989*_Complex_I /* ( 1, 3) */,
+   -0.115507997317 +  -1.823416830395*_Complex_I /* ( 1, 4) */,
+   -0.628286275869 +  -0.653261344190*_Complex_I /* ( 2, 0) */,
+   -0.268847669275 +  -1.016808442009*_Complex_I /* ( 2, 1) */,
+    2.833715966403 +   0.000000000000*_Complex_I /* ( 2, 2) */,
+    0.483955462699 +   2.897799335427*_Complex_I /* ( 2, 3) */,
+   -0.036118440085 +  -1.523720724413*_Complex_I /* ( 2, 4) */,
+    7.410888277177 +  -5.014956782605*_Complex_I /* ( 3, 0) */,
+    1.329226044140 +   1.905118394989*_Complex_I /* ( 3, 1) */,
+    0.483955462699 +  -2.897799335427*_Complex_I /* ( 3, 2) */,
+   12.063002732969 +   0.000000000000*_Complex_I /* ( 3, 3) */,
+    0.988561701830 +  -0.460512464248*_Complex_I /* ( 3, 4) */,
+    4.776506544166 +   0.740548606324*_Complex_I /* ( 4, 0) */,
+   -0.115507997317 +   1.823416830395*_Complex_I /* ( 4, 1) */,
+   -0.036118440085 +   1.523720724413*_Complex_I /* ( 4, 2) */,
+    0.988561701830 +   0.460512464248*_Complex_I /* ( 4, 3) */,
+    5.591988375464 +   0.000000000000*_Complex_I /* ( 4, 4) */};
+
+// matrixcf_data_transmul_xxH [size: 5 x 5]
+float complex matrixcf_data_transmul_xxH[] = {
+    2.693069394806 +   5.019630491560*_Complex_I /* ( 0, 0) */,
+    1.208446246635 +  -4.757020341403*_Complex_I /* ( 0, 1) */,
+    2.656451825557 +   2.224444954914*_Complex_I /* ( 0, 2) */,
+    5.911512147844 +  -2.959566260460*_Complex_I /* ( 0, 3) */,
+   -4.483236639053 +   3.806796594938*_Complex_I /* ( 0, 4) */,
+    1.208446246635 +  -4.757020341403*_Complex_I /* ( 1, 0) */,
+   -3.086513006704 +   0.932888319797*_Complex_I /* ( 1, 1) */,
+   -0.210317709361 +  -2.269045302266*_Complex_I /* ( 1, 2) */,
+   -5.412513485879 +  -6.508039464439*_Complex_I /* ( 1, 3) */,
+   -2.749055759920 +   0.459776624619*_Complex_I /* ( 1, 4) */,
+    2.656451825557 +   2.224444954914*_Complex_I /* ( 2, 0) */,
+   -0.210317709361 +  -2.269045302266*_Complex_I /* ( 2, 1) */,
+   -1.885163224141 +  -0.592788922020*_Complex_I /* ( 2, 2) */,
+    1.153042421508 +   3.232018360881*_Complex_I /* ( 2, 3) */,
+    1.287247559495 +   0.601010412549*_Complex_I /* ( 2, 4) */,
+    5.911512147844 +  -2.959566260460*_Complex_I /* ( 3, 0) */,
+   -5.412513485879 +  -6.508039464439*_Complex_I /* ( 3, 1) */,
+    1.153042421508 +   3.232018360881*_Complex_I /* ( 3, 2) */,
+    5.142853158215 +  -4.354648092153*_Complex_I /* ( 3, 3) */,
+   -0.034391537899 +   2.674865932467*_Complex_I /* ( 3, 4) */,
+   -4.483236639053 +   3.806796594938*_Complex_I /* ( 4, 0) */,
+   -2.749055759920 +   0.459776624619*_Complex_I /* ( 4, 1) */,
+    1.287247559495 +   0.601010412549*_Complex_I /* ( 4, 2) */,
+   -0.034391537899 +   2.674865932467*_Complex_I /* ( 4, 3) */,
+   -3.106642538055 +   1.664057265760*_Complex_I /* ( 4, 4) */};
+
+// matrixcf_data_transmul_xTx [size: 4 x 4]
+float complex matrixcf_data_transmul_xTx[] = {
+    9.323024431917 +   0.000000000000*_Complex_I /* ( 0, 0) */,
+    0.563876592623 +   2.570030362211*_Complex_I /* ( 0, 1) */,
+    3.226123027525 +   2.636644463529*_Complex_I /* ( 0, 2) */,
+    1.129305368076 +  -4.064920271606*_Complex_I /* ( 0, 3) */,
+    0.563876592623 +  -2.570030362211*_Complex_I /* ( 1, 0) */,
+    9.108918860377 +   0.000000000000*_Complex_I /* ( 1, 1) */,
+    5.467585123601 +   2.017612849734*_Complex_I /* ( 1, 2) */,
+    0.956522192172 +   0.211168853425*_Complex_I /* ( 1, 3) */,
+    3.226123027525 +  -2.636644463529*_Complex_I /* ( 2, 0) */,
+    5.467585123601 +  -2.017612849734*_Complex_I /* ( 2, 1) */,
+   18.177787287820 +   0.000000000000*_Complex_I /* ( 2, 2) */,
+   -3.137608134941 +  -7.366300567700*_Complex_I /* ( 2, 3) */,
+    1.129305368076 +   4.064920271606*_Complex_I /* ( 3, 0) */,
+    0.956522192172 +  -0.211168853425*_Complex_I /* ( 3, 1) */,
+   -3.137608134941 +   7.366300567700*_Complex_I /* ( 3, 2) */,
+   10.759653720678 +   0.000000000000*_Complex_I /* ( 3, 3) */};
+
+// matrixcf_data_transmul_xHx [size: 4 x 4]
+float complex matrixcf_data_transmul_xHx[] = {
+   -1.137085237839 +  -2.939337742229*_Complex_I /* ( 0, 0) */,
+   -1.429264118470 +  -4.526184217761*_Complex_I /* ( 0, 1) */,
+   -1.049795781222 +  -9.317515018345*_Complex_I /* ( 0, 2) */,
+   -6.923890470327 +   1.308742276041*_Complex_I /* ( 0, 3) */,
+   -1.429264118470 +  -4.526184217761*_Complex_I /* ( 1, 0) */,
+    3.863557186128 +  -2.360596346275*_Complex_I /* ( 1, 1) */,
+    6.914587648894 +  -0.110888488534*_Complex_I /* ( 1, 2) */,
+   -1.585896210450 +   0.361787209777*_Complex_I /* ( 1, 3) */,
+   -1.049795781222 +  -9.317515018345*_Complex_I /* ( 2, 0) */,
+    6.914587648894 +  -0.110888488534*_Complex_I /* ( 2, 1) */,
+    1.672484488523 +   2.446622681596*_Complex_I /* ( 2, 2) */,
+   -2.591648390404 +  -1.678750072577*_Complex_I /* ( 2, 3) */,
+   -6.923890470327 +   1.308742276041*_Complex_I /* ( 3, 0) */,
+   -1.585896210450 +   0.361787209777*_Complex_I /* ( 3, 1) */,
+   -2.591648390404 +  -1.678750072577*_Complex_I /* ( 3, 2) */,
+   -4.641352652692 +   5.522450469852*_Complex_I /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_add.c b/src/matrix/tests/data/matrixf_data_add.c
new file mode 100644
index 0000000..93cff0b
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_add.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix addition
+//
+
+#include <complex.h>
+
+// matrixf_data_add_x [size: 5 x 4]
+float matrixf_data_add_x[] = {
+   -2.153858900070 /* ( 0, 0) */,
+    0.374406367540 /* ( 0, 1) */,
+   -0.356648415327 /* ( 0, 2) */,
+    0.673922061920 /* ( 0, 3) */,
+   -1.686753273010 /* ( 1, 0) */,
+   -0.382442235947 /* ( 1, 1) */,
+    0.287308961153 /* ( 1, 2) */,
+   -0.479356884956 /* ( 1, 3) */,
+   -0.336519986391 /* ( 2, 0) */,
+    0.173820436001 /* ( 2, 1) */,
+   -1.243059277534 /* ( 2, 2) */,
+   -1.508571028709 /* ( 2, 3) */,
+    0.903724849224 /* ( 3, 0) */,
+    0.490690946579 /* ( 3, 1) */,
+    0.242906242609 /* ( 3, 2) */,
+    0.192125678062 /* ( 3, 3) */,
+    0.053418140858 /* ( 4, 0) */,
+    0.389735013247 /* ( 4, 1) */,
+    0.781731247902 /* ( 4, 2) */,
+   -1.207890510559 /* ( 4, 3) */};
+
+// matrixf_data_add_y [size: 5 x 4]
+float matrixf_data_add_y[] = {
+   -1.569433808327 /* ( 0, 0) */,
+    0.182892739773 /* ( 0, 1) */,
+    2.420134067535 /* ( 0, 2) */,
+   -0.114732131362 /* ( 0, 3) */,
+   -1.274159908295 /* ( 1, 0) */,
+   -1.230959534645 /* ( 1, 1) */,
+    0.574799835682 /* ( 1, 2) */,
+   -0.756966531277 /* ( 1, 3) */,
+    1.426752924919 /* ( 2, 0) */,
+    1.018160581589 /* ( 2, 1) */,
+   -0.099268406630 /* ( 2, 2) */,
+    0.683501064777 /* ( 2, 3) */,
+    0.145665585995 /* ( 3, 0) */,
+    0.337123543024 /* ( 3, 1) */,
+    0.754367768764 /* ( 3, 2) */,
+    0.908503055573 /* ( 3, 3) */,
+   -1.320610523224 /* ( 4, 0) */,
+   -1.090982913971 /* ( 4, 1) */,
+    0.494600951672 /* ( 4, 2) */,
+    0.713486075401 /* ( 4, 3) */};
+
+// matrixf_data_add_z [size: 5 x 4]
+float matrixf_data_add_z[] = {
+   -3.723292708397 /* ( 0, 0) */,
+    0.557299107313 /* ( 0, 1) */,
+    2.063485652208 /* ( 0, 2) */,
+    0.559189930558 /* ( 0, 3) */,
+   -2.960913181305 /* ( 1, 0) */,
+   -1.613401770592 /* ( 1, 1) */,
+    0.862108796835 /* ( 1, 2) */,
+   -1.236323416233 /* ( 1, 3) */,
+    1.090232938528 /* ( 2, 0) */,
+    1.191981017590 /* ( 2, 1) */,
+   -1.342327684164 /* ( 2, 2) */,
+   -0.825069963932 /* ( 2, 3) */,
+    1.049390435219 /* ( 3, 0) */,
+    0.827814489603 /* ( 3, 1) */,
+    0.997274011374 /* ( 3, 2) */,
+    1.100628733635 /* ( 3, 3) */,
+   -1.267192382365 /* ( 4, 0) */,
+   -0.701247900724 /* ( 4, 1) */,
+    1.276332199574 /* ( 4, 2) */,
+   -0.494404435158 /* ( 4, 3) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_aug.c b/src/matrix/tests/data/matrixf_data_aug.c
new file mode 100644
index 0000000..482d620
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_aug.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix augmentation
+//
+
+#include <complex.h>
+
+// matrixf_data_aug_x [size: 5 x 4]
+float matrixf_data_aug_x[] = {
+   -0.747572302818 /* ( 0, 0) */,
+    1.023007512093 /* ( 0, 1) */,
+   -0.806419134140 /* ( 0, 2) */,
+    1.476346969604 /* ( 0, 3) */,
+   -0.456311076880 /* ( 1, 0) */,
+    1.049571633339 /* ( 1, 1) */,
+    0.041211493313 /* ( 1, 2) */,
+    0.870350718498 /* ( 1, 3) */,
+   -0.585918903351 /* ( 2, 0) */,
+   -2.498867988586 /* ( 2, 1) */,
+    1.247432827950 /* ( 2, 2) */,
+   -1.840264678001 /* ( 2, 3) */,
+    0.618996977806 /* ( 3, 0) */,
+   -1.083691835403 /* ( 3, 1) */,
+   -1.827050209045 /* ( 3, 2) */,
+   -0.579039454460 /* ( 3, 3) */,
+    1.507880568504 /* ( 4, 0) */,
+    1.633087396622 /* ( 4, 1) */,
+   -0.439950227737 /* ( 4, 2) */,
+   -0.058893665671 /* ( 4, 3) */};
+
+// matrixf_data_aug_y [size: 5 x 3]
+float matrixf_data_aug_y[] = {
+    0.376702636480 /* ( 0, 0) */,
+    0.790158689022 /* ( 0, 1) */,
+    2.111151933670 /* ( 0, 2) */,
+   -0.690664231777 /* ( 1, 0) */,
+   -0.598035037518 /* ( 1, 1) */,
+   -0.137144193053 /* ( 1, 2) */,
+    1.078616261482 /* ( 2, 0) */,
+    0.907722294331 /* ( 2, 1) */,
+   -0.432205766439 /* ( 2, 2) */,
+   -1.615019798279 /* ( 3, 0) */,
+    0.122782632709 /* ( 3, 1) */,
+    1.174023866653 /* ( 3, 2) */,
+    0.233828529716 /* ( 4, 0) */,
+    0.032883912325 /* ( 4, 1) */,
+   -0.896481394768 /* ( 4, 2) */};
+
+// matrixf_data_aug_z [size: 5 x 7]
+float matrixf_data_aug_z[] = {
+   -0.747572302818 /* ( 0, 0) */,
+    1.023007512093 /* ( 0, 1) */,
+   -0.806419134140 /* ( 0, 2) */,
+    1.476346969604 /* ( 0, 3) */,
+    0.376702636480 /* ( 0, 4) */,
+    0.790158689022 /* ( 0, 5) */,
+    2.111151933670 /* ( 0, 6) */,
+   -0.456311076880 /* ( 1, 0) */,
+    1.049571633339 /* ( 1, 1) */,
+    0.041211493313 /* ( 1, 2) */,
+    0.870350718498 /* ( 1, 3) */,
+   -0.690664231777 /* ( 1, 4) */,
+   -0.598035037518 /* ( 1, 5) */,
+   -0.137144193053 /* ( 1, 6) */,
+   -0.585918903351 /* ( 2, 0) */,
+   -2.498867988586 /* ( 2, 1) */,
+    1.247432827950 /* ( 2, 2) */,
+   -1.840264678001 /* ( 2, 3) */,
+    1.078616261482 /* ( 2, 4) */,
+    0.907722294331 /* ( 2, 5) */,
+   -0.432205766439 /* ( 2, 6) */,
+    0.618996977806 /* ( 3, 0) */,
+   -1.083691835403 /* ( 3, 1) */,
+   -1.827050209045 /* ( 3, 2) */,
+   -0.579039454460 /* ( 3, 3) */,
+   -1.615019798279 /* ( 3, 4) */,
+    0.122782632709 /* ( 3, 5) */,
+    1.174023866653 /* ( 3, 6) */,
+    1.507880568504 /* ( 4, 0) */,
+    1.633087396622 /* ( 4, 1) */,
+   -0.439950227737 /* ( 4, 2) */,
+   -0.058893665671 /* ( 4, 3) */,
+    0.233828529716 /* ( 4, 4) */,
+    0.032883912325 /* ( 4, 5) */,
+   -0.896481394768 /* ( 4, 6) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_cgsolve.c b/src/matrix/tests/data/matrixf_data_cgsolve.c
new file mode 100644
index 0000000..187fd7e
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_cgsolve.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing conjugate gradient solver
+//
+
+#include <complex.h>
+
+// matrixf_data_cgsolve_A [size: 8 x 8]
+float matrixf_data_cgsolve_A[] = {
+   12.722920400000 /* ( 0, 0) */,
+    0.000000000000 /* ( 0, 1) */,
+   -7.952912300000 /* ( 0, 2) */,
+    0.000000000000 /* ( 0, 3) */,
+    4.111499800000 /* ( 0, 4) */,
+    0.000000000000 /* ( 0, 5) */,
+    0.000000000000 /* ( 0, 6) */,
+    0.000000000000 /* ( 0, 7) */,
+    0.000000000000 /* ( 1, 0) */,
+    0.065151200000 /* ( 1, 1) */,
+    0.000000000000 /* ( 1, 2) */,
+   -0.218259800000 /* ( 1, 3) */,
+    0.000000000000 /* ( 1, 4) */,
+    0.000000000000 /* ( 1, 5) */,
+    0.000000000000 /* ( 1, 6) */,
+    0.000000000000 /* ( 1, 7) */,
+   -7.952912300000 /* ( 2, 0) */,
+    0.000000000000 /* ( 2, 1) */,
+    5.031585200000 /* ( 2, 2) */,
+    0.000000000000 /* ( 2, 3) */,
+   -2.570038800000 /* ( 2, 4) */,
+   -0.110545700000 /* ( 2, 5) */,
+    0.000000000000 /* ( 2, 6) */,
+    0.000000000000 /* ( 2, 7) */,
+    0.000000000000 /* ( 3, 0) */,
+   -0.218259800000 /* ( 3, 1) */,
+    0.000000000000 /* ( 3, 2) */,
+    0.733045600000 /* ( 3, 3) */,
+    0.000000000000 /* ( 3, 4) */,
+    0.000000000000 /* ( 3, 5) */,
+    0.000000000000 /* ( 3, 6) */,
+    0.000000000000 /* ( 3, 7) */,
+    4.111499800000 /* ( 4, 0) */,
+    0.000000000000 /* ( 4, 1) */,
+   -2.570038800000 /* ( 4, 2) */,
+    0.000000000000 /* ( 4, 3) */,
+    1.338132900000 /* ( 4, 4) */,
+    0.239381000000 /* ( 4, 5) */,
+    0.078430200000 /* ( 4, 6) */,
+    0.000000000000 /* ( 4, 7) */,
+    0.000000000000 /* ( 5, 0) */,
+    0.000000000000 /* ( 5, 1) */,
+   -0.110545700000 /* ( 5, 2) */,
+    0.000000000000 /* ( 5, 3) */,
+    0.239381000000 /* ( 5, 4) */,
+    7.472388300000 /* ( 5, 5) */,
+    1.981894700000 /* ( 5, 6) */,
+   -1.373365000000 /* ( 5, 7) */,
+    0.000000000000 /* ( 6, 0) */,
+    0.000000000000 /* ( 6, 1) */,
+    0.000000000000 /* ( 6, 2) */,
+    0.000000000000 /* ( 6, 3) */,
+    0.078430200000 /* ( 6, 4) */,
+    1.981894700000 /* ( 6, 5) */,
+    3.489272600000 /* ( 6, 6) */,
+    0.000000000000 /* ( 6, 7) */,
+    0.000000000000 /* ( 7, 0) */,
+    0.000000000000 /* ( 7, 1) */,
+    0.000000000000 /* ( 7, 2) */,
+    0.000000000000 /* ( 7, 3) */,
+    0.000000000000 /* ( 7, 4) */,
+   -1.373365000000 /* ( 7, 5) */,
+    0.000000000000 /* ( 7, 6) */,
+    2.063114900000 /* ( 7, 7) */};
+
+// matrixf_data_cgsolve_x [size: 8 x 1]
+float matrixf_data_cgsolve_x[] = {
+    0.162052200000 /* ( 0, 0) */,
+   -0.012720300000 /* ( 1, 0) */,
+    1.043375100000 /* ( 2, 0) */,
+    0.006205200000 /* ( 3, 0) */,
+    0.878157000000 /* ( 4, 0) */,
+    0.146412900000 /* ( 5, 0) */,
+    0.782585200000 /* ( 6, 0) */,
+   -0.825784600000 /* ( 7, 0) */};
+
+// matrixf_data_cgsolve_b [size: 8 x 1]
+float matrixf_data_cgsolve_b[] = {
+   -2.625551095190 /* ( 0, 0) */,
+   -0.002183088520 /* ( 1, 0) */,
+    1.687960897575 /* ( 2, 0) */,
+    0.007325024691 /* ( 3, 0) */,
+   -0.743719348831 /* ( 4, 0) */,
+    3.874032638311 /* ( 5, 0) */,
+    3.089702075189 /* ( 6, 0) */,
+   -1.904766864859 /* ( 7, 0) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_chol.c b/src/matrix/tests/data/matrixf_data_chol.c
new file mode 100644
index 0000000..5079659
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_chol.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing Cholesky decomposition
+//
+
+#include <complex.h>
+
+// matrixf_data_chol_L [size: 4 x 4]
+float matrixf_data_chol_L[] = {
+    1.010000000000 /* ( 0, 0) */,
+    0.000000000000 /* ( 0, 1) */,
+    0.000000000000 /* ( 0, 2) */,
+    0.000000000000 /* ( 0, 3) */,
+   -1.420000000000 /* ( 1, 0) */,
+    0.500000000000 /* ( 1, 1) */,
+    0.000000000000 /* ( 1, 2) */,
+    0.000000000000 /* ( 1, 3) */,
+    0.320000000000 /* ( 2, 0) */,
+    2.010000000000 /* ( 2, 1) */,
+    0.300000000000 /* ( 2, 2) */,
+    0.000000000000 /* ( 2, 3) */,
+   -1.020000000000 /* ( 3, 0) */,
+   -0.320000000000 /* ( 3, 1) */,
+   -1.650000000000 /* ( 3, 2) */,
+    1.070000000000 /* ( 3, 3) */};
+
+// matrixf_data_chol_A [size: 4 x 4]
+float matrixf_data_chol_A[] = {
+    1.020100000000 /* ( 0, 0) */,
+   -1.434200000000 /* ( 0, 1) */,
+    0.323200000000 /* ( 0, 2) */,
+   -1.030200000000 /* ( 0, 3) */,
+   -1.434200000000 /* ( 1, 0) */,
+    2.266400000000 /* ( 1, 1) */,
+    0.550600000000 /* ( 1, 2) */,
+    1.288400000000 /* ( 1, 3) */,
+    0.323200000000 /* ( 2, 0) */,
+    0.550600000000 /* ( 2, 1) */,
+    4.232500000000 /* ( 2, 2) */,
+   -1.464600000000 /* ( 2, 3) */,
+   -1.030200000000 /* ( 3, 0) */,
+    1.288400000000 /* ( 3, 1) */,
+   -1.464600000000 /* ( 3, 2) */,
+    5.010200000000 /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_gramschmidt.c b/src/matrix/tests/data/matrixf_data_gramschmidt.c
new file mode 100644
index 0000000..c019be3
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_gramschmidt.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing Gram-Schmidt orthonormalization
+//
+
+#include <complex.h>
+
+// matrixf_data_gramschmidt_A [size: 4 x 3]
+float matrixf_data_gramschmidt_A[] = {
+    1.000000000000 /* ( 0, 0) */,
+    2.000000000000 /* ( 0, 1) */,
+    1.000000000000 /* ( 0, 2) */,
+    0.000000000000 /* ( 1, 0) */,
+    2.000000000000 /* ( 1, 1) */,
+    0.000000000000 /* ( 1, 2) */,
+    2.000000000000 /* ( 2, 0) */,
+    3.000000000000 /* ( 2, 1) */,
+    1.000000000000 /* ( 2, 2) */,
+    1.000000000000 /* ( 3, 0) */,
+    1.000000000000 /* ( 3, 1) */,
+    0.000000000000 /* ( 3, 2) */};
+
+// matrixf_data_gramschmidt_V [size: 4 x 3]
+float matrixf_data_gramschmidt_V[] = {
+    0.408248290464 /* ( 0, 0) */,
+    0.235702260396 /* ( 0, 1) */,
+    0.666666666667 /* ( 0, 2) */,
+    0.000000000000 /* ( 1, 0) */,
+    0.942809041582 /* ( 1, 1) */,
+   -0.333333333333 /* ( 1, 2) */,
+    0.816496580928 /* ( 2, 0) */,
+    0.000000000000 /* ( 2, 1) */,
+    0.000000000000 /* ( 2, 2) */,
+    0.408248290464 /* ( 3, 0) */,
+   -0.235702260396 /* ( 3, 1) */,
+   -0.666666666667 /* ( 3, 2) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_inv.c b/src/matrix/tests/data/matrixf_data_inv.c
new file mode 100644
index 0000000..ab6ea3a
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_inv.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix inversion
+//
+
+#include <complex.h>
+
+// matrixf_data_inv_x [size: 5 x 5]
+float matrixf_data_inv_x[] = {
+    0.145655393600 /* ( 0, 0) */,
+   -2.292126655579 /* ( 0, 1) */,
+    0.928358852863 /* ( 0, 2) */,
+    0.995244622231 /* ( 0, 3) */,
+   -0.719965457916 /* ( 0, 4) */,
+    1.625229239464 /* ( 1, 0) */,
+    1.179069876671 /* ( 1, 1) */,
+    0.023814691231 /* ( 1, 2) */,
+   -0.458529949188 /* ( 1, 3) */,
+    0.870123147964 /* ( 1, 4) */,
+    1.599076509476 /* ( 2, 0) */,
+    1.012132167816 /* ( 2, 1) */,
+    0.240342438221 /* ( 2, 2) */,
+   -0.663878023624 /* ( 2, 3) */,
+    1.523158550262 /* ( 2, 4) */,
+    1.400263786316 /* ( 3, 0) */,
+   -0.016515849158 /* ( 3, 1) */,
+    0.525676131248 /* ( 3, 2) */,
+   -0.526886940002 /* ( 3, 3) */,
+   -0.605886101723 /* ( 3, 4) */,
+   -0.291201651096 /* ( 4, 0) */,
+    0.635409533978 /* ( 4, 1) */,
+    0.016531571746 /* ( 4, 2) */,
+    0.113017730415 /* ( 4, 3) */,
+   -0.886025428772 /* ( 4, 4) */};
+
+// matrixf_data_inv_y [size: 5 x 5]
+float matrixf_data_inv_y[] = {
+    0.123047731616 /* ( 0, 0) */,
+    1.264793339850 /* ( 0, 1) */,
+   -0.888020214878 /* ( 0, 2) */,
+    0.146648698334 /* ( 0, 3) */,
+   -0.484762774689 /* ( 0, 4) */,
+    0.031615676756 /* ( 1, 0) */,
+   -0.041217620573 /* ( 1, 1) */,
+    0.486809371567 /* ( 1, 2) */,
+   -0.307386761818 /* ( 1, 3) */,
+    0.980900315396 /* ( 1, 4) */,
+    0.456515830075 /* ( 2, 0) */,
+   -2.168499777786 /* ( 2, 1) */,
+    2.469455722213 /* ( 2, 2) */,
+    0.010642598564 /* ( 2, 3) */,
+    1.737407148356 /* ( 2, 4) */,
+    0.690799395919 /* ( 3, 0) */,
+    1.532809684521 /* ( 3, 1) */,
+   -0.611813824735 /* ( 3, 2) */,
+   -1.028413056396 /* ( 3, 3) */,
+    0.595460566672 /* ( 3, 4) */,
+    0.078865348162 /* ( 4, 0) */,
+   -0.290188077617 /* ( 4, 1) */,
+    0.609005594780 /* ( 4, 2) */,
+   -0.399620351004 /* ( 4, 3) */,
+   -0.157493442155 /* ( 4, 4) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_linsolve.c b/src/matrix/tests/data/matrixf_data_linsolve.c
new file mode 100644
index 0000000..5544f7b
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_linsolve.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing linear solver
+//
+
+#include <complex.h>
+
+// matrixf_data_linsolve_A [size: 5 x 5]
+float matrixf_data_linsolve_A[] = {
+    0.359868824482 /* ( 0, 0) */,
+   -0.821193814278 /* ( 0, 1) */,
+   -0.267460018396 /* ( 0, 2) */,
+    0.886115014553 /* ( 0, 3) */,
+    0.153591111302 /* ( 0, 4) */,
+    0.298885852098 /* ( 1, 0) */,
+   -1.239024162292 /* ( 1, 1) */,
+   -0.948822617531 /* ( 1, 2) */,
+   -0.779868483543 /* ( 1, 3) */,
+   -0.334943383932 /* ( 1, 4) */,
+   -0.071195065975 /* ( 2, 0) */,
+    0.763968944550 /* ( 2, 1) */,
+    0.294695496559 /* ( 2, 2) */,
+    0.060610540211 /* ( 2, 3) */,
+    0.016189640388 /* ( 2, 4) */,
+    1.150504231453 /* ( 3, 0) */,
+   -0.605459213257 /* ( 3, 1) */,
+    0.055004067719 /* ( 3, 2) */,
+    1.185544967651 /* ( 3, 3) */,
+    0.555612862110 /* ( 3, 4) */,
+    1.054118633270 /* ( 4, 0) */,
+   -0.494105964899 /* ( 4, 1) */,
+   -0.824876368046 /* ( 4, 2) */,
+    0.667240202427 /* ( 4, 3) */,
+    1.367745161057 /* ( 4, 4) */};
+
+// matrixf_data_linsolve_x [size: 5 x 1]
+float matrixf_data_linsolve_x[] = {
+   -0.848446607590 /* ( 0, 0) */,
+    1.041861057281 /* ( 1, 0) */,
+    0.453321367502 /* ( 2, 0) */,
+   -0.949143886566 /* ( 3, 0) */,
+    1.200846910477 /* ( 4, 0) */};
+
+// matrixf_data_linsolve_b [size: 5 x 1]
+float matrixf_data_linsolve_b[] = {
+   -1.938755917550 /* ( 0, 0) */,
+   -1.636609601788 /* ( 1, 0) */,
+    0.951859625938 /* ( 2, 0) */,
+   -2.040058038471 /* ( 3, 0) */,
+   -0.773941632607 /* ( 4, 0) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_ludecomp.c b/src/matrix/tests/data/matrixf_data_ludecomp.c
new file mode 100644
index 0000000..ec38e09
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_ludecomp.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing L/U decomposition
+//
+
+#include <complex.h>
+
+// matrixf_data_ludecomp_A [size: 8 x 8]
+float matrixf_data_ludecomp_A[] = {
+    0.936903119087 /* ( 0, 0) */,
+    1.048182249069 /* ( 0, 1) */,
+    0.464600890875 /* ( 0, 2) */,
+    1.122355699539 /* ( 0, 3) */,
+   -0.661124527454 /* ( 0, 4) */,
+   -0.953127145767 /* ( 0, 5) */,
+   -0.759313285351 /* ( 0, 6) */,
+    1.418183445930 /* ( 0, 7) */,
+   -0.272643178701 /* ( 1, 0) */,
+   -1.166662335396 /* ( 1, 1) */,
+    1.556591391563 /* ( 1, 2) */,
+   -0.323065608740 /* ( 1, 3) */,
+   -0.267991930246 /* ( 1, 4) */,
+    0.396302074194 /* ( 1, 5) */,
+    0.238355115056 /* ( 1, 6) */,
+   -0.437593698502 /* ( 1, 7) */,
+    0.431114047766 /* ( 2, 0) */,
+   -0.916567981243 /* ( 2, 1) */,
+    0.108782351017 /* ( 2, 2) */,
+   -0.714223206043 /* ( 2, 3) */,
+    0.197309300303 /* ( 2, 4) */,
+    1.105972766876 /* ( 2, 5) */,
+   -0.014590717852 /* ( 2, 6) */,
+    0.288964867592 /* ( 2, 7) */,
+    1.536642432213 /* ( 3, 0) */,
+    1.810190558434 /* ( 3, 1) */,
+    0.722570478916 /* ( 3, 2) */,
+    0.184841006994 /* ( 3, 3) */,
+   -0.239855647087 /* ( 3, 4) */,
+    0.494688391685 /* ( 3, 5) */,
+   -0.372100114822 /* ( 3, 6) */,
+   -0.754012823105 /* ( 3, 7) */,
+    0.139140784740 /* ( 4, 0) */,
+   -0.755531311035 /* ( 4, 1) */,
+    1.567769289017 /* ( 4, 2) */,
+   -0.774845600128 /* ( 4, 3) */,
+    1.536481976509 /* ( 4, 4) */,
+   -1.498587012291 /* ( 4, 5) */,
+    0.262655615807 /* ( 4, 6) */,
+   -1.045227766037 /* ( 4, 7) */,
+    0.445236086845 /* ( 5, 0) */,
+   -0.573900520802 /* ( 5, 1) */,
+    0.550646543503 /* ( 5, 2) */,
+    0.073093712330 /* ( 5, 3) */,
+    0.700358152390 /* ( 5, 4) */,
+    0.659417688847 /* ( 5, 5) */,
+    0.990632474422 /* ( 5, 6) */,
+   -0.596979260445 /* ( 5, 7) */,
+   -1.469601035118 /* ( 6, 0) */,
+   -1.366319775581 /* ( 6, 1) */,
+   -1.536668300629 /* ( 6, 2) */,
+    0.301474511623 /* ( 6, 3) */,
+    0.205486327410 /* ( 6, 4) */,
+    1.184612751007 /* ( 6, 5) */,
+    1.984294533730 /* ( 6, 6) */,
+    0.846946001053 /* ( 6, 7) */,
+   -0.780786097050 /* ( 7, 0) */,
+   -1.778358221054 /* ( 7, 1) */,
+   -0.621561229229 /* ( 7, 2) */,
+    0.809134125710 /* ( 7, 3) */,
+   -0.395780056715 /* ( 7, 4) */,
+    0.095775716007 /* ( 7, 5) */,
+    1.116999864578 /* ( 7, 6) */,
+   -0.937837302685 /* ( 7, 7) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_mul.c b/src/matrix/tests/data/matrixf_data_mul.c
new file mode 100644
index 0000000..11a5ab5
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_mul.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing matrix multiplication
+//
+
+#include <complex.h>
+
+// matrixf_data_mul_x [size: 5 x 4]
+float matrixf_data_mul_x[] = {
+   -0.348304957151 /* ( 0, 0) */,
+    1.638695955276 /* ( 0, 1) */,
+    0.618153512478 /* ( 0, 2) */,
+    0.580581486225 /* ( 0, 3) */,
+    1.125966548920 /* ( 1, 0) */,
+    0.590879321098 /* ( 1, 1) */,
+   -0.499333083630 /* ( 1, 2) */,
+   -0.144607886672 /* ( 1, 3) */,
+   -0.169909551740 /* ( 2, 0) */,
+    1.626509308815 /* ( 2, 1) */,
+   -0.776567280293 /* ( 2, 2) */,
+   -1.341656446457 /* ( 2, 3) */,
+    0.492572665215 /* ( 3, 0) */,
+   -0.075633287430 /* ( 3, 1) */,
+    1.035362601280 /* ( 3, 2) */,
+    0.842321217060 /* ( 3, 3) */,
+   -0.209287241101 /* ( 4, 0) */,
+   -0.789002001286 /* ( 4, 1) */,
+   -0.397730469704 /* ( 4, 2) */,
+    0.034179374576 /* ( 4, 3) */};
+
+// matrixf_data_mul_y [size: 4 x 3]
+float matrixf_data_mul_y[] = {
+    0.445414155722 /* ( 0, 0) */,
+    0.233421698213 /* ( 0, 1) */,
+   -0.682938814163 /* ( 0, 2) */,
+   -0.934787094593 /* ( 1, 0) */,
+    0.500407993793 /* ( 1, 1) */,
+   -0.053248234093 /* ( 1, 2) */,
+   -0.784089267254 /* ( 2, 0) */,
+    0.127269089222 /* ( 2, 1) */,
+   -0.477077603340 /* ( 2, 2) */,
+   -0.105383664370 /* ( 3, 0) */,
+   -1.556528329849 /* ( 3, 1) */,
+   -0.184585332870 /* ( 3, 2) */};
+
+// matrixf_data_mul_z [size: 5 x 3]
+float matrixf_data_mul_z[] = {
+   -2.232843128510 /* ( 0, 0) */,
+   -0.086305075740 /* ( 0, 1) */,
+   -0.251460714553 /* ( 0, 2) */,
+    0.355936096586 /* ( 1, 0) */,
+    0.720042365177 /* ( 1, 1) */,
+   -0.535516414414 /* ( 1, 2) */,
+   -0.845833288222 /* ( 2, 0) */,
+    2.763750941354 /* ( 2, 1) */,
+    0.647562038031 /* ( 2, 2) */,
+   -0.610483740991 /* ( 3, 0) */,
+   -1.102197535527 /* ( 3, 1) */,
+   -0.981798103518 /* ( 3, 2) */,
+    0.952583633427 /* ( 4, 0) */,
+   -0.547495051253 /* ( 4, 1) */,
+    0.368382631550 /* ( 4, 2) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_qrdecomp.c b/src/matrix/tests/data/matrixf_data_qrdecomp.c
new file mode 100644
index 0000000..d2d2c2b
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_qrdecomp.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing Q/R decomposition
+//
+
+#include <complex.h>
+
+// matrixf_data_qrdecomp_A [size: 4 x 4]
+float matrixf_data_qrdecomp_A[] = {
+    1.000000000000 /* ( 0, 0) */,
+    2.000000000000 /* ( 0, 1) */,
+    3.000000000000 /* ( 0, 2) */,
+    4.000000000000 /* ( 0, 3) */,
+    5.000000000000 /* ( 1, 0) */,
+    5.000000000000 /* ( 1, 1) */,
+    7.000000000000 /* ( 1, 2) */,
+    8.000000000000 /* ( 1, 3) */,
+    6.000000000000 /* ( 2, 0) */,
+    4.000000000000 /* ( 2, 1) */,
+    8.000000000000 /* ( 2, 2) */,
+    7.000000000000 /* ( 2, 3) */,
+    1.000000000000 /* ( 3, 0) */,
+    0.000000000000 /* ( 3, 1) */,
+    3.000000000000 /* ( 3, 2) */,
+    1.000000000000 /* ( 3, 3) */};
+
+// matrixf_data_qrdecomp_Q [size: 4 x 4]
+float matrixf_data_qrdecomp_Q[] = {
+    0.125988157670 /* ( 0, 0) */,
+    0.617707763884 /* ( 0, 1) */,
+    0.571886263590 /* ( 0, 2) */,
+    0.524890659168 /* ( 0, 3) */,
+    0.629940788349 /* ( 1, 0) */,
+    0.494166211107 /* ( 1, 1) */,
+   -0.137252703262 /* ( 1, 2) */,
+   -0.583211843520 /* ( 1, 3) */,
+    0.755928946018 /* ( 2, 0) */,
+   -0.444749589997 /* ( 2, 1) */,
+   -0.114377252718 /* ( 2, 2) */,
+    0.466569474816 /* ( 2, 3) */,
+    0.125988157670 /* ( 3, 0) */,
+   -0.420041279441 /* ( 3, 1) */,
+    0.800640769025 /* ( 3, 2) */,
+   -0.408248290464 /* ( 3, 3) */};
+
+// matrixf_data_qrdecomp_R [size: 4 x 4]
+float matrixf_data_qrdecomp_R[] = {
+    7.937253933194 /* ( 0, 0) */,
+    6.425396041157 /* ( 0, 1) */,
+   11.212946032607 /* ( 0, 2) */,
+   10.960969717268 /* ( 0, 3) */,
+    0.000000000000 /* ( 1, 0) */,
+    1.927248223319 /* ( 1, 1) */,
+    0.494166211107 /* ( 1, 2) */,
+    2.890872334978 /* ( 1, 3) */,
+    0.000000000000 /* ( 2, 0) */,
+    0.000000000000 /* ( 2, 1) */,
+    2.241794153271 /* ( 2, 2) */,
+    1.189523428266 /* ( 2, 3) */,
+    0.000000000000 /* ( 3, 0) */,
+    0.000000000000 /* ( 3, 1) */,
+    0.000000000000 /* ( 3, 2) */,
+    0.291605921760 /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/data/matrixf_data_transmul.c b/src/matrix/tests/data/matrixf_data_transmul.c
new file mode 100644
index 0000000..99084d8
--- /dev/null
+++ b/src/matrix/tests/data/matrixf_data_transmul.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// data for testing multiply/transpose
+//
+
+#include <complex.h>
+
+// matrixf_data_transmul_x [size: 5 x 4]
+float matrixf_data_transmul_x[] = {
+    1.366575479507 /* ( 0, 0) */,
+    1.982354640961 /* ( 0, 1) */,
+    0.913504719734 /* ( 0, 2) */,
+    0.671039104462 /* ( 0, 3) */,
+    0.264611721039 /* ( 1, 0) */,
+    0.995791137218 /* ( 1, 1) */,
+   -1.983934879303 /* ( 1, 2) */,
+   -0.375840932131 /* ( 1, 3) */,
+    0.245937779546 /* ( 2, 0) */,
+   -0.343152791262 /* ( 2, 1) */,
+   -0.143777698278 /* ( 2, 2) */,
+   -1.387172579765 /* ( 2, 3) */,
+    0.781192481518 /* ( 3, 0) */,
+    1.444583415985 /* ( 3, 1) */,
+    0.043851174414 /* ( 3, 2) */,
+    0.399744838476 /* ( 3, 3) */,
+   -0.488068610430 /* ( 4, 0) */,
+    0.573721885681 /* ( 4, 1) */,
+   -1.381630182266 /* ( 4, 2) */,
+   -0.176615595818 /* ( 4, 3) */};
+
+// matrixf_data_transmul_xxT [size: 5 x 5]
+float matrixf_data_transmul_xxT[] = {
+    7.082042816423 /* ( 0, 0) */,
+    0.271085233449 /* ( 0, 1) */,
+   -1.406346640934 /* ( 0, 2) */,
+    4.239537802168 /* ( 0, 3) */,
+   -0.910304016309 /* ( 0, 4) */,
+    0.271085233449 /* ( 1, 0) */,
+    5.138873363454 /* ( 1, 1) */,
+    0.529971336750 /* ( 1, 2) */,
+    1.407977702482 /* ( 1, 3) */,
+    3.249602173055 /* ( 1, 4) */,
+   -1.406346640934 /* ( 2, 0) */,
+    0.529971336750 /* ( 2, 1) */,
+    2.123159022134 /* ( 2, 2) */,
+   -0.864407986864 /* ( 2, 3) */,
+    0.126735142361 /* ( 2, 4) */,
+    4.239537802168 /* ( 3, 0) */,
+    1.407977702482 /* ( 3, 1) */,
+   -0.864407986864 /* ( 3, 2) */,
+    2.858801800305 /* ( 3, 3) */,
+    0.316326313589 /* ( 3, 4) */,
+   -0.910304016309 /* ( 4, 0) */,
+    3.249602173055 /* ( 4, 1) */,
+    0.126735142361 /* ( 4, 2) */,
+    0.316326313589 /* ( 4, 3) */,
+    2.507462799831 /* ( 4, 4) */};
+
+// matrixf_data_transmul_xxH [size: 5 x 5]
+float matrixf_data_transmul_xxH[] = {
+    7.082042816423 /* ( 0, 0) */,
+    0.271085233449 /* ( 0, 1) */,
+   -1.406346640934 /* ( 0, 2) */,
+    4.239537802168 /* ( 0, 3) */,
+   -0.910304016309 /* ( 0, 4) */,
+    0.271085233449 /* ( 1, 0) */,
+    5.138873363454 /* ( 1, 1) */,
+    0.529971336750 /* ( 1, 2) */,
+    1.407977702482 /* ( 1, 3) */,
+    3.249602173055 /* ( 1, 4) */,
+   -1.406346640934 /* ( 2, 0) */,
+    0.529971336750 /* ( 2, 1) */,
+    2.123159022134 /* ( 2, 2) */,
+   -0.864407986864 /* ( 2, 3) */,
+    0.126735142361 /* ( 2, 4) */,
+    4.239537802168 /* ( 3, 0) */,
+    1.407977702482 /* ( 3, 1) */,
+   -0.864407986864 /* ( 3, 2) */,
+    2.858801800305 /* ( 3, 3) */,
+    0.316326313589 /* ( 3, 4) */,
+   -0.910304016309 /* ( 4, 0) */,
+    3.249602173055 /* ( 4, 1) */,
+    0.126735142361 /* ( 4, 2) */,
+    0.316326313589 /* ( 4, 3) */,
+    2.507462799831 /* ( 4, 4) */};
+
+// matrixf_data_transmul_xTx [size: 4 x 4]
+float matrixf_data_transmul_xTx[] = {
+    2.846505957177 /* ( 0, 0) */,
+    3.736623075087 /* ( 0, 1) */,
+    1.396626890644 /* ( 0, 2) */,
+    0.874893716720 /* ( 0, 3) */,
+    3.736623075087 /* ( 1, 0) */,
+    7.455061797501 /* ( 1, 1) */,
+   -0.844681524592 /* ( 1, 2) */,
+    1.908127088099 /* ( 1, 3) */,
+    1.396626890644 /* ( 2, 0) */,
+   -0.844681524592 /* ( 2, 1) */,
+    6.701985390860 /* ( 2, 2) */,
+    1.819632522483 /* ( 2, 3) */,
+    0.874893716720 /* ( 3, 0) */,
+    1.908127088099 /* ( 3, 1) */,
+    1.819632522483 /* ( 3, 2) */,
+    2.706786656609 /* ( 3, 3) */};
+
+// matrixf_data_transmul_xHx [size: 4 x 4]
+float matrixf_data_transmul_xHx[] = {
+    2.846505957177 /* ( 0, 0) */,
+    3.736623075087 /* ( 0, 1) */,
+    1.396626890644 /* ( 0, 2) */,
+    0.874893716720 /* ( 0, 3) */,
+    3.736623075087 /* ( 1, 0) */,
+    7.455061797501 /* ( 1, 1) */,
+   -0.844681524592 /* ( 1, 2) */,
+    1.908127088099 /* ( 1, 3) */,
+    1.396626890644 /* ( 2, 0) */,
+   -0.844681524592 /* ( 2, 1) */,
+    6.701985390860 /* ( 2, 2) */,
+    1.819632522483 /* ( 2, 3) */,
+    0.874893716720 /* ( 3, 0) */,
+    1.908127088099 /* ( 3, 1) */,
+    1.819632522483 /* ( 3, 2) */,
+    2.706786656609 /* ( 3, 3) */};
+
diff --git a/src/matrix/tests/gendata_write_autotests.m b/src/matrix/tests/gendata_write_autotests.m
new file mode 100644
index 0000000..b25fb5a
--- /dev/null
+++ b/src/matrix/tests/gendata_write_autotests.m
@@ -0,0 +1,350 @@
+%
+% Write matrix autotest data
+%
+
+clear all;
+close all;
+
+% global options
+dirname = 'data/';  % output directory name
+randn('seed',0.0);  % set random seed for normal distribution
+rand('seed',0.0);   % set random seed for uniform distribution
+
+%
+% real floating-point data
+%
+
+%
+% matrix_add
+%
+x = randn(5,4);
+y = randn(5,4);
+z = x + y;
+basename = 'matrixf_data_add';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix addition');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_aug
+%
+x = randn(5,4);
+y = randn(5,3);
+z = [x y];
+basename = 'matrixf_data_aug';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix augmentation');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_cgsolve
+%
+A = [
+  12.7229204   0.0000000  -7.9529123   0.0000000   4.1114998   0.0000000   0.0000000   0.0000000
+   0.0000000   0.0651512   0.0000000  -0.2182598   0.0000000   0.0000000   0.0000000   0.0000000
+  -7.9529123   0.0000000   5.0315852   0.0000000  -2.5700388  -0.1105457   0.0000000   0.0000000
+   0.0000000  -0.2182598   0.0000000   0.7330456   0.0000000   0.0000000   0.0000000   0.0000000
+   4.1114998   0.0000000  -2.5700388   0.0000000   1.3381329   0.2393810   0.0784302   0.0000000
+   0.0000000   0.0000000  -0.1105457   0.0000000   0.2393810   7.4723883   1.9818947  -1.3733650
+   0.0000000   0.0000000   0.0000000   0.0000000   0.0784302   1.9818947   3.4892726   0.0000000
+   0.0000000   0.0000000   0.0000000   0.0000000   0.0000000  -1.3733650   0.0000000   2.0631149];
+x = [
+   0.1620522
+  -0.0127203
+   1.0433751
+   0.0062052
+   0.8781570
+   0.1464129
+   0.7825852
+  -0.8257846];
+b = A*x;
+basename = 'matrixf_data_cgsolve';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing conjugate gradient solver');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,b,[basename '_b']);
+fclose(fid);
+
+%
+% matrix_chol
+%
+L = [   1.01,   0,      0,      0;
+       -1.42,   0.5,    0,      0;
+        0.32,   2.01,   0.3,    0;
+       -1.02,  -0.32,  -1.65,   1.07];
+A = L * L';
+basename = 'matrixf_data_chol';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing Cholesky decomposition');
+gendata_write_matrix(fid,L,[basename '_L']);
+gendata_write_matrix(fid,A,[basename '_A']);
+fclose(fid);
+
+%
+% matrix_gramschmidt
+%
+A = [1 2 1; 0 2 0; 2 3 1; 1 1 0];
+V = [sqrt(6)/6, sqrt(2)/6,    2/3 ;
+     0          2*sqrt(2)/3, -1/3 ;
+     sqrt(6)/3, 0,            0   ;
+     sqrt(6)/6, -sqrt(2)/6,  -2/3 ];
+basename = 'matrixf_data_gramschmidt';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing Gram-Schmidt orthonormalization');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,V,[basename '_V']);
+fclose(fid);
+
+%
+% matrix_inv
+%
+x = randn(5,5);
+y = inv(x);
+basename = 'matrixf_data_inv';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix inversion');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+fclose(fid);
+
+%
+% matrix_linsolve
+%
+A = randn(5,5);
+x = randn(5,1);
+b = A*x;
+basename = 'matrixf_data_linsolve';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing linear solver');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,b,[basename '_b']);
+fclose(fid);
+
+%
+% matrix_ludecomp
+%
+A = randn(8,8);
+basename = 'matrixf_data_ludecomp';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing L/U decomposition');
+gendata_write_matrix(fid,A,[basename '_A']);
+fclose(fid);
+
+%
+% matrix_mul
+%
+x = randn(5,4);
+y = randn(4,3);
+z = x*y;
+basename = 'matrixf_data_mul';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix multiplication');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_qrdecomp
+%
+A = [1 2 3 4; 5 5 7 8; 6 4 8 7; 1 0 3 1];
+[Q R] = qr(A);
+basename = 'matrixf_data_qrdecomp';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing Q/R decomposition');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,Q,[basename '_Q']);
+gendata_write_matrix(fid,R,[basename '_R']);
+fclose(fid);
+
+%
+% matrix_transmul
+% 
+% transpose_mul
+% hermitian_mul
+% mul_transpose
+% mul_hermitian
+%
+x = randn(5,4);
+xxT = x   * x';
+xxH = x   * x.';
+xTx = x'  * x;
+xHx = x.' * x;
+basename = 'matrixf_data_transmul';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing multiply/transpose');
+gendata_write_matrix(fid,x,  [basename '_x']);
+gendata_write_matrix(fid,xxT,[basename '_xxT']);
+gendata_write_matrix(fid,xxH,[basename '_xxH']);
+gendata_write_matrix(fid,xTx,[basename '_xTx']);
+gendata_write_matrix(fid,xHx,[basename '_xHx']);
+fclose(fid);
+
+
+
+
+
+%
+% complex floating-point data
+%
+
+%
+% matrix_add
+%
+x = randn(5,4) + j*randn(5,4);
+y = randn(5,4) + j*randn(5,4);
+z = x + y;
+basename = 'matrixcf_data_add';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix addition');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_aug
+%
+x = randn(5,4) + j*randn(5,4);
+y = randn(5,3) + j*randn(5,3);
+z = [x y];
+basename = 'matrixcf_data_aug';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix augmentation');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_chol
+%
+L = [   1.01,       0,          0,          0;
+       -1.42+0.25j, 0.5,        0,          0;
+        0.32-1.23j, 2.01+0.78j, 0.3,        0;
+       -1.02+1.02j,-0.32-0.03j,-1.65+2.01j, 1.07];
+A = L * L';
+basename = 'matrixcf_data_chol';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing Cholesky decomposition');
+gendata_write_matrix(fid,L,[basename '_L']);
+gendata_write_matrix(fid,A,[basename '_A']);
+fclose(fid);
+
+%
+% matrix_inv
+%
+x = randn(5,5) +j*randn(5,5);
+y = inv(x);
+basename = 'matrixcf_data_inv';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix inversion');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+fclose(fid);
+
+%
+% matrix_linsolve
+%
+A = randn(5,5) + j*randn(5,5);
+x = randn(5,1) + j*randn(5,1);
+b = A*x;
+basename = 'matrixcf_data_linsolve';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing linear solver');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,b,[basename '_b']);
+fclose(fid);
+
+%
+% matrix_ludecomp
+%
+A = randn(8,8) + j*randn(8,8);
+basename = 'matrixcf_data_ludecomp';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing L/U decomposition');
+gendata_write_matrix(fid,A,[basename '_A']);
+fclose(fid);
+
+%
+% matrix_mul
+%
+x = randn(5,4) + j*randn(5,4);
+y = randn(4,3) + j*randn(4,3);
+z = x*y;
+basename = 'matrixcf_data_mul';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing matrix multiplication');
+gendata_write_matrix(fid,x,[basename '_x']);
+gendata_write_matrix(fid,y,[basename '_y']);
+gendata_write_matrix(fid,z,[basename '_z']);
+fclose(fid);
+
+%
+% matrix_qrdecomp
+%
+A = [  2.11402 - 0.57604i,  0.41750 + 1.00833i, -0.96264 - 3.62196i, -0.20679 - 1.02668i,
+       0.00854 + 1.61626i,  0.84695 - 0.32736i, -1.01862 - 1.10786i, -1.78877 + 1.84456i,
+      -2.97901 - 1.30384i,  0.52289 + 1.89110i,  1.32576 - 0.36737i,  0.04717 + 0.20628i,
+       0.28970 + 0.64247i, -0.55916 + 0.68302i,  1.40615 + 0.62398i, -0.12767 - 0.53997i];
+[Q R] = qr(A);
+basename = 'matrixcf_data_qrdecomp';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing Q/R decomposition');
+gendata_write_matrix(fid,A,[basename '_A']);
+gendata_write_matrix(fid,Q,[basename '_Q']);
+gendata_write_matrix(fid,R,[basename '_R']);
+fclose(fid);
+
+%
+% matrix_transmul
+%
+% transpose_mul
+% hermitian_mul
+% mul_transpose
+% mul_hermitian
+%
+x = randn(5,4) + j*randn(5,4);
+xxT = x   * x';
+xxH = x   * x.';
+xTx = x'  * x;
+xHx = x.' * x;
+basename = 'matrixcf_data_transmul';
+filename = [dirname basename '.c'];
+fid = fopen(filename,'w');
+gendata_write_header(fid,'data for testing multiply/transpose');
+gendata_write_matrix(fid,x,  [basename '_x']);
+gendata_write_matrix(fid,xxT,[basename '_xxT']);
+gendata_write_matrix(fid,xxH,[basename '_xxH']);
+gendata_write_matrix(fid,xTx,[basename '_xTx']);
+gendata_write_matrix(fid,xHx,[basename '_xHx']);
+fclose(fid);
+
diff --git a/src/matrix/tests/gendata_write_header.m b/src/matrix/tests/gendata_write_header.m
new file mode 100644
index 0000000..71b9601
--- /dev/null
+++ b/src/matrix/tests/gendata_write_header.m
@@ -0,0 +1,39 @@
+%
+% write_header(fid,comment)
+%
+% write copyright and header to file descriptor 'fid' with optional comment
+%
+function gendata_write_header(fid,comment)
+
+if nargin < 2,
+    comment = 'auto-generated data file';
+end;
+
+fprintf(fid,'/* Copyright (c) 2007 - 2015 Joseph Gaeddert\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * Permission is hereby granted, free of charge, to any person obtaining a copy\n');
+fprintf(fid,' * of this software and associated documentation files (the "Software"), to deal\n');
+fprintf(fid,' * in the Software without restriction, including without limitation the rights\n');
+fprintf(fid,' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n');
+fprintf(fid,' * copies of the Software, and to permit persons to whom the Software is\n');
+fprintf(fid,' * furnished to do so, subject to the following conditions:\n');
+fprintf(fid,' * \n');
+fprintf(fid,' * The above copyright notice and this permission notice shall be included in\n');
+fprintf(fid,' * all copies or substantial portions of the Software.\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n');
+fprintf(fid,' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n');
+fprintf(fid,' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n');
+fprintf(fid,' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n');
+fprintf(fid,' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n');
+fprintf(fid,' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n');
+fprintf(fid,' * THE SOFTWARE.\n');
+fprintf(fid,' */\n');
+fprintf(fid,'\n');
+fprintf(fid,'//\n');
+fprintf(fid,'// %s\n', comment);
+fprintf(fid,'//\n');
+fprintf(fid,'\n');
+fprintf(fid,'#include <complex.h>\n');
+fprintf(fid,'\n');
+
diff --git a/src/matrix/tests/gendata_write_matrix.m b/src/matrix/tests/gendata_write_matrix.m
new file mode 100644
index 0000000..71c2396
--- /dev/null
+++ b/src/matrix/tests/gendata_write_matrix.m
@@ -0,0 +1,63 @@
+%
+% write_matrix(fid,x,comment)
+%
+% write matrix 'x' to file descriptor 'fid' with optional comment
+%
+function gendata_write_matrix(fid,x,varname,comment)
+
+if nargin < 3,
+    error('must specify output file descriptor, input matrix, and variable name');
+elseif nargin < 4,
+    comment = varname;
+end;
+
+% other options
+one_line_per_element = 1;   % display one line per element?
+print_index_as_comment = 1; % print index of element as comment?
+
+% get size of matrix
+[rows cols] = size(x);
+
+% even though matrix might have two dimensions, we store it as
+% a one-dimensional array
+n = rows*cols;
+
+% determine if matrix is complex or not
+if iscomplex(x),
+    type = 'float complex';
+else,
+    type = 'float';
+end;
+
+% write array comment
+fprintf(fid,'// %s [size: %u x %u]\n', comment, rows, cols);
+
+% write variable declaration
+fprintf(fid,'%s %s[] = {\n',type, varname);
+
+% write array data
+n=0;
+for r=1:rows,
+    for c=1:cols,
+        n = n+1;
+        if iscomplex(x),
+            fprintf(fid,'  %16.12f + %16.12f*_Complex_I', real(x(r,c)), imag(x(r,c)));
+        else,
+            fprintf(fid,'  %16.12f', real(x(r,c)));
+        end;
+
+        if print_index_as_comment,
+            fprintf(fid,' /* (%2u,%2u) */', r-1, c-1);
+        end;
+
+        if r==rows && c==cols,
+            fprintf(fid,'};\n');
+        elseif c==cols || one_line_per_element,
+            fprintf(fid,',\n');
+        else,
+            fprintf(fid,', ');
+        end;
+    end;
+end;
+fprintf(fid,'\n');
+
diff --git a/src/matrix/tests/matrix_data.h b/src/matrix/tests/matrix_data.h
new file mode 100644
index 0000000..b17593e
--- /dev/null
+++ b/src/matrix/tests/matrix_data.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest matrix data definitions
+//
+
+#ifndef __LIQUID_MATRIX_DATA_H__
+#define __LIQUID_MATRIX_DATA_H__
+
+// 
+// single-precision real floating-point data
+//
+
+// add
+extern float matrixf_data_add_x[];
+extern float matrixf_data_add_y[];
+extern float matrixf_data_add_z[];
+
+// aug
+extern float matrixf_data_aug_x[];
+extern float matrixf_data_aug_y[];
+extern float matrixf_data_aug_z[];
+
+// cgsolve
+extern float matrixf_data_cgsolve_A[];
+extern float matrixf_data_cgsolve_x[];
+extern float matrixf_data_cgsolve_b[];
+
+// chol
+extern float matrixf_data_chol_A[];
+extern float matrixf_data_chol_L[];
+
+// gramschmidt
+extern float matrixf_data_gramschmidt_A[];
+extern float matrixf_data_gramschmidt_V[];
+
+// inv
+extern float matrixf_data_inv_x[];
+extern float matrixf_data_inv_y[];
+
+// linsolve
+extern float matrixf_data_linsolve_A[];
+extern float matrixf_data_linsolve_x[];
+extern float matrixf_data_linsolve_b[];
+
+// ludecomp
+extern float matrixf_data_ludecomp_A[];
+
+// mul
+extern float matrixf_data_mul_x[];
+extern float matrixf_data_mul_y[];
+extern float matrixf_data_mul_z[];
+
+// qrdecomp
+extern float matrixf_data_qrdecomp_A[];
+extern float matrixf_data_qrdecomp_Q[];
+extern float matrixf_data_qrdecomp_R[];
+
+
+// transmul
+extern float matrixf_data_transmul_x[];
+extern float matrixf_data_transmul_xxT[];
+extern float matrixf_data_transmul_xxH[];
+extern float matrixf_data_transmul_xTx[];
+extern float matrixf_data_transmul_xHx[];
+
+
+// 
+// single-precision complex floating-point data
+//
+
+// add
+extern float complex matrixcf_data_add_x[];
+extern float complex matrixcf_data_add_y[];
+extern float complex matrixcf_data_add_z[];
+
+// aug
+extern float complex matrixcf_data_aug_x[];
+extern float complex matrixcf_data_aug_y[];
+extern float complex matrixcf_data_aug_z[];
+
+// chol
+extern float complex matrixcf_data_chol_A[];
+extern float complex matrixcf_data_chol_L[];
+
+// inv
+extern float complex matrixcf_data_inv_x[];
+extern float complex matrixcf_data_inv_y[];
+
+// linsolve
+extern float complex matrixcf_data_linsolve_A[];
+extern float complex matrixcf_data_linsolve_x[];
+extern float complex matrixcf_data_linsolve_b[];
+
+// ludecomp
+extern float complex matrixcf_data_ludecomp_A[];
+
+// mul
+extern float complex matrixcf_data_mul_x[];
+extern float complex matrixcf_data_mul_y[];
+extern float complex matrixcf_data_mul_z[];
+
+// qrdecomp
+extern float complex matrixcf_data_qrdecomp_A[];
+extern float complex matrixcf_data_qrdecomp_Q[];
+extern float complex matrixcf_data_qrdecomp_R[];
+
+// transmul
+extern float complex matrixcf_data_transmul_x[];
+extern float complex matrixcf_data_transmul_xxT[];
+extern float complex matrixcf_data_transmul_xxH[];
+extern float complex matrixcf_data_transmul_xTx[];
+extern float complex matrixcf_data_transmul_xHx[];
+
+#endif // __LIQUID_MATRIX_DATA_H__
+
diff --git a/src/matrix/tests/matrixcf_autotest.c b/src/matrix/tests/matrixcf_autotest.c
new file mode 100644
index 0000000..0df3a70
--- /dev/null
+++ b/src/matrix/tests/matrixcf_autotest.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/matrix/tests/matrix_data.h"
+
+// test matrix addition
+void autotest_matrixcf_add()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 5 x 4]
+    // z [size: 5 x 4]
+    float complex z[20];
+    matrixcf_add(matrixcf_data_add_x,
+                 matrixcf_data_add_y,
+                 z,
+                 5, 4);
+
+    unsigned int i;
+    for (i=0; i<20; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_add_z[i]), crealf(z[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_add_z[i]), cimagf(z[i]), tol );
+    }
+}
+
+// test matrix augmentation
+void autotest_matrixcf_aug()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 5 x 3]
+    // z [size: 5 x 7]
+    float complex z[35];
+    matrixcf_aug(matrixcf_data_aug_x, 5, 4,
+                 matrixcf_data_aug_y, 5, 3,
+                 z,                  5, 7);
+    
+    // print result
+    if (liquid_autotest_verbose) {
+        printf("augment:\n");
+        printf("  x: ");        matrixcf_print(matrixcf_data_aug_x, 5, 4);
+        printf("  y: ");        matrixcf_print(matrixcf_data_aug_y, 5, 3);
+        printf("  expected: "); matrixcf_print(matrixcf_data_aug_z, 5, 7);
+        printf("  z: ");        matrixcf_print(z,5,3);
+    }
+
+    unsigned int i;
+    for (i=0; i<35; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_aug_z[i]), crealf(z[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_aug_z[i]), cimagf(z[i]), tol );
+    }
+}
+
+// Cholesky decomposition
+void autotest_matrixcf_chol()
+{
+    float tol = 1e-3f;  // error tolerance
+
+    // A [size: 4 x 4]
+    // L [size: 4 x 4]
+    float complex L[16];
+
+    // run decomposition
+    matrixcf_chol(matrixcf_data_chol_A, 4, L);
+
+    if (liquid_autotest_verbose) {
+        printf("chol:\n");
+        printf("  A: ");        matrixcf_print(matrixcf_data_chol_A, 4, 4);
+        printf("  expected: "); matrixcf_print(matrixcf_data_chol_L, 4, 4);
+        printf("  L: ");        matrixcf_print(L,                    4, 4);
+    }
+
+    unsigned int i;
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_chol_L[i]), crealf(L[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_chol_L[i]), cimagf(L[i]), tol );
+    }
+}
+
+// matrix inversion
+void autotest_matrixcf_inv()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    // x [size: 5 x 5]
+    // y [size: 5 x 5]
+    float complex y[25];
+    memmove(y, matrixcf_data_inv_x, 5*5*sizeof(float complex));
+    matrixcf_inv(y, 5, 5);
+
+    if (liquid_autotest_verbose) {
+        printf("inv:\n");
+        printf("  x: ");        matrixcf_print(matrixcf_data_inv_x, 5, 5);
+        printf("  expected: "); matrixcf_print(matrixcf_data_inv_y, 5, 5);
+        printf("  y: ");        matrixcf_print(y,                   5, 5);
+    }
+
+    unsigned int i;
+    for (i=0; i<25; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_inv_y[i]), crealf(y[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_inv_y[i]), cimagf(y[i]), tol );
+    }
+}
+
+// linsolve (solve linear system of equations)
+void autotest_matrixcf_linsolve()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    // A [size: 5 x 5]
+    // x [size: 5 x 1]
+    // b [size: 5 x 1]
+    float complex x[5];
+    
+    // run solver
+    matrixcf_linsolve(matrixcf_data_linsolve_A, 5,
+                      matrixcf_data_linsolve_b,
+                      x, NULL);
+
+    if (liquid_autotest_verbose) {
+        printf("linsolve:\n");
+        printf("  A: ");        matrixcf_print(matrixcf_data_linsolve_A, 5, 5);
+        printf("  b: ");        matrixcf_print(matrixcf_data_linsolve_b, 5, 1);
+        printf("  expected: "); matrixcf_print(matrixcf_data_linsolve_x, 5, 1);
+        printf("  x: ");        matrixcf_print(x,                        5, 1);
+    }
+
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_linsolve_x[i]), crealf(x[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_linsolve_x[i]), cimagf(x[i]), tol );
+    }
+}
+
+// L/U decomp (Crout)
+void autotest_matrixcf_ludecomp_crout()
+{
+    float tol = 1e-5f;  // error tolerance
+
+    float complex L[64];
+    float complex U[64];
+    float complex P[64];
+
+    float complex LU_test[64];
+
+    // run decomposition
+    matrixcf_ludecomp_crout(matrixcf_data_ludecomp_A, 8, 8, L, U, P);
+
+    // multiply LU
+    matrixcf_mul(L,       8, 8,
+                 U,       8, 8,
+                 LU_test, 8, 8);
+
+    if (liquid_autotest_verbose) {
+        printf("ludecomp_crout:\n");
+        printf("  A: ");  matrixcf_print(matrixcf_data_ludecomp_A, 8, 8);
+        printf("  L: ");  matrixcf_print(L,                        8, 8);
+        printf("  U: ");  matrixcf_print(U,                        8, 8);
+        printf("  LU: "); matrixcf_print(LU_test,                  8, 8);
+    }
+
+    unsigned int r,c;
+    for (r=0; r<8; r++) {
+        for (c=0; c<8; c++) {
+            if (r < c) {
+                CONTEND_DELTA( crealf(matrix_access(L,8,8,r,c)), 0.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(L,8,8,r,c)), 0.0f, tol );
+            } else if (r==c) {
+                CONTEND_DELTA( crealf(matrix_access(U,8,8,r,c)), 1.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(U,8,8,r,c)), 0.0f, tol );
+            } else {
+                CONTEND_DELTA( crealf(matrix_access(U,8,8,r,c)), 0.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(U,8,8,r,c)), 0.0f, tol );
+            }
+        }
+    }
+
+    unsigned int i;
+    for (i=0; i<64; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_ludecomp_A[i]), crealf(LU_test[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_ludecomp_A[i]), cimagf(LU_test[i]), tol );
+    }
+}
+
+// L/U decomp (Doolittle)
+void autotest_matrixcf_ludecomp_doolittle()
+{
+    float tol = 1e-5f;  // error tolerance
+
+    float complex L[64];
+    float complex U[64];
+    float complex P[64];
+
+    float complex LU_test[64];
+
+    // run decomposition
+    matrixcf_ludecomp_doolittle(matrixcf_data_ludecomp_A, 8, 8, L, U, P);
+
+    // multiply LU
+    matrixcf_mul(L,       8, 8,
+                 U,       8, 8,
+                 LU_test, 8, 8);
+
+    if (liquid_autotest_verbose) {
+        printf("ludecomp_doolittle:\n");
+        printf("  A: ");  matrixcf_print(matrixcf_data_ludecomp_A, 8, 8);
+        printf("  L: ");  matrixcf_print(L,                        8, 8);
+        printf("  U: ");  matrixcf_print(U,                        8, 8);
+        printf("  LU: "); matrixcf_print(LU_test,                  8, 8);
+    }
+
+    unsigned int r,c;
+    for (r=0; r<8; r++) {
+        for (c=0; c<8; c++) {
+            if (r < c) {
+                CONTEND_DELTA( crealf(matrix_access(L,8,8,r,c)), 0.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(L,8,8,r,c)), 0.0f, tol );
+            } else if (r==c) {
+                CONTEND_DELTA( crealf(matrix_access(L,8,8,r,c)), 1.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(L,8,8,r,c)), 0.0f, tol );
+            } else {
+                CONTEND_DELTA( crealf(matrix_access(U,8,8,r,c)), 0.0f, tol );
+                CONTEND_DELTA( cimagf(matrix_access(U,8,8,r,c)), 0.0f, tol );
+            }
+        }
+    }
+
+    unsigned int i;
+    for (i=0; i<64; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_ludecomp_A[i]), crealf(LU_test[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_ludecomp_A[i]), cimagf(LU_test[i]), tol );
+    }
+}
+
+// test matrix multiplication
+void autotest_matrixcf_mul()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 4 x 3]
+    // z [size: 5 x 3]
+    float complex z[35];
+    matrixcf_mul(matrixcf_data_mul_x, 5, 4,
+                 matrixcf_data_mul_y, 4, 3,
+                 z,                   5, 3);
+
+    // print result
+    if (liquid_autotest_verbose) {
+        printf("multiplication:\n");
+        printf("  x: ");        matrixcf_print(matrixcf_data_mul_x,5,4);
+        printf("  y: ");        matrixcf_print(matrixcf_data_mul_y,4,3);
+        printf("  expected: "); matrixcf_print(matrixcf_data_mul_z,5,3);
+        printf("  z: ");        matrixcf_print(z,5,3);
+    }
+
+    unsigned int i;
+    for (i=0; i<15; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_mul_z[i]), crealf(z[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_mul_z[i]), cimagf(z[i]), tol );
+    }
+}
+
+// Q/R decomp (Gram-Schmidt)
+void autotest_matrixcf_qrdecomp()
+{
+    float tol = 1e-4f;  // error tolerance
+
+    float complex Q[16];
+    float complex R[16];
+
+    float complex QR_test[16];  // Q*R
+    float complex QQT_test[16]; // Q*Q^T
+
+    // run decomposition
+    matrixcf_qrdecomp_gramschmidt(matrixcf_data_qrdecomp_A, 4, 4, Q, R);
+
+    // compute Q*R
+    matrixcf_mul(Q,       4, 4,
+                 R,       4, 4,
+                 QR_test, 4, 4);
+
+    // compute Q*Q^T
+    matrixcf_mul_transpose(Q, 4, 4, QQT_test);
+
+    if (liquid_autotest_verbose) {
+        printf("qrdecomp_gramschmidt:\n");
+        printf("  A: ");          matrixcf_print(matrixcf_data_qrdecomp_A, 4, 4);
+        printf("  Q: ");          matrixcf_print(Q,                        4, 4);
+        printf("  R: ");          matrixcf_print(R,                        4, 4);
+        printf("  Q expected: "); matrixcf_print(matrixcf_data_qrdecomp_Q, 4, 4);
+        printf("  R expected: "); matrixcf_print(matrixcf_data_qrdecomp_R, 4, 4);
+        printf("  QR: ");         matrixcf_print(QR_test,                  4, 4);
+        printf("  QQ: ");         matrixcf_print(QQT_test,                 4, 4);
+    }
+
+    unsigned int i;
+
+    // ensure Q*R = A
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_qrdecomp_A[i]), crealf(QR_test[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_qrdecomp_A[i]), cimagf(QR_test[i]), tol );
+    }
+
+    // ensure Q*Q^T = I(4)
+    float complex I4[16];
+    matrixcf_eye(I4,4);
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( QQT_test[i], I4[i], tol );
+
+    // ensure Q and R are correct
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_qrdecomp_Q[i]), crealf(Q[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_qrdecomp_Q[i]), cimagf(Q[i]), tol );
+
+        CONTEND_DELTA( crealf(matrixcf_data_qrdecomp_R[i]), crealf(R[i]), tol );
+        CONTEND_DELTA( cimagf(matrixcf_data_qrdecomp_R[i]), cimagf(R[i]), tol );
+    }
+}
+
+// transpose/multiply
+void autotest_matrixcf_transmul()
+{
+    float tol = 1e-4f;  // error tolerance
+
+    float complex xxT[25];  // [size: 5 x 5]
+    float complex xxH[25];  // [size: 5 x 5]
+    float complex xTx[16];  // [size: 4 x 4]
+    float complex xHx[16];  // [size: 4 x 4]
+
+    // run matrix multiplications
+    matrixcf_mul_transpose(matrixcf_data_transmul_x, 5, 4, xxT);
+    matrixcf_mul_hermitian(matrixcf_data_transmul_x, 5, 4, xxH);
+    matrixcf_transpose_mul(matrixcf_data_transmul_x, 5, 4, xTx);
+    matrixcf_hermitian_mul(matrixcf_data_transmul_x, 5, 4, xHx);
+
+    if (liquid_autotest_verbose) {
+        printf("transmul:\n");
+        printf("  x: ");            matrixcf_print(matrixcf_data_transmul_x,  5,4);
+        printf("\n");
+        printf("  xxT: ");          matrixcf_print(xxT,                      5,5);
+        printf("  xxT expected: "); matrixcf_print(matrixcf_data_transmul_xxT,5,5);
+        printf("\n");
+        printf("  xxH: ");          matrixcf_print(xxH,                      5,5);
+        printf("  xxH expected: "); matrixcf_print(matrixcf_data_transmul_xxH,5,5);
+        printf("\n");
+        printf("  xTx: ");          matrixcf_print(xTx,                      4,4);
+        printf("  xTx expected: "); matrixcf_print(matrixcf_data_transmul_xTx,4,4);
+        printf("\n");
+        printf("  xHx: ");          matrixcf_print(xHx,                      4,4);
+        printf("  xHx expected: "); matrixcf_print(matrixcf_data_transmul_xHx,4,4);
+        printf("\n");
+    }
+
+    // run tests
+    unsigned int i;
+
+    for (i=0; i<25; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_transmul_xxT[i]), crealf(xxT[i]), tol);
+        CONTEND_DELTA( cimagf(matrixcf_data_transmul_xxT[i]), cimagf(xxT[i]), tol);
+    }
+
+    for (i=0; i<25; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_transmul_xxH[i]), crealf(xxH[i]), tol);
+        CONTEND_DELTA( cimagf(matrixcf_data_transmul_xxH[i]), cimagf(xxH[i]), tol);
+    }
+
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_transmul_xTx[i]), crealf(xTx[i]), tol);
+        CONTEND_DELTA( cimagf(matrixcf_data_transmul_xTx[i]), cimagf(xTx[i]), tol);
+    }
+
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( crealf(matrixcf_data_transmul_xHx[i]), crealf(xHx[i]), tol);
+        CONTEND_DELTA( cimagf(matrixcf_data_transmul_xHx[i]), cimagf(xHx[i]), tol);
+    }
+}
+
+
+
diff --git a/src/matrix/tests/matrixf_autotest.c b/src/matrix/tests/matrixf_autotest.c
new file mode 100644
index 0000000..846895f
--- /dev/null
+++ b/src/matrix/tests/matrixf_autotest.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest data definitions
+#include "src/matrix/tests/matrix_data.h"
+
+// test matrix addition
+void autotest_matrixf_add()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 5 x 4]
+    // z [size: 5 x 4]
+    float z[20];
+    matrixf_add(matrixf_data_add_x,
+                matrixf_data_add_y,
+                z,
+                5, 4);
+
+    unsigned int i;
+    for (i=0; i<20; i++)
+        CONTEND_DELTA( matrixf_data_add_z[i], z[i], tol );
+}
+
+// test matrix augmentation
+void autotest_matrixf_aug()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 5 x 3]
+    // z [size: 5 x 7]
+    float z[35];
+    matrixf_aug(matrixf_data_aug_x, 5, 4,
+                matrixf_data_aug_y, 5, 3,
+                z,                  5, 7);
+    
+    // print result
+    if (liquid_autotest_verbose) {
+        printf("augment:\n");
+        printf("  x: ");        matrixf_print(matrixf_data_aug_x,5,4);
+        printf("  y: ");        matrixf_print(matrixf_data_aug_y,5,3);
+        printf("  expected: "); matrixf_print(matrixf_data_aug_z,5,7);
+        printf("  z: ");        matrixf_print(z,5,3);
+    }
+
+    unsigned int i;
+    for (i=0; i<35; i++)
+        CONTEND_DELTA( matrixf_data_aug_z[i], z[i], tol );
+}
+
+// conjugate gradient solver
+void autotest_matrixf_cgsolve()
+{
+    float tol = 0.01;  // error tolerance
+
+    // A [size: 8 x 8], symmetric positive definite matrx
+    // x [size: 8 x 1]
+    // b [size: 8 x 1]
+    float x[8];
+    matrixf_cgsolve(matrixf_data_cgsolve_A, 8,
+                    matrixf_data_cgsolve_b,
+                    x, NULL);
+
+    if (liquid_autotest_verbose) {
+        printf("cgsolve:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_cgsolve_A, 8, 8);
+        printf("  b: ");        matrixf_print(matrixf_data_cgsolve_b, 8, 1);
+        printf("  expected: "); matrixf_print(matrixf_data_cgsolve_x, 8, 1);
+        printf("  x: ");        matrixf_print(x,                      8, 1);
+    }
+
+    unsigned int i;
+    for (i=0; i<8; i++)
+        CONTEND_DELTA( matrixf_data_cgsolve_x[i], x[i], tol );
+}
+
+// Cholesky decomposition
+void autotest_matrixf_chol()
+{
+    float tol = 1e-4f;  // error tolerance
+
+    // A [size: 4 x 4]
+    // L [size: 4 x 4]
+    float L[16];
+
+    // run decomposition
+    matrixf_chol(matrixf_data_chol_A, 4, L);
+
+    if (liquid_autotest_verbose) {
+        printf("chol:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_chol_A, 4, 4);
+        printf("  expected: "); matrixf_print(matrixf_data_chol_L, 4, 4);
+        printf("  L: ");        matrixf_print(L,                   4, 4);
+    }
+
+    unsigned int i;
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( matrixf_data_chol_L[i], L[i], tol );
+}
+
+// Gram-Schmidt Orthonormalization
+void autotest_matrixf_gramschmidt()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    // A [size: 4 x 3]
+    // V [size: 4 x 3]
+    float V[12];
+    matrixf_gramschmidt(matrixf_data_gramschmidt_A, 4, 3, V);
+
+    if (liquid_autotest_verbose) {
+        printf("gramschmidt:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_gramschmidt_A, 4, 3);
+        printf("  expected: "); matrixf_print(matrixf_data_gramschmidt_V, 4, 3);
+        printf("  V: ");        matrixf_print(V,                          4, 3);
+    }
+
+    unsigned int i;
+    for (i=0; i<12; i++)
+        CONTEND_DELTA( matrixf_data_gramschmidt_V[i], V[i], tol );
+}
+
+
+// matrix inversion
+void autotest_matrixf_inv()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    // x [size: 5 x 5]
+    // y [size: 5 x 5]
+    float y[25];
+    memmove(y, matrixf_data_inv_x, 5*5*sizeof(float));
+    matrixf_inv(y, 5, 5);
+
+    if (liquid_autotest_verbose) {
+        printf("inv:\n");
+        printf("  x: ");        matrixf_print(matrixf_data_inv_x, 5, 5);
+        printf("  expected: "); matrixf_print(matrixf_data_inv_y, 5, 5);
+        printf("  y: ");        matrixf_print(y,                  5, 5);
+    }
+
+    unsigned int i;
+    for (i=0; i<25; i++)
+        CONTEND_DELTA( matrixf_data_inv_y[i], y[i], tol );
+}
+
+// linsolve (solve linear system of equations)
+void autotest_matrixf_linsolve()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    // A [size: 5 x 5]
+    // x [size: 5 x 1]
+    // b [size: 5 x 1]
+    float x[5];
+    
+    // run solver
+    matrixf_linsolve(matrixf_data_linsolve_A, 5,
+                     matrixf_data_linsolve_b,
+                     x, NULL);
+
+    if (liquid_autotest_verbose) {
+        printf("linsolve:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_linsolve_A, 5, 5);
+        printf("  b: ");        matrixf_print(matrixf_data_linsolve_b, 5, 1);
+        printf("  expected: "); matrixf_print(matrixf_data_linsolve_x, 5, 1);
+        printf("  x: ");        matrixf_print(x,                       5, 1);
+    }
+
+    unsigned int i;
+    for (i=0; i<5; i++)
+        CONTEND_DELTA( matrixf_data_linsolve_x[i], x[i], tol );
+}
+
+
+// L/U decomp (Crout)
+void autotest_matrixf_ludecomp_crout()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    float L[64];
+    float U[64];
+    float P[64];
+
+    float LU_test[64];
+
+    // run decomposition
+    matrixf_ludecomp_crout(matrixf_data_ludecomp_A, 8, 8, L, U, P);
+
+    // multiply LU
+    matrixf_mul(L,       8, 8,
+                U,       8, 8,
+                LU_test, 8, 8);
+
+    if (liquid_autotest_verbose) {
+        printf("ludecomp_crout:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_ludecomp_A,8,8);
+        printf("  L: ");        matrixf_print(L,                      8,8);
+        printf("  U: ");        matrixf_print(U,                      8,8);
+        printf("  LU: ");       matrixf_print(LU_test,                8,8);
+    }
+
+    unsigned int r,c;
+    for (r=0; r<8; r++) {
+        for (c=0; c<8; c++) {
+            if (r < c) {
+                CONTEND_DELTA( matrix_access(L,8,8,r,c), 0.0f, tol );
+            } else if (r==c) {
+                CONTEND_DELTA( matrix_access(U,8,8,r,c), 1.0f, tol );
+            } else {
+                CONTEND_DELTA( matrix_access(U,8,8,r,c), 0.0f, tol );
+            }
+        }
+    }
+
+    unsigned int i;
+    for (i=0; i<64; i++)
+        CONTEND_DELTA( matrixf_data_ludecomp_A[i], LU_test[i], tol );
+}
+
+// L/U decomp (Doolittle)
+void autotest_matrixf_ludecomp_doolittle()
+{
+    float tol = 1e-6f;  // error tolerance
+
+    float L[64];
+    float U[64];
+    float P[64];
+
+    float LU_test[64];
+
+    // run decomposition
+    matrixf_ludecomp_doolittle(matrixf_data_ludecomp_A, 8, 8, L, U, P);
+
+    // multiply LU
+    matrixf_mul(L,       8, 8,
+                U,       8, 8,
+                LU_test, 8, 8);
+
+    if (liquid_autotest_verbose) {
+        printf("ludecomp_doolittle:\n");
+        printf("  A: ");        matrixf_print(matrixf_data_ludecomp_A,8,8);
+        printf("  L: ");        matrixf_print(L,                      8,8);
+        printf("  U: ");        matrixf_print(U,                      8,8);
+        printf("  LU: ");       matrixf_print(LU_test,                8,8);
+    }
+
+    unsigned int r,c;
+    for (r=0; r<8; r++) {
+        for (c=0; c<8; c++) {
+            if (r < c) {
+                CONTEND_DELTA( matrix_access(L,8,8,r,c), 0.0f, tol );
+            } else if (r==c) {
+                CONTEND_DELTA( matrix_access(L,8,8,r,c), 1.0f, tol );
+            } else {
+                CONTEND_DELTA( matrix_access(U,8,8,r,c), 0.0f, tol );
+            }
+        }
+    }
+
+    unsigned int i;
+    for (i=0; i<64; i++)
+        CONTEND_DELTA( matrixf_data_ludecomp_A[i], LU_test[i], tol );
+}
+
+// test matrix multiplication
+void autotest_matrixf_mul()
+{
+    float tol = 1e-6f;
+
+    // x [size: 5 x 4]
+    // y [size: 4 x 3]
+    // z [size: 5 x 3]
+    float z[35];
+    matrixf_mul(matrixf_data_mul_x, 5, 4,
+                matrixf_data_mul_y, 4, 3,
+                z,                  5, 3);
+
+    // print result
+    if (liquid_autotest_verbose) {
+        printf("multiplication:\n");
+        printf("  x: ");        matrixf_print(matrixf_data_mul_x,5,4);
+        printf("  y: ");        matrixf_print(matrixf_data_mul_y,4,3);
+        printf("  expected: "); matrixf_print(matrixf_data_mul_z,5,3);
+        printf("  z: ");        matrixf_print(z,5,3);
+    }
+
+    unsigned int i;
+    for (i=0; i<15; i++)
+        CONTEND_DELTA( matrixf_data_mul_z[i], z[i], tol );
+}
+
+// Q/R decomp (Gram-Schmidt)
+void autotest_matrixf_qrdecomp()
+{
+    float tol = 1e-4f;  // error tolerance
+
+    float Q[16];
+    float R[16];
+
+    float QR_test[16];  // Q*R
+    float QQT_test[16]; // Q*Q^T
+
+    // run decomposition
+    matrixf_qrdecomp_gramschmidt(matrixf_data_qrdecomp_A, 4, 4, Q, R);
+
+    // compute Q*R
+    matrixf_mul(Q,       4, 4,
+                R,       4, 4,
+                QR_test, 4, 4);
+
+    // compute Q*Q^T
+    matrixf_mul_transpose(Q, 4, 4, QQT_test);
+
+    if (liquid_autotest_verbose) {
+        printf("qrdecomp_gramschmidt:\n");
+        printf("  A: ");          matrixf_print(matrixf_data_qrdecomp_A,4,4);
+        printf("  Q: ");          matrixf_print(Q,                      4,4);
+        printf("  R: ");          matrixf_print(R,                      4,4);
+        printf("  Q expected: "); matrixf_print(matrixf_data_qrdecomp_Q,4,4);
+        printf("  R expected: "); matrixf_print(matrixf_data_qrdecomp_R,4,4);
+        printf("  QR: ");         matrixf_print(QR_test,                4,4);
+        printf("  QQ: ");         matrixf_print(QQT_test,               4,4);
+    }
+
+    unsigned int i;
+
+    // ensure Q*R = A
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( matrixf_data_qrdecomp_A[i], QR_test[i], tol );
+
+    // ensure Q*Q = I(4)
+    float I4[16];
+    matrixf_eye(I4,4);
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( QQT_test[i], I4[i], tol );
+
+    // ensure Q and R are correct
+    for (i=0; i<16; i++) {
+        CONTEND_DELTA( matrixf_data_qrdecomp_Q[i], Q[i], tol );
+        CONTEND_DELTA( matrixf_data_qrdecomp_R[i], R[i], tol );
+    }
+}
+
+// transpose/multiply
+void autotest_matrixf_transmul()
+{
+    float tol = 1e-4f;  // error tolerance
+
+    float xxT[25];      // [size: 5 x 5]
+    float xxH[25];      // [size: 5 x 5]
+    float xTx[16];      // [size: 4 x 4]
+    float xHx[16];      // [size: 4 x 4]
+
+    // run matrix multiplications
+    matrixf_mul_transpose(matrixf_data_transmul_x, 5, 4, xxT);
+    matrixf_mul_hermitian(matrixf_data_transmul_x, 5, 4, xxH);
+    matrixf_transpose_mul(matrixf_data_transmul_x, 5, 4, xTx);
+    matrixf_hermitian_mul(matrixf_data_transmul_x, 5, 4, xHx);
+
+    if (liquid_autotest_verbose) {
+        printf("transmul:\n");
+        printf("  x: ");            matrixf_print(matrixf_data_transmul_x,  5,4);
+        printf("\n");
+        printf("  xxT: ");          matrixf_print(xxT,                      5,5);
+        printf("  xxT expected: "); matrixf_print(matrixf_data_transmul_xxT,5,5);
+        printf("\n");
+        printf("  xxH: ");          matrixf_print(xxH,                      5,5);
+        printf("  xxH expected: "); matrixf_print(matrixf_data_transmul_xxH,5,5);
+        printf("\n");
+        printf("  xTx: ");          matrixf_print(xTx,                      4,4);
+        printf("  xTx expected: "); matrixf_print(matrixf_data_transmul_xTx,4,4);
+        printf("\n");
+        printf("  xHx: ");          matrixf_print(xHx,                      4,4);
+        printf("  xHx expected: "); matrixf_print(matrixf_data_transmul_xHx,4,4);
+        printf("\n");
+    }
+
+    // run tests
+    unsigned int i;
+
+    for (i=0; i<25; i++)
+        CONTEND_DELTA( matrixf_data_transmul_xxT[i], xxT[i], tol);
+
+    for (i=0; i<25; i++)
+        CONTEND_DELTA( matrixf_data_transmul_xxH[i], xxH[i], tol);
+
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( matrixf_data_transmul_xTx[i], xTx[i], tol);
+
+    for (i=0; i<16; i++)
+        CONTEND_DELTA( matrixf_data_transmul_xHx[i], xHx[i], tol);
+}
+
+
+
diff --git a/src/matrix/tests/smatrixb_autotest.c b/src/matrix/tests/smatrixb_autotest.c
new file mode 100644
index 0000000..0ccaae6
--- /dev/null
+++ b/src/matrix/tests/smatrixb_autotest.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTESTS: basic sparse matrix functionality
+//
+
+// test sparse binary matrix methods
+void autotest_smatrixb_vmul()
+{
+    // A = [
+    //  1   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   1   0   0   0   0   0   0   0   0
+    //  1   0   0   0   0   0   0   0   1   1   0   0
+    //  0   0   1   0   0   0   1   1   0   0   0   0
+    //  0   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   0   0   0   1   0   1   0   1   0
+    //  1   0   1   0   0   0   0   0   0   0   1   1
+    //  0   0   1   0   0   1   1   0   0   0   0   0]
+    //
+    // x = [   1   1   0   0   1   0   0   1   1   1   0   1 ]
+    // y = [   1   0   1   1   0   1   0   0 ]
+    //
+    
+    // create sparse matrix and set values
+    smatrixb A = smatrixb_create(8,12);
+    smatrixb_set(A,0,0,  1);
+    smatrixb_set(A,2,0,  1);
+    smatrixb_set(A,6,0,  1);
+    smatrixb_set(A,3,2,  1);
+    smatrixb_set(A,6,2,  1);
+    smatrixb_set(A,7,2,  1);
+    smatrixb_set(A,1,3,  1);
+    smatrixb_set(A,7,5,  1);
+    smatrixb_set(A,3,6,  1);
+    smatrixb_set(A,5,6,  1);
+    smatrixb_set(A,7,6,  1);
+    smatrixb_set(A,3,7,  1);
+    smatrixb_set(A,2,8,  1);
+    smatrixb_set(A,5,8,  1);
+    smatrixb_set(A,2,9,  1);
+    smatrixb_set(A,5,10, 1);
+    smatrixb_set(A,6,10, 1);
+    smatrixb_set(A,6,11, 1);
+
+    // generate vectors
+    unsigned char x[12]     = {1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1};
+    unsigned char y_test[8] = {1, 0, 1, 1, 0, 1, 0, 0};
+    unsigned char y[8];
+
+    // multiply and run test
+    smatrixb_vmul(A,x,y);
+
+    CONTEND_EQUALITY( y[0], y_test[0] );
+    CONTEND_EQUALITY( y[1], y_test[1] );
+    CONTEND_EQUALITY( y[2], y_test[2] );
+    CONTEND_EQUALITY( y[3], y_test[3] );
+    CONTEND_EQUALITY( y[4], y_test[4] );
+    CONTEND_EQUALITY( y[5], y_test[5] );
+    CONTEND_EQUALITY( y[6], y_test[6] );
+    CONTEND_EQUALITY( y[7], y_test[7] );
+
+    // print results (verbose)
+    if (liquid_autotest_verbose) {
+        printf("\ncompact form:\n");
+        smatrixb_print(A);
+
+        printf("\nexpanded form:\n");
+        smatrixb_print_expanded(A);
+
+        unsigned int i;
+        unsigned int j;
+
+        printf("x = [");
+        for (j=0; j<12; j++) printf("%2u", x[j]);
+        printf(" ];\n");
+
+        printf("y      = [");
+        for (i=0; i<8; i++) printf("%2u", y[i]);
+        printf(" ];\n");
+
+        printf("y_test = [");
+        for (i=0; i<8; i++) printf("%2u", y_test[i]);
+        printf(" ];\n");
+    }
+
+    // destroy matrix object
+    smatrixb_destroy(A);
+}
+
+// test sparse binary matrix multiplication
+void autotest_smatrixb_mul()
+{
+    // a: [8 x 12]
+    unsigned char a_test[96] = {
+        0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
+        0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0};
+
+    // b: [12 x 5]
+    unsigned char b_test[60] = {
+        1, 1, 0, 0, 0,
+        0, 0, 0, 0, 1,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 1,
+        0, 0, 0, 1, 0,
+        0, 0, 0, 1, 0,
+        0, 0, 0, 0, 0,
+        0, 1, 0, 0, 1,
+        1, 0, 0, 1, 0,
+        0, 1, 0, 0, 0};
+
+    // output: [8 x 5]
+    unsigned char c_test[40] = {
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 1, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0,
+        0, 0, 0, 0, 1,
+        0, 0, 0, 1, 0};
+
+    smatrixb a = smatrixb_create_array(a_test,  8,12);
+    smatrixb b = smatrixb_create_array(b_test, 12, 5);
+    smatrixb c = smatrixb_create(8, 5);
+
+    // compute output
+    smatrixb_mul(a,b,c);
+
+    // print results (verbose)
+    if (liquid_autotest_verbose) {
+        printf("a:\n"); smatrixb_print_expanded(a);
+        printf("b:\n"); smatrixb_print_expanded(b);
+        printf("c:\n"); smatrixb_print_expanded(c);
+    }
+
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<8; i++) {
+        for (j=0; j<5; j++) {
+            CONTEND_EQUALITY( smatrixb_get(c,i,j), c_test[i*5+j]);
+        }
+    }
+    
+    // destroy objects
+    smatrixb_destroy(a);
+    smatrixb_destroy(b);
+    smatrixb_destroy(c);
+}
+
+// 
+void autotest_smatrixb_mulf()
+{
+    // A = [
+    //  1   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   1   0   0   0   0   0   0   0   0
+    //  1   0   0   0   0   0   0   0   1   1   0   0
+    //  0   0   1   0   0   0   1   1   0   0   0   0
+    //  0   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   0   0   0   1   0   1   0   1   0
+    //  1   0   1   0   0   0   0   0   0   0   1   1
+    //  0   0   1   0   0   1   1   0   0   0   0   0]
+    //
+    
+    float tol = 1e-6f;
+
+    // create sparse matrix and set values
+    smatrixb A = smatrixb_create(8,12);
+    smatrixb_set(A,0,0,  1);
+    smatrixb_set(A,2,0,  1);
+    smatrixb_set(A,6,0,  1);
+    smatrixb_set(A,3,2,  1);
+    smatrixb_set(A,6,2,  1);
+    smatrixb_set(A,7,2,  1);
+    smatrixb_set(A,1,3,  1);
+    smatrixb_set(A,7,5,  1);
+    smatrixb_set(A,3,6,  1);
+    smatrixb_set(A,5,6,  1);
+    smatrixb_set(A,7,6,  1);
+    smatrixb_set(A,3,7,  1);
+    smatrixb_set(A,2,8,  1);
+    smatrixb_set(A,5,8,  1);
+    smatrixb_set(A,2,9,  1);
+    smatrixb_set(A,5,10, 1);
+    smatrixb_set(A,6,10, 1);
+    smatrixb_set(A,6,11, 1);
+
+    // generate vectors
+    float x[36] = {
+      -4.3,  -0.7,   3.7,
+      -1.7,   2.8,   4.3,
+       2.0,   1.9,   0.6,
+       3.6,   1.0,  -3.7,
+       4.3,   0.7,   2.1,
+       4.6,   0.5,   0.8,
+       1.6,  -3.8,  -0.8,
+      -1.9,  -2.1,   2.8,
+      -1.5,   2.5,   0.8,
+       8.4,   1.5,  -3.1,
+      -5.8,   0.0,   2.5,
+      -4.9,  -2.1,  -1.5};
+
+    float y_test[24] = {
+       -4.3,   -0.7,    3.7,
+        3.6,    1.0,   -3.7,
+        2.6,    3.3,    1.4,
+        1.7,   -4.0,    2.6,
+        0.0,    0.0,    0.0,
+       -5.7,   -1.3,    2.5,
+      -13.0,   -0.9,    5.3,
+        8.2,   -1.4,    0.6};
+
+    float y[24];
+
+    // multiply and run test
+    smatrixb_mulf(A,
+                  x,12,3,
+                  y, 8,3);
+
+    unsigned int i;
+    for (i=0; i<24; i++)
+        CONTEND_DELTA( y[i], y_test[i], tol );
+
+    // print results (verbose)
+    if (liquid_autotest_verbose) {
+        printf("A:\n");
+        smatrixb_print_expanded(A);
+
+        printf("x = [\n");
+        for (i=0; i<36; i++) printf("%6.2f%s", x[i], ((i+1)%3)==0 ? "\n" : "");
+        printf(" ];\n");
+
+        printf("y = [\n");
+        for (i=0; i<24; i++) printf("%6.2f%s", y[i], ((i+1)%3)==0 ? "\n" : "");
+        printf(" ];\n");
+
+        printf("y_test = [\n");
+        for (i=0; i<24; i++) printf("%6.2f%s", y_test[i], ((i+1)%3)==0 ? "\n" : "");
+        printf(" ];\n");
+    }
+
+    // destroy matrix object
+    smatrixb_destroy(A);
+}
+
+// 
+void autotest_smatrixb_vmulf()
+{
+    // A = [
+    //  1   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   1   0   0   0   0   0   0   0   0
+    //  1   0   0   0   0   0   0   0   1   1   0   0
+    //  0   0   1   0   0   0   1   1   0   0   0   0
+    //  0   0   0   0   0   0   0   0   0   0   0   0
+    //  0   0   0   0   0   0   1   0   1   0   1   0
+    //  1   0   1   0   0   0   0   0   0   0   1   1
+    //  0   0   1   0   0   1   1   0   0   0   0   0]
+    //
+    // x = [3.4,-5.7, 0.3, 2.3, 1.9, 3.9, 2.3,-4.0,-0.5, 1.5,-0.6,-1.0]^T
+    // y = [3.4, 2.3, 4.4,-1.4, 0.0, 1.2, 2.1, 6.5]^T
+    //
+    
+    float tol = 1e-6f;
+
+    // create sparse matrix and set values
+    smatrixb A = smatrixb_create(8,12);
+    smatrixb_set(A,0,0,  1);
+    smatrixb_set(A,2,0,  1);
+    smatrixb_set(A,6,0,  1);
+    smatrixb_set(A,3,2,  1);
+    smatrixb_set(A,6,2,  1);
+    smatrixb_set(A,7,2,  1);
+    smatrixb_set(A,1,3,  1);
+    smatrixb_set(A,7,5,  1);
+    smatrixb_set(A,3,6,  1);
+    smatrixb_set(A,5,6,  1);
+    smatrixb_set(A,7,6,  1);
+    smatrixb_set(A,3,7,  1);
+    smatrixb_set(A,2,8,  1);
+    smatrixb_set(A,5,8,  1);
+    smatrixb_set(A,2,9,  1);
+    smatrixb_set(A,5,10, 1);
+    smatrixb_set(A,6,10, 1);
+    smatrixb_set(A,6,11, 1);
+
+    // generate vectors
+    float x[12] = {
+        3.4,  -5.7,   0.3,   2.3,   1.9,   3.9,
+        2.3,  -4.0,  -0.5,   1.5,  -0.6,  -1.0};
+
+    float y_test[8] = {
+        3.4,   2.3,   4.4,  -1.4,   0.0,   1.2,   2.1,   6.5};
+
+    float y[8];
+
+    // multiply and run test
+    smatrixb_vmulf(A,x,y);
+
+    CONTEND_DELTA( y[0], y_test[0], tol );
+    CONTEND_DELTA( y[1], y_test[1], tol );
+    CONTEND_DELTA( y[2], y_test[2], tol );
+    CONTEND_DELTA( y[3], y_test[3], tol );
+    CONTEND_DELTA( y[4], y_test[4], tol );
+    CONTEND_DELTA( y[5], y_test[5], tol );
+    CONTEND_DELTA( y[6], y_test[6], tol );
+    CONTEND_DELTA( y[7], y_test[7], tol );
+
+    // print results (verbose)
+    if (liquid_autotest_verbose) {
+        printf("\ncompact form:\n");
+        smatrixb_print(A);
+
+        printf("\nexpanded form:\n");
+        smatrixb_print_expanded(A);
+
+        unsigned int i;
+        unsigned int j;
+
+        printf("x = [");
+        for (j=0; j<12; j++) printf("%8.4f", x[j]);
+        printf(" ];\n");
+
+        printf("y      = [");
+        for (i=0; i<8; i++) printf("%8.4f", y[i]);
+        printf(" ];\n");
+
+        printf("y_test = [");
+        for (i=0; i<8; i++) printf("%8.4f", y_test[i]);
+        printf(" ];\n");
+    }
+
+    // destroy matrix object
+    smatrixb_destroy(A);
+}
+
diff --git a/src/matrix/tests/smatrixf_autotest.c b/src/matrix/tests/smatrixf_autotest.c
new file mode 100644
index 0000000..099b91e
--- /dev/null
+++ b/src/matrix/tests/smatrixf_autotest.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTESTS: basic sparse matrix functionality
+//
+
+// test sparse floating-point vector multiplication
+void autotest_smatrixf_vmul()
+{
+    float tol = 1e-6f;
+
+    // A = [
+    //  0 0 0 0 4
+    //  0 0 0 0 0
+    //  0 0 0 3 0
+    //  2 0 0 0 1
+
+    // create sparse matrix and set values
+    smatrixf A = smatrixf_create(4, 5);
+    smatrixf_set(A, 0,4, 4);
+    smatrixf_set(A, 2,3, 3);
+    smatrixf_set(A, 3,0, 2);
+    smatrixf_set(A, 3,4, 0);
+    smatrixf_set(A, 3,4, 1);
+
+    // initialize input vector
+    float x[5] = {7, 1, 5, 2, 2};
+
+    float y_test[4] = {8, 0, 6, 16};
+    float y[4];
+
+    // multiply and run test
+    smatrixf_vmul(A,x,y);
+
+    // check values
+    CONTEND_DELTA( y[0], y_test[0], tol );
+    CONTEND_DELTA( y[1], y_test[1], tol );
+    CONTEND_DELTA( y[2], y_test[2], tol );
+    CONTEND_DELTA( y[3], y_test[3], tol );
+
+    smatrixf_destroy(A);
+}
+// test sparse floating-point matrix multiplication
+void autotest_smatrixf_mul()
+{
+    float tol = 1e-6f;
+
+    // intialize matrices
+    smatrixf a = smatrixf_create(4, 5);
+    smatrixf b = smatrixf_create(5, 3);
+    smatrixf c = smatrixf_create(4, 3);
+
+    // initialize 'a'
+    // 0 0 0 0 4
+    // 0 0 0 0 0
+    // 0 0 0 3 0
+    // 2 0 0 0 1
+    smatrixf_set(a, 0,4, 4);
+    smatrixf_set(a, 2,3, 3);
+    smatrixf_set(a, 3,0, 2);
+    smatrixf_set(a, 3,4, 0);
+    smatrixf_set(a, 3,4, 1);
+
+    // initialize 'b'
+    // 7 6 0
+    // 0 0 0
+    // 0 0 0
+    // 0 5 0
+    // 2 0 0
+    smatrixf_set(b, 0,0, 7);
+    smatrixf_set(b, 0,1, 6);
+    smatrixf_set(b, 3,1, 5);
+    smatrixf_set(b, 4,0, 2);
+
+    // compute 'c'
+    //  8   0   0
+    //  0   0   0
+    //  0  15   0
+    // 16  12   0
+    smatrixf_mul(a,b,c);
+
+    float c_test[12] = {
+         8,   0,   0,
+         0,   0,   0,
+         0,  15,   0,
+        16,  12,   0};
+
+    // check values
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<4; i++) {
+        for (j=0; j<3; j++) {
+            CONTEND_DELTA(smatrixf_get(c,i,j),
+                          matrixf_access(c_test,4,3,i,j),
+                          tol);
+        }
+    }
+
+    smatrixf_destroy(a);
+    smatrixf_destroy(b);
+    smatrixf_destroy(c);
+}
diff --git a/src/matrix/tests/smatrixi_autotest.c b/src/matrix/tests/smatrixi_autotest.c
new file mode 100644
index 0000000..1f5cd9d
--- /dev/null
+++ b/src/matrix/tests/smatrixi_autotest.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTESTS: basic sparse matrix functionality
+//
+
+// test sparse integer vector multiplication
+void autotest_smatrixi_vmul()
+{
+    // A = [
+    //  0 0 0 0 4
+    //  0 0 0 0 0
+    //  0 0 0 3 0
+    //  2 0 0 0 1
+
+    // create sparse matrix and set values
+    smatrixi A = smatrixi_create(4, 5);
+    smatrixi_set(A, 0,4, 4);
+    smatrixi_set(A, 2,3, 3);
+    smatrixi_set(A, 3,0, 2);
+    smatrixi_set(A, 3,4, 0);
+    smatrixi_set(A, 3,4, 1);
+
+    // initialize input vector
+    short int x[5] = {7, 1, 5, 2, 2};
+
+    short int y_test[4] = {8, 0, 6, 16};
+    short int y[4];
+
+    // multiply and run test
+    smatrixi_vmul(A,x,y);
+
+    // check values
+    CONTEND_EQUALITY( y[0], y_test[0] );
+    CONTEND_EQUALITY( y[1], y_test[1] );
+    CONTEND_EQUALITY( y[2], y_test[2] );
+    CONTEND_EQUALITY( y[3], y_test[3] );
+
+    smatrixi_destroy(A);
+}
+
+// test sparse integer matrix multiplication
+void autotest_smatrixi_mul()
+{
+    // intialize matrices
+    smatrixi a = smatrixi_create(4, 5);
+    smatrixi b = smatrixi_create(5, 3);
+    smatrixi c = smatrixi_create(4, 3);
+
+    // initialize 'a'
+    // 0 0 0 0 4
+    // 0 0 0 0 0
+    // 0 0 0 3 0
+    // 2 0 0 0 1
+    smatrixi_set(a, 0,4, 4);
+    smatrixi_set(a, 2,3, 3);
+    smatrixi_set(a, 3,0, 2);
+    smatrixi_set(a, 3,4, 0);
+    smatrixi_set(a, 3,4, 1);
+
+    // initialize 'b'
+    // 7 6 0
+    // 0 0 0
+    // 0 0 0
+    // 0 5 0
+    // 2 0 0
+    smatrixi_set(b, 0,0, 7);
+    smatrixi_set(b, 0,1, 6);
+    smatrixi_set(b, 3,1, 5);
+    smatrixi_set(b, 4,0, 2);
+
+    // compute 'c'
+    //  8   0   0
+    //  0   0   0
+    //  0  15   0
+    // 16  12   0
+    smatrixi_mul(a,b,c);
+
+    short int c_test[12] = {
+         8,   0,   0,
+         0,   0,   0,
+         0,  15,   0,
+        16,  12,   0};
+
+    // check values
+    unsigned int i;
+    unsigned int j;
+    for (i=0; i<4; i++) {
+        for (j=0; j<3; j++) {
+            CONTEND_EQUALITY(smatrixi_get(c,i,j),
+                             matrixf_access(c_test,4,3,i,j))
+        }
+    }
+
+    smatrixi_destroy(a);
+    smatrixi_destroy(b);
+    smatrixi_destroy(c);
+}
diff --git a/src/modem/bench/freqdem_benchmark.c b/src/modem/bench/freqdem_benchmark.c
new file mode 100644
index 0000000..11c2d54
--- /dev/null
+++ b/src/modem/bench/freqdem_benchmark.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+// frequency demodulator benchmark
+void benchmark_freqdem(struct rusage *     _start,
+                       struct rusage *     _finish,
+                       unsigned long int * _num_iterations)
+{
+    // create demodulator
+    float   kf  = 0.05f; // modulation index
+    freqdem dem = freqdem_create(kf);
+
+    float complex r[20];    // modulated signal
+    float         m[20];    // message signal
+
+    unsigned long int i;
+
+    // generate modulated signal
+    for (i=0; i<20; i++)
+        r[i] = 0.3f*cexpf(_Complex_I*2*M_PI*i/20.0f);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        freqdem_demodulate(dem, r[ 0], &m[ 0]);
+        freqdem_demodulate(dem, r[ 1], &m[ 1]);
+        freqdem_demodulate(dem, r[ 2], &m[ 2]);
+        freqdem_demodulate(dem, r[ 3], &m[ 3]);
+        freqdem_demodulate(dem, r[ 4], &m[ 4]);
+        freqdem_demodulate(dem, r[ 5], &m[ 5]);
+        freqdem_demodulate(dem, r[ 6], &m[ 6]);
+        freqdem_demodulate(dem, r[ 7], &m[ 7]);
+        freqdem_demodulate(dem, r[ 8], &m[ 8]);
+        freqdem_demodulate(dem, r[ 9], &m[ 9]);
+        freqdem_demodulate(dem, r[10], &m[10]);
+        freqdem_demodulate(dem, r[11], &m[11]);
+        freqdem_demodulate(dem, r[12], &m[12]);
+        freqdem_demodulate(dem, r[13], &m[13]);
+        freqdem_demodulate(dem, r[14], &m[14]);
+        freqdem_demodulate(dem, r[15], &m[15]);
+        freqdem_demodulate(dem, r[16], &m[16]);
+        freqdem_demodulate(dem, r[17], &m[17]);
+        freqdem_demodulate(dem, r[18], &m[18]);
+        freqdem_demodulate(dem, r[19], &m[19]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 20;
+
+    // destroy demodulator
+    freqdem_destroy(dem);
+}
+
+
diff --git a/src/modem/bench/freqmod_benchmark.c b/src/modem/bench/freqmod_benchmark.c
new file mode 100644
index 0000000..a0eb376
--- /dev/null
+++ b/src/modem/bench/freqmod_benchmark.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+// frequency modulator benchmark
+void benchmark_freqmod(struct rusage *     _start,
+                       struct rusage *     _finish,
+                       unsigned long int * _num_iterations)
+{
+    // create modulator
+    float   kf  = 0.05f; // modulation index
+    freqmod mod = freqmod_create(kf);
+
+    float         m[20];    // message signal
+    float complex r[20];    // modulated signal
+
+    unsigned long int i;
+
+    // generate message signal (sum of sines)
+    for (i=0; i<20; i++) {
+        m[i] = 0.3f*cosf(2*M_PI*1*i/20.0f + 0.0f) +
+               0.2f*cosf(2*M_PI*2*i/20.0f + 0.4f) +
+               0.4f*cosf(2*M_PI*3*i/20.0f + 1.7f);
+    }
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        freqmod_modulate(mod, m[ 0], &r[ 0]);
+        freqmod_modulate(mod, m[ 1], &r[ 1]);
+        freqmod_modulate(mod, m[ 2], &r[ 2]);
+        freqmod_modulate(mod, m[ 3], &r[ 3]);
+        freqmod_modulate(mod, m[ 4], &r[ 4]);
+        freqmod_modulate(mod, m[ 5], &r[ 5]);
+        freqmod_modulate(mod, m[ 6], &r[ 6]);
+        freqmod_modulate(mod, m[ 7], &r[ 7]);
+        freqmod_modulate(mod, m[ 8], &r[ 8]);
+        freqmod_modulate(mod, m[ 9], &r[ 9]);
+        freqmod_modulate(mod, m[10], &r[10]);
+        freqmod_modulate(mod, m[11], &r[11]);
+        freqmod_modulate(mod, m[12], &r[12]);
+        freqmod_modulate(mod, m[13], &r[13]);
+        freqmod_modulate(mod, m[14], &r[14]);
+        freqmod_modulate(mod, m[15], &r[15]);
+        freqmod_modulate(mod, m[16], &r[16]);
+        freqmod_modulate(mod, m[17], &r[17]);
+        freqmod_modulate(mod, m[18], &r[18]);
+        freqmod_modulate(mod, m[19], &r[19]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 20;
+
+    // destroy modulator
+    freqmod_destroy(mod);
+}
+
+
diff --git a/src/modem/bench/fskdem_benchmark.c b/src/modem/bench/fskdem_benchmark.c
new file mode 100644
index 0000000..50e9ea8
--- /dev/null
+++ b/src/modem/bench/fskdem_benchmark.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define FSKDEM_BENCH_API(m,k,bandwidth)     \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ fskdem_bench(_start, _finish, _num_iterations, m, k, bandwidth); }
+
+// Helper function to keep code base small
+void fskdem_bench(struct rusage *     _start,
+                  struct rusage *     _finish,
+                  unsigned long int * _num_iterations,
+                  unsigned int        _m,
+                  unsigned int        _k,
+                  float               _bandwidth)
+{
+    // normalize number of iterations
+    *_num_iterations /= _k;
+
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // initialize demodulator
+    fskdem dem = fskdem_create(_m,_k,_bandwidth);
+
+    //unsigned int M = 1 << _m;   // constellation size
+    
+    unsigned long int i;
+
+    // generate input vector to demodulate (spiral)
+    float complex buf[_k+10];
+    for (i=0; i<_k+10; i++)
+        buf[i] = 0.07 * i * cexpf(_Complex_I*2*M_PI*0.1*i);
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fskdem_demodulate(dem, &buf[0]);
+        fskdem_demodulate(dem, &buf[1]);
+        fskdem_demodulate(dem, &buf[2]);
+        fskdem_demodulate(dem, &buf[3]);
+        fskdem_demodulate(dem, &buf[4]);
+        fskdem_demodulate(dem, &buf[5]);
+        fskdem_demodulate(dem, &buf[6]);
+        fskdem_demodulate(dem, &buf[7]);
+        fskdem_demodulate(dem, &buf[8]);
+        fskdem_demodulate(dem, &buf[9]);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 10;
+
+    fskdem_destroy(dem);
+}
+
+// BENCHMARKS: basic properties: M=2^m, k = 2*M, bandwidth = 0.25
+void benchmark_fskdem_norm_M2      FSKDEM_BENCH_API( 1,    4, 0.25f    )
+void benchmark_fskdem_norm_M4      FSKDEM_BENCH_API( 2,    8, 0.25f    )
+void benchmark_fskdem_norm_M8      FSKDEM_BENCH_API( 3,   16, 0.25f    )
+void benchmark_fskdem_norm_M16     FSKDEM_BENCH_API( 4,   32, 0.25f    )
+void benchmark_fskdem_norm_M32     FSKDEM_BENCH_API( 5,   64, 0.25f    )
+void benchmark_fskdem_norm_M64     FSKDEM_BENCH_API( 6,  128, 0.25f    )
+void benchmark_fskdem_norm_M128    FSKDEM_BENCH_API( 7,  256, 0.25f    )
+void benchmark_fskdem_norm_M256    FSKDEM_BENCH_API( 8,  512, 0.25f    )
+void benchmark_fskdem_norm_M512    FSKDEM_BENCH_API( 9, 1024, 0.25f    )
+void benchmark_fskdem_norm_M1024   FSKDEM_BENCH_API(10, 2048, 0.25f    )
+
+// BENCHMARKS: obscure properties: M=2^m, k not relative to M, bandwidth basically irrational
+void benchmark_fskdem_misc_M2      FSKDEM_BENCH_API( 1,    5, 0.3721451)
+void benchmark_fskdem_misc_M4      FSKDEM_BENCH_API( 2,   10, 0.3721451)
+void benchmark_fskdem_misc_M8      FSKDEM_BENCH_API( 3,   20, 0.3721451)
+void benchmark_fskdem_misc_M16     FSKDEM_BENCH_API( 4,   30, 0.3721451)
+void benchmark_fskdem_misc_M32     FSKDEM_BENCH_API( 5,   60, 0.3721451)
+void benchmark_fskdem_misc_M64     FSKDEM_BENCH_API( 6,  100, 0.3721451)
+void benchmark_fskdem_misc_M128    FSKDEM_BENCH_API( 7,  200, 0.3721451)
+void benchmark_fskdem_misc_M256    FSKDEM_BENCH_API( 8,  500, 0.3721451)
+void benchmark_fskdem_misc_M512    FSKDEM_BENCH_API( 9, 1000, 0.3721451)
+void benchmark_fskdem_misc_M1024   FSKDEM_BENCH_API(10, 2000, 0.3721451)
+
diff --git a/src/modem/bench/fskmod_benchmark.c b/src/modem/bench/fskmod_benchmark.c
new file mode 100644
index 0000000..58184d2
--- /dev/null
+++ b/src/modem/bench/fskmod_benchmark.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define FSKMOD_BENCH_API(m,k,bandwidth)     \
+(   struct rusage *     _start,             \
+    struct rusage *     _finish,            \
+    unsigned long int * _num_iterations)    \
+{ fskmod_bench(_start, _finish, _num_iterations, m, k, bandwidth); }
+
+// Helper function to keep code base small
+void fskmod_bench(struct rusage *     _start,
+                  struct rusage *     _finish,
+                  unsigned long int * _num_iterations,
+                  unsigned int        _m,
+                  unsigned int        _k,
+                  float               _bandwidth)
+{
+    // normalize number of iterations
+    *_num_iterations /= _k;
+
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // initialize modulator
+    fskmod mod = fskmod_create(_m,_k,_bandwidth);
+
+    unsigned int M = 1 << _m;   // constellation size
+    float complex buf[_k];      // transmit buffer
+    
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        fskmod_modulate(mod, (i+ 0) % M, buf);
+        fskmod_modulate(mod, (i+ 1) % M, buf);
+        fskmod_modulate(mod, (i+ 2) % M, buf);
+        fskmod_modulate(mod, (i+ 3) % M, buf);
+        fskmod_modulate(mod, (i+ 4) % M, buf);
+        fskmod_modulate(mod, (i+ 5) % M, buf);
+        fskmod_modulate(mod, (i+ 6) % M, buf);
+        fskmod_modulate(mod, (i+ 7) % M, buf);
+        fskmod_modulate(mod, (i+ 8) % M, buf);
+        fskmod_modulate(mod, (i+ 9) % M, buf);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 10;
+
+    fskmod_destroy(mod);
+}
+
+// BENCHMARKS: basic properties: M=2^m, k = 2*M, bandwidth = 0.25
+void benchmark_fskmod_norm_M2      FSKMOD_BENCH_API( 1,    4, 0.25f    )
+void benchmark_fskmod_norm_M4      FSKMOD_BENCH_API( 2,    8, 0.25f    )
+void benchmark_fskmod_norm_M8      FSKMOD_BENCH_API( 3,   16, 0.25f    )
+void benchmark_fskmod_norm_M16     FSKMOD_BENCH_API( 4,   32, 0.25f    )
+void benchmark_fskmod_norm_M32     FSKMOD_BENCH_API( 5,   64, 0.25f    )
+void benchmark_fskmod_norm_M64     FSKMOD_BENCH_API( 6,  128, 0.25f    )
+void benchmark_fskmod_norm_M128    FSKMOD_BENCH_API( 7,  256, 0.25f    )
+void benchmark_fskmod_norm_M256    FSKMOD_BENCH_API( 8,  512, 0.25f    )
+void benchmark_fskmod_norm_M512    FSKMOD_BENCH_API( 9, 1024, 0.25f    )
+void benchmark_fskmod_norm_M1024   FSKMOD_BENCH_API(10, 2048, 0.25f    )
+
+// BENCHMARKS: obscure properties: M=2^m, k not relative to M, bandwidth basically irrational
+void benchmark_fskmod_misc_M2      FSKMOD_BENCH_API( 1,    5, 0.3721451)
+void benchmark_fskmod_misc_M4      FSKMOD_BENCH_API( 2,   10, 0.3721451)
+void benchmark_fskmod_misc_M8      FSKMOD_BENCH_API( 3,   20, 0.3721451)
+void benchmark_fskmod_misc_M16     FSKMOD_BENCH_API( 4,   30, 0.3721451)
+void benchmark_fskmod_misc_M32     FSKMOD_BENCH_API( 5,   60, 0.3721451)
+void benchmark_fskmod_misc_M64     FSKMOD_BENCH_API( 6,  100, 0.3721451)
+void benchmark_fskmod_misc_M128    FSKMOD_BENCH_API( 7,  200, 0.3721451)
+void benchmark_fskmod_misc_M256    FSKMOD_BENCH_API( 8,  500, 0.3721451)
+void benchmark_fskmod_misc_M512    FSKMOD_BENCH_API( 9, 1000, 0.3721451)
+void benchmark_fskmod_misc_M1024   FSKMOD_BENCH_API(10, 2000, 0.3721451)
+
diff --git a/src/modem/bench/gmskmodem_benchmark.c b/src/modem/bench/gmskmodem_benchmark.c
new file mode 100644
index 0000000..55f1527
--- /dev/null
+++ b/src/modem/bench/gmskmodem_benchmark.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+// 
+void benchmark_gmskmodem_modulate(struct rusage *_start,
+                                  struct rusage *_finish,
+                                  unsigned long int *_num_iterations)
+{
+    // options
+    unsigned int k=2;   // filter samples/symbol
+    unsigned int m=3;   // filter delay (symbols)
+    float BT=0.3f;      // bandwidth-time product
+
+    // create modem object
+    gmskmod mod   = gmskmod_create(k, m, BT);
+
+    float complex x[k];
+    unsigned int symbol_in = 0;
+    
+    unsigned long int i;
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        gmskmod_modulate(mod, symbol_in, x);
+        gmskmod_modulate(mod, symbol_in, x);
+        gmskmod_modulate(mod, symbol_in, x);
+        gmskmod_modulate(mod, symbol_in, x);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy modem objects
+    gmskmod_destroy(mod);
+}
+
+// 
+void benchmark_gmskmodem_demodulate(struct rusage *_start,
+                                    struct rusage *_finish,
+                                    unsigned long int *_num_iterations)
+{
+    // options
+    unsigned int k=2;   // filter samples/symbol
+    unsigned int m=3;   // filter delay (symbols)
+    float BT=0.3f;      // bandwidth-time product
+
+    // create modem object
+    gmskdem demod = gmskdem_create(k, m, BT);
+
+    float complex x[k];
+    unsigned int symbol_out = 0;
+    
+    unsigned long int i;
+    for (i=0; i<k; i++)
+        x[i] = randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        gmskdem_demodulate(demod, x, &symbol_out);
+        gmskdem_demodulate(demod, x, &symbol_out);
+        gmskdem_demodulate(demod, x, &symbol_out);
+        gmskdem_demodulate(demod, x, &symbol_out);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy modem objects
+    gmskdem_destroy(demod);
+}
+
diff --git a/src/modem/bench/modem_demodsoft_benchmark.c b/src/modem/bench/modem_demodsoft_benchmark.c
new file mode 100644
index 0000000..bd4043d
--- /dev/null
+++ b/src/modem/bench/modem_demodsoft_benchmark.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define MODEM_DEMODSOFT_BENCH_API(MS)   \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ modem_demodulate_soft_bench(_start, _finish, _num_iterations, MS); }
+
+// Helper function to keep code base small
+void modem_demodulate_soft_bench(struct rusage *_start,
+                                 struct rusage *_finish,
+                                 unsigned long int *_num_iterations,
+                                 modulation_scheme _ms)
+{
+    // normalize number of iterations
+    switch (_ms) {
+    case LIQUID_MODEM_UNKNOWN:
+        fprintf(stderr,"error: modem_modulate_bench(), unknown modem scheme\n");
+        exit(1);
+    case LIQUID_MODEM_BPSK:     *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_QPSK:     *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_OOK:      *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_SQAM32:   *_num_iterations /= 10;     break;
+    case LIQUID_MODEM_SQAM128:  *_num_iterations /= 20;     break;
+    case LIQUID_MODEM_V29:      *_num_iterations /= 16;     break;
+    case LIQUID_MODEM_ARB16OPT: *_num_iterations /= 16;     break;
+    case LIQUID_MODEM_ARB32OPT: *_num_iterations /= 32;     break;
+    case LIQUID_MODEM_ARB64VT:  *_num_iterations /= 64;     break;
+    default:;
+        *_num_iterations /= 8;
+    }
+
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+
+    // initialize modulator
+    modem demod = modem_create(_ms);
+    unsigned int bps = modem_get_bps(demod);
+
+    unsigned long int i;
+
+    // generate input vector to demodulate (spiral)
+    float complex x[20];
+    for (i=0; i<20; i++)
+        x[i] = 0.07 * i * cexpf(_Complex_I*2*M_PI*0.1*i);
+
+    unsigned int symbol_out;
+    unsigned char soft_bits[bps];
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        modem_demodulate_soft(demod, x[ 0], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 1], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 2], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 3], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 4], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 5], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 6], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 7], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 8], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[ 9], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[10], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[11], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[12], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[13], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[14], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[15], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[16], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[17], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[18], &symbol_out, soft_bits);
+        modem_demodulate_soft(demod, x[19], &symbol_out, soft_bits);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 20;
+
+    modem_destroy(demod);
+}
+
+// specific modems
+void benchmark_demodsoft_bpsk    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_BPSK)
+void benchmark_demodsoft_qpsk    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QPSK)
+void benchmark_demodsoft_ook     MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_OOK)
+void benchmark_demodsoft_sqam32  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_SQAM32)
+void benchmark_demodsoft_sqam128 MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_SQAM128)
+
+// ASK
+void benchmark_demodsoft_ask2    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ASK2)
+void benchmark_demodsoft_ask4    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ASK4)
+void benchmark_demodsoft_ask8    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ASK8)
+void benchmark_demodsoft_ask16   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ASK16)
+
+// PSK
+void benchmark_demodsoft_psk2    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK2)
+void benchmark_demodsoft_psk4    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK4)
+void benchmark_demodsoft_psk8    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK8)
+void benchmark_demodsoft_psk16   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK16)
+void benchmark_demodsoft_psk32   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK32)
+void benchmark_demodsoft_psk64   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_PSK64)
+
+// Differential PSK
+void benchmark_demodsoft_dpsk2   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK2)
+void benchmark_demodsoft_dpsk4   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK4)
+void benchmark_demodsoft_dpsk8   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK8)
+void benchmark_demodsoft_dpsk16  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK16)
+void benchmark_demodsoft_dpsk32  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK32)
+void benchmark_demodsoft_dpsk64  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_DPSK64)
+
+// QAM
+void benchmark_demodsoft_qam4    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM4)
+void benchmark_demodsoft_qam8    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM8)
+void benchmark_demodsoft_qam16   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM16)
+void benchmark_demodsoft_qam32   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM32)
+void benchmark_demodsoft_qam64   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM64)
+void benchmark_demodsoft_qam128  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM128)
+void benchmark_demodsoft_qam256  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_QAM256)
+
+// APSK
+void benchmark_demodsoft_apsk4   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK4)
+void benchmark_demodsoft_apsk8   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK8)
+void benchmark_demodsoft_apsk16  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK16)
+void benchmark_demodsoft_apsk32  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK32)
+void benchmark_demodsoft_apsk64  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK64)
+void benchmark_demodsoft_apsk128 MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK128)
+void benchmark_demodsoft_apsk256 MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_APSK256)
+
+// ARB
+void benchmark_demodsoft_arbV29    MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_V29)
+void benchmark_demodsoft_arb16opt  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB16OPT)
+void benchmark_demodsoft_arb32opt  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB32OPT)
+void benchmark_demodsoft_arb64opt  MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB64OPT)
+void benchmark_demodsoft_arb128opt MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB128OPT)
+void benchmark_demodsoft_arb256opt MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB256OPT)
+void benchmark_demodsoft_arb64vt   MODEM_DEMODSOFT_BENCH_API(LIQUID_MODEM_ARB64VT)
+
diff --git a/src/modem/bench/modem_demodulate_benchmark.c b/src/modem/bench/modem_demodulate_benchmark.c
new file mode 100644
index 0000000..4897dc7
--- /dev/null
+++ b/src/modem/bench/modem_demodulate_benchmark.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define MODEM_DEMODULATE_BENCH_API(MS)  \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ modem_demodulate_bench(_start, _finish, _num_iterations, MS); }
+
+// Helper function to keep code base small
+void modem_demodulate_bench(struct rusage *_start,
+                            struct rusage *_finish,
+                            unsigned long int *_num_iterations,
+                            modulation_scheme _ms)
+{
+    // normalize number of iterations
+    switch (_ms) {
+    case LIQUID_MODEM_UNKNOWN:
+        fprintf(stderr,"error: modem_demodulate_bench(), unknown modem scheme\n");
+        exit(1);
+    case LIQUID_MODEM_BPSK:     *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_QPSK:     *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_OOK:      *_num_iterations *= 2;      break;
+    case LIQUID_MODEM_SQAM32:   *_num_iterations /= 10;     break;
+    case LIQUID_MODEM_SQAM128:  *_num_iterations /= 20;     break;
+    case LIQUID_MODEM_V29:      *_num_iterations /= 16;     break;
+    case LIQUID_MODEM_ARB16OPT: *_num_iterations /= 8;      break;
+    case LIQUID_MODEM_ARB32OPT: *_num_iterations /= 16;     break;
+    case LIQUID_MODEM_ARB64OPT: *_num_iterations /= 32;     break;
+    case LIQUID_MODEM_ARB128OPT: *_num_iterations /= 64;    break;
+    case LIQUID_MODEM_ARB256OPT: *_num_iterations /= 128;   break;
+    case LIQUID_MODEM_ARB64VT:  *_num_iterations /= 64;     break;
+    default:;
+        *_num_iterations /= 8;
+    }
+
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+
+    // initialize modulator
+    modem demod = modem_create(_ms);
+
+    unsigned long int i;
+
+    // generate input vector to demodulate (spiral)
+    float complex x[20];
+    for (i=0; i<20; i++)
+        x[i] = 0.07 * i * cexpf(_Complex_I*2*M_PI*0.1*i);
+
+    unsigned int symbol_out;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        modem_demodulate(demod, x[ 0], &symbol_out);
+        modem_demodulate(demod, x[ 1], &symbol_out);
+        modem_demodulate(demod, x[ 2], &symbol_out);
+        modem_demodulate(demod, x[ 3], &symbol_out);
+        modem_demodulate(demod, x[ 4], &symbol_out);
+        modem_demodulate(demod, x[ 5], &symbol_out);
+        modem_demodulate(demod, x[ 6], &symbol_out);
+        modem_demodulate(demod, x[ 7], &symbol_out);
+        modem_demodulate(demod, x[ 8], &symbol_out);
+        modem_demodulate(demod, x[ 9], &symbol_out);
+        modem_demodulate(demod, x[10], &symbol_out);
+        modem_demodulate(demod, x[11], &symbol_out);
+        modem_demodulate(demod, x[12], &symbol_out);
+        modem_demodulate(demod, x[13], &symbol_out);
+        modem_demodulate(demod, x[14], &symbol_out);
+        modem_demodulate(demod, x[15], &symbol_out);
+        modem_demodulate(demod, x[16], &symbol_out);
+        modem_demodulate(demod, x[17], &symbol_out);
+        modem_demodulate(demod, x[18], &symbol_out);
+        modem_demodulate(demod, x[19], &symbol_out);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 20;
+
+    modem_destroy(demod);
+}
+
+// specific modems
+void benchmark_demodulate_bpsk    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_BPSK)
+void benchmark_demodulate_qpsk    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QPSK)
+void benchmark_demodulate_ook     MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_OOK)
+void benchmark_demodulate_sqam32  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_SQAM32)
+void benchmark_demodulate_sqam128 MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_SQAM128)
+
+// ASK
+void benchmark_demodulate_ask2    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ASK2)
+void benchmark_demodulate_ask4    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ASK4)
+void benchmark_demodulate_ask8    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ASK8)
+void benchmark_demodulate_ask16   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ASK16)
+
+// PSK
+void benchmark_demodulate_psk2    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK2)
+void benchmark_demodulate_psk4    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK4)
+void benchmark_demodulate_psk8    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK8)
+void benchmark_demodulate_psk16   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK16)
+void benchmark_demodulate_psk32   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK32)
+void benchmark_demodulate_psk64   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_PSK64)
+
+// Differential PSK
+void benchmark_demodulate_dpsk2   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK2)
+void benchmark_demodulate_dpsk4   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK4)
+void benchmark_demodulate_dpsk8   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK8)
+void benchmark_demodulate_dpsk16  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK16)
+void benchmark_demodulate_dpsk32  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK32)
+void benchmark_demodulate_dpsk64  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_DPSK64)
+
+// QAM
+void benchmark_demodulate_qam4    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM4)
+void benchmark_demodulate_qam8    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM8)
+void benchmark_demodulate_qam16   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM16)
+void benchmark_demodulate_qam32   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM32)
+void benchmark_demodulate_qam64   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM64)
+void benchmark_demodulate_qam128  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM128)
+void benchmark_demodulate_qam256  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_QAM256)
+
+// APSK
+void benchmark_demodulate_apsk4   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK4)
+void benchmark_demodulate_apsk8   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK8)
+void benchmark_demodulate_apsk16  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK16)
+void benchmark_demodulate_apsk32  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK32)
+void benchmark_demodulate_apsk64  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK64)
+void benchmark_demodulate_apsk128 MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK128)
+void benchmark_demodulate_apsk256 MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_APSK256)
+
+// ARB
+void benchmark_demodulate_arbV29    MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_V29)
+void benchmark_demodulate_arb16opt  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB16OPT)
+void benchmark_demodulate_arb32opt  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB32OPT)
+void benchmark_demodulate_arb64opt  MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB64OPT)
+void benchmark_demodulate_arb128opt MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB128OPT)
+void benchmark_demodulate_arb256opt MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB256OPT)
+void benchmark_demodulate_arb64vt   MODEM_DEMODULATE_BENCH_API(LIQUID_MODEM_ARB64VT)
+
diff --git a/src/modem/bench/modem_modulate_benchmark.c b/src/modem/bench/modem_modulate_benchmark.c
new file mode 100644
index 0000000..a8577f4
--- /dev/null
+++ b/src/modem/bench/modem_modulate_benchmark.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define MODEM_MODULATE_BENCH_API(MS)    \
+(   struct rusage *_start,              \
+    struct rusage *_finish,             \
+    unsigned long int *_num_iterations) \
+{ modem_modulate_bench(_start, _finish, _num_iterations, MS); }
+
+// Helper function to keep code base small
+void modem_modulate_bench(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations,
+                          modulation_scheme _ms)
+{
+    // normalize number of iterations
+    switch (_ms) {
+    case LIQUID_MODEM_UNKNOWN:
+        fprintf(stderr,"error: modem_modulate_bench(), unknown modem scheme\n");
+        exit(1);
+    case LIQUID_MODEM_BPSK:     *_num_iterations *= 64;     break;
+    case LIQUID_MODEM_QPSK:     *_num_iterations *= 64;     break;
+    case LIQUID_MODEM_OOK:      *_num_iterations *= 64;     break;
+    case LIQUID_MODEM_SQAM32:   *_num_iterations *= 16;     break;
+    case LIQUID_MODEM_SQAM128:  *_num_iterations *= 16;     break;
+    case LIQUID_MODEM_V29:      *_num_iterations *= 100;    break;
+    case LIQUID_MODEM_ARB16OPT: *_num_iterations *= 100;    break;
+    case LIQUID_MODEM_ARB32OPT: *_num_iterations *= 100;    break;
+    case LIQUID_MODEM_ARB64OPT: *_num_iterations *= 100;    break;
+    case LIQUID_MODEM_ARB128OPT: *_num_iterations *= 100;   break;
+    case LIQUID_MODEM_ARB256OPT: *_num_iterations *= 100;   break;
+    case LIQUID_MODEM_ARB64VT:  *_num_iterations *= 100;    break;
+    default:;
+        *_num_iterations *= 32;
+    }
+
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+
+    // initialize modulator
+    modem mod = modem_create(_ms);
+
+    float complex x;
+    unsigned int symbol_in = 0;
+    
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        modem_modulate(mod, symbol_in, &x);
+        modem_modulate(mod, symbol_in, &x);
+        modem_modulate(mod, symbol_in, &x);
+        modem_modulate(mod, symbol_in, &x);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    modem_destroy(mod);
+}
+
+// specific modems
+void benchmark_modulate_bpsk    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_BPSK)
+void benchmark_modulate_qpsk    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QPSK)
+void benchmark_modulate_ook     MODEM_MODULATE_BENCH_API(LIQUID_MODEM_OOK)
+void benchmark_modulate_sqam32  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_SQAM32)
+void benchmark_modulate_sqam128 MODEM_MODULATE_BENCH_API(LIQUID_MODEM_SQAM128)
+
+// ASK
+void benchmark_modulate_ask2    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ASK2)
+void benchmark_modulate_ask4    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ASK4)
+void benchmark_modulate_ask8    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ASK8)
+void benchmark_modulate_ask16   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ASK16)
+
+// PSK
+void benchmark_modulate_psk2    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK2)
+void benchmark_modulate_psk4    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK4)
+void benchmark_modulate_psk8    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK8)
+void benchmark_modulate_psk16   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK16)
+void benchmark_modulate_psk32   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK32)
+void benchmark_modulate_psk64   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_PSK64)
+
+// Differential PSK
+void benchmark_modulate_dpsk2   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK2)
+void benchmark_modulate_dpsk4   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK4)
+void benchmark_modulate_dpsk8   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK8)
+void benchmark_modulate_dpsk16  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK16)
+void benchmark_modulate_dpsk32  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK32)
+void benchmark_modulate_dpsk64  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_DPSK64)
+
+// QAM
+void benchmark_modulate_qam4    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM4)
+void benchmark_modulate_qam8    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM8)
+void benchmark_modulate_qam16   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM16)
+void benchmark_modulate_qam32   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM32)
+void benchmark_modulate_qam64   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM64)
+void benchmark_modulate_qam128  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM128)
+void benchmark_modulate_qam256  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_QAM256)
+
+// APSK
+void benchmark_modulate_apsk4   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK4)
+void benchmark_modulate_apsk8   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK8)
+void benchmark_modulate_apsk16  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK16)
+void benchmark_modulate_apsk32  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK32)
+void benchmark_modulate_apsk64  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK64)
+void benchmark_modulate_apsk128 MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK128)
+void benchmark_modulate_apsk256 MODEM_MODULATE_BENCH_API(LIQUID_MODEM_APSK256)
+
+// ARB
+void benchmark_modulate_arbV29    MODEM_MODULATE_BENCH_API(LIQUID_MODEM_V29)
+void benchmark_modulate_arb16opt  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB16OPT)
+void benchmark_modulate_arb32opt  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB32OPT)
+void benchmark_modulate_arb64opt  MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB64OPT)
+void benchmark_modulate_arb128opt MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB128OPT)
+void benchmark_modulate_arb256opt MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB256OPT)
+void benchmark_modulate_arb64vt   MODEM_MODULATE_BENCH_API(LIQUID_MODEM_ARB64VT)
+
diff --git a/src/modem/src/ampmodem.c b/src/modem/src/ampmodem.c
new file mode 100644
index 0000000..565d692
--- /dev/null
+++ b/src/modem/src/ampmodem.c
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Amplitude modulator/demodulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_AMPMODEM             0
+#define DEBUG_AMPMODEM_FILENAME    "ampmodem_internal_debug.m"
+#define DEBUG_AMPMODEM_BUFFER_LEN  (400)
+
+#if DEBUG_AMPMODEM
+void ampmodem_debug_print(ampmodem _q, const char * _filename);
+#endif
+
+struct ampmodem_s {
+    float m;                    // modulation index
+    liquid_ampmodem_type type;  // modulation type
+    int suppressed_carrier;     // suppressed carrier flag
+    float fc;                   // carrier frequency
+
+    // demod objects
+    nco_crcf oscillator;
+
+    // suppressed carrier
+    // TODO : replace DC bias removal with iir filter object
+    float ssb_alpha;    // dc bias removal
+    float ssb_q_hat;
+
+    // single side-band
+    firhilbf hilbert;   // hilbert transform
+
+    // double side-band
+
+    // debugging
+#if DEBUG_AMPMODEM
+    windowcf debug_x;
+    windowf  debug_phase_error;
+    windowf  debug_freq_error;
+#endif
+};
+
+// create ampmodem object
+//  _m                  :   modulation index
+//  _fc                 :   carrier frequency
+//  _type               :   AM type (e.g. LIQUID_AMPMODEM_DSB)
+//  _suppressed_carrier :   carrier suppression flag
+ampmodem ampmodem_create(float _m,
+                         float _fc,
+                         liquid_ampmodem_type _type,
+                         int _suppressed_carrier)
+{
+    ampmodem q = (ampmodem) malloc(sizeof(struct ampmodem_s));
+    q->type = _type;
+    q->m    = _m;
+    q->fc   = _fc;
+    q->suppressed_carrier = (_suppressed_carrier == 0) ? 0 : 1;
+
+    // create nco, pll objects
+    q->oscillator = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_frequency(q->oscillator, 2*M_PI*q->fc);
+    
+    nco_crcf_pll_set_bandwidth(q->oscillator,0.05f);
+
+    // suppressed carrier
+    q->ssb_alpha = 0.01f;
+    q->ssb_q_hat = 0.0f;
+
+    // single side-band
+    q->hilbert = firhilbf_create(9, 60.0f);
+
+    // double side-band
+
+    ampmodem_reset(q);
+
+    // debugging
+#if DEBUG_AMPMODEM
+    q->debug_x =           windowcf_create(DEBUG_AMPMODEM_BUFFER_LEN);
+    q->debug_phase_error =  windowf_create(DEBUG_AMPMODEM_BUFFER_LEN);
+    q->debug_freq_error =   windowf_create(DEBUG_AMPMODEM_BUFFER_LEN);
+#endif
+
+    return q;
+}
+
+void ampmodem_destroy(ampmodem _q)
+{
+#if DEBUG_AMPMODEM
+    // export output debugging file
+    ampmodem_debug_print(_q, DEBUG_AMPMODEM_FILENAME);
+
+    // destroy debugging objects
+    windowcf_destroy(_q->debug_x);
+    windowf_destroy(_q->debug_phase_error);
+    windowf_destroy(_q->debug_freq_error);
+#endif
+
+    // destroy nco object
+    nco_crcf_destroy(_q->oscillator);
+
+    // destroy hilbert transform
+    firhilbf_destroy(_q->hilbert);
+
+    // free main object memory
+    free(_q);
+}
+
+void ampmodem_print(ampmodem _q)
+{
+    printf("ampmodem:\n");
+    printf("    type            :   ");
+    switch (_q->type) {
+    case LIQUID_AMPMODEM_DSB: printf("double side-band\n");         break;
+    case LIQUID_AMPMODEM_USB: printf("single side-band (upper)\n"); break;
+    case LIQUID_AMPMODEM_LSB: printf("single side-band (lower)\n"); break;
+    default:                  printf("unknown\n");
+    }
+    printf("    supp. carrier   :   %s\n", _q->suppressed_carrier ? "yes" : "no");
+    printf("    mod. index      :   %-8.4f\n", _q->m);
+}
+
+void ampmodem_reset(ampmodem _q)
+{
+    // single side-band
+    _q->ssb_q_hat = 0.5f;
+}
+
+void ampmodem_modulate(ampmodem _q,
+                       float _x,
+                       float complex *_y)
+{
+    float complex x_hat = 0.0f;
+    float complex y_hat;
+
+    if (_q->type == LIQUID_AMPMODEM_DSB) {
+        x_hat = _x;
+    } else {
+        // push through Hilbert transform
+        // LIQUID_AMPMODEM_USB:
+        // LIQUID_AMPMODEM_LSB: conjugate Hilbert transform output
+        firhilbf_r2c_execute(_q->hilbert, _x, &x_hat);
+
+        if (_q->type == LIQUID_AMPMODEM_LSB)
+            x_hat = conjf(x_hat);
+    }
+
+    if (_q->suppressed_carrier)
+        y_hat = x_hat;
+    else
+        y_hat = 0.5f*(x_hat + 1.0f);
+    
+    // mix up
+    nco_crcf_mix_up(_q->oscillator, y_hat, _y);
+    nco_crcf_step(_q->oscillator);
+}
+
+// modulate block of samples
+//  _q      :   ampmodem object
+//  _m      :   message signal m(t), [size: _n x 1]
+//  _n      :   number of input, output samples
+//  _s      :   complex baseband signal s(t) [size: _n x 1]
+void ampmodem_modulate_block(ampmodem        _q,
+                             float *         _m,
+                             unsigned int    _n,
+                             float complex * _s)
+{
+    // TODO: implement more efficient method
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        ampmodem_modulate(_q, _m[i], &_s[i]);
+}
+
+
+void ampmodem_demodulate(ampmodem _q,
+                         float complex _y,
+                         float *_x)
+{
+#if DEBUG_AMPMODEM
+    windowcf_push(_q->debug_x, _y);
+#endif
+
+    if (_q->suppressed_carrier) {
+        // single side-band suppressed carrier
+        if (_q->type != LIQUID_AMPMODEM_DSB) {
+            *_x = crealf(_y);
+            return;
+        }
+
+        // coherent demodulation
+        
+        // mix signal down
+        float complex y_hat;
+        nco_crcf_mix_down(_q->oscillator, _y, &y_hat);
+
+        // compute phase error
+        float phase_error = tanhf( crealf(y_hat) * cimagf(y_hat) );
+#if DEBUG_AMPMODEM
+        // compute frequency error
+        float nco_freq   = nco_crcf_get_frequency(_q->oscillator);
+        float freq_error = nco_freq/(2*M_PI) - _q->fc/(2*M_PI);
+
+        // retain phase and frequency errors
+        windowf_push(_q->debug_phase_error, phase_error);
+        windowf_push(_q->debug_freq_error, freq_error);
+#endif
+
+        // adjust nco, pll objects
+        nco_crcf_pll_step(_q->oscillator, phase_error);
+
+        // step NCO
+        nco_crcf_step(_q->oscillator);
+
+        // set output
+        *_x = crealf(y_hat);
+    } else {
+        // non-coherent demodulation (peak detector)
+        float t = cabsf(_y);
+
+        // remove DC bias
+        _q->ssb_q_hat = (    _q->ssb_alpha)*t +
+                        (1 - _q->ssb_alpha)*_q->ssb_q_hat;
+        *_x = 2.0f*(t - _q->ssb_q_hat);
+    }
+}
+
+// demodulate block of samples
+//  _q      :   frequency demodulator object
+//  _r      :   received signal r(t) [size: _n x 1]
+//  _n      :   number of input, output samples
+//  _m      :   message signal m(t), [size: _n x 1]
+void ampmodem_demodulate_block(ampmodem        _q,
+                               float complex * _r,
+                               unsigned int    _n,
+                               float *         _m)
+{
+    // TODO: implement more efficient method
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        ampmodem_demodulate(_q, _r[i], &_m[i]);
+}
+
+// export debugging file
+void ampmodem_debug_print(ampmodem _q,
+                          const char * _filename)
+{
+    FILE * fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: ofdmframe_debug_print(), could not open '%s' for writing\n", _filename);
+        return;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", DEBUG_AMPMODEM_FILENAME);
+#if DEBUG_AMPMODEM
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"n = %u;\n", DEBUG_AMPMODEM_BUFFER_LEN);
+    unsigned int i;
+    float complex * rc;
+    float * r;
+
+    // plot received signal
+    fprintf(fid,"x = zeros(1,n);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_AMPMODEM_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(0:(n-1),real(x),0:(n-1),imag(x));\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('received signal, x');\n");
+
+    // plot phase/freq error
+    fprintf(fid,"phase_error = zeros(1,n);\n");
+    windowf_read(_q->debug_phase_error, &r);
+    for (i=0; i<DEBUG_AMPMODEM_BUFFER_LEN; i++)
+        fprintf(fid,"phase_error(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"freq_error = zeros(1,n);\n");
+    windowf_read(_q->debug_freq_error, &r);
+    for (i=0; i<DEBUG_AMPMODEM_BUFFER_LEN; i++)
+        fprintf(fid,"freq_error(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1),\n");
+    fprintf(fid,"  plot(0:(n-1),phase_error);\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('phase error');\n");
+    fprintf(fid,"subplot(2,1,2),\n");
+    fprintf(fid,"  plot(0:(n-1),freq_error);\n");
+    fprintf(fid,"  xlabel('sample index');\n");
+    fprintf(fid,"  ylabel('freq error');\n");
+
+#else
+    fprintf(fid,"disp('no debugging info available');\n");
+#endif
+
+    fclose(fid);
+    printf("ampmodem/debug: results written to '%s'\n", _filename);
+}
+
+
diff --git a/src/modem/src/cpfskdem.c b/src/modem/src/cpfskdem.c
new file mode 100644
index 0000000..89b1efc
--- /dev/null
+++ b/src/modem/src/cpfskdem.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// continuous phase frequency-shift keying demodulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_CPFSKDEM  0
+
+// 
+// internal methods
+//
+
+// initialize coherent demodulator
+void cpfskdem_init_coherent(cpfskdem _q);
+
+// initialize non-coherent demodulator
+void cpfskdem_init_noncoherent(cpfskdem _q);
+
+#if 0
+// demodulate array of samples (coherent)
+void cpfskdem_demodulate_coherent(cpfskdem        _q,
+                                  float complex   _y,
+                                  unsigned int  * _s,
+                                  unsigned int  * _nw);
+
+// demodulate array of samples (non-coherent)
+void cpfskdem_demodulate_noncoherent(cpfskdem        _q,
+                                     float complex   _y,
+                                     unsigned int  * _s,
+                                     unsigned int  * _nw);
+#else
+// demodulate array of samples (coherent)
+unsigned int cpfskdem_demodulate_coherent(cpfskdem        _q,
+                                          float complex * _y);
+
+// demodulate array of samples (non-coherent)
+unsigned int cpfskdem_demodulate_noncoherent(cpfskdem        _q,
+                                             float complex * _y);
+#endif
+
+// cpfskdem
+struct cpfskdem_s {
+    // common
+    unsigned int bps;           // bits per symbol
+    unsigned int k;             // samples per symbol
+    unsigned int m;             // filter delay (symbols)
+    float        beta;          // filter bandwidth parameter
+    float        h;             // modulation index
+    int          type;          // filter type (e.g. LIQUID_CPFSK_GMSK)
+    unsigned int M;             // constellation size
+    unsigned int symbol_delay;  // receiver filter delay [symbols]
+
+    // demodulator type
+    enum {
+        CPFSKDEM_COHERENT=0,    // coherent demodulator
+        CPFSKDEM_NONCOHERENT    // non-coherent demodulator
+    } demod_type;
+
+    // demodulation function pointer
+#if 0
+    void (*demodulate)(cpfskdem        _q,
+                       float complex   _y,
+                       unsigned int  * _s,
+                       unsigned int  * _nw);
+#else
+    unsigned int (*demodulate)(cpfskdem        _q,
+                               float complex * _y);
+#endif
+
+    // common data structure shared between coherent and non-coherent
+    // demodulator receivers
+    union {
+        // coherent demodulator
+        struct {
+            /*
+            nco_crcf nco;       // oscillator/phase-locked loop
+            firpfb_crcf mf;     // matched filter
+            firpfb_crcf dmf;    // matched filter (derivative)
+            */
+            
+            firfilt_crcf mf;    // matched filter
+        } coherent;
+
+        // non-coherent demodulator
+        struct {
+            firpfb_rrrf mf;     // matched filter
+            firpfb_rrrf dmf;    // matched filter (derivative)
+            eqlms_rrrf equalizer;
+        } noncoherent;
+    } data;
+
+    // state variables
+    unsigned int  index;    // debug
+    unsigned int  counter;  // sample counter
+    float complex z_prime;  // (coherent only)
+};
+
+// create cpfskdem object (frequency demodulator)
+//  _bps    :   bits per symbol, _bps > 0
+//  _h      :   modulation index, _h > 0
+//  _k      :   samples/symbol, _k > 1, _k even
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   filter bandwidth parameter, _beta > 0
+//  _type   :   filter type (e.g. LIQUID_CPFSK_SQUARE)
+cpfskdem cpfskdem_create(unsigned int _bps,
+                         float        _h,
+                         unsigned int _k,
+                         unsigned int _m,
+                         float        _beta,
+                         int          _type)
+{
+    // validate input
+    if (_bps == 0) {
+        fprintf(stderr,"error: cpfskdem_create(), bits/symbol must be greater than 0\n");
+        exit(1);
+    } else if (_k < 2 || (_k%2)) {
+        fprintf(stderr,"error: cpfskmod_create(), samples/symbol must be greater than 2 and even\n");
+    } else if (_m == 0) {
+        fprintf(stderr,"error: cpfskdem_create(), filter delay must be greater than 0\n");
+        exit(1);
+    } else if (_beta <= 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: cpfskdem_create(), filter roll-off must be in (0,1]\n");
+        exit(1);
+    } else if (_h <= 0.0f) {
+        fprintf(stderr,"error: cpfskdem_create(), modulation index must be greater than 0\n");
+        exit(1);
+    }
+
+    // create main object memory
+    cpfskdem q = (cpfskdem) malloc(sizeof(struct cpfskdem_s));
+
+    // set basic internal properties
+    q->bps  = _bps;     // bits per symbol
+    q->h    = _h;       // modulation index
+    q->k    = _k;       // samples per symbol
+    q->m    = _m;       // filter delay (symbols)
+    q->beta = _beta;    // filter roll-off factor (only for certain filters)
+    q->type = _type;    // filter type
+
+    // derived values
+    q->M = 1 << q->bps; // constellation size
+
+    // coherent or non-coherent?
+    // TODO: allow user to specify
+    if (q->h > 0.66667f) {
+        cpfskdem_init_noncoherent(q);
+    } else {
+        cpfskdem_init_coherent(q);
+    }
+
+    // reset modem object
+    cpfskdem_reset(q);
+
+    return q;
+}
+
+// initialize coherent demodulator
+void cpfskdem_init_coherent(cpfskdem _q)
+{
+    // specify coherent receiver
+    _q->demod_type = CPFSKDEM_COHERENT;
+
+    // set demodulate function pointer
+    _q->demodulate = cpfskdem_demodulate_coherent;
+
+    // create object depending upon input type
+    float bw = 0.0f;
+    float beta = 0.0f;
+    float gmsk_bt = _q->beta;
+    switch(_q->type) {
+    case LIQUID_CPFSK_SQUARE:
+        //bw = 0.9f / (float)k;
+        bw = 0.4f;
+        _q->symbol_delay = _q->m;
+        _q->data.coherent.mf = firfilt_crcf_create_kaiser(2*_q->k*_q->m+1, bw, 60.0f, 0.0f);
+        firfilt_crcf_set_scale(_q->data.coherent.mf, 2.0f * bw);
+        break;
+    case LIQUID_CPFSK_RCOS_FULL:
+        if (_q->M==2) {
+            _q->data.coherent.mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,_q->k,_q->m,0.5f,0);
+            firfilt_crcf_set_scale(_q->data.coherent.mf, 1.33f / (float)_q->k);
+            _q->symbol_delay = _q->m;
+        } else {
+            _q->data.coherent.mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,_q->k/2,2*_q->m,0.9f,0);
+            firfilt_crcf_set_scale(_q->data.coherent.mf, 3.25f / (float)_q->k);
+            _q->symbol_delay = 0; // TODO: fix this value
+        }
+        break;
+    case LIQUID_CPFSK_RCOS_PARTIAL:
+        if (_q->M==2) {
+            _q->data.coherent.mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,_q->k,_q->m,0.3f,0);
+            firfilt_crcf_set_scale(_q->data.coherent.mf, 1.10f / (float)_q->k);
+            _q->symbol_delay = _q->m;
+        } else {
+            _q->data.coherent.mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,_q->k/2,2*_q->m,0.27f,0);
+            firfilt_crcf_set_scale(_q->data.coherent.mf, 2.90f / (float)_q->k);
+            _q->symbol_delay = 0; // TODO: fix this value
+        }
+        break;
+    case LIQUID_CPFSK_GMSK:
+        bw = 0.5f / (float)_q->k;
+        // TODO: figure out beta value here
+        beta = (_q->M == 2) ? 0.8*gmsk_bt : 1.0*gmsk_bt;
+        _q->data.coherent.mf = firfilt_crcf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,_q->k,_q->m,_q->beta,0);
+        firfilt_crcf_set_scale(_q->data.coherent.mf, 2.0f * bw);
+        _q->symbol_delay = _q->m;
+        break;
+    default:
+        fprintf(stderr,"error: cpfskdem_init_coherent(), invalid tx filter type\n");
+        exit(1);
+    }
+}
+
+// initialize non-coherent demodulator
+void cpfskdem_init_noncoherent(cpfskdem _q)
+{
+    // specify non-coherent receiver
+    _q->demod_type = CPFSKDEM_NONCOHERENT;
+    
+    // set demodulate function pointer
+    _q->demodulate = cpfskdem_demodulate_noncoherent;
+
+    // create object depending upon input type
+    switch(_q->type) {
+    case LIQUID_CPFSK_SQUARE:
+    case LIQUID_CPFSK_RCOS_FULL:
+    case LIQUID_CPFSK_RCOS_PARTIAL:
+    case LIQUID_CPFSK_GMSK:
+        break;
+    }
+
+}
+
+// destroy modem object
+void cpfskdem_destroy(cpfskdem _q)
+{
+    switch(_q->demod_type) {
+    case CPFSKDEM_COHERENT:
+        firfilt_crcf_destroy(_q->data.coherent.mf);
+        break;
+    case CPFSKDEM_NONCOHERENT:
+        break;
+    }
+
+    // free main object memory
+    free(_q);
+}
+
+// print modulation internals
+void cpfskdem_print(cpfskdem _q)
+{
+    printf("cpfskdem:\n");
+    printf("    k   :   %u\n", _q->k);
+}
+
+// reset modem object
+void cpfskdem_reset(cpfskdem _q)
+{
+    switch(_q->demod_type) {
+    case CPFSKDEM_COHERENT:
+        firfilt_crcf_reset(_q->data.coherent.mf);
+        break;
+    case CPFSKDEM_NONCOHERENT:
+        break;
+    default:
+        break;
+    }
+
+    _q->index   = 0;
+    _q->counter = _q->k-1;
+    _q->z_prime = 0;
+}
+
+// get transmit delay [symbols]
+unsigned int cpfskdem_get_delay(cpfskdem _q)
+{
+    return _q->symbol_delay;
+}
+
+#if 0
+// demodulate array of samples
+//  _q      :   continuous-phase frequency demodulator object
+//  _y      :   input sample array [size: _n x 1]
+//  _n      :   input sample array length
+//  _s      :   output symbol array
+//  _nw     :   number of output symbols written
+void cpfskdem_demodulate(cpfskdem        _q,
+                         float complex * _y,
+                         unsigned int    _n,
+                         unsigned int  * _s,
+                         unsigned int  * _nw)
+{
+    // iterate through each sample calling type-specific demodulation function
+    unsigned int i;
+    unsigned int num_written = 0;
+    for (i=0; i<_n; i++) {
+        unsigned int nw;
+        _q->demodulate(_q, _y[i], &_s[num_written], &nw);
+
+        // update number of symbols written
+        num_written += nw;
+    }
+
+    // set output number of bits written
+    *_nw = num_written;
+}
+
+// demodulate array of samples (coherent)
+void cpfskdem_demodulate_coherent(cpfskdem        _q,
+                                  float complex   _y,
+                                  unsigned int  * _s,
+                                  unsigned int  * _nw)
+{
+    // clear output counter
+    *_nw = 0;
+
+    // push input sample through filter
+    firfilt_crcf_push(_q->data.coherent.mf, _y);
+
+#if DEBUG_CPFSKDEM
+    // compute output sample
+    float complex zp;
+    firfilt_crcf_execute(_q->data.coherent.mf, &zp);
+    printf("y(end+1) = %12.8f + 1i*%12.8f;\n", crealf(_y), cimagf(_y));
+    printf("z(end+1) = %12.8f + 1i*%12.8f;\n", crealf(zp), cimagf(zp));
+#endif
+
+    // decimate output
+    _q->counter++;
+    if ( (_q->counter % _q->k)==0 ) {
+        // reset sample counter
+        _q->counter = 0;
+    
+        // compute output sample
+        float complex z;
+        firfilt_crcf_execute(_q->data.coherent.mf, &z);
+
+        // compute instantaneous frequency scaled by modulation index
+        // TODO: pre-compute scaling factor
+        float phi_hat = cargf(conjf(_q->z_prime) * z) / (_q->h * M_PI);
+
+        // estimate transmitted symbol
+        float v = (phi_hat + (_q->M-1.0))*0.5f;
+        unsigned int sym_out = ((int) roundf(v)) % _q->M;
+
+        // save current point
+        _q->z_prime = z;
+
+#if 1
+        // print result to screen
+        printf("  %3u : %12.8f + j%12.8f, <f=%8.4f : %8.4f> (%1u)\n",
+                _q->index++, crealf(z), cimagf(z), phi_hat, v, sym_out);
+#endif
+
+        // save output
+        *_s  = sym_out;
+        *_nw = 1;
+    }
+}
+
+// demodulate array of samples (non-coherent)
+void cpfskdem_demodulate_noncoherent(cpfskdem        _q,
+                                     float complex   _y,
+                                     unsigned int  * _s,
+                                     unsigned int  * _nw)
+{
+    *_nw = 0;
+}
+
+#else
+
+// demodulate array of samples
+//  _q      :   continuous-phase frequency demodulator object
+//  _y      :   input sample array [size: _k x 1]
+unsigned int cpfskdem_demodulate(cpfskdem        _q,
+                                 float complex * _y)
+{
+    return _q->demodulate(_q, _y);
+}
+
+// demodulate array of samples (coherent)
+unsigned int cpfskdem_demodulate_coherent(cpfskdem        _q,
+                                          float complex * _y)
+{
+    unsigned int i;
+    unsigned int sym_out = 0;
+
+    for (i=0; i<_q->k; i++) {
+        // push input sample through filter
+        firfilt_crcf_push(_q->data.coherent.mf, _y[i]);
+
+#if DEBUG_CPFSKDEM
+        // compute output sample
+        float complex zp;
+        firfilt_crcf_execute(_q->data.coherent.mf, &zp);
+        printf("y(end+1) = %12.8f + 1i*%12.8f;\n", crealf(_y), cimagf(_y));
+        printf("z(end+1) = %12.8f + 1i*%12.8f;\n", crealf(zp), cimagf(zp));
+#endif
+
+        // decimate output
+        if ( i == 0 ) {
+            // compute output sample
+            float complex z;
+            firfilt_crcf_execute(_q->data.coherent.mf, &z);
+
+            // compute instantaneous frequency scaled by modulation index
+            // TODO: pre-compute scaling factor
+            float phi_hat = cargf(conjf(_q->z_prime) * z) / (_q->h * M_PI);
+
+            // estimate transmitted symbol
+            float v = (phi_hat + (_q->M-1.0))*0.5f;
+            sym_out = ((int) roundf(v)) % _q->M;
+
+            // save current point
+            _q->z_prime = z;
+
+#if DEBUG_CPFSKDEM
+            // print result to screen
+            printf("  %3u : %12.8f + j%12.8f, <f=%8.4f : %8.4f> (%1u)\n",
+                    _q->index++, crealf(z), cimagf(z), phi_hat, v, sym_out);
+#endif
+        }
+    }
+    return sym_out;
+}
+
+// demodulate array of samples (non-coherent)
+unsigned int cpfskdem_demodulate_noncoherent(cpfskdem        _q,
+                                             float complex * _y)
+{
+    return 0;
+}
+#endif
+
diff --git a/src/modem/src/cpfskmod.c b/src/modem/src/cpfskmod.c
new file mode 100644
index 0000000..e44cd82
--- /dev/null
+++ b/src/modem/src/cpfskmod.c
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// continuous phase frequency-shift keying modulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// internal methods
+//
+
+// design transmit filter
+void cpfskmod_firdes(unsigned int _k,
+                     unsigned int _m,
+                     float        _beta,
+                     int          _type,
+                     float *      _h,
+                     unsigned int _h_len);
+
+// cpfskmod
+struct cpfskmod_s {
+    // common
+    unsigned int bps;           // bits per symbol
+    unsigned int k;             // samples per symbol
+    unsigned int m;             // filter delay (symbols)
+    float        beta;          // filter bandwidth parameter
+    float        h;             // modulation index
+    int          type;          // filter type (e.g. LIQUID_CPFSK_SQUARE)
+    unsigned int M;             // constellation size
+    unsigned int symbol_delay;  // transmit filter delay [symbols]
+
+    // pulse-shaping filter
+    float * ht;                 // filter coefficients
+    unsigned int ht_len;        // filter length
+    firinterp_rrrf  interp;     // interpolator
+
+    // phase integrator
+    float * phase_interp;       // phase interpolation buffer
+    iirfilt_rrrf integrator;    // integrator
+};
+
+// create cpfskmod object (frequency modulator)
+//  _bps    :   bits per symbol, _bps > 0
+//  _h      :   modulation index, _h > 0
+//  _k      :   samples/symbol, _k > 1, _k even
+//  _m      :   filter delay (symbols), _m > 0
+//  _beta   :   filter bandwidth parameter, _beta > 0
+//  _type   :   filter type (e.g. LIQUID_CPFSK_SQUARE)
+cpfskmod cpfskmod_create(unsigned int _bps,
+                         float        _h,
+                         unsigned int _k,
+                         unsigned int _m,
+                         float        _beta,
+                         int          _type)
+{
+    // validate input
+    if (_bps == 0) {
+        fprintf(stderr,"error: cpfskmod_create(), bits/symbol must be greater than 0\n");
+        exit(1);
+    } else if (_k < 2 || (_k%2)) {
+        fprintf(stderr,"error: cpfskmod_create(), samples/symbol must be greater than 2 and even\n");
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: cpfskmod_create(), filter delay must be greater than 0\n");
+        exit(1);
+    } else if (_beta <= 0.0f || _beta > 1.0f) {
+        fprintf(stderr,"error: cpfskmod_create(), filter roll-off must be in (0,1]\n");
+        exit(1);
+    } else if (_h <= 0.0f) {
+        fprintf(stderr,"error: cpfskmod_create(), modulation index must be greater than 0\n");
+        exit(1);
+    }
+
+    // create main object memory
+    cpfskmod q = (cpfskmod) malloc(sizeof(struct cpfskmod_s));
+
+    // set basic internal properties
+    q->bps  = _bps;     // bits per symbol
+    q->h    = _h;       // modulation index
+    q->k    = _k;       // samples per symbol
+    q->m    = _m;       // filter delay (symbols)
+    q->beta = _beta;    // filter roll-off factor (only for certain filters)
+    q->type = _type;    // filter type
+
+    // derived values
+    q->M = 1 << q->bps; // constellation size
+
+    // create object depending upon input type
+    float b[2] = {0.5f,  0.5f}; // integrator feed-forward coefficients
+    float a[2] = {1.0f, -1.0f}; // integrator feed-back coefficients
+    q->ht_len = 0;
+    q->ht = NULL;
+    unsigned int i;
+    switch(q->type) {
+    case LIQUID_CPFSK_SQUARE:
+        q->ht_len = q->k;
+        q->symbol_delay = 1;
+        // modify integrator
+        b[0] = 0.0f;
+        b[1] = 1.0f;
+        break;
+    case LIQUID_CPFSK_RCOS_FULL:
+        q->ht_len = q->k;
+        q->symbol_delay = 1;
+        break;
+    case LIQUID_CPFSK_RCOS_PARTIAL:
+        // TODO: adjust reponse based on 'm'
+        q->ht_len = 3*q->k;
+        q->symbol_delay = 2;
+        break;
+    case LIQUID_CPFSK_GMSK:
+        q->symbol_delay = q->m + 1;
+        q->ht_len = 2*(q->k)*(q->m) + (q->k) + 1;
+        break;
+    default:
+        fprintf(stderr,"error: cpfskmodem_create(), invalid filter type '%d'\n", q->type);
+        exit(1);
+    }
+
+    // create pulse-shaping filter and scale by modulation index
+    q->ht = (float*) malloc(q->ht_len *sizeof(float));
+    cpfskmod_firdes(q->k, q->m, q->beta, q->type, q->ht, q->ht_len);
+    for (i=0; i<q->ht_len; i++)
+        q->ht[i] *= M_PI * q->h;
+    q->interp = firinterp_rrrf_create(q->k, q->ht, q->ht_len);
+
+    // create phase integrator
+    q->phase_interp = (float*) malloc(q->k*sizeof(float));
+    q->integrator = iirfilt_rrrf_create(b,2,a,2);
+
+    // reset modem object
+    cpfskmod_reset(q);
+
+    return q;
+}
+
+// destroy cpfskmod object
+void cpfskmod_destroy(cpfskmod _q)
+{
+    // destroy pulse-shaping filter/interpolator
+    free(_q->ht);
+    free(_q->phase_interp);
+    firinterp_rrrf_destroy(_q->interp);
+
+    // destroy phase integrator
+    iirfilt_rrrf_destroy(_q->integrator);
+
+    // free main object memory
+    free(_q);
+}
+
+// print cpfskmod object internals
+void cpfskmod_print(cpfskmod _q)
+{
+    printf("cpfskmod : continuous-phase frequency-shift keying modem\n");
+    printf("    bits/symbol     :   %u\n", _q->bps);
+    printf("    modulation index:   %-6.3f\n", _q->h);
+    printf("    samples/symbol  :   %u\n", _q->k);
+    printf("    filter delay    :   %u symbols\n", _q->m);
+    printf("    filter roll-off :   %-6.3f\n", _q->beta);
+    printf("    filter type     :   ");
+    switch(_q->type) {
+    case LIQUID_CPFSK_SQUARE:       printf("square\n");         break;
+    case LIQUID_CPFSK_RCOS_FULL:    printf("rcos (full)\n");    break;
+    case LIQUID_CPFSK_RCOS_PARTIAL: printf("rcos (partial)\n"); break;
+    case LIQUID_CPFSK_GMSK:         printf("gmsk\n");           break;
+    default:                        printf("unknown\n");        break;
+    }
+    printf("    filter          :\n");
+    // print filter coefficients
+    unsigned int i;
+    for (i=0; i<_q->ht_len; i++)
+        printf("        h(%3u) = %12.8f;\n", i+1, _q->ht[i]);
+}
+
+// reset state
+void cpfskmod_reset(cpfskmod _q)
+{
+    // reset interpolator
+    firinterp_rrrf_reset(_q->interp);
+
+    // reset phase integrator
+    iirfilt_rrrf_reset(_q->integrator);
+}
+
+// get transmit delay [symbols]
+unsigned int cpfskmod_get_delay(cpfskmod _q)
+{
+    return _q->symbol_delay;
+}
+
+// modulate sample
+//  _q      :   frequency modulator object
+//  _s      :   input symbol
+//  _y      :   output sample array [size: _k x 1]
+void cpfskmod_modulate(cpfskmod        _q,
+                       unsigned int    _s,
+                       float complex * _y)
+{
+    // run interpolator
+    float v = 2.0f*_s - (float)(_q->M) + 1.0f;
+    firinterp_rrrf_execute(_q->interp, v, _q->phase_interp);
+
+    // integrate phase state
+    unsigned int i;
+    float theta;
+    for (i=0; i<_q->k; i++) {
+        // push phase through integrator
+        iirfilt_rrrf_execute(_q->integrator, _q->phase_interp[i], &theta);
+
+        // compute output
+        _y[i] = liquid_cexpjf(theta);
+    }
+}
+
+// 
+// internal methods
+//
+
+// design transmit filter
+void cpfskmod_firdes(unsigned int _k,
+                     unsigned int _m,
+                     float        _beta,
+                     int          _type,
+                     float *      _ht,
+                     unsigned int _ht_len)
+{
+    unsigned int i;
+    // create filter based on specified type
+    switch(_type) {
+    case LIQUID_CPFSK_SQUARE:
+        // square pulse
+        if (_ht_len != _k) {
+            fprintf(stderr,"error: cpfskmodem_firdes(), invalid filter length (square)\n");
+            exit(1);
+        }
+        for (i=0; i<_ht_len; i++)
+            _ht[i] = 1.0f;
+        break;
+    case LIQUID_CPFSK_RCOS_FULL:
+        // full-response raised-cosine pulse
+        if (_ht_len != _k) {
+            fprintf(stderr,"error: cpfskmodem_firdes(), invalid filter length (rcos)\n");
+            exit(1);
+        }
+        for (i=0; i<_ht_len; i++)
+            _ht[i] = 1.0f - cosf(2.0f*M_PI*i/(float)_ht_len);
+        break;
+    case LIQUID_CPFSK_RCOS_PARTIAL:
+        // full-response raised-cosine pulse
+        if (_ht_len != 3*_k) {
+            fprintf(stderr,"error: cpfskmodem_firdes(), invalid filter length (rcos)\n");
+            exit(1);
+        }
+        // initialize with zeros
+        for (i=0; i<_ht_len; i++)
+            _ht[i] = 0.0f;
+        // adding raised-cosine pulse with half-symbol delay
+        for (i=0; i<2*_k; i++)
+            _ht[i+_k/2] = 1.0f - cosf(2.0f*M_PI*i/(float)(2*_k));
+        break;
+    case LIQUID_CPFSK_GMSK:
+        // Gauss minimum-shift keying pulse
+        if (_ht_len != 2*_k*_m + _k + 1) {
+            fprintf(stderr,"error: cpfskmodem_firdes(), invalid filter length (gmsk)\n");
+            exit(1);
+        }
+        // initialize with zeros
+        for (i=0; i<_ht_len; i++)
+            _ht[i] = 0.0f;
+        // adding Gauss pulse with half-symbol delay
+        liquid_firdes_gmsktx(_k,_m,_beta,0.0f,&_ht[_k/2]);
+        break;
+    default:
+        fprintf(stderr,"error: cpfskmodem_firdes(), invalid filter type '%d'\n", _type);
+        exit(1);
+    }
+
+    // normalize pulse area to unity
+    float ht_sum = 0.0f;
+    for (i=0; i<_ht_len; i++)
+        ht_sum += _ht[i];
+    for (i=0; i<_ht_len; i++)
+        _ht[i] *= 1.0f / ht_sum;
+}
+
diff --git a/src/modem/src/freqdem.c b/src/modem/src/freqdem.c
new file mode 100644
index 0000000..5a47904
--- /dev/null
+++ b/src/modem/src/freqdem.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Frequency demodulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// freqdem
+struct FREQDEM(_s) {
+    // common
+    float kf;   // modulation index
+    T     ref;  // 1/(2*pi*kf)
+
+    TC r_prime; // previous received sample
+};
+
+// create freqdem object
+//  _kf     :   modulation factor
+FREQDEM() FREQDEM(_create)(float _kf)
+{
+    // validate input
+    if (_kf <= 0.0f || _kf > 1.0) {
+        fprintf(stderr,"error: freqdem_create(), modulation factor %12.4e out of range [0,1]\n", _kf);
+        exit(1);
+    }
+
+    // create main object memory
+    FREQDEM() q = (freqdem) malloc(sizeof(struct FREQDEM(_s)));
+
+    // set internal modulation factor
+    q->kf = _kf;
+
+    // compute derived values
+    q->ref = 1.0f / (2*M_PI*q->kf);
+
+    // reset modem object
+    FREQDEM(_reset)(q);
+
+    // return object
+    return q;
+}
+
+// destroy modem object
+void FREQDEM(_destroy)(FREQDEM() _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+// print modulation internals
+void FREQDEM(_print)(FREQDEM() _q)
+{
+    printf("freqdem:\n");
+    printf("    mod. factor :   %8.4f\n", _q->kf);
+}
+
+// reset modem object
+void FREQDEM(_reset)(FREQDEM() _q)
+{
+    // clear complex phase term
+    _q->r_prime = 0;
+}
+
+// demodulate sample
+//  _q      :   FM demodulator object
+//  _r      :   received signal
+//  _m      :   output message signal
+void FREQDEM(_demodulate)(FREQDEM() _q,
+                          TC        _r,
+                          T *       _m)
+{
+    // compute phase difference and normalize by modulation index
+    *_m = cargf( conjf(_q->r_prime)*_r ) * _q->ref;
+
+    // save previous input sample
+    _q->r_prime = _r;
+}
+
+// demodulate block of samples
+//  _q      :   frequency demodulator object
+//  _r      :   received signal r(t) [size: _n x 1]
+//  _n      :   number of input, output samples
+//  _m      :   message signal m(t), [size: _n x 1]
+void FREQDEM(_demodulate_block)(FREQDEM()    _q,
+                                TC *         _r,
+                                unsigned int _n,
+                                T *          _m)
+{
+    // TODO: implement more efficient method
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        FREQDEM(_demodulate)(_q, _r[i], &_m[i]);
+}
+
diff --git a/src/modem/src/freqmod.c b/src/modem/src/freqmod.c
new file mode 100644
index 0000000..31abdd3
--- /dev/null
+++ b/src/modem/src/freqmod.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Frequency modulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// freqmod
+struct FREQMOD(_s) {
+    float kf;   // modulation factor for FM
+    T     ref;  // phase reference: kf*2^16
+
+    // look-up table
+    unsigned int sincos_table_len;      // table length: 10 bits
+    uint16_t     sincos_table_phase;    // accumulated phase: 16 bits
+    TC *         sincos_table;          // sin|cos look-up table: 2^10 entries
+};
+
+// create freqmod object
+//  _kf     :   modulation factor
+FREQMOD() FREQMOD(_create)(float _kf)
+{
+    // validate input
+    if (_kf <= 0.0f || _kf > 1.0) {
+        fprintf(stderr,"error: freqmod_create(), modulation factor %12.4e out of range [0,1]\n", _kf);
+        exit(1);
+    }
+
+    // create main object memory
+    FREQMOD() q = (freqmod) malloc(sizeof(struct FREQMOD(_s)));
+
+    // set modulation factor
+    q->kf  = _kf;
+    q->ref = q->kf * (1<<16);
+
+    // initialize look-up table
+    q->sincos_table_len = 1024;
+    q->sincos_table     = (TC*) malloc( q->sincos_table_len*sizeof(TC) );
+    unsigned int i;
+    for (i=0; i<q->sincos_table_len; i++) {
+        q->sincos_table[i] = cexpf(_Complex_I*2*M_PI*(float)i / (float)(q->sincos_table_len) );
+    }
+
+    // reset modem object
+    FREQMOD(_reset)(q);
+
+    // return object
+    return q;
+}
+
+// destroy modem object
+void FREQMOD(_destroy)(FREQMOD() _q)
+{
+    // free table
+    free(_q->sincos_table);
+
+    // free main object memory
+    free(_q);
+}
+
+// print modulation internals
+void FREQMOD(_print)(FREQMOD() _q)
+{
+    printf("freqmod:\n");
+    printf("    mod. factor         :   %8.4f\n", _q->kf);
+    printf("    sincos table len    :   %u\n",    _q->sincos_table_len);
+}
+
+// reset modem object
+void FREQMOD(_reset)(FREQMOD() _q)
+{
+    // reset phase accumulation
+    _q->sincos_table_phase = 0;
+}
+
+// modulate sample
+//  _q      :   frequency modulator object
+//  _m      :   message signal m(t)
+//  _s      :   complex baseband signal s(t)
+void FREQMOD(_modulate)(FREQMOD()   _q,
+                        T           _m,
+                        TC *        _s)
+{
+    // accumulate phase; this wraps around a 16-bit boundary and ensures
+    // that negative numbers are mapped to positive numbers
+    _q->sincos_table_phase =
+        (_q->sincos_table_phase + (1<<16) + (int)roundf(_q->ref*_m)) & 0xffff;
+
+    // compute table index: mask out 10 most significant bits with rounding
+    // (adding 0x0020 effectively rounds to nearest value with 10 bits of
+    // precision)
+    unsigned int index = ( (_q->sincos_table_phase+0x0020) >> 6) & 0x03ff;
+
+    // return table value at index
+    *_s = _q->sincos_table[index];
+}
+
+// modulate block of samples
+//  _q      :   frequency modulator object
+//  _m      :   message signal m(t), [size: _n x 1]
+//  _n      :   number of input, output samples
+//  _s      :   complex baseband signal s(t) [size: _n x 1]
+void FREQMOD(_modulate_block)(FREQMOD()    _q,
+                              T *          _m,
+                              unsigned int _n,
+                              TC *         _s)
+{
+    // TODO: implement more efficient method
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        FREQMOD(_modulate)(_q, _m[i], &_s[i]);
+}
+
diff --git a/src/modem/src/fskdem.c b/src/modem/src/fskdem.c
new file mode 100644
index 0000000..f84ce28
--- /dev/null
+++ b/src/modem/src/fskdem.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// M-ary frequency-shift keying demodulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FSKDEM 0
+
+// 
+// internal methods
+//
+
+// fskdem
+struct fskdem_s {
+    // common
+    unsigned int m;             // bits per symbol
+    unsigned int k;             // samples per symbol
+    float        bandwidth;     // filter bandwidth parameter
+
+    // derived values
+    unsigned int    M;          // constellation size
+    float           M2;         // (M-1)/2
+    unsigned int    K;          // FFT size
+    float complex * buf_time;   // FFT input buffer
+    float complex * buf_freq;   // FFT output buffer
+    FFT_PLAN        fft;        // FFT object
+    unsigned int *  demod_map;  // demodulation map
+
+    // state variables
+    unsigned int    s_demod;    // demodulated symbol (used for frequency error)
+};
+
+// create fskdem object (frequency demodulator)
+//  _m          :   bits per symbol, _m > 0
+//  _k          :   samples/symbol, _k >= 2^_m
+//  _bandwidth  :   total signal bandwidth, (0,0.5)
+fskdem fskdem_create(unsigned int _m,
+                     unsigned int _k,
+                     float        _bandwidth)
+{
+    // validate input
+    if (_m == 0) {
+        fprintf(stderr,"error: fskdem_create(), bits/symbol must be greater than 0\n");
+        exit(1);
+    } else if (_k < 2 || _k > 2048) {
+        fprintf(stderr,"error: fskdem_create(), samples/symbol must be in [2^_m, 2048]\n");
+        exit(1);
+    } else if (_bandwidth <= 0.0f || _bandwidth >= 0.5f) {
+        fprintf(stderr,"error: fskdem_create(), bandwidth must be in (0,0.5)\n");
+        exit(1);
+    }
+
+    // create main object memory
+    fskdem q = (fskdem) malloc(sizeof(struct fskdem_s));
+
+    // set basic internal properties
+    q->m         = _m;              // bits per symbol
+    q->k         = _k;              // samples per symbol
+    q->bandwidth = _bandwidth;      // signal bandwidth
+
+    // derived values
+    q->M  = 1 << q->m;              // constellation size
+    q->M2 = 0.5f*(float)(q->M-1);   // (M-1)/2
+
+    // compute demodulation FFT size such that FFT output bin frequencies are
+    // as close to modulated frequencies as possible
+    float        df = q->bandwidth / q->M2;         // frequency spacing
+    float        err_min = 1e9f;                    // minimum error value
+    unsigned int K_min = q->k;                      // minimum FFT size
+    unsigned int K_max = q->k*4 < 16 ? 16 : q->k*4; // maximum FFT size
+    unsigned int K_hat;
+    for (K_hat=K_min; K_hat<=K_max; K_hat++) {
+        // compute candidate FFT size
+        float v     = 0.5f*df*(float)K_hat;         // bin spacing
+        float err = fabsf( roundf(v) - v );         // fractional bin spacing
+
+#if DEBUG_FSKDEM
+        // print results
+        printf("  K_hat = %4u : v = %12.8f, err=%12.8f %s\n", K_hat, v, err, err < err_min ? "*" : "");
+#endif
+
+        // save best result
+        if (K_hat==K_min || err < err_min) {
+            q->K    = K_hat;
+            err_min = err;
+        }
+
+        // perfect match; no need to continue searching
+        if (err < 1e-6f)
+            break;
+    }
+    
+    // determine demodulation mapping between tones and frequency bins
+    // TODO: use gray coding
+    q->demod_map = (unsigned int *) malloc(q->M * sizeof(unsigned int));
+    unsigned int i;
+    for (i=0; i<q->M; i++) {
+        // print frequency bins
+        float freq = ((float)i - q->M2) * q->bandwidth / q->M2;
+        float idx  = freq * (float)(q->K);
+        unsigned int index = (unsigned int) (idx < 0 ? roundf(idx + q->K) : roundf(idx));
+        q->demod_map[i] = index;
+#if DEBUG_FSKDEM
+        printf("  s=%3u, f = %12.8f, index=%3u\n", i, freq, index);
+#endif
+    }
+
+    // check for uniqueness
+    for (i=1; i<q->M; i++) {
+        if (q->demod_map[i] == q->demod_map[i-1]) {
+            fprintf(stderr,"warning: fskdem_create(), demod map is not unique; consider increasing bandwidth\n");
+            break;
+        }
+    }
+
+    // allocate memory for transform
+    q->buf_time = (float complex*) malloc(q->K * sizeof(float complex));
+    q->buf_freq = (float complex*) malloc(q->K * sizeof(float complex));
+    q->fft = FFT_CREATE_PLAN(q->K, q->buf_time, q->buf_freq, FFT_DIR_FORWARD, 0);
+
+    // reset modem object
+    fskdem_reset(q);
+
+    // return main object
+    return q;
+}
+
+// destroy fskdem object
+void fskdem_destroy(fskdem _q)
+{
+    // free allocated arrays
+    free(_q->demod_map);
+    free(_q->buf_time);
+    free(_q->buf_freq);
+    FFT_DESTROY_PLAN(_q->fft);
+
+    // free main object memory
+    free(_q);
+}
+
+// print fskdem object internals
+void fskdem_print(fskdem _q)
+{
+    printf("fskdem : frequency-shift keying demodulator\n");
+    printf("    bits/symbol     :   %u\n", _q->m);
+    printf("    samples/symbol  :   %u\n", _q->k);
+    printf("    bandwidth       :   %8.5f\n", _q->bandwidth);
+}
+
+// reset state
+void fskdem_reset(fskdem _q)
+{
+    // reset time and frequency buffers
+    unsigned int i;
+    for (i=0; i<_q->K; i++) {
+        _q->buf_time[i] = 0.0f;
+        _q->buf_freq[i] = 0.0f;
+    }
+
+    // clear state variables
+    _q->s_demod = 0;
+}
+
+// demodulate symbol, assuming perfect symbol timing
+//  _q      :   fskdem object
+//  _y      :   input sample array [size: _k x 1]
+unsigned int fskdem_demodulate(fskdem          _q,
+                               float complex * _y)
+{
+    // copy input to internal time buffer
+    memmove(_q->buf_time, _y, _q->k*sizeof(float complex));
+
+    // compute transform, storing result in 'buf_freq'
+    FFT_EXECUTE(_q->fft);
+
+    // find maximum by looking at particular bins
+    float        vmax  = 0;
+    unsigned int s     = 0;
+
+    // run search
+    for (s=0; s<_q->M; s++) {
+        float v = cabsf( _q->buf_freq[_q->demod_map[s]] );
+        if (s==0 || v > vmax) {
+            // save optimal output symbol
+            _q->s_demod = s;
+
+            // save peak FFT bin value
+            vmax = v;
+        }
+    }
+
+    // save best result
+    return _q->s_demod;
+}
+
+// get demodulator frequency error
+float fskdem_get_frequency_error(fskdem _q)
+{
+    // get index of peak bin
+    //unsigned int index = _q->buf_freq[ _q->s_demod ];
+
+    // extract peak value of previous, post FFT index
+    float vm = cabsf(_q->buf_freq[(_q->s_demod+_q->K-1)%_q->K]);  // previous
+    float v0 = cabsf(_q->buf_freq[ _q->s_demod               ]);  // peak
+    float vp = cabsf(_q->buf_freq[(_q->s_demod+      1)%_q->K]);  // post
+
+    // compute derivative
+    // TODO: compensate for bin spacing
+    // TODO: just find peak using polynomial interpolation
+    return (vp - vm) / v0;
+}
+
diff --git a/src/modem/src/fskmod.c b/src/modem/src/fskmod.c
new file mode 100644
index 0000000..d19f610
--- /dev/null
+++ b/src/modem/src/fskmod.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// M-ary frequency-shift keying modulator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+// internal methods
+//
+
+// fskmod
+struct fskmod_s {
+    // common
+    unsigned int m;             // bits per symbol
+    unsigned int k;             // samples per symbol
+    float        bandwidth;     // filter bandwidth parameter
+
+    unsigned int M;             // constellation size
+    float        M2;            // (M-1)/2
+    nco_crcf     oscillator;    // nco
+};
+
+// create fskmod object (frequency modulator)
+//  _m          :   bits per symbol, _m > 0
+//  _k          :   samples/symbol, _k >= 2^_m
+//  _bandwidth  :   total signal bandwidth, (0,0.5)
+fskmod fskmod_create(unsigned int _m,
+                     unsigned int _k,
+                     float        _bandwidth)
+{
+    // validate input
+    if (_m == 0) {
+        fprintf(stderr,"error: fskmod_create(), bits/symbol must be greater than 0\n");
+        exit(1);
+    } else if (_k < 2 || _k > 2048) {
+        fprintf(stderr,"error: fskmod_create(), samples/symbol must be in [2^_m, 2048]\n");
+        exit(1);
+    } else if (_bandwidth <= 0.0f || _bandwidth >= 0.5f) {
+        fprintf(stderr,"error: fskmod_create(), bandwidth must be in (0,0.5)\n");
+        exit(1);
+    }
+
+    // create main object memory
+    fskmod q = (fskmod) malloc(sizeof(struct fskmod_s));
+
+    // set basic internal properties
+    q->m         = _m;              // bits per symbol
+    q->k         = _k;              // samples per symbol
+    q->bandwidth = _bandwidth;      // signal bandwidth
+
+    // derived values
+    q->M  = 1 << q->m;              // constellation size
+    q->M2 = 0.5f*(float)(q->M-1);   // (M-1)/2
+
+    q->oscillator = nco_crcf_create(LIQUID_VCO);
+
+    // reset modem object
+    fskmod_reset(q);
+
+    // return main object
+    return q;
+}
+
+// destroy fskmod object
+void fskmod_destroy(fskmod _q)
+{
+    // destroy oscillator object
+    nco_crcf_destroy(_q->oscillator);
+
+    // free main object memory
+    free(_q);
+}
+
+// print fskmod object internals
+void fskmod_print(fskmod _q)
+{
+    printf("fskmod : frequency-shift keying modulator\n");
+    printf("    bits/symbol     :   %u\n", _q->m);
+    printf("    samples/symbol  :   %u\n", _q->k);
+    printf("    bandwidth       :   %8.5f\n", _q->bandwidth);
+}
+
+// reset state
+void fskmod_reset(fskmod _q)
+{
+    // reset internal objects
+    nco_crcf_reset(_q->oscillator);
+}
+
+// modulate sample
+//  _q      :   frequency modulator object
+//  _s      :   input symbol
+//  _y      :   output sample array [size: _k x 1]
+void fskmod_modulate(fskmod          _q,
+                     unsigned int    _s,
+                     float complex * _y)
+{
+    // validate input
+    if (_s >= _q->M) {
+        fprintf(stderr,"warning: fskmod_modulate(), input symbol (%u) exceeds maximum (%u)\n",
+                _s, _q->M);
+        _s = 0;
+    }
+
+    // compute appropriate frequency
+    float dphi = ((float)_s - _q->M2) * 2 * M_PI * _q->bandwidth / _q->M2;
+
+    // set frequency appropriately
+    nco_crcf_set_frequency(_q->oscillator, dphi);
+
+    // generate output tone
+    unsigned int i;
+    for (i=0; i<_q->k; i++) {
+        // compute complex output
+        nco_crcf_cexpf(_q->oscillator, &_y[i]);
+        
+        // step oscillator
+        nco_crcf_step(_q->oscillator);
+    }
+}
+
diff --git a/src/modem/src/gmskdem.c b/src/modem/src/gmskdem.c
new file mode 100644
index 0000000..c174c19
--- /dev/null
+++ b/src/modem/src/gmskdem.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// gmskdem.c : Gauss minimum-shift keying modem
+//
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_GMSKDEM           0
+#define DEBUG_GMSKDEM_FILENAME  "gmskdem_internal_debug.m"
+#define DEBUG_BUFFER_LEN        (1000)
+
+#define GMSKDEM_USE_EQUALIZER   1
+
+void gmskdem_debug_print(gmskdem _q,
+                         const char * _filename);
+
+struct gmskdem_s {
+    unsigned int k;         // samples/symbol
+    unsigned int m;         // symbol delay
+    float BT;               // bandwidth/time product
+    unsigned int h_len;     // filter length
+    float * h;              // pulse shaping filter
+
+    // filter object
+#if GMSKDEM_USE_EQUALIZER
+    eqlms_rrrf eq;          // receiver matched filter/equalizer
+    float k_inv;            // 1 / k
+#else
+    firfilt_rrrf filter;    // receiver matched filter
+#endif
+
+    float complex x_prime;  // received signal state
+
+    // demodulated symbols counter
+    unsigned int num_symbols_demod;
+
+#if DEBUG_GMSKDEM
+    windowf  debug_mfout;
+#endif
+};
+
+// create gmskdem object
+//  _k      :   samples/symbol
+//  _m      :   filter delay (symbols)
+//  _BT     :   excess bandwidth factor
+gmskdem gmskdem_create(unsigned int _k,
+                       unsigned int _m,
+                       float _BT)
+{
+    if (_k < 2) {
+        fprintf(stderr,"error: gmskdem_create(), samples/symbol must be at least 2\n");
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: gmskdem_create(), symbol delay must be at least 1\n");
+        exit(1);
+    } else if (_BT <= 0.0f || _BT >= 1.0f) {
+        fprintf(stderr,"error: gmskdem_create(), bandwidth/time product must be in (0,1)\n");
+        exit(1);
+    }
+
+    // allocate memory for main object
+    gmskdem q = (gmskdem)malloc(sizeof(struct gmskdem_s));
+
+    // set properties
+    q->k  = _k;
+    q->m  = _m;
+    q->BT = _BT;
+
+    // allocate memory for filter taps
+    q->h_len = 2*(q->k)*(q->m)+1;
+    q->h = (float*) malloc(q->h_len * sizeof(float));
+
+    // compute filter coefficients
+    liquid_firdes_gmskrx(q->k, q->m, q->BT, 0.0f, q->h);
+
+#if GMSKDEM_USE_EQUALIZER
+    // receiver matched filter/equalizer
+    q->eq = eqlms_rrrf_create_rnyquist(LIQUID_FIRFILT_GMSKRX,
+                                       q->k,
+                                       q->m,
+                                       q->BT,
+                                       0.0f);
+    eqlms_rrrf_set_bw(q->eq, 0.01f);
+    q->k_inv = 1.0f / (float)(q->k);
+#else
+    // create filter object
+    q->filter = firfilt_rrrf_create(q->h, q->h_len);
+#endif
+
+    // reset modem state
+    gmskdem_reset(q);
+
+#if DEBUG_GMSKDEM
+    q->debug_mfout  = windowf_create(DEBUG_BUFFER_LEN);
+#endif
+
+    // return modem object
+    return q;
+}
+
+void gmskdem_destroy(gmskdem _q)
+{
+#if DEBUG_GMSKDEM
+    // print to external file
+    gmskdem_debug_print(_q, DEBUG_GMSKDEM_FILENAME);
+
+    // destroy debugging objects
+    windowf_destroy(_q->debug_mfout);
+#endif
+
+    // destroy filter object
+#if GMSKDEM_USE_EQUALIZER
+    eqlms_rrrf_destroy(_q->eq);
+#else
+    firfilt_rrrf_destroy(_q->filter);
+#endif
+
+    // free filter array
+    free(_q->h);
+
+    // free main object memory
+    free(_q);
+}
+
+void gmskdem_print(gmskdem _q)
+{
+    printf("gmskdem [k=%u, m=%u, BT=%8.3f]\n", _q->k, _q->m, _q->BT);
+#if GMSKDEM_USE_EQUALIZER
+    printf("    equalizer bandwidth :   %12.8f\n", eqlms_rrrf_get_bw(_q->eq));
+#endif
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        printf("  hr(%4u) = %12.8f;\n", i+1, _q->h[i]);
+}
+
+void gmskdem_reset(gmskdem _q)
+{
+    // reset phase state
+    _q->x_prime = 0.0f;
+
+    // set demod. counter to zero
+    _q->num_symbols_demod = 0;
+
+    // clear filter buffer
+#if GMSKDEM_USE_EQUALIZER
+    eqlms_rrrf_reset(_q->eq);
+#else
+    firfilt_rrrf_reset(_q->filter);
+#endif
+}
+
+// set equalizer bandwidth
+void gmskdem_set_eq_bw(gmskdem _q,
+                       float _bw)
+{
+    // validate input
+    if (_bw < 0.0f || _bw > 0.5f) {
+        fprintf(stderr,"error: gmskdem_set_eq_bw(), bandwith must be in [0,0.5]\n");
+        exit(1);
+    }
+
+#if GMSKDEM_USE_EQUALIZER
+    // set internal equalizer bandwidth
+    eqlms_rrrf_set_bw(_q->eq, _bw);
+#else
+    fprintf(stderr,"warning: gmskdem_set_eq_bw(), equalizer is disabled\n");
+#endif
+}
+
+void gmskdem_demodulate(gmskdem _q,
+                        float complex * _x,
+                        unsigned int * _s)
+{
+    // increment symbol counter
+    _q->num_symbols_demod++;
+
+    // run matched filter
+    unsigned int i;
+    float phi;
+    float d_hat;
+    for (i=0; i<_q->k; i++) {
+        // compute phase difference
+        phi = cargf( conjf(_q->x_prime)*_x[i] );
+        _q->x_prime = _x[i];
+
+        // run through matched filter
+#if GMSKDEM_USE_EQUALIZER
+        eqlms_rrrf_push(_q->eq, phi);
+#else
+        firfilt_rrrf_push(_q->filter, phi);
+#endif
+
+#if DEBUG_GMSKDEM
+        // compute output
+        float d_tmp;
+#  if GMSKDEM_USE_EQUALIZER
+        eqlms_rrrf_execute(_q->eq, &d_tmp);
+#  else
+        firfilt_rrrf_execute(_q->filter, &d_tmp);
+#  endif
+        windowf_push(_q->debug_mfout, d_tmp);
+#endif
+
+        // decimate by k
+        if ( i != 0 ) continue;
+
+#if GMSKDEM_USE_EQUALIZER
+        // compute filter/equalizer output
+        eqlms_rrrf_execute(_q->eq, &d_hat);
+#else
+        // compute filter output
+        firfilt_rrrf_execute(_q->filter, &d_hat);
+#endif
+    }
+
+    // make decision
+    *_s = d_hat > 0.0f ? 1 : 0;
+
+#if GMSKDEM_USE_EQUALIZER
+    // update equalizer, after appropriate delay
+    if (_q->num_symbols_demod >= 2*_q->m) {
+        // compute expected output, scaling by samples/symbol
+        float d_prime = d_hat > 0 ? _q->k_inv : -_q->k_inv;
+        eqlms_rrrf_step(_q->eq, d_prime, d_hat);
+    }
+#endif
+}
+
+//
+// output debugging file
+//
+void gmskdem_debug_print(gmskdem _q,
+                         const char * _filename)
+{
+    // open output filen for writing
+    FILE * fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: gmskdem_debug_print(), could not open '%s' for writing\n", _filename);
+        exit(1);
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", _filename);
+    fprintf(fid,"clear all\n");
+    fprintf(fid,"close all\n");
+
+#if DEBUG_GMSKDEM
+    //
+    unsigned int i;
+    float * r;
+    fprintf(fid,"n = %u;\n", DEBUG_BUFFER_LEN);
+    fprintf(fid,"k = %u;\n", _q->k);
+    fprintf(fid,"m = %u;\n", _q->m);
+    fprintf(fid,"t = [0:(n-1)]/k;\n");
+
+    // plot receive filter response
+    fprintf(fid,"ht = zeros(1,2*k*m+1);\n");
+    float ht[_q->h_len];
+    liquid_firdes_gmsktx(_q->k, _q->m, _q->BT, 0.0f, ht);
+    for (i=0; i<_q->h_len; i++)
+        fprintf(fid,"ht(%4u) = %12.4e;\n", i+1, ht[i]);
+#if GMSKDEM_USE_EQUALIZER
+    float hr[_q->h_len];
+    eqlms_rrrf_get_weights(_q->eq, hr);
+    for (i=0; i<_q->h_len; i++)
+        fprintf(fid,"hr(%4u) = %12.4e * %u;\n", i+1, hr[i], _q->k);
+#else
+    for (i=0; i<_q->h_len; i++)
+        fprintf(fid,"hr(%4u) = %12.4e;\n", i+1, _q->h[i]);
+#endif
+    fprintf(fid,"hc = conv(ht,hr)/k;\n");
+    fprintf(fid,"nfft = 1024;\n");
+    fprintf(fid,"f = [0:(nfft-1)]/nfft - 0.5;\n");
+    fprintf(fid,"Ht = 20*log10(abs(fftshift(fft(ht/k, nfft))));\n");
+    fprintf(fid,"Hr = 20*log10(abs(fftshift(fft(hr/k, nfft))));\n");
+    fprintf(fid,"Hc = 20*log10(abs(fftshift(fft(hc/k, nfft))));\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(f,Ht, f,Hr, f,Hc,'-k','LineWidth',2);\n");
+    fprintf(fid,"axis([-0.5 0.5 -50 10]);\n");
+    fprintf(fid,"xlabel('Normalized Frequency');\n");
+    fprintf(fid,"ylabel('Power Spectral Density [dB]');\n");
+    fprintf(fid,"legend('transmit','receive','composite',1);\n");
+    fprintf(fid,"grid on;\n");
+
+    fprintf(fid,"mfout = zeros(1,n);\n");
+    windowf_read(_q->debug_mfout, &r);
+    for (i=0; i<DEBUG_BUFFER_LEN; i++)
+        fprintf(fid,"mfout(%5u) = %12.4e;\n", i+1, r[i]);
+    fprintf(fid,"i0 = 1; %%mod(k+n,k)+k;\n");
+    fprintf(fid,"isym = i0:k:n;\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(t,mfout,'-', t(isym),mfout(isym),'o','MarkerSize',4);\n");
+    fprintf(fid,"grid on;\n");
+#endif
+
+    fclose(fid);
+    printf("gmskdem: internal debugging written to '%s'\n", _filename);
+}
diff --git a/src/modem/src/gmskmod.c b/src/modem/src/gmskmod.c
new file mode 100644
index 0000000..0b3d138
--- /dev/null
+++ b/src/modem/src/gmskmod.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// gmskmod.c : Gauss minimum-shift keying modem
+//
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+struct gmskmod_s {
+    unsigned int k;         // samples/symbol
+    unsigned int m;         // symbol delay
+    float BT;               // bandwidth/time product
+    unsigned int h_len;     // filter length
+    float * h;              // pulse shaping filter
+
+    // interpolator
+    firinterp_rrrf interp_tx;
+
+    float theta;            // phase state
+    float k_inv;            // 1/k
+};
+
+// create gmskmod object
+//  _k      :   samples/symbol
+//  _m      :   filter delay (symbols)
+//  _BT     :   excess bandwidth factor
+gmskmod gmskmod_create(unsigned int _k,
+                       unsigned int _m,
+                       float _BT)
+{
+    if (_k < 2) {
+        fprintf(stderr,"error: gmskmod_create(), samples/symbol must be at least 2\n");
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: gmskmod_create(), symbol delay must be at least 1\n");
+        exit(1);
+    } else if (_BT <= 0.0f || _BT >= 1.0f) {
+        fprintf(stderr,"error: gmskmod_create(), bandwidth/time product must be in (0,1)\n");
+        exit(1);
+    }
+
+    gmskmod q = (gmskmod)malloc(sizeof(struct gmskmod_s));
+
+    // set properties
+    q->k  = _k;
+    q->m  = _m;
+    q->BT = _BT;
+
+    // derived values
+    q->k_inv = 1.0f / (float)(q->k);
+
+    // allocate memory for filter taps
+    q->h_len = 2*(q->k)*(q->m)+1;
+    q->h = (float*) malloc(q->h_len * sizeof(float));
+
+    // compute filter coefficients
+    liquid_firdes_gmsktx(q->k, q->m, q->BT, 0.0f, q->h);
+
+    // create interpolator object
+    q->interp_tx = firinterp_rrrf_create_prototype(LIQUID_FIRFILT_GMSKTX, q->k, q->m, q->BT, 0);
+
+    // reset modem state
+    gmskmod_reset(q);
+
+    // return modem object
+    return q;
+}
+
+void gmskmod_destroy(gmskmod _q)
+{
+    // destroy interpolator
+    firinterp_rrrf_destroy(_q->interp_tx);
+
+    // free transmit filter array
+    free(_q->h);
+
+    // free main object memory
+    free(_q);
+}
+
+void gmskmod_print(gmskmod _q)
+{
+    printf("gmskmod [k=%u, m=%u, BT=%8.3f]\n", _q->k, _q->m, _q->BT);
+    unsigned int i;
+    for (i=0; i<_q->h_len; i++)
+        printf("  ht(%4u) = %12.8f;\n", i+1, _q->h[i]);
+
+}
+
+void gmskmod_reset(gmskmod _q)
+{
+    // reset phase state
+    _q->theta = 0.0f;
+
+    // clear interpolator buffer
+    firinterp_rrrf_reset(_q->interp_tx);
+}
+
+void gmskmod_modulate(gmskmod _q,
+                      unsigned int _s,
+                      float complex * _y)
+{
+    // generate sample from symbol
+    float x = _s==0 ? -_q->k_inv : _q->k_inv;
+
+    // run interpolator
+    float phi[_q->k];
+    firinterp_rrrf_execute(_q->interp_tx, x, phi);
+
+    // integrate phase state
+    unsigned int i;
+    for (i=0; i<_q->k; i++) {
+        // integrate phase state
+        _q->theta += phi[i];
+
+        // ensure phase in [-pi, pi]
+        if (_q->theta >  M_PI) _q->theta -= 2*M_PI;
+        if (_q->theta < -M_PI) _q->theta += 2*M_PI;
+
+        // compute output
+        _y[i] = liquid_cexpjf(_q->theta);
+    }
+
+
+}
+
+
diff --git a/src/modem/src/modem_apsk.c b/src/modem/src/modem_apsk.c
new file mode 100644
index 0000000..8dddf44
--- /dev/null
+++ b/src/modem/src/modem_apsk.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_apsk.c
+//
+
+// create an apsk (amplitude/phase-shift keying) modem object
+MODEM() MODEM(_create_apsk)(unsigned int _bits_per_symbol)
+{
+    // pointer to APSK definition container
+    struct liquid_apsk_s * apskdef = NULL;
+
+    switch (_bits_per_symbol) {
+    case 2: apskdef = &liquid_apsk4;    break;
+    case 3: apskdef = &liquid_apsk8;    break;
+    case 4: apskdef = &liquid_apsk16;   break;
+    case 5: apskdef = &liquid_apsk32;   break;
+    case 6: apskdef = &liquid_apsk64;   break;
+    case 7: apskdef = &liquid_apsk128;  break;
+    case 8: apskdef = &liquid_apsk256;  break;
+    default:
+        fprintf(stderr,"error: modem_create_apsk(), unsupported modulation level (%u)\n",
+                _bits_per_symbol);
+        exit(1);
+    }
+
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = apskdef->scheme;
+    MODEM(_init)(q, _bits_per_symbol);
+
+    // set APSK internals
+    unsigned int i;
+    q->data.apsk.num_levels = apskdef->num_levels;
+    for (i=0; i<q->data.apsk.num_levels; i++) {
+        q->data.apsk.p[i]   = apskdef->p[i];
+        q->data.apsk.r[i]   = apskdef->r[i];
+        q->data.apsk.phi[i] = apskdef->phi[i];
+    }
+
+    // radius slicer
+    for (i=0; i<q->data.apsk.num_levels-1; i++)
+        q->data.apsk.r_slicer[i] = apskdef->r_slicer[i];
+
+    // copy symbol map
+    q->data.apsk.map = (unsigned char *) malloc(q->M*sizeof(unsigned char));
+    memmove(q->data.apsk.map, apskdef->map, q->M*sizeof(unsigned char));
+
+    // set modulation/demodulation function pointers
+    q->modulate_func = &MODEM(_modulate_apsk);
+    q->demodulate_func = &MODEM(_demodulate_apsk);
+
+    // initialize soft-demodulation look-up table
+    switch (q->m) {
+    case 2: MODEM(_demodsoft_gentab)(q, 3); break;
+    case 3: MODEM(_demodsoft_gentab)(q, 3); break;
+    case 4: MODEM(_demodsoft_gentab)(q, 4); break;
+    case 5: MODEM(_demodsoft_gentab)(q, 4); break;
+    case 6: MODEM(_demodsoft_gentab)(q, 4); break;
+    case 7: MODEM(_demodsoft_gentab)(q, 5); break;
+    case 8: MODEM(_demodsoft_gentab)(q, 5); break;
+    default:;
+    }
+
+    // initialize symbol map
+    q->symbol_map = (TC*)malloc(q->M*sizeof(TC));
+    MODEM(_init_map)(q);
+    q->modulate_using_map = 1;
+
+    // reset modem and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate APSK
+void MODEM(_modulate_apsk)(MODEM()      _q,
+                           unsigned int _sym_in,
+                           TC *         _y)
+{
+    if (_sym_in >= _q->M) {
+        fprintf(stderr,"error: modem_modulate_apsk(), input symbol exceeds maximum\n");
+        return;
+    }
+
+    // map input symbol to constellation symbol
+    unsigned int i;
+    unsigned int s = _q->data.apsk.map[_sym_in];
+
+    // determine in which level the symbol is located
+    unsigned int p=0;   // level
+    unsigned int t=0;   // accumulated number of points per level
+    for (i=0; i<_q->data.apsk.num_levels; i++) {
+        if (s < t + _q->data.apsk.p[i]) {
+            p = i;
+            break;
+        }
+        t += _q->data.apsk.p[i];
+    }
+    unsigned int s0 = s - t;
+    unsigned int s1 = _q->data.apsk.p[p];
+
+#if 0
+    printf("  s : %3u -> %3u in level %3u (t = %3u) [symbol %3u / %3u]\n", _sym_in, s, p, t, s0,s1);
+#endif
+
+    // map symbol to constellation point (radius, angle)
+    T r = _q->data.apsk.r[p];
+    T phi = _q->data.apsk.phi[p] + (T)(s0)*2.0f*M_PI / (T)(s1);
+
+    // compute output symbol
+    *_y = r * liquid_cexpjf(phi);
+}
+
+// demodulate APSK
+void MODEM(_demodulate_apsk)(MODEM()        _q,
+                             TC             _x,
+                             unsigned int * _sym_out)
+{
+    // compute amplitude
+    T r = cabsf(_x);
+
+    // determine which ring to demodulate with
+    unsigned int i, p=0;
+    for (i=0; i<_q->data.apsk.num_levels-1; i++) {
+        if (r < _q->data.apsk.r_slicer[i]) {
+            p = i;
+            break;
+        } else {
+            p = _q->data.apsk.num_levels-1;
+        }
+    }
+
+    // find closest point in ring
+    T theta = cargf(_x);
+    if (theta < 0.0f) theta += 2.0f*M_PI;
+    T dphi = 2.0f*M_PI / (T) _q->data.apsk.p[p];
+    unsigned int s_hat=0;
+    T i_hat = (theta - _q->data.apsk.phi[p]) / dphi;
+    s_hat = roundf(i_hat);      // compute symbol (closest angle)
+    s_hat %= _q->data.apsk.p[p];   // ensure symbol is in range
+    //printf("          i_hat : %12.8f (%3u)\n", i_hat, s_hat);
+
+    // accumulate symbol points
+    for (i=0; i<p; i++)
+        s_hat += _q->data.apsk.p[i];
+    //assert(s_hat < _q->M);
+
+    // reverse symbol mapping
+    unsigned int s_prime=0;
+    for (i=0; i<_q->M; i++) {
+        if ( _q->data.apsk.map[i] == s_hat) {
+            s_prime = i;
+            break;
+        }
+    }
+
+#if 0
+    printf("              x : %12.8f + j*%12.8f\n", crealf(_x), cimagf(_x));
+    printf("              p : %3u\n", p);
+    printf("          theta : %12.8f\n", theta);
+    printf("           dmin : %12.8f\n", dmin);
+    printf("              s : %3u > %3u\n", s_hat, s_prime);
+#endif
+
+    *_sym_out = s_prime;
+
+    // re-modulate symbol and store state
+    MODEM(_modulate)(_q, s_prime, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_apsk_const.c b/src/modem/src/modem_apsk_const.c
new file mode 100644
index 0000000..a3a7ea7
--- /dev/null
+++ b/src/modem/src/modem_apsk_const.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_apsk_const.c
+//
+// APSK modem constants
+//
+
+#include <stdlib.h>
+#include "liquid.internal.h"
+
+// APSK4(1,3)
+unsigned int  apsk4_num_levels  = 2;
+unsigned int  apsk4_p[2]        = {1,3};
+float         apsk4_r[2]        = {0.0f, 1.15470052};
+float         apsk4_phi[2]      = {0.0f, 0.0f};
+float         apsk4_r_slicer[1] = {0.57735026};
+unsigned char apsk4_map[4]      = {3,2,1,0};
+struct liquid_apsk_s liquid_apsk4 = {
+    LIQUID_MODEM_APSK4,
+    2,
+    apsk4_p,
+    apsk4_r,
+    apsk4_phi,
+    apsk4_r_slicer,
+    apsk4_map};
+
+
+// APSK8(1,7)
+unsigned int  apsk8_num_levels  = 2;
+unsigned int  apsk8_p[2]        = {1,7};
+float         apsk8_r[2]        = {0.0, 1.06904495};
+float         apsk8_phi[2]      = {0.0, 0.0};
+float         apsk8_r_slicer[1] = {0.53452247};
+unsigned char apsk8_map[8] = {
+     0,   2,   4,   3,   1,   7,   5,   6};
+struct liquid_apsk_s liquid_apsk8 = {
+    LIQUID_MODEM_APSK8,
+    2,
+    apsk8_p,
+    apsk8_r,
+    apsk8_phi,
+    apsk8_r_slicer,
+    apsk8_map};
+
+
+// APSK16(4,12)
+unsigned int  apsk16_num_levels  = 2;
+unsigned int  apsk16_p[2]        = {4,12};
+float         apsk16_r[2]        = {0.43246540f, 1.12738252f};
+float         apsk16_phi[2]      = {0.0f, 0.0f};
+float         apsk16_r_slicer[1] = {0.77992396f};
+unsigned char apsk16_map[16] = {
+    11,  10,   8,   9,  12,   2,   7,   1,
+    14,  15,   5,   4,  13,   3,   6,   0};
+struct liquid_apsk_s liquid_apsk16 = {
+    LIQUID_MODEM_APSK16,
+    2,
+    apsk16_p,
+    apsk16_r,
+    apsk16_phi,
+    apsk16_r_slicer,
+    apsk16_map};
+
+
+// APSK32(4,12,16)
+unsigned int apsk32_num_levels = 3;
+unsigned int apsk32_p[3] = {4,12,16};
+float apsk32_r[3] = {
+    0.27952856f,
+    0.72980529f,
+    1.25737989f};
+float apsk32_phi[3] = {
+    0.0f,
+    0.0f,
+    0.0f};
+float apsk32_r_slicer[2] = {
+    0.504666925,
+    0.993592590};
+unsigned char apsk32_map[32] = {
+  26,  25,  22,  23,  27,  11,  21,   9,
+  13,   3,   7,   1,  12,  10,   8,  24,
+  30,  31,  18,  17,  29,  15,  19,   5,
+  28,   0,  20,   2,  14,  16,   6,   4};
+struct liquid_apsk_s liquid_apsk32 = {
+    LIQUID_MODEM_APSK32,
+    3,
+    apsk32_p,
+    apsk32_r,
+    apsk32_phi,
+    apsk32_r_slicer,
+    apsk32_map};
+
+
+// APSK64(4,14,20,26)
+unsigned int apsk64_num_levels = 4;
+unsigned int apsk64_p[4] = {4, 14, 20, 26};
+float apsk64_r[4] = {
+    0.18916586,
+    0.52466476,
+    0.88613129,
+    1.30529201};
+float apsk64_phi[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+float apsk64_r_slicer[3] = {
+    0.35691531,
+    0.70539802,
+    1.09571165};
+unsigned char apsk64_map[64] = {
+    54,  53,  51,  52,  48,  49,  28,  50,
+    55,  30,  11,  29,  47,  25,  27,  26,
+    57,  32,   2,  14,  45,  23,   1,   8,
+    56,  31,  12,  13,  46,  24,  10,   9,
+    61,  62,  38,  63,  41,  40,  18,  39,
+    60,  35,  37,  36,  42,  20,   4,  19,
+    58,  33,   3,  15,  44,  22,   0,   7,
+    59,  34,  17,  16,  43,  21,   5,   6};
+struct liquid_apsk_s liquid_apsk64 = {
+    LIQUID_MODEM_APSK64,
+    4,
+    apsk64_p,
+    apsk64_r,
+    apsk64_phi,
+    apsk64_r_slicer,
+    apsk64_map};
+
+
+// APSK128(8,18,24,36,42)
+unsigned int apsk128_num_levels = 5;
+unsigned int apsk128_p[5] = {8,18,24,36,42};
+float apsk128_r[5] = {
+    0.20241030,
+    0.46255755,
+    0.70972824,
+    0.99172282,
+    1.34806108};
+float apsk128_phi[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+float apsk128_r_slicer[4] = {
+    0.33248392,
+    0.58614290,
+    0.85072553,
+    1.16989195};
+unsigned char apsk128_map[128] = {
+   112, 111, 108, 109, 102, 103, 106, 105,
+   113, 110, 107,  71, 101, 104,  67,  66,
+   115,  73,  39,  41,  99,  63,  38,  36,
+   114,  72,  69,  70, 100,  64,  68,  65,
+   117,  77,   1,  21,  97,  59,   2,  13,
+    76,  43,   4,  20,  60,  33,   3,  14,
+   116,  74,  18,  40,  98,  62,  37,  35,
+    75,  42,  17,  19,  61,  34,  16,  15,
+   123, 124, 127, 126,  91,  90,  87,  88,
+   122, 125,  85,  84,  92,  89,  51,  53,
+   120,  81,  49,  48,  94,  55,  26,  29,
+   121,  82,  50,  83,  93,  54,  27,  52,
+   118,  44,   7,   5,  96,  32,   0,  10,
+    78,  45,   6,  22,  58,  30,  86,  12,
+    80,  79,  25,  47,  57,  56,   9,  28,
+   119,  46,  24,  23,  95,  31,   8,  11};
+struct liquid_apsk_s liquid_apsk128 = {
+    LIQUID_MODEM_APSK128,
+    5,
+    apsk128_p,
+    apsk128_r,
+    apsk128_phi,
+    apsk128_r_slicer,
+    apsk128_map};
+
+
+// APSK256(6,18,32,36,46,54,64)
+unsigned int apsk256_num_levels = 7;
+unsigned int apsk256_p[7] = {6,18,32,36,46,54,64};
+float apsk256_r[7] = {
+    0.19219166,
+    0.41951191,
+    0.60772800,
+    0.77572918,
+    0.94819963,
+    1.12150347,
+    1.31012368};
+float apsk256_phi[7] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+float apsk256_r_slicer[6] = {
+    0.30585179,
+    0.51361996,
+    0.69172859,
+    0.86196440,
+    1.03485155,
+    1.21581364};
+unsigned char apsk256_map[256] = {
+   232, 231, 229, 230, 224, 225, 227, 226,
+   216, 217, 219, 218, 164, 223, 221, 222,
+   233, 228, 170, 171, 166, 167, 169, 168,
+   215, 220, 160, 159, 165, 163, 161, 162,
+   235, 173, 123, 121,  75,  76,  78,  77,
+   213, 157,  70, 109,  73, 115,  71,  72,
+   234, 172, 120, 174, 116, 117, 119, 118,
+   214, 158, 110, 156, 114, 113, 111, 112,
+   239, 178,  82, 126, 255,  19,  47,   5,
+   209, 152,  66, 105,  56,  12,  33,  13,
+   238, 177,  81, 125, 138, 193,  46,   4,
+   210, 153,  67, 106,   7,   1,  34,   2,
+   236, 175,  79, 122,  74,  42,  44,  43,
+   212, 155,  69, 108,  39,  38,  36,  37,
+   237, 176,  80, 124,   3,  40,  45,  41,
+   211, 154,  68, 107,  16,  15,  35,  14,
+   248, 249, 251, 250, 191, 190, 252, 253,
+   200, 199, 197, 198, 139, 140, 196, 195,
+   247, 185, 187, 186, 137, 136, 188, 189,
+   201, 145, 143, 144,  93,  94, 142, 141,
+   245, 183,  87, 131,  54,  53,  88,  89,
+   203, 147,  61,  99,  26,  27,  60,  59,
+   246, 184, 133, 132,  91,  90, 134, 135,
+   202, 146,  97,  98,  57,  58,  96,  95,
+   240, 179,  83, 127,  23, 192,  48,  17,
+   208, 151,  65, 104,   6,  11,  32, 206,
+   241, 180,  84, 128, 254, 242,  49,  18,
+   207, 150,  64, 102,  92, 194,  31,  10,
+   244, 182,  86, 130,  55,  21,  52,  51,
+   204, 148,  62, 100,  25,   8,  28,  29,
+   243, 181,  85, 129,   0,  22,  50,  20,
+   205, 149,  63, 101,  24,   9,  30, 103};
+struct liquid_apsk_s liquid_apsk256 = {
+    LIQUID_MODEM_APSK256,
+    7,
+    apsk256_p,
+    apsk256_r,
+    apsk256_phi,
+    apsk256_r_slicer,
+    apsk256_map};
+  
diff --git a/src/modem/src/modem_arb.c b/src/modem/src/modem_arb.c
new file mode 100644
index 0000000..0badecb
--- /dev/null
+++ b/src/modem/src/modem_arb.c
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_arb.c
+//
+
+// create arbitrary digital modem object
+MODEM() MODEM(_create_arbitrary)(TC * _table,
+                               unsigned int _M)
+{
+    // strip out bits/symbol
+    unsigned int m = liquid_nextpow2(_M);
+    if ( (1<<m) != _M ) {
+        // TODO : eventually support non radix-2 constellation sizes
+        fprintf(stderr,"error: modem_create_arbitrary(), input constellation size must be power of 2\n");
+        exit(1);
+    }
+
+    // create arbitrary modem object, not initialized
+    MODEM() q = MODEM(_create_arb)(m);
+
+    // initialize object from table
+    MODEM(_arb_init)(q, _table, _M);
+
+    // return object
+    return q;
+}
+
+
+// create an arbitrary modem object
+MODEM() MODEM(_create_arb)(unsigned int _bits_per_symbol)
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_ARB;
+
+    MODEM(_init)(q, _bits_per_symbol);
+
+    q->M = q->M;
+    q->symbol_map = (TC*) calloc( q->M, sizeof(TC) );
+
+    q->modulate_func   = &MODEM(_modulate_arb);
+    q->demodulate_func = &MODEM(_demodulate_arb);
+
+    return q;
+}
+
+// modulate arbitrary modem type
+void MODEM(_modulate_arb)(MODEM()      _q,
+                          unsigned int _sym_in,
+                          TC *         _y)
+{
+    if (_sym_in >= _q->M) {
+        fprintf(stderr,"error: modulate_arb(), input symbol exceeds maximum\n");
+        exit(1);
+    }
+
+    // map sample directly to output
+    *_y = _q->symbol_map[_sym_in]; 
+}
+
+// demodulate arbitrary modem type
+void MODEM(_demodulate_arb)(MODEM()        _q,
+                            TC             _x,
+                            unsigned int * _sym_out)
+{
+    //printf("modem_demodulate_arb() invoked with I=%d, Q=%d\n", x);
+    
+    // search for symbol nearest to received sample
+    unsigned int i;
+    unsigned int s=0;
+    T d;            // distance
+    T d_min = 0.0f; // minimum distance
+
+    for (i=0; i<_q->M; i++) {
+        // compute distance from received symbol to constellation point
+        d = cabsf(_x - _q->symbol_map[i]);
+
+        // retain symbol with minimum distance
+        if ( i==0 || d < d_min ) {
+            d_min = d;
+            s = i;
+        }
+    }
+
+    // set output symbol
+    *_sym_out = s;
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_arb)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
+// create a V.29 modem object (4 bits/symbol)
+MODEM() MODEM(_create_V29)()
+{
+    MODEM() q = MODEM(_create_arb)(4);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb_V29,16);
+#endif
+    return q;
+}
+
+// create an arb16opt (optimal 16-qam) modem object
+MODEM() MODEM(_create_arb16opt)()
+{
+    MODEM() q = MODEM(_create_arb)(4);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb16opt,16);
+#endif
+    return q;
+}
+
+// create an arb32opt (optimal 32-qam) modem object
+MODEM() MODEM(_create_arb32opt)()
+{
+    MODEM() q = MODEM(_create_arb)(5);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb32opt,32);
+#endif
+    return q;
+}
+
+// create an arb64opt (optimal 64-qam) modem object
+MODEM() MODEM(_create_arb64opt)()
+{
+    MODEM() q = MODEM(_create_arb)(6);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb64opt,64);
+#endif
+    return q;
+}
+
+// create an arb128opt (optimal 128-qam) modem object
+MODEM() MODEM(_create_arb128opt)()
+{
+    MODEM() q = MODEM(_create_arb)(7);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb128opt,128);
+#endif
+    return q;
+}
+
+// create an arb256opt (optimal 256-qam) modem object
+MODEM() MODEM(_create_arb256opt)()
+{
+    MODEM() q = MODEM(_create_arb)(8);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb256opt,256);
+#endif
+    return q;
+}
+
+// create an arb64vt (64-qam vt logo) modem object
+MODEM() MODEM(_create_arb64vt)()
+{
+    MODEM() q = MODEM(_create_arb)(6);
+#if T == float
+    MODEM(_arb_init)(q,(TC*)modem_arb_vt64,64);
+#endif
+    return q;
+}
+
+// initialize an arbitrary modem object
+//  _mod        :   modem object
+//  _symbol_map :   arbitrary modem symbol map
+//  _len        :   number of symbols in the map
+void MODEM(_arb_init)(MODEM()      _q,
+                      TC *         _symbol_map,
+                      unsigned int _len)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if (_q->scheme != LIQUID_MODEM_ARB) {
+        fprintf(stderr,"error: modem_arb_init(), modem is not of arbitrary type\n");
+        exit(1);
+    } else if (_len != _q->M) {
+        fprintf(stderr,"error: modem_arb_init(), array sizes do not match\n");
+        exit(1);
+    }
+#endif
+
+    unsigned int i;
+    for (i=0; i<_len; i++)
+        _q->symbol_map[i] = _symbol_map[i];
+
+    // balance I/Q channels
+    if (_q->scheme == LIQUID_MODEM_ARB)
+        MODEM(_arb_balance_iq)(_q);
+
+    // scale modem to have unity energy
+    MODEM(_arb_scale)(_q);
+
+}
+
+// initialize an arbitrary modem object on a file
+//  _mod        :   modem object
+//  _filename   :   name of the data file
+void MODEM(_arb_init_file)(MODEM() _q,
+                           char *  _filename)
+{
+    // try to open file
+    FILE * fid = fopen(_filename, "r");
+    if (fid == NULL) {
+        fprintf(stderr,"error: modem_arb_init_file(), could not open file\n");
+        exit(1);
+    }
+
+    unsigned int i, results;
+    T sym_i, sym_q;
+    for (i=0; i<_q->M; i++) {
+        if ( feof(fid) ) {
+            fprintf(stderr,"error: modem_arb_init_file(), premature EOF for '%s'\n", _filename);
+            exit(1);
+        }
+
+        results = fscanf(fid, "%f %f\n", &sym_i, &sym_q);
+        _q->symbol_map[i] = sym_i + _Complex_I*sym_q;
+
+        // ensure proper number of symbols were read
+        if (results < 2) {
+            fprintf(stderr,"error: modem_arb_init_file(), unable to parse line\n");
+            exit(1);
+        }
+    }
+
+    fclose(fid);
+
+    // balance I/Q channels
+    if (_q->scheme == LIQUID_MODEM_ARB)
+        MODEM(_arb_balance_iq)(_q);
+
+    // scale modem to have unity energy
+    MODEM(_arb_scale)(_q);
+}
+
+// scale arbitrary modem constellation points
+void MODEM(_arb_scale)(MODEM() _q)
+{
+    unsigned int i;
+
+    // calculate energy
+    T mag, e = 0.0f;
+    for (i=0; i<_q->M; i++) {
+        mag = cabsf(_q->symbol_map[i]);
+        e += mag*mag;
+    }
+
+    e = sqrtf( e / _q->M );
+
+    for (i=0; i<_q->M; i++) {
+        _q->symbol_map[i] /= e;
+    }
+}
+
+// balance an arbitrary modem's I/Q points
+void MODEM(_arb_balance_iq)(MODEM() _q)
+{
+    TC mean=0.0f;
+    unsigned int i;
+
+    // accumulate average signal
+    for (i=0; i<_q->M; i++) {
+        mean += _q->symbol_map[i];
+    }
+    mean /= (T) (_q->M);
+
+    // subtract mean value from reference levels
+    for (i=0; i<_q->M; i++) {
+        _q->symbol_map[i] -= mean;
+    }
+}
+
+// demodulate arbitrary modem type (soft)
+void MODEM(_demodulate_soft_arb)(MODEM()         _q,
+                                 TC              _r,
+                                 unsigned int  * _s,
+                                 unsigned char * _soft_bits)
+{
+    unsigned int bps = _q->m;
+    unsigned int M   = _q->M;
+
+    // gamma = 1/(2*sigma^2), approximate for constellation size
+    T gamma = 1.2f*_q->M;
+
+    unsigned int s=0;       // hard decision output
+    unsigned int k;         // bit index
+    unsigned int i;         // symbol index
+    T d;                // distance for this symbol
+    TC x_hat;    // re-modulated symbol
+
+    T dmin_0[bps];
+    T dmin_1[bps];
+    for (k=0; k<bps; k++) {
+        dmin_0[k] = 4.0f;
+        dmin_1[k] = 4.0f;
+    }
+    T dmin = 0.0f;
+
+    for (i=0; i<M; i++) {
+        // compute distance from received symbol
+        x_hat = _q->symbol_map[i];
+        d = crealf( (_r-x_hat)*conjf(_r-x_hat) );
+
+        // set hard-decision...
+        if (d < dmin || i==0) {
+            s = i;
+            dmin = d;
+        }
+
+        for (k=0; k<bps; k++) {
+            // strip bit
+            if ( (s >> (bps-k-1)) & 0x01 ) {
+                if (d < dmin_1[k]) dmin_1[k] = d;
+            } else {
+                if (d < dmin_0[k]) dmin_0[k] = d;
+            }
+        }
+    }
+
+    // make assignments
+    for (k=0; k<bps; k++) {
+        int soft_bit = ((dmin_0[k] - dmin_1[k])*gamma)*16 + 127;
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit = 0;
+        _soft_bits[k] = (unsigned char)soft_bit;
+    }
+
+    // hard decision
+
+    // set hard output symbol
+    *_s = s;
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_arb)(_q, *_s, &_q->x_hat);
+    _q->r = _r;
+}
+
diff --git a/src/modem/src/modem_arb_const.c b/src/modem/src/modem_arb_const.c
new file mode 100644
index 0000000..4c99a64
--- /dev/null
+++ b/src/modem/src/modem_arb_const.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_arb_const.c
+//
+// Constant arbitrary linear modems
+//
+
+#include <stdlib.h>
+#include "liquid.internal.h"
+
+// 'square' 32-QAM (first quadrant)
+const float complex modem_arb_sqam32[8] = {
+      0.22361000+  0.22361000*_Complex_I,   0.67082000+  0.22361000*_Complex_I, 
+      0.67082000+  1.11800000*_Complex_I,   1.11800000+  0.22361000*_Complex_I, 
+      0.22361000+  0.67082000*_Complex_I,   0.67082000+  0.67082000*_Complex_I, 
+      0.22361000+  1.11800000*_Complex_I,   1.11800000+  0.67082000*_Complex_I
+};
+
+// 'square' 128-QAM (first quadrant)
+const float complex modem_arb_sqam128[32] = {
+      0.11043000+  0.11043000*_Complex_I,   0.33129000+  0.11043000*_Complex_I, 
+      0.11043000+  0.33129000*_Complex_I,   0.33129000+  0.33129000*_Complex_I, 
+      0.77302000+  0.11043000*_Complex_I,   0.55216000+  0.11043000*_Complex_I, 
+      0.77302000+  0.33129000*_Complex_I,   0.55216000+  0.33129000*_Complex_I, 
+      0.77302000+  0.99388000*_Complex_I,   0.55216000+  0.99388000*_Complex_I, 
+      0.77302000+  1.21470000*_Complex_I,   0.55216000+  1.21470000*_Complex_I, 
+      0.99388000+  0.11043000*_Complex_I,   1.21470000+  0.11043000*_Complex_I, 
+      0.99388000+  0.33129000*_Complex_I,   1.21470000+  0.33129000*_Complex_I, 
+      0.11043000+  0.77302000*_Complex_I,   0.33129000+  0.77302000*_Complex_I, 
+      0.11043000+  0.55216000*_Complex_I,   0.33129000+  0.55216000*_Complex_I, 
+      0.77302000+  0.77302000*_Complex_I,   0.55216000+  0.77302000*_Complex_I, 
+      0.77302000+  0.55216000*_Complex_I,   0.55216000+  0.55216000*_Complex_I, 
+      0.11043000+  0.99388000*_Complex_I,   0.33129000+  0.99388000*_Complex_I, 
+      0.11043000+  1.21470000*_Complex_I,   0.33129000+  1.21470000*_Complex_I, 
+      0.99388000+  0.77302000*_Complex_I,   1.21470000+  0.77302000*_Complex_I, 
+      0.99388000+  0.55216000*_Complex_I,   1.21470000+  0.55216000*_Complex_I
+};
+
+// V.29 star constellation
+const float complex modem_arb_V29[16] = {
+      0.06804100+  0.06804100*_Complex_I,   0.20412000+  0.00000000*_Complex_I, 
+      0.00000000+  0.20412000*_Complex_I,  -0.06804100+  0.06804100*_Complex_I, 
+      0.00000000+ -0.20412000*_Complex_I,   0.06804100+ -0.06804100*_Complex_I, 
+     -0.06804100+ -0.06804100*_Complex_I,  -0.20412000+  0.00000000*_Complex_I, 
+      0.20412000+  0.20412000*_Complex_I,   0.34021000+  0.00000000*_Complex_I, 
+      0.00000000+  0.34021000*_Complex_I,  -0.20412000+  0.20412000*_Complex_I, 
+      0.00000000+ -0.34021000*_Complex_I,   0.20412000+ -0.20412000*_Complex_I, 
+     -0.20412000+ -0.20412000*_Complex_I,  -0.34021000+  0.00000000*_Complex_I
+};
+
+// Virginia Tech logo
+const float complex modem_arb_vt64[64] = {
+     -1.5633e+00+  5.5460e-01*_Complex_I,  -1.3833e+00+  5.5460e-01*_Complex_I,
+     -1.0234e+00+  5.5460e-01*_Complex_I,  -1.2034e+00+  5.5460e-01*_Complex_I,
+     -7.3553e-01+  5.0751e-02*_Complex_I,  -8.0750e-01+  1.7671e-01*_Complex_I,
+     -9.5146e-01+  4.2863e-01*_Complex_I,  -8.7948e-01+  3.0267e-01*_Complex_I,
+     -3.3741e-02+  5.5460e-01*_Complex_I,  -2.1368e-01+  5.5460e-01*_Complex_I,
+     -4.4761e-01+  4.2863e-01*_Complex_I,  -3.9363e-01+  5.5460e-01*_Complex_I,
+     -6.6355e-01+ -7.5211e-02*_Complex_I,  -6.0956e-01+  5.0751e-02*_Complex_I,
+     -5.0160e-01+  3.0267e-01*_Complex_I,  -5.5558e-01+  1.7671e-01*_Complex_I,
+      9.5596e-01+  1.0473e-01*_Complex_I,   1.1359e+00+  1.0473e-01*_Complex_I,
+      1.4958e+00+  1.0473e-01*_Complex_I,   1.3158e+00+  1.0473e-01*_Complex_I,
+      1.5858e+00+  5.5460e-01*_Complex_I,   1.7657e+00+  5.5460e-01*_Complex_I,
+      1.5858e+00+  2.5499e-01*_Complex_I,   1.6757e+00+  4.0434e-01*_Complex_I,
+      1.4621e-01+  5.5460e-01*_Complex_I,   3.2615e-01+  5.5460e-01*_Complex_I,
+      6.8604e-01+  5.5460e-01*_Complex_I,   5.0610e-01+  5.5460e-01*_Complex_I,
+      1.4058e+00+  5.5460e-01*_Complex_I,   1.2259e+00+  5.5460e-01*_Complex_I,
+      8.6599e-01+  5.5460e-01*_Complex_I,   1.0459e+00+  5.5460e-01*_Complex_I,
+     -1.4508e+00+  3.6385e-01*_Complex_I,  -1.3383e+00+  1.7221e-01*_Complex_I,
+     -1.1134e+00+ -2.1017e-01*_Complex_I,  -1.2259e+00+ -1.8529e-02*_Complex_I,
+     -6.6355e-01+ -9.7494e-01*_Complex_I,  -7.7601e-01+ -7.8420e-01*_Complex_I,
+     -1.0009e+00+ -4.0181e-01*_Complex_I,  -8.8848e-01+ -5.9255e-01*_Complex_I,
+      1.4621e-01+  1.0473e-01*_Complex_I,   1.1246e-02+  1.0473e-01*_Complex_I,
+     -2.1368e-01+ -7.5211e-02*_Complex_I,  -1.2371e-01+  1.0473e-01*_Complex_I,
+     -5.7358e-01+ -7.9499e-01*_Complex_I,  -4.8360e-01+ -6.1505e-01*_Complex_I,
+     -3.0366e-01+ -2.5516e-01*_Complex_I,  -3.9363e-01+ -4.3510e-01*_Complex_I,
+      8.5069e-01+ -7.5211e-02*_Complex_I,   7.4632e-01+ -2.5516e-01*_Complex_I,
+      5.3579e-01+ -6.1505e-01*_Complex_I,   6.4105e-01+ -4.3510e-01*_Complex_I,
+     -3.3741e-02+ -9.7494e-01*_Complex_I,   1.4621e-01+ -9.7494e-01*_Complex_I,
+      4.3142e-01+ -7.9499e-01*_Complex_I,   3.2615e-01+ -9.7494e-01*_Complex_I,
+      2.8116e-01+  1.0473e-01*_Complex_I,   4.1612e-01+  1.0473e-01*_Complex_I,
+      2.0649e-01+ -2.5516e-01*_Complex_I,   3.1086e-01+ -7.5211e-02*_Complex_I,
+     -2.1368e-01+ -9.7494e-01*_Complex_I,  -1.0842e-01+ -7.9499e-01*_Complex_I,
+      1.0122e-01+ -4.3510e-01*_Complex_I,  -4.0500e-03+ -6.1505e-01*_Complex_I
+};
+
+// optimal 16-QAM
+const float complex modem_arb16opt[16] = {
+     -0.87119000+ -0.87970000*_Complex_I,  -1.15090000+ -0.26101000*_Complex_I, 
+     -1.10090000+  0.87457000*_Complex_I,  -0.81088000+  0.29689000*_Complex_I, 
+     -0.21295000+ -0.91897000*_Complex_I,  -0.46984000+ -0.29804000*_Complex_I, 
+     -0.43443000+  0.91820000*_Complex_I,  -0.16786000+  0.30338000*_Complex_I, 
+      1.10000000+ -0.87511000*_Complex_I,   0.81125000+ -0.29671000*_Complex_I, 
+      0.87134000+  0.87989000*_Complex_I,   1.15170000+  0.26143000*_Complex_I, 
+      0.43379000+ -0.91801000*_Complex_I,   0.16807000+ -0.30335000*_Complex_I, 
+      0.21246000+  0.91867000*_Complex_I,   0.47033000+  0.29787000*_Complex_I
+};
+
+// optimal 32-QAM
+const float complex modem_arb32opt[32] = {
+     -1.04570000+ -0.72639000*_Complex_I,  -1.26710000+ -0.00824020*_Complex_I, 
+     -0.99868000+  0.92359000*_Complex_I,  -1.13660000+  0.45833000*_Complex_I, 
+     -0.67812000+ -1.07470000*_Complex_I,  -0.88363000+ -0.25914000*_Complex_I, 
+     -0.64443000+  0.62407000*_Complex_I,  -0.76238000+  0.18163000*_Complex_I, 
+     -0.20985000+ -0.91032000*_Complex_I,  -0.08603100+ -0.45730000*_Complex_I, 
+     -0.18073000+  0.75747000*_Complex_I,   0.02826900+ -0.01908400*_Complex_I, 
+     -0.55206000+ -0.61092000*_Complex_I,  -0.42946000+ -0.16611000*_Complex_I, 
+     -0.51187000+  1.09190000*_Complex_I,  -0.30769000+  0.27731000*_Complex_I, 
+      1.20280000+ -0.58421000*_Complex_I,   1.29120000+ -0.09249800*_Complex_I, 
+      1.10450000+  0.69170000*_Complex_I,   0.96433000+  0.24011000*_Complex_I, 
+      0.60130000+ -1.16500000*_Complex_I,   0.83106000+ -0.23576000*_Complex_I, 
+      0.73090000+  1.01880000*_Complex_I,   0.62090000+  0.54470000*_Complex_I, 
+      0.23884000+ -0.81583000*_Complex_I,   0.36064000+ -0.36749000*_Complex_I, 
+     -0.02439300+  1.23570000*_Complex_I,   0.15492000+  0.41465000*_Complex_I, 
+      0.11172000+ -1.28340000*_Complex_I,   0.70560000+ -0.68005000*_Complex_I, 
+      0.28323000+  0.89268000*_Complex_I,   0.48844000+  0.10367000*_Complex_I
+};
+
+// optimal 64-QAM
+const float complex modem_arb64opt[64] = {
+     -9.6048e-01 +  -1.0031e+00*_Complex_I,
+     -1.1105e+00 +  -6.8896e-01*_Complex_I,
+     -1.1029e+00 +  -9.5064e-02*_Complex_I,
+     -1.2692e+00 +  -3.8774e-01*_Complex_I,
+     -7.9322e-01 +   1.1014e+00*_Complex_I,
+     -9.5999e-01 +   7.9701e-01*_Complex_I,
+     -1.2734e+00 +   1.9621e-01*_Complex_I,
+     -1.1121e+00 +   4.9520e-01*_Complex_I,
+     -6.2388e-01 +  -9.8614e-01*_Complex_I,
+     -7.7108e-01 +  -6.8653e-01*_Complex_I,
+     -7.6793e-01 +  -1.0097e-01*_Complex_I,
+     -9.1812e-01 +  -3.9210e-01*_Complex_I,
+     -4.5416e-01 +   1.0712e+00*_Complex_I,
+     -6.1578e-01 +   7.7701e-01*_Complex_I,
+     -9.1845e-01 +   1.9699e-01*_Complex_I,
+     -7.7026e-01 +   4.8601e-01*_Complex_I,
+     -1.1067e-01 +  -1.2612e+00*_Complex_I,
+     -2.6544e-01 +  -9.6343e-01*_Complex_I,
+      1.3076e+00 +   4.5615e-01*_Complex_I,
+     -2.5827e-01 +  -3.9458e-01*_Complex_I,
+     -1.0044e-01 +   1.0486e+00*_Complex_I,
+      3.9086e-02 +   7.4394e-01*_Complex_I,
+     -2.6144e-01 +   1.7968e-01*_Complex_I,
+     -1.1912e-01 +   4.5495e-01*_Complex_I,
+     -4.5452e-01 +  -1.2733e+00*_Complex_I,
+     -4.3866e-01 +  -6.8570e-01*_Complex_I,
+     -4.3802e-01 +  -1.0872e-01*_Complex_I,
+     -5.8466e-01 +  -3.9550e-01*_Complex_I,
+     -2.6246e-01 +   1.3479e+00*_Complex_I,
+     -2.8816e-01 +   7.5672e-01*_Complex_I,
+     -5.8784e-01 +   1.8705e-01*_Complex_I,
+     -4.4008e-01 +   4.7244e-01*_Complex_I,
+      7.9849e-01 +  -1.0124e+00*_Complex_I,
+      9.5371e-01 +  -7.1194e-01*_Complex_I,
+      1.2696e+00 +  -1.1448e-01*_Complex_I,
+      1.1145e+00 +  -4.2441e-01*_Complex_I,
+      9.0580e-01 +   1.0653e+00*_Complex_I,
+      1.0641e+00 +   7.6363e-01*_Complex_I,
+      1.0837e+00 +   1.7467e-01*_Complex_I,
+      9.1489e-01 +   4.6389e-01*_Complex_I,
+      5.8983e-01 +  -1.2906e+00*_Complex_I,
+      6.0746e-01 +  -7.0336e-01*_Complex_I,
+      9.1373e-01 +  -1.3004e-01*_Complex_I,
+      7.4917e-01 +  -4.1576e-01*_Complex_I,
+      5.6608e-01 +   1.0707e+00*_Complex_I,
+      7.1464e-01 +   7.5794e-01*_Complex_I,
+      7.4460e-01 +   1.6652e-01*_Complex_I,
+      5.7594e-01 +   4.5036e-01*_Complex_I,
+      7.6128e-02 +  -9.7664e-01*_Complex_I,
+     -9.2551e-02 +  -6.8763e-01*_Complex_I,
+     -9.4285e-02 +  -1.0993e-01*_Complex_I,
+      6.5368e-02 +  -4.0109e-01*_Complex_I,
+      1.0945e-01 +   1.3350e+00*_Complex_I,
+      2.2470e-01 +   4.7166e-01*_Complex_I,
+      5.5987e-01 +  -1.1259e-01*_Complex_I,
+      7.0006e-02 +   1.9154e-01*_Complex_I,
+      2.4876e-01 +  -1.2685e+00*_Complex_I,
+      4.2452e-01 +  -9.8023e-01*_Complex_I,
+      2.5356e-01 +  -6.8613e-01*_Complex_I,
+      4.0576e-01 +  -4.1184e-01*_Complex_I,
+      2.4615e-01 +   1.0373e+00*_Complex_I,
+      3.9219e-01 +   7.4440e-01*_Complex_I,
+      2.4704e-01 +  -1.0509e-01*_Complex_I,
+      3.9791e-01 +   1.8370e-01*_Complex_I,
+};
+// optimal 128-QAM
+const float complex modem_arb128opt[128] = {
+     -1.0682e+00 +  -9.2598e-01*_Complex_I,
+     -1.1831e+00 +  -7.0704e-01*_Complex_I,
+     -1.3029e+00 +  -1.0555e-01*_Complex_I,
+     -1.3061e+00 +  -4.9292e-01*_Complex_I,
+     -9.4032e-01 +   9.7928e-01*_Complex_I,
+     -1.1807e+00 +   5.4821e-01*_Complex_I,
+     -1.4078e+00 +   1.1265e-01*_Complex_I,
+     -1.2954e+00 +   3.3623e-01*_Complex_I,
+     -7.3838e-01 +  -1.1381e+00*_Complex_I,
+     -9.4161e-01 +  -7.0518e-01*_Complex_I,
+     -1.1625e+00 +  -2.9080e-01*_Complex_I,
+     -1.0477e+00 +  -4.9556e-01*_Complex_I,
+     -1.0664e+00 +   7.7185e-01*_Complex_I,
+     -8.3123e-01 +   7.4171e-01*_Complex_I,
+     -1.1636e+00 +   1.1831e-01*_Complex_I,
+     -1.0506e+00 +   3.2582e-01*_Complex_I,
+     -4.8913e-01 +  -1.1074e+00*_Complex_I,
+     -5.8148e-01 +  -8.9486e-01*_Complex_I,
+     -8.0747e-01 +  -9.4398e-02*_Complex_I,
+     -9.1178e-01 +  -2.9590e-01*_Complex_I,
+     -7.0503e-01 +   9.5290e-01*_Complex_I,
+     -5.8794e-01 +   7.4046e-01*_Complex_I,
+     -6.9655e-01 +   1.0350e-01*_Complex_I,
+     -6.8968e-01 +   5.2969e-01*_Complex_I,
+     -8.2001e-01 +  -9.1531e-01*_Complex_I,
+     -7.0810e-01 +  -7.0021e-01*_Complex_I,
+     -1.0403e+00 +  -8.9321e-02*_Complex_I,
+     -8.0893e-01 +  -4.9762e-01*_Complex_I,
+     -6.1299e-01 +   1.1733e+00*_Complex_I,
+     -9.2994e-01 +   5.3436e-01*_Complex_I,
+     -9.2384e-01 +   1.1851e-01*_Complex_I,
+     -8.0873e-01 +   3.2535e-01*_Complex_I,
+      8.5789e-03 +  -1.1069e+00*_Complex_I,
+      1.3354e-01 +  -9.0426e-01*_Complex_I,
+      1.2277e+00 +  -3.0336e-01*_Complex_I,
+     -2.1578e-01 +  -2.9289e-01*_Complex_I,
+      2.5555e-03 +   1.3535e+00*_Complex_I,
+     -1.0737e-01 +   7.4055e-01*_Complex_I,
+      5.0972e-03 +   1.1376e-01*_Complex_I,
+     -4.5018e-03 +   5.4428e-01*_Complex_I,
+     -1.0895e-01 +  -1.3162e+00*_Complex_I,
+     -2.2365e-01 +  -6.9809e-01*_Complex_I,
+      1.3461e+00 +  -9.5212e-02*_Complex_I,
+     -4.3183e-01 +  -2.9421e-01*_Complex_I,
+     -1.1598e-01 +   1.1402e+00*_Complex_I,
+     -2.2730e-01 +   9.3180e-01*_Complex_I,
+     -2.1915e-01 +   1.1528e-01*_Complex_I,
+     -2.2953e-01 +   5.3517e-01*_Complex_I,
+     -3.6962e-01 +  -1.3195e+00*_Complex_I,
+     -4.6545e-01 +  -6.9766e-01*_Complex_I,
+     -6.7387e-01 +  -2.8466e-01*_Complex_I,
+     -5.6923e-01 +  -4.9130e-01*_Complex_I,
+     -3.6828e-01 +   1.1445e+00*_Complex_I,
+     -4.6503e-01 +   9.3615e-01*_Complex_I,
+     -5.7039e-01 +  -8.3381e-02*_Complex_I,
+     -4.6304e-01 +   5.2568e-01*_Complex_I,
+     -2.4438e-01 +  -1.1062e+00*_Complex_I,
+     -3.4724e-01 +  -8.9072e-01*_Complex_I,
+     -3.3719e-01 +  -8.1507e-02*_Complex_I,
+     -3.4116e-01 +  -4.9075e-01*_Complex_I,
+     -2.5467e-01 +   1.3510e+00*_Complex_I,
+     -3.4640e-01 +   7.4004e-01*_Complex_I,
+     -4.5303e-01 +   1.1207e-01*_Complex_I,
+     -5.7498e-01 +   3.1560e-01*_Complex_I,
+      8.7471e-01 +  -9.3403e-01*_Complex_I,
+      1.1932e+00 +  -7.1750e-01*_Complex_I,
+      1.0817e+00 +  -9.5165e-02*_Complex_I,
+      1.0850e+00 +  -5.0487e-01*_Complex_I,
+      9.7521e-01 +   9.8123e-01*_Complex_I,
+      1.0879e+00 +   7.6120e-01*_Complex_I,
+      1.1902e+00 +   1.1187e-01*_Complex_I,
+      1.1983e+00 +   5.4100e-01*_Complex_I,
+      7.5089e-01 +  -1.1405e+00*_Complex_I,
+      9.5031e-01 +  -7.0315e-01*_Complex_I,
+      9.6679e-01 +  -2.8997e-01*_Complex_I,
+      8.4619e-01 +  -4.8958e-01*_Complex_I,
+      7.4379e-01 +   9.5772e-01*_Complex_I,
+      8.4793e-01 +   7.4658e-01*_Complex_I,
+      9.4820e-01 +   1.1332e-01*_Complex_I,
+      9.5910e-01 +   5.3750e-01*_Complex_I,
+      5.0524e-01 +  -1.1045e+00*_Complex_I,
+      4.9092e-01 +  -6.9263e-01*_Complex_I,
+      6.0143e-01 +  -8.7696e-02*_Complex_I,
+      6.0078e-01 +  -4.8901e-01*_Complex_I,
+      5.0450e-01 +   9.5208e-01*_Complex_I,
+      7.1207e-01 +   5.3740e-01*_Complex_I,
+      1.0790e+00 +   3.1868e-01*_Complex_I,
+      6.0339e-01 +   3.3768e-01*_Complex_I,
+      6.2518e-01 +  -9.0613e-01*_Complex_I,
+      7.2199e-01 +  -6.9979e-01*_Complex_I,
+      8.2756e-01 +  -9.3837e-02*_Complex_I,
+      7.2238e-01 +  -2.9345e-01*_Complex_I,
+      6.4006e-01 +   1.1740e+00*_Complex_I,
+      6.1312e-01 +   7.3911e-01*_Complex_I,
+      7.1908e-01 +   1.2320e-01*_Complex_I,
+      8.3253e-01 +   3.2769e-01*_Complex_I,
+     -9.4871e-02 +  -9.0259e-01*_Complex_I,
+     -9.6676e-02 +  -4.9277e-01*_Complex_I,
+      4.8818e-01 +  -2.7584e-01*_Complex_I,
+     -9.8973e-02 +  -1.0402e-01*_Complex_I,
+      2.0955e-02 +   9.4046e-01*_Complex_I,
+      1.3132e-01 +   7.3484e-01*_Complex_I,
+      1.3357e+00 +   3.0715e-01*_Complex_I,
+     -3.3806e-01 +   3.2795e-01*_Complex_I,
+      1.4149e-01 +  -1.3293e+00*_Complex_I,
+      1.4162e-02 +  -6.8874e-01*_Complex_I,
+      1.3376e-01 +  -9.5389e-02*_Complex_I,
+      2.5367e-02 +  -2.8878e-01*_Complex_I,
+      1.3413e-01 +   1.1437e+00*_Complex_I,
+      2.3299e-01 +   5.3149e-01*_Complex_I,
+      4.7428e-01 +   1.2730e-01*_Complex_I,
+      1.1767e-01 +   3.3670e-01*_Complex_I,
+      4.1070e-01 +  -1.3283e+00*_Complex_I,
+      3.7197e-01 +  -8.8947e-01*_Complex_I,
+      2.5771e-01 +  -2.8158e-01*_Complex_I,
+      3.6892e-01 +  -4.8660e-01*_Complex_I,
+      3.8837e-01 +   1.1516e+00*_Complex_I,
+      3.7013e-01 +   7.3189e-01*_Complex_I,
+      2.3982e-01 +   1.2673e-01*_Complex_I,
+      4.7588e-01 +   5.2723e-01*_Complex_I,
+      2.6342e-01 +  -1.1217e+00*_Complex_I,
+      2.4743e-01 +  -6.8820e-01*_Complex_I,
+      3.5855e-01 +  -7.8191e-02*_Complex_I,
+      1.4393e-01 +  -4.8365e-01*_Complex_I,
+      2.7111e-01 +   1.3506e+00*_Complex_I,
+      2.6368e-01 +   9.3477e-01*_Complex_I,
+      3.5476e-01 +   3.1767e-01*_Complex_I,
+     -1.0269e-01 +   3.2880e-01*_Complex_I,};
+
+
+// optimal 256-QAM
+const float complex modem_arb256opt[256] = {
+     -9.3121e-01 +  -1.0845e+00*_Complex_I,
+     -1.0165e+00 +  -9.3103e-01*_Complex_I,
+     -1.1864e+00 +  -6.3650e-01*_Complex_I,
+     -1.1012e+00 +  -7.8304e-01*_Complex_I,
+     -1.3617e+00 +  -4.3833e-02*_Complex_I,
+     -1.2644e+00 +  -1.9022e-01*_Complex_I,
+     -1.2686e+00 +  -4.8780e-01*_Complex_I,
+     -1.3534e+00 +  -3.3726e-01*_Complex_I,
+     -8.4260e-01 +   1.1480e+00*_Complex_I,
+     -9.2614e-01 +   9.9761e-01*_Complex_I,
+     -1.1955e+00 +   5.5467e-01*_Complex_I,
+     -1.1148e+00 +   6.9890e-01*_Complex_I,
+     -1.1792e+00 +  -3.7010e-02*_Complex_I,
+     -1.2648e+00 +   1.1118e-01*_Complex_I,
+     -1.2769e+00 +   4.0872e-01*_Complex_I,
+     -1.3599e+00 +   2.5518e-01*_Complex_I,
+     -6.7924e-01 +  -1.2278e+00*_Complex_I,
+     -8.4406e-01 +  -9.1876e-01*_Complex_I,
+     -1.0044e+00 +  -6.2843e-01*_Complex_I,
+     -9.2155e-01 +  -7.7299e-01*_Complex_I,
+     -1.0806e+00 +   1.0494e-01*_Complex_I,
+     -1.0866e+00 +  -1.9152e-01*_Complex_I,
+     -1.0854e+00 +  -4.8371e-01*_Complex_I,
+     -1.1661e+00 +  -3.3876e-01*_Complex_I,
+     -1.0146e+00 +   8.4545e-01*_Complex_I,
+     -7.5625e-01 +   9.8074e-01*_Complex_I,
+     -9.2315e-01 +   6.9214e-01*_Complex_I,
+     -8.3445e-01 +   8.3483e-01*_Complex_I,
+     -1.0046e+00 +  -3.8507e-02*_Complex_I,
+     -1.1695e+00 +   2.5672e-01*_Complex_I,
+     -1.0054e+00 +   5.4855e-01*_Complex_I,
+     -1.0894e+00 +   4.0650e-01*_Complex_I,
+     -4.9806e-01 +  -1.2285e+00*_Complex_I,
+     -6.6615e-01 +  -9.1531e-01*_Complex_I,
+     -6.6471e-01 +  -6.1531e-01*_Complex_I,
+     -5.0291e-01 +  -9.1017e-01*_Complex_I,
+     -6.6983e-01 +  -4.8903e-02*_Complex_I,
+     -7.4988e-01 +  -1.8703e-01*_Complex_I,
+     -7.4628e-01 +  -4.7626e-01*_Complex_I,
+     -8.2246e-01 +  -3.3201e-01*_Complex_I,
+     -6.7081e-01 +   1.1403e+00*_Complex_I,
+     -5.0407e-01 +   1.1300e+00*_Complex_I,
+     -6.6636e-01 +   5.3994e-01*_Complex_I,
+     -4.9992e-01 +   8.2622e-01*_Complex_I,
+     -8.3007e-01 +  -3.4761e-02*_Complex_I,
+     -7.3978e-01 +   1.0568e-01*_Complex_I,
+     -7.4366e-01 +   3.9573e-01*_Complex_I,
+     -6.6403e-01 +   2.4968e-01*_Complex_I,
+     -7.5405e-01 +  -1.0716e+00*_Complex_I,
+     -5.8636e-01 +  -1.0663e+00*_Complex_I,
+     -8.3409e-01 +  -6.2296e-01*_Complex_I,
+     -7.5222e-01 +  -7.6536e-01*_Complex_I,
+     -9.1403e-01 +   1.0898e-01*_Complex_I,
+     -9.1712e-01 +  -1.8842e-01*_Complex_I,
+     -9.1470e-01 +  -4.7918e-01*_Complex_I,
+     -9.8965e-01 +  -3.3463e-01*_Complex_I,
+     -5.9594e-01 +   1.2959e+00*_Complex_I,
+     -5.8407e-01 +   9.7847e-01*_Complex_I,
+     -7.5480e-01 +   6.8214e-01*_Complex_I,
+     -6.6815e-01 +   8.2880e-01*_Complex_I,
+     -8.3654e-01 +   2.4948e-01*_Complex_I,
+     -9.9781e-01 +   2.5965e-01*_Complex_I,
+     -8.3730e-01 +   5.3876e-01*_Complex_I,
+     -9.1612e-01 +   3.9942e-01*_Complex_I,
+     -1.6318e-01 +  -1.2127e+00*_Complex_I,
+      3.3430e-03 +  -1.2073e+00*_Complex_I,
+     -1.5729e-01 +  -6.1725e-01*_Complex_I,
+      4.1148e-03 +  -9.0838e-01*_Complex_I,
+      1.7582e-01 +  -3.3603e-01*_Complex_I,
+      9.8785e-03 +  -3.7206e-02*_Complex_I,
+     -8.5107e-02 +  -4.7781e-01*_Complex_I,
+     -1.5814e-01 +  -4.2120e-02*_Complex_I,
+     -7.2012e-02 +   1.2831e+00*_Complex_I,
+      9.0714e-04 +   1.1297e+00*_Complex_I,
+     -7.8182e-02 +   6.8626e-01*_Complex_I,
+     -1.5437e-01 +   8.2912e-01*_Complex_I,
+      5.1610e-01 +  -3.3653e-02*_Complex_I,
+      1.2961e+00 +   4.0304e-01*_Complex_I,
+     -5.9161e-03 +   5.4276e-01*_Complex_I,
+     -1.6148e-01 +   2.5858e-01*_Complex_I,
+     -8.4651e-02 +  -1.3668e+00*_Complex_I,
+     -3.2532e-01 +  -9.1229e-01*_Complex_I,
+     -3.2442e-01 +  -6.1566e-01*_Complex_I,
+     -1.6208e-01 +  -9.0614e-01*_Complex_I,
+      1.8048e-01 +  -3.7457e-02*_Complex_I,
+      1.3866e+00 +  -3.3074e-01*_Complex_I,
+     -2.4797e-01 +  -4.7456e-01*_Complex_I,
+     -2.4267e-01 +  -1.8754e-01*_Complex_I,
+     -1.6601e-01 +   1.4313e+00*_Complex_I,
+     -1.6374e-01 +   1.1268e+00*_Complex_I,
+     -2.4284e-01 +   6.8228e-01*_Complex_I,
+     -3.1958e-01 +   8.2714e-01*_Complex_I,
+      1.4029e+00 +  -4.1466e-02*_Complex_I,
+     -2.4744e-01 +   1.0415e-01*_Complex_I,
+     -1.7024e-01 +   5.3942e-01*_Complex_I,
+     -2.4815e-01 +   3.9378e-01*_Complex_I,
+     -3.3086e-01 +  -1.2116e+00*_Complex_I,
+     -4.1344e-01 +  -1.0631e+00*_Complex_I,
+     -4.9938e-01 +  -6.1504e-01*_Complex_I,
+     -5.7571e-01 +  -7.6257e-01*_Complex_I,
+     -4.9789e-01 +  -3.2872e-01*_Complex_I,
+     -5.7916e-01 +  -1.9183e-01*_Complex_I,
+     -5.7533e-01 +  -4.6997e-01*_Complex_I,
+     -6.6353e-01 +  -3.3083e-01*_Complex_I,
+     -4.1537e-01 +   1.2917e+00*_Complex_I,
+     -3.3797e-01 +   1.1288e+00*_Complex_I,
+     -4.1372e-01 +   6.7949e-01*_Complex_I,
+     -5.7762e-01 +   6.8425e-01*_Complex_I,
+     -5.0299e-01 +  -3.9519e-02*_Complex_I,
+     -5.7735e-01 +   1.0080e-01*_Complex_I,
+     -5.7869e-01 +   3.9486e-01*_Complex_I,
+     -4.1708e-01 +   3.8814e-01*_Complex_I,
+     -2.6216e-01 +  -1.3722e+00*_Complex_I,
+     -2.4777e-01 +  -1.0554e+00*_Complex_I,
+     -2.4828e-01 +  -7.5682e-01*_Complex_I,
+     -4.1106e-01 +  -7.6293e-01*_Complex_I,
+     -3.3539e-01 +  -4.5041e-02*_Complex_I,
+     -4.1183e-01 +  -1.8648e-01*_Complex_I,
+     -4.0960e-01 +  -4.6924e-01*_Complex_I,
+     -3.2757e-01 +  -3.2493e-01*_Complex_I,
+     -2.4811e-01 +   1.2791e+00*_Complex_I,
+     -4.1293e-01 +   9.7796e-01*_Complex_I,
+     -4.9363e-01 +   5.4125e-01*_Complex_I,
+     -2.4841e-01 +   9.7484e-01*_Complex_I,
+     -3.3291e-01 +   2.4287e-01*_Complex_I,
+     -4.1270e-01 +   1.0610e-01*_Complex_I,
+     -3.3149e-01 +   5.3493e-01*_Complex_I,
+     -4.9570e-01 +   2.4793e-01*_Complex_I,
+      1.0268e+00 +  -9.4120e-01*_Complex_I,
+      1.1117e+00 +  -7.9063e-01*_Complex_I,
+      1.0229e+00 +  -6.2590e-01*_Complex_I,
+      1.1972e+00 +  -6.4225e-01*_Complex_I,
+      1.1800e+00 +  -3.7594e-02*_Complex_I,
+      1.0994e+00 +  -1.8428e-01*_Complex_I,
+      1.1013e+00 +  -4.8132e-01*_Complex_I,
+      1.1761e+00 +  -3.3277e-01*_Complex_I,
+      9.5265e-01 +   9.9923e-01*_Complex_I,
+      1.0339e+00 +   8.4543e-01*_Complex_I,
+      1.1967e+00 +   5.5722e-01*_Complex_I,
+      1.1136e+00 +   7.0186e-01*_Complex_I,
+      1.1048e+00 +   1.1370e-01*_Complex_I,
+      1.1933e+00 +   2.5489e-01*_Complex_I,
+      1.0283e+00 +   5.4096e-01*_Complex_I,
+      1.1081e+00 +   3.9628e-01*_Complex_I,
+      7.8700e-01 +  -1.0692e+00*_Complex_I,
+      8.5334e-01 +  -9.1694e-01*_Complex_I,
+      8.5637e-01 +  -6.2050e-01*_Complex_I,
+      9.3281e-01 +  -7.7176e-01*_Complex_I,
+      1.0137e+00 +  -3.3408e-02*_Complex_I,
+      9.3260e-01 +  -1.7917e-01*_Complex_I,
+      9.3071e-01 +  -4.7454e-01*_Complex_I,
+      1.0048e+00 +  -3.2937e-01*_Complex_I,
+      8.6318e-01 +   1.1482e+00*_Complex_I,
+      7.7447e-01 +   9.8650e-01*_Complex_I,
+      8.5413e-01 +   5.5129e-01*_Complex_I,
+      9.3949e-01 +   6.9420e-01*_Complex_I,
+      8.5475e-01 +  -2.3510e-02*_Complex_I,
+      9.3037e-01 +   1.2129e-01*_Complex_I,
+      9.3010e-01 +   4.0514e-01*_Complex_I,
+      1.0095e+00 +   2.6363e-01*_Complex_I,
+      5.2780e-01 +  -1.2149e+00*_Complex_I,
+      5.9973e-01 +  -1.0535e+00*_Complex_I,
+      5.9400e-01 +  -7.5805e-01*_Complex_I,
+      5.2055e-01 +  -9.0495e-01*_Complex_I,
+      1.2793e+00 +   9.5945e-02*_Complex_I,
+      6.0607e-01 +  -1.8528e-01*_Complex_I,
+      6.0178e-01 +  -4.7516e-01*_Complex_I,
+      6.7818e-01 +  -3.2877e-01*_Complex_I,
+      6.0752e-01 +   1.2941e+00*_Complex_I,
+      6.0140e-01 +   9.9091e-01*_Complex_I,
+      5.9529e-01 +   6.9382e-01*_Complex_I,
+      5.2016e-01 +   8.4000e-01*_Complex_I,
+      6.0208e-01 +   1.1391e-01*_Complex_I,
+      6.7047e-01 +   2.5780e-01*_Complex_I,
+      5.2294e-01 +   5.4883e-01*_Complex_I,
+      5.9654e-01 +   4.0163e-01*_Complex_I,
+      7.0156e-01 +  -1.2190e+00*_Complex_I,
+      6.9013e-01 +  -9.1191e-01*_Complex_I,
+      6.9013e-01 +  -6.2098e-01*_Complex_I,
+      7.6463e-01 +  -7.6594e-01*_Complex_I,
+      6.9136e-01 +  -3.6954e-02*_Complex_I,
+      7.6789e-01 +  -1.7954e-01*_Complex_I,
+      7.6457e-01 +  -4.7424e-01*_Complex_I,
+      8.3961e-01 +  -3.2637e-01*_Complex_I,
+      6.8917e-01 +   1.1431e+00*_Complex_I,
+      8.5163e-01 +   8.4194e-01*_Complex_I,
+      7.6086e-01 +   6.8960e-01*_Complex_I,
+      6.8618e-01 +   8.3840e-01*_Complex_I,
+      7.6026e-01 +   1.0911e-01*_Complex_I,
+      8.3319e-01 +   2.5542e-01*_Complex_I,
+      6.8351e-01 +   5.4604e-01*_Complex_I,
+      7.5901e-01 +   4.0203e-01*_Complex_I,
+      9.6338e-02 +  -1.3604e+00*_Complex_I,
+     -7.0831e-02 +  -1.0602e+00*_Complex_I,
+     -9.4577e-04 +  -6.3305e-01*_Complex_I,
+     -6.9734e-02 +  -7.6470e-01*_Complex_I,
+      4.2835e-01 +  -4.6668e-01*_Complex_I,
+      3.4710e-01 +  -3.3413e-01*_Complex_I,
+     -1.5430e-01 +  -3.3941e-01*_Complex_I,
+      8.0032e-02 +  -1.7572e-01*_Complex_I,
+      2.5905e-02 +   1.4320e+00*_Complex_I,
+     -6.9530e-02 +   9.7883e-01*_Complex_I,
+      1.7048e-01 +   8.2571e-01*_Complex_I,
+      1.0068e-02 +   8.3542e-01*_Complex_I,
+      3.4137e-01 +   2.5976e-01*_Complex_I,
+      9.5063e-02 +   1.1412e-01*_Complex_I,
+     -7.6478e-02 +   4.0262e-01*_Complex_I,
+      1.3861e+00 +   2.4822e-01*_Complex_I,
+      1.8256e-01 +  -1.2079e+00*_Complex_I,
+      9.5999e-02 +  -1.0579e+00*_Complex_I,
+      1.7231e-01 +  -6.3492e-01*_Complex_I,
+      9.5184e-02 +  -7.6915e-01*_Complex_I,
+      4.2346e-01 +  -1.8043e-01*_Complex_I,
+      3.4732e-01 +  -3.9482e-02*_Complex_I,
+      6.7179e-03 +  -3.3361e-01*_Complex_I,
+      1.2869e+00 +  -4.7883e-01*_Complex_I,
+      1.0396e-01 +   1.2834e+00*_Complex_I,
+      1.7477e-01 +   1.1309e+00*_Complex_I,
+      9.3443e-02 +   6.8396e-01*_Complex_I,
+      1.0164e-01 +   9.8006e-01*_Complex_I,
+      2.6248e-01 +   4.0262e-01*_Complex_I,
+      1.4300e-02 +   2.5562e-01*_Complex_I,
+      1.7725e-01 +   5.4449e-01*_Complex_I,
+      1.7520e-01 +   2.5568e-01*_Complex_I,
+      3.5711e-01 +  -1.2043e+00*_Complex_I,
+      4.3106e-01 +  -1.0550e+00*_Complex_I,
+      4.3130e-01 +  -7.5874e-01*_Complex_I,
+      3.5671e-01 +  -9.0533e-01*_Complex_I,
+      1.2810e+00 +  -1.8054e-01*_Complex_I,
+      2.5776e-01 +  -4.7036e-01*_Complex_I,
+      5.1032e-01 +  -6.1269e-01*_Complex_I,
+      5.1825e-01 +  -3.2616e-01*_Complex_I,
+      4.3563e-01 +   1.2900e+00*_Complex_I,
+      5.0429e-01 +   1.1325e+00*_Complex_I,
+      4.3124e-01 +   6.9057e-01*_Complex_I,
+      4.2828e-01 +   9.7727e-01*_Complex_I,
+      2.6878e-01 +   1.1645e-01*_Complex_I,
+      5.1065e-01 +   2.5785e-01*_Complex_I,
+      3.5648e-01 +   5.4453e-01*_Complex_I,
+      4.3802e-01 +   4.0358e-01*_Complex_I,
+      2.8096e-01 +  -1.3583e+00*_Complex_I,
+      2.6017e-01 +  -1.0511e+00*_Complex_I,
+      2.6553e-01 +  -7.5915e-01*_Complex_I,
+      1.8554e-01 +  -9.0436e-01*_Complex_I,
+      2.7459e-01 +  -1.6584e-01*_Complex_I,
+      9.6288e-02 +  -4.7108e-01*_Complex_I,
+      3.4524e-01 +  -6.2309e-01*_Complex_I,
+     -7.6534e-02 +  -1.8689e-01*_Complex_I,
+      2.6953e-01 +   1.2917e+00*_Complex_I,
+      3.3760e-01 +   1.1286e+00*_Complex_I,
+      3.3928e-01 +   8.2592e-01*_Complex_I,
+      2.6659e-01 +   9.7456e-01*_Complex_I,
+      4.2936e-01 +   1.1261e-01*_Complex_I,
+      9.1148e-02 +   4.0330e-01*_Complex_I,
+      2.6333e-01 +   6.7210e-01*_Complex_I,
+     -7.8413e-02 +   1.1615e-01*_Complex_I,
+};
diff --git a/src/modem/src/modem_ask.c b/src/modem/src/modem_ask.c
new file mode 100644
index 0000000..df1d7c9
--- /dev/null
+++ b/src/modem/src/modem_ask.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_ask.c
+//
+
+// create an ask (amplitude-shift keying) modem object
+MODEM() MODEM(_create_ask)(unsigned int _bits_per_symbol)
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+
+    MODEM(_init)(q, _bits_per_symbol);
+
+    switch (q->M) {
+    case 2:     q->data.ask.alpha = ASK2_ALPHA;   q->scheme = LIQUID_MODEM_ASK2;   break;
+    case 4:     q->data.ask.alpha = ASK4_ALPHA;   q->scheme = LIQUID_MODEM_ASK4;   break;
+    case 8:     q->data.ask.alpha = ASK8_ALPHA;   q->scheme = LIQUID_MODEM_ASK8;   break;
+    case 16:    q->data.ask.alpha = ASK16_ALPHA;  q->scheme = LIQUID_MODEM_ASK16;  break;
+    case 32:    q->data.ask.alpha = ASK32_ALPHA;  q->scheme = LIQUID_MODEM_ASK32;  break;
+    case 64:    q->data.ask.alpha = ASK64_ALPHA;  q->scheme = LIQUID_MODEM_ASK64;  break;
+    case 128:   q->data.ask.alpha = ASK128_ALPHA; q->scheme = LIQUID_MODEM_ASK128; break;
+    case 256:   q->data.ask.alpha = ASK256_ALPHA; q->scheme = LIQUID_MODEM_ASK256; break;
+    default:
+#if 0
+        // calculate alpha dynamically
+        q->data.ask.alpha = expf(-0.70735 + 0.63653*q->m);
+#else
+        fprintf(stderr,"error: modem_create_ask(), cannot support ASK with m > 8\n");
+        exit(1);
+#endif
+    }
+
+    unsigned int k;
+    for (k=0; k<(q->m); k++)
+        q->ref[k] = (1<<k) * q->data.ask.alpha;
+
+    q->modulate_func = &MODEM(_modulate_ask);
+    q->demodulate_func = &MODEM(_demodulate_ask);
+
+    // initialize soft-demodulation look-up table
+    if (q->m >= 2 && q->m < 8)
+        MODEM(_demodsoft_gentab)(q, 2);
+
+    // reset modem and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate ASK
+void MODEM(_modulate_ask)(MODEM()      _q,
+                          unsigned int _sym_in,
+                          TC *         _y)
+{
+    // 'encode' input symbol (actually gray decoding)
+    _sym_in = gray_decode(_sym_in);
+
+    // modulate symbol
+    *_y = (2*(int)_sym_in - (int)(_q->M) + 1) * _q->data.ask.alpha;
+}
+
+// demodulate ASK
+void MODEM(_demodulate_ask)(MODEM()        _q,
+                            TC             _x,
+                            unsigned int * _sym_out)
+{
+    // demodulate on linearly-spaced array
+    unsigned int s;
+    T res_i;
+    MODEM(_demodulate_linear_array_ref)(crealf(_x), _q->m, _q->ref, &s, &res_i);
+
+    // 'decode' output symbol (actually gray encoding)
+    *_sym_out = gray_encode(s);
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_ask)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_bpsk.c b/src/modem/src/modem_bpsk.c
new file mode 100644
index 0000000..cd14c78
--- /dev/null
+++ b/src/modem/src/modem_bpsk.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_bpsk.c : specific BPSK modem
+//
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// create a bpsk (binary phase-shift keying) modem object
+MODEM() MODEM(_create_bpsk)()
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_BPSK;
+
+    MODEM(_init)(q, 1);
+
+    q->modulate_func   = &MODEM(_modulate_bpsk);
+    q->demodulate_func = &MODEM(_demodulate_bpsk);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate BPSK
+void MODEM(_modulate_bpsk)(MODEM()      _q,
+                           unsigned int _sym_in,
+                           TC *         _y)
+{
+    // compute output sample directly from input
+    *_y = _sym_in ? -1.0f : 1.0f;
+}
+
+// demodulate BPSK
+void MODEM(_demodulate_bpsk)(MODEM()        _q,
+                             TC             _x,
+                             unsigned int * _sym_out)
+{
+    // slice directly to output symbol
+    *_sym_out = (crealf(_x) > 0 ) ? 0 : 1;
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_bpsk)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
+// demodulate BPSK (soft)
+void MODEM(_demodulate_soft_bpsk)(MODEM()         _q,
+                                  TC              _x,
+                                  unsigned int  * _s,
+                                  unsigned char * _soft_bits)
+{
+    // gamma = 1/(2*sigma^2), approximate for constellation size
+    T gamma = 4.0f;
+
+    // approximate log-likelihood ratio
+    T LLR = -2.0f * crealf(_x) * gamma;
+    int soft_bit = LLR*16 + 127;
+    if (soft_bit > 255) soft_bit = 255;
+    if (soft_bit <   0) soft_bit = 0;
+    _soft_bits[0] = (unsigned char) ( soft_bit );
+
+    // re-modulate symbol and store state
+    unsigned int symbol_out = (crealf(_x) > 0 ) ? 0 : 1;
+    MODEM(_modulate_bpsk)(_q, symbol_out, &_q->x_hat);
+    _q->r = _x;
+    *_s = symbol_out;
+}
+
diff --git a/src/modem/src/modem_common.c b/src/modem/src/modem_common.c
new file mode 100644
index 0000000..f5cbe82
--- /dev/null
+++ b/src/modem/src/modem_common.c
@@ -0,0 +1,711 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_common.c : common utilities specific to precision
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_DEMODULATE_SOFT 0
+
+// modem structure used for both modulation and demodulation 
+//
+// The modem structure implements a variety of common modulation schemes,
+// including (differential) phase-shift keying, and (quadrature) amplitude
+// modulation.
+//
+// While the same modem structure may be used for both modulation and
+// demodulation for most schemes, it is important to use separate objects
+// for differential-mode modems (e.g. LIQUID_MODEM_DPSK) as the internal state
+// will change after each symbol.  It is usually good practice to keep
+// separate instances of modulators and demodulators.
+struct MODEM(_s)
+{
+    // common data
+    modulation_scheme scheme;   // modulation scheme
+    unsigned int m;             // bits per symbol (modulation depth)
+    unsigned int M;             // constellation size, M=2^m
+
+    // Reference vector for demodulating linear arrays
+    //
+    // By storing these values in an array they do not need to be
+    // calculated during run-time.  This speeds up the demodulation by
+    // approximately 8%.
+    T ref[MAX_MOD_BITS_PER_SYMBOL];
+
+    // modulation
+    TC * symbol_map;     // complete symbol map
+    int modulate_using_map;         // modulate using map (look-up table) flag
+
+    // demodulation
+    TC r;                // received state vector
+    TC x_hat;            // estimated symbol (demodulator)
+
+    // common data structure shared between specific modem types
+    union {
+        // PSK modem
+        struct {
+            T d_phi;            // half of phase between symbols
+            T alpha;            // scaling factor for phase symbols
+        } psk;
+
+        // DPSK modem
+        struct {
+            T d_phi;            // half of phase between symbols
+            T phi;              // angle state for differential PSK
+            T alpha;            // scaling factor for phase symbols
+        } dpsk;
+
+        // ASK modem
+        struct {
+            T alpha;            // scaling factor to ensure unity energy
+        } ask;
+
+        // QAM modem
+        struct {
+            unsigned int m_i;   // bits per symbol, in-phase
+            unsigned int m_q;   // bits per symbol, quadrature
+            unsigned int M_i;   // in-phase dimension, M_i=2^{m_i}
+            unsigned int M_q;   // quadrature dimension, M_q=2^{m_q}
+            T alpha;            // scaling factor to ensure unity energy
+        } qam;
+
+        // APSK modem
+        struct {
+            unsigned int num_levels;    // number of levels
+            unsigned int p[8];          // number of symbols per level
+            T r[8];                     // radii of levels
+            T r_slicer[8];              // slicer radii of levels
+            T phi[8];                   // phase offset of levels
+            unsigned char * map;        // symbol mapping (allocated)
+        } apsk;
+
+        // 'square' 32-QAM
+        struct {
+            TC * map;           // 8-sample sub-map (first quadrant)
+        } sqam32;
+
+        // 'square' 128-QAM
+        struct {
+            TC * map;           // 32-sample sub-map (first quadrant)
+        } sqam128;
+    } data;
+
+    // modulate function pointer
+    void (*modulate_func)(MODEM() _q,
+                          unsigned int _symbol_in,
+                          TC * _y);
+
+    // demodulate function pointer
+    void (*demodulate_func)(MODEM() _q,
+                            TC _x,
+                            unsigned int * _symbol_out);
+
+    // soft demodulation
+    //int demodulate_soft;    // soft demodulation flag
+    // neighbors array
+    unsigned char * demod_soft_neighbors;   // array of nearest neighbors
+    unsigned int demod_soft_p;              // number of neighbors in array
+};
+
+// create digital modem of a specific scheme and bits/symbol
+MODEM() MODEM(_create)(modulation_scheme _scheme)
+{
+    switch (_scheme) {
+    
+    // Phase-shift keying (PSK)
+    case LIQUID_MODEM_PSK2:     return MODEM(_create_psk)(1);
+    case LIQUID_MODEM_PSK4:     return MODEM(_create_psk)(2);
+    case LIQUID_MODEM_PSK8:     return MODEM(_create_psk)(3);
+    case LIQUID_MODEM_PSK16:    return MODEM(_create_psk)(4);
+    case LIQUID_MODEM_PSK32:    return MODEM(_create_psk)(5);
+    case LIQUID_MODEM_PSK64:    return MODEM(_create_psk)(6);
+    case LIQUID_MODEM_PSK128:   return MODEM(_create_psk)(7);
+    case LIQUID_MODEM_PSK256:   return MODEM(_create_psk)(8);
+
+    // Differential phase-shift keying (DPSK)
+    case LIQUID_MODEM_DPSK2:    return MODEM(_create_dpsk)(1);
+    case LIQUID_MODEM_DPSK4:    return MODEM(_create_dpsk)(2);
+    case LIQUID_MODEM_DPSK8:    return MODEM(_create_dpsk)(3);
+    case LIQUID_MODEM_DPSK16:   return MODEM(_create_dpsk)(4);
+    case LIQUID_MODEM_DPSK32:   return MODEM(_create_dpsk)(5);
+    case LIQUID_MODEM_DPSK64:   return MODEM(_create_dpsk)(6);
+    case LIQUID_MODEM_DPSK128:  return MODEM(_create_dpsk)(7);
+    case LIQUID_MODEM_DPSK256:  return MODEM(_create_dpsk)(8);
+
+    // amplitude-shift keying (ASK)
+    case LIQUID_MODEM_ASK2:     return MODEM(_create_ask)(1);
+    case LIQUID_MODEM_ASK4:     return MODEM(_create_ask)(2);
+    case LIQUID_MODEM_ASK8:     return MODEM(_create_ask)(3);
+    case LIQUID_MODEM_ASK16:    return MODEM(_create_ask)(4);
+    case LIQUID_MODEM_ASK32:    return MODEM(_create_ask)(5);
+    case LIQUID_MODEM_ASK64:    return MODEM(_create_ask)(6);
+    case LIQUID_MODEM_ASK128:   return MODEM(_create_ask)(7);
+    case LIQUID_MODEM_ASK256:   return MODEM(_create_ask)(8);
+
+    // rectangular quadrature amplitude-shift keying (QAM)
+    case LIQUID_MODEM_QAM4:     return MODEM(_create_qam)(2);
+    case LIQUID_MODEM_QAM8:     return MODEM(_create_qam)(3);
+    case LIQUID_MODEM_QAM16:    return MODEM(_create_qam)(4);
+    case LIQUID_MODEM_QAM32:    return MODEM(_create_qam)(5);
+    case LIQUID_MODEM_QAM64:    return MODEM(_create_qam)(6);
+    case LIQUID_MODEM_QAM128:   return MODEM(_create_qam)(7);
+    case LIQUID_MODEM_QAM256:   return MODEM(_create_qam)(8);
+
+    // amplitude phase-shift keying (APSK)
+    case LIQUID_MODEM_APSK4:    return MODEM(_create_apsk)(2);
+    case LIQUID_MODEM_APSK8:    return MODEM(_create_apsk)(3);
+    case LIQUID_MODEM_APSK16:   return MODEM(_create_apsk)(4);
+    case LIQUID_MODEM_APSK32:   return MODEM(_create_apsk)(5);
+    case LIQUID_MODEM_APSK64:   return MODEM(_create_apsk)(6);
+    case LIQUID_MODEM_APSK128:  return MODEM(_create_apsk)(7);
+    case LIQUID_MODEM_APSK256:  return MODEM(_create_apsk)(8);
+
+    // specific modems
+    case LIQUID_MODEM_BPSK:      return MODEM(_create_bpsk)();
+    case LIQUID_MODEM_QPSK:      return MODEM(_create_qpsk)();
+    case LIQUID_MODEM_OOK:       return MODEM(_create_ook)();
+    case LIQUID_MODEM_SQAM32:    return MODEM(_create_sqam32)();
+    case LIQUID_MODEM_SQAM128:   return MODEM(_create_sqam128)();
+    case LIQUID_MODEM_V29:       return MODEM(_create_V29)();
+    case LIQUID_MODEM_ARB16OPT:  return MODEM(_create_arb16opt)();
+    case LIQUID_MODEM_ARB32OPT:  return MODEM(_create_arb32opt)();
+    case LIQUID_MODEM_ARB64OPT:  return MODEM(_create_arb64opt)();
+    case LIQUID_MODEM_ARB128OPT: return MODEM(_create_arb128opt)();
+    case LIQUID_MODEM_ARB256OPT: return MODEM(_create_arb256opt)();
+    case LIQUID_MODEM_ARB64VT:   return MODEM(_create_arb64vt)();
+    
+    // arbitrary modem
+    case LIQUID_MODEM_ARB:
+        fprintf(stderr,"error: modem_create(), cannot create arbitrary modem (LIQUID_MODEM_ARB) without specifying constellation\n");
+        exit(1);
+
+    // unknown modulation scheme
+    default:
+        fprintf(stderr,"error: modem_create(), unknown/unsupported modulation scheme : %u\n", _scheme);
+        exit(1);
+    }
+
+    // should never get to this point, but adding return statment
+    // to keep compiler happy
+    return NULL;
+}
+
+// recreate modulation scheme, re-allocating memory as necessary
+MODEM() MODEM(_recreate)(MODEM() _q,
+                         modulation_scheme _scheme)
+{
+    // TODO : regenerate modem only when truly necessary
+    if (_q->scheme != _scheme) {
+        // destroy and re-create modem
+        MODEM(_destroy)(_q);
+        _q = MODEM(_create)(_scheme);
+    }
+
+    // return object
+    return _q;
+}
+
+// destroy a modem object
+void MODEM(_destroy)(MODEM() _q)
+{
+    // free symbol map
+    if (_q->symbol_map != NULL)
+        free(_q->symbol_map);
+
+    // free soft-demodulation neighbors table
+    if (_q->demod_soft_neighbors != NULL)
+        free(_q->demod_soft_neighbors);
+
+    // free memory in specific data types
+    if (_q->scheme == LIQUID_MODEM_SQAM32) {
+        free(_q->data.sqam32.map);
+    } else if (_q->scheme == LIQUID_MODEM_SQAM128) {
+        free(_q->data.sqam128.map);
+    } else if (liquid_modem_is_apsk(_q->scheme)) {
+        free(_q->data.apsk.map);
+    }
+
+    // free main object memory
+    free(_q);
+}
+
+// print a modem object
+void MODEM(_print)(MODEM() _q)
+{
+    printf("linear modem:\n");
+    printf("    scheme:         %s\n", modulation_types[_q->scheme].name);
+    printf("    bits/symbol:    %u\n", _q->m);
+}
+
+// reset a modem object (only an issue with dpsk)
+void MODEM(_reset)(MODEM() _q)
+{
+    _q->r = 1.0f;         // received sample
+    _q->x_hat = _q->r;  // estimated symbol
+
+    if ( liquid_modem_is_dpsk(_q->scheme) )
+        _q->data.dpsk.phi = 0.0f;  // reset differential PSK phase state
+}
+
+// initialize a generic modem object
+void MODEM(_init)(MODEM() _q,
+                  unsigned int _bits_per_symbol)
+{
+    if (_bits_per_symbol < 1 ) {
+        fprintf(stderr,"error: modem_init(), modem must have at least 1 bit/symbol\n");
+        exit(1);
+    } else if (_bits_per_symbol > MAX_MOD_BITS_PER_SYMBOL) {
+        fprintf(stderr,"error: modem_init(), maximum number of bits per symbol exceeded\n");
+        exit(1);
+    }
+
+    // initialize common elements
+    _q->symbol_map = NULL;    // symbol map (LIQUID_MODEM_ARB only)
+    _q->modulate_using_map=0; // modulate using map flag
+
+    // common data
+    _q->m = _bits_per_symbol; // bits/symbol
+    _q->M = 1 << (_q->m);   // constellation size (2^m)
+
+    // set function pointers initially to NULL
+    _q->modulate_func = NULL;
+    _q->demodulate_func = NULL;
+
+    // soft demodulation
+    _q->demod_soft_neighbors = NULL;
+    _q->demod_soft_p = 0;
+}
+
+// initialize symbol map for fast modulation
+void MODEM(_init_map)(MODEM() _q)
+{
+    // validate input
+    if (_q->symbol_map == NULL) {
+        fprintf(stderr,"error: modem_init_map(), symbol map array has not been allocated\n");
+        exit(1);
+    } else if (_q->M == 0 || _q->M > (1<<MAX_MOD_BITS_PER_SYMBOL)) {
+        fprintf(stderr,"error: modem_init_map(), constellation size is out of range\n");
+        exit(1);
+    } else if (_q->modulate_func == NULL) {
+        fprintf(stderr,"error: modem_init_map(), modulation function has not been initialized\n");
+        exit(1);
+    }
+
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        _q->modulate_func(_q, i, &_q->symbol_map[i]);
+}
+
+// Generate random symbol
+unsigned int MODEM(_gen_rand_sym)(MODEM() _q)
+{
+    return rand() % (_q->M);
+}
+
+// Get modem depth (bits/symbol)
+unsigned int MODEM(_get_bps)(MODEM() _q)
+{
+    return _q->m;
+}
+
+// get modulation scheme
+modulation_scheme MODEM(_get_scheme)(MODEM() _q)
+{
+    return _q->scheme;
+}
+
+// generic modulatio function
+//  _q          :   modem object
+//  _symbol_in  :   input symbol
+//  _y          :   output sample
+void MODEM(_modulate)(MODEM() _q,
+                      unsigned int _symbol_in,
+                      TC * _y)
+{
+    // validate input
+    if (_symbol_in >= _q->M) {
+        fprintf(stderr,"error: modem_modulate(), input symbol exceeds constellation size\n");
+        exit(1);
+    }
+
+    if (_q->modulate_using_map) {
+        // modulate simply using map (look-up table)
+        MODEM(_modulate_map)(_q, _symbol_in, _y);
+    } else {
+        // invoke method specific to scheme (calculate symbol on the fly)
+        _q->modulate_func(_q, _symbol_in, _y);
+    }
+}
+
+// modulate using symbol map (look-up table)
+void MODEM(_modulate_map)(MODEM() _q,
+                          unsigned int _symbol_in,
+                          TC * _y)
+{
+    if (_symbol_in >= _q->M) {
+        fprintf(stderr,"error: modem_modulate_table(), input symbol exceeds maximum\n");
+        exit(1);
+    } else if (_q == NULL) {
+        fprintf(stderr,"error: modem_modulate_table(), symbol table not initialized\n");
+        exit(1);
+    }
+
+    // map sample directly to output
+    *_y = _q->symbol_map[_symbol_in]; 
+}
+
+// generic demodulation
+void MODEM(_demodulate)(MODEM() _q,
+                        TC x,
+                        unsigned int *symbol_out)
+{
+    // invoke method specific to scheme (calculate symbol on the fly)
+    _q->demodulate_func(_q, x, symbol_out);
+}
+
+// generic soft demodulation
+void MODEM(_demodulate_soft)(MODEM() _q,
+                             TC _x,
+                             unsigned int  * _s,
+                             unsigned char * _soft_bits)
+{
+    // switch scheme
+    switch (_q->scheme) {
+    case LIQUID_MODEM_ARB:  MODEM(_demodulate_soft_arb)( _q,_x,_s,_soft_bits); return;
+    case LIQUID_MODEM_BPSK: MODEM(_demodulate_soft_bpsk)(_q,_x,_s,_soft_bits); return;
+    case LIQUID_MODEM_QPSK: MODEM(_demodulate_soft_qpsk)(_q,_x,_s,_soft_bits); return;
+    default:;
+    }
+
+    // check if...
+    if (_q->demod_soft_neighbors != NULL && _q->demod_soft_p != 0) {
+        // demodulate using approximate log-likelihood method with
+        // look-up table for nearest neighbors
+        MODEM(_demodulate_soft_table)(_q, _x, _s, _soft_bits);
+
+        return;
+    }
+
+    // for now demodulate normally and simply copy the
+    // hard-demodulated bits
+    unsigned int symbol_out;
+    _q->demodulate_func(_q, _x, &symbol_out);
+    *_s = symbol_out;
+
+    // unpack soft bits
+    liquid_unpack_soft_bits(symbol_out, _q->m, _soft_bits);
+}
+
+#if DEBUG_DEMODULATE_SOFT
+// print a string of bits to the standard output
+void print_bitstring_demod_soft(unsigned int _x,
+                                unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+#endif
+
+// generic soft demodulation using look-up table...
+//  _q          :   demodulator object
+//  _r          :   received sample
+//  _s          :   hard demodulator output
+//  _soft_bits  :   soft bit ouput (approximate log-likelihood ratio)
+void MODEM(_demodulate_soft_table)(MODEM() _q,
+                                   TC _r,
+                                   unsigned int * _s,
+                                   unsigned char * _soft_bits)
+{
+    // run hard demodulation; this will store re-modulated sample
+    // as internal variable x_hat
+    unsigned int s;
+    MODEM(_demodulate)(_q, _r, &s);
+
+    unsigned int bps = MODEM(_get_bps)(_q);
+
+    // gamma = 1/(2*sigma^2), approximate for constellation size
+    T gamma = 1.2f*_q->M;
+
+    // set and initialize minimum bit values
+    unsigned int i;
+    unsigned int k;
+    T dmin_0[bps];
+    T dmin_1[bps];
+    for (k=0; k<bps; k++) {
+        dmin_0[k] = 8.0f;
+        dmin_1[k] = 8.0f;
+    }
+
+    unsigned int bit;
+    T d;
+    TC x_hat;    // re-modulated symbol
+    unsigned char * softab = _q->demod_soft_neighbors;
+    unsigned int p = _q->demod_soft_p;
+
+    // check hard demodulation
+    d = crealf( (_r-_q->x_hat)*conjf(_r-_q->x_hat) );
+    for (k=0; k<bps; k++) {
+        bit = (s >> (bps-k-1)) & 0x01;
+        if (bit) dmin_1[k] = d;
+        else     dmin_0[k] = d;
+    }
+
+    // parse all 'nearest neighbors' and find minimum distance for each bit
+    for (i=0; i<p; i++) {
+        // remodulate symbol
+        if (_q->modulate_using_map)
+            x_hat = _q->symbol_map[ softab[s*p + i] ];
+        else
+            MODEM(_modulate)(_q, softab[s*p+i], &x_hat);
+
+        // compute magnitude squared of Euclidean distance
+        //d = crealf( (_r-x_hat)*conjf(_r-x_hat) );
+        // (same as above, but faster)
+        TC e = _r - x_hat;
+        d = crealf(e)*crealf(e) + cimagf(e)*cimagf(e);
+
+        // look at each bit in 'nearest neighbor' and update minimum
+        for (k=0; k<bps; k++) {
+            // strip bit
+            unsigned int bit = (softab[s*p+i] >> (bps-k-1)) & 0x01;
+            if ( bit ) {
+                if (d < dmin_1[k]) dmin_1[k] = d;
+            } else {
+                if (d < dmin_0[k]) dmin_0[k] = d;
+            }
+        }
+    }
+
+    // make soft bit assignments
+    for (k=0; k<bps; k++) {
+        int soft_bit = ((dmin_0[k] - dmin_1[k])*gamma)*16 + 127;
+        if (soft_bit > 255) soft_bit = 255;
+        if (soft_bit <   0) soft_bit = 0;
+        _soft_bits[k] = (unsigned char)soft_bit;
+    }
+
+    // set hard output symbol
+    *_s = s;
+}
+
+
+
+// get demodulator's estimated transmit sample
+void MODEM(_get_demodulator_sample)(MODEM() _q,
+                                    TC * _x_hat)
+{
+    *_x_hat = _q->x_hat;
+}
+
+// get demodulator phase error
+T MODEM(_get_demodulator_phase_error)(MODEM() _q)
+{
+    return cimagf(_q->r*conjf(_q->x_hat));
+}
+
+// get error vector magnitude
+T MODEM(_get_demodulator_evm)(MODEM() _q)
+{
+    return cabsf(_q->x_hat - _q->r);
+}
+
+// Demodulate a linear symbol constellation using dynamic threshold calculation
+//  _v      :   input value
+//  _m      :   bits per symbol
+//  _alpha  :   scaling factor
+//  _s      :   demodulated symbol
+//  _res    :   residual
+void MODEM(_demodulate_linear_array)(T              _v,
+                                     unsigned int   _m,
+                                     T              _alpha,
+                                     unsigned int * _s,
+                                     T *            _res)
+{
+    unsigned int s=0;
+    unsigned int i, k = _m;
+    T ref=0.0f;
+    for (i=0; i<_m; i++) {
+        s <<= 1;
+        s |= (_v > 0) ? 1 : 0;
+        ref = _alpha * (1<<(k-1));
+        _v += (_v < 0) ? ref : -ref;
+        k--;
+    }
+    *_s = s;
+    *_res = _v;
+}
+
+// Demodulate a linear symbol constellation using refereneced lookup table
+//  _v      :   input value
+//  _m      :   bits per symbol
+//  _ref    :   array of thresholds
+//  _s      :   demodulated symbol
+//  _res    :   residual
+void MODEM(_demodulate_linear_array_ref)(T              _v,
+                                         unsigned int   _m,
+                                         T *            _ref,
+                                         unsigned int * _s,
+                                         T *            _res)
+{
+    // initialize loop counter
+    register unsigned int i;
+
+    // initialize demodulated symbol
+    register unsigned int s=0;
+
+    for (i=0; i<_m; i++) {
+        // prepare symbol for next demodulated bit
+        s <<= 1;
+
+        // compare received value to zero
+        if ( _v > 0 ) {
+            // shift '1' into symbol, subtract reference
+            s |= 1;
+            _v -= _ref[_m-i-1];
+        } else {
+            // shift '0' into symbol, add reference
+            s |= 0;
+            _v += _ref[_m-i-1];
+        }
+    }
+    // return demodulated symbol
+    *_s = s;
+
+    // return residual
+    *_res = _v;
+}
+
+
+// generate soft demodulation look-up table
+void MODEM(_demodsoft_gentab)(MODEM()      _q,
+                              unsigned int _p)
+{
+    // validate input: ensure number of nearest symbols is not too large
+    if (_p > (_q->M-1)) {
+        fprintf(stderr,"error: modem_demodsoft_gentab(), requesting too many neighbors\n");
+        exit(1);
+    }
+    
+    // allocate internal memory
+    _q->demod_soft_p = _p;
+    _q->demod_soft_neighbors = (unsigned char*)malloc(_q->M*_p*sizeof(unsigned char));
+
+    unsigned int i;
+    unsigned int j;
+    unsigned int k;
+
+    // generate constellation
+    // TODO : enforce full constellation for modulation
+    unsigned int M = _q->M;  // constellation size
+    TC c[M];         // constellation
+    for (i=0; i<M; i++)
+        modem_modulate(_q, i, &c[i]);
+
+    // 
+    // find nearest symbols (see algorithm in sandbox/modem_demodulate_soft_gentab.c)
+    //
+
+    // initialize table (with 'M' as invalid symbol)
+    for (i=0; i<M; i++) {
+        for (k=0; k<_p; k++)
+            _q->demod_soft_neighbors[i*_p + k] = M;
+    }
+
+    int symbol_valid;
+    for (i=0; i<M; i++) {
+        for (k=0; k<_p; k++) {
+            T dmin=1e9f;
+            for (j=0; j<M; j++) {
+                symbol_valid = 1;
+                // ignore this symbol
+                if (i==j) symbol_valid = 0;
+
+                // also ignore symbol if it is already in the index
+                unsigned int l;
+                for (l=0; l<_p; l++) {
+                    if ( _q->demod_soft_neighbors[i*_p + l] == j )
+                        symbol_valid = 0;
+                }
+
+                // compute distance
+                T d = cabsf( c[i] - c[j] );
+
+                if ( d < dmin && symbol_valid ) {
+                    dmin = d;
+                    _q->demod_soft_neighbors[i*_p + k] = j;
+                }
+
+            }
+        }
+    }
+
+#if DEBUG_DEMODULATE_SOFT
+    // 
+    // print results
+    //
+    unsigned int bps = _q->m;
+    for (i=0; i<M; i++) {
+        printf(" %4u [", i);
+        print_bitstring_demod_soft(i,bps);
+        printf("] : ");
+        for (k=0; k<_p; k++) {
+            printf(" ");
+            print_bitstring_demod_soft(_q->demod_soft_neighbors[i*_p + k],bps);
+            if (_q->demod_soft_neighbors[i*_p + k] < M)
+                printf("(%6.4f)", cabsf( c[i]-c[_q->demod_soft_neighbors[i*_p+k]] ));
+            else
+                printf("        ");
+        }
+        printf("\n");
+    }
+
+    // print c-type array
+    printf("\n");
+    printf("// %s%u soft demodulation nearest neighbors (p=%u)\n", modulation_types[_q->scheme].name, M, _p);
+    printf("const unsigned char %s%u_demod_soft_neighbors[%u] = {\n", modulation_types[_q->scheme].name, M, _p*M);
+    printf("    ");
+    for (i=0; i<M; i++) {
+        for (k=0; k<_p; k++) {
+            printf("%4u", _q->demod_soft_neighbors[i*_p+k]);
+            if (k != _p-1) printf(",");
+        }
+        if (i != M-1) {
+            printf(",   // ");
+            print_bitstring_demod_soft(i,bps);
+            printf("\n    ");
+        } else {
+            printf("};  // ");
+            print_bitstring_demod_soft(i,bps);
+            printf("\n\n");
+        }
+    }
+#endif
+}
+
+
diff --git a/src/modem/src/modem_dpsk.c b/src/modem/src/modem_dpsk.c
new file mode 100644
index 0000000..727087a
--- /dev/null
+++ b/src/modem/src/modem_dpsk.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_dpsk.c
+//
+
+// create a dpsk (differential phase-shift keying) modem object
+MODEM() MODEM(_create_dpsk)(unsigned int _bits_per_symbol)
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    
+    switch (_bits_per_symbol) {
+    case 1: q->scheme = LIQUID_MODEM_DPSK2;   break;
+    case 2: q->scheme = LIQUID_MODEM_DPSK4;   break;
+    case 3: q->scheme = LIQUID_MODEM_DPSK8;   break;
+    case 4: q->scheme = LIQUID_MODEM_DPSK16;  break;
+    case 5: q->scheme = LIQUID_MODEM_DPSK32;  break;
+    case 6: q->scheme = LIQUID_MODEM_DPSK64;  break;
+    case 7: q->scheme = LIQUID_MODEM_DPSK128; break;
+    case 8: q->scheme = LIQUID_MODEM_DPSK256; break;
+    default:
+        fprintf(stderr,"error: modem_create_dpsk(), cannot support DPSK with m > 8\n");
+        exit(1);
+    }
+
+    MODEM(_init)(q, _bits_per_symbol);
+
+    q->data.dpsk.alpha = M_PI/(T)(q->M);
+    
+    q->data.dpsk.phi = 0.0f;
+
+    unsigned int k;
+    for (k=0; k<(q->m); k++)
+        q->ref[k] = (1<<k) * q->data.dpsk.alpha;
+
+    q->data.dpsk.d_phi = M_PI*(1.0f - 1.0f/(T)(q->M));
+
+    q->modulate_func = &MODEM(_modulate_dpsk);
+    q->demodulate_func = &MODEM(_demodulate_dpsk);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate DPSK
+void MODEM(_modulate_dpsk)(MODEM()      _q,
+                           unsigned int _sym_in,
+                           TC *         _y)
+{
+    // 'encode' input symbol (actually gray decoding)
+    _sym_in = gray_decode(_sym_in);
+
+    // compute phase difference between this symbol and the previous
+    _q->data.dpsk.phi += _sym_in * 2 * _q->data.dpsk.alpha;
+
+    // limit phase
+    _q->data.dpsk.phi -= (_q->data.dpsk.phi > 2*M_PI) ? 2*M_PI : 0.0f;
+    
+    // compute output sample
+    *_y = liquid_cexpjf(_q->data.dpsk.phi);
+
+    // save symbol state
+    _q->r = *_y;
+}
+
+
+void MODEM(_demodulate_dpsk)(MODEM()        _q,
+                             TC             _x,
+                             unsigned int * _sym_out)
+{
+    // compute angle difference
+    T theta = cargf(_x);
+    T d_theta = cargf(_x) - _q->data.dpsk.phi;
+    _q->data.dpsk.phi = theta;
+
+    // subtract phase offset, ensuring phase is in [-pi,pi)
+    d_theta -= _q->data.dpsk.d_phi;
+    if (d_theta > M_PI)
+        d_theta -= 2*M_PI;
+    else if (d_theta < -M_PI)
+        d_theta += 2*M_PI;
+
+    // demodulate on linearly-spaced array
+    unsigned int s;             // demodulated symbol
+    T demod_phase_error;    // demodulation phase error
+    MODEM(_demodulate_linear_array_ref)(d_theta, _q->m, _q->ref, &s, &demod_phase_error);
+
+    // 'decode' output symbol (actually gray encoding)
+    *_sym_out = gray_encode(s);
+
+    // re-modulate symbol (accounting for differential rotation)
+    // and store state
+    _q->x_hat = liquid_cexpjf(theta - demod_phase_error);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_ook.c b/src/modem/src/modem_ook.c
new file mode 100644
index 0000000..7f7b62f
--- /dev/null
+++ b/src/modem/src/modem_ook.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_ook.c
+//
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// create an ook (on/off keying) modem object
+MODEM() MODEM(_create_ook)()
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_OOK;
+
+    MODEM(_init)(q, 1);
+
+    q->modulate_func   = &MODEM(_modulate_ook);
+    q->demodulate_func = &MODEM(_demodulate_ook);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate symbol using on/off keying
+void MODEM(_modulate_ook)(MODEM()         _q,
+                          unsigned int    _sym_in,
+                          float complex * _y)
+{
+    // compute output sample directly from input
+    *_y = _sym_in ? 0.0f : M_SQRT2;
+}
+
+// demodulate OOK
+void MODEM(_demodulate_ook)(MODEM()        _q,
+                            float complex  _x,
+                            unsigned int * _sym_out)
+{
+    // slice directly to output symbol
+    *_sym_out = (crealf(_x) > M_SQRT1_2 ) ? 0 : 1;
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_ook)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_psk.c b/src/modem/src/modem_psk.c
new file mode 100644
index 0000000..173532a
--- /dev/null
+++ b/src/modem/src/modem_psk.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_psk.c
+//
+
+// create a psk (phase-shift keying) modem object
+MODEM() MODEM(_create_psk)(unsigned int _bits_per_symbol)
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+
+    switch (_bits_per_symbol) {
+    case 1: q->scheme = LIQUID_MODEM_PSK2;   break;
+    case 2: q->scheme = LIQUID_MODEM_PSK4;   break;
+    case 3: q->scheme = LIQUID_MODEM_PSK8;   break;
+    case 4: q->scheme = LIQUID_MODEM_PSK16;  break;
+    case 5: q->scheme = LIQUID_MODEM_PSK32;  break;
+    case 6: q->scheme = LIQUID_MODEM_PSK64;  break;
+    case 7: q->scheme = LIQUID_MODEM_PSK128; break;
+    case 8: q->scheme = LIQUID_MODEM_PSK256; break;
+    default:
+        fprintf(stderr,"error: modem_create_psk(), cannot support PSK with m > 8\n");
+        exit(1);
+    }
+
+    // initialize basic modem structure
+    MODEM(_init)(q, _bits_per_symbol);
+
+    // compute alpha
+    q->data.psk.alpha = M_PI/(T)(q->M);
+
+    // initialize demodulation array reference
+    unsigned int k;
+    for (k=0; k<(q->m); k++)
+        q->ref[k] = (1<<k) * q->data.psk.alpha;
+
+    // compute phase offset (half of phase difference between symbols)
+    q->data.psk.d_phi = M_PI*(1.0f - 1.0f/(T)(q->M));
+
+    // set modulation/demodulation functions
+    q->modulate_func = &MODEM(_modulate_psk);
+    q->demodulate_func = &MODEM(_demodulate_psk);
+
+    // initialize symbol map
+    q->symbol_map = (TC*)malloc(q->M*sizeof(TC));
+    MODEM(_init_map)(q);
+    q->modulate_using_map = 1;
+
+    // initialize soft-demodulation look-up table
+    if (q->m >= 3)
+        MODEM(_demodsoft_gentab)(q, 2);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate PSK
+void MODEM(_modulate_psk)(MODEM()      _q,
+                          unsigned int _sym_in,
+                          TC *         _y)
+{
+    // 'encode' input symbol (actually gray decoding)
+    _sym_in = gray_decode(_sym_in);
+
+    // compute output sample
+    *_y = liquid_cexpjf(_sym_in * 2 * _q->data.psk.alpha );
+}
+
+// demodulate PSK
+void MODEM(_demodulate_psk)(MODEM()        _q,
+                            TC             _x,
+                            unsigned int * _sym_out)
+{
+    // compute angle and subtract phase offset, ensuring phase is in [-pi,pi)
+    T theta = cargf(_x);
+    theta -= _q->data.psk.d_phi;
+    if (theta < -M_PI)
+        theta += 2*M_PI;
+
+    // demodulate on linearly-spaced array
+    unsigned int s;             // demodulated symbol
+    T demod_phase_error;        // demodulation phase error
+    MODEM(_demodulate_linear_array_ref)(theta, _q->m, _q->ref, &s, &demod_phase_error);
+
+    // 'decode' output symbol (actually gray encoding)
+    *_sym_out = gray_encode(s);
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_psk)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_qam.c b/src/modem/src/modem_qam.c
new file mode 100644
index 0000000..78baf96
--- /dev/null
+++ b/src/modem/src/modem_qam.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_qam.c
+//
+
+#include <assert.h>
+
+// create a qam (quaternary amplitude-shift keying) modem object
+MODEM() MODEM(_create_qam)(unsigned int _bits_per_symbol)
+{
+    if (_bits_per_symbol < 1 ) {
+        fprintf(stderr,"error: modem_create_qam(), modem must have at least 2 bits/symbol\n");
+        exit(1);
+    }
+
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+
+    MODEM(_init)(q, _bits_per_symbol);
+
+    if (q->m % 2) {
+        // rectangular qam
+        q->data.qam.m_i = (q->m + 1) >> 1;
+        q->data.qam.m_q = (q->m - 1) >> 1;
+    } else {
+        // square qam
+        q->data.qam.m_i = q->m >> 1;
+        q->data.qam.m_q = q->m >> 1;
+    }
+
+    q->data.qam.M_i = 1 << (q->data.qam.m_i);
+    q->data.qam.M_q = 1 << (q->data.qam.m_q);
+
+    assert(q->data.qam.m_i + q->data.qam.m_q == q->m);
+    assert(q->data.qam.M_i * q->data.qam.M_q == q->M);
+
+    switch (q->M) {
+    case 4:    q->data.qam.alpha = RQAM4_ALPHA;    q->scheme = LIQUID_MODEM_QAM4;   break;
+    case 8:    q->data.qam.alpha = RQAM8_ALPHA;    q->scheme = LIQUID_MODEM_QAM8;   break;
+    case 16:   q->data.qam.alpha = RQAM16_ALPHA;   q->scheme = LIQUID_MODEM_QAM16;  break;
+    case 32:   q->data.qam.alpha = RQAM32_ALPHA;   q->scheme = LIQUID_MODEM_QAM32;  break;
+    case 64:   q->data.qam.alpha = RQAM64_ALPHA;   q->scheme = LIQUID_MODEM_QAM64;  break;
+    case 128:  q->data.qam.alpha = RQAM128_ALPHA;  q->scheme = LIQUID_MODEM_QAM128; break;
+    case 256:  q->data.qam.alpha = RQAM256_ALPHA;  q->scheme = LIQUID_MODEM_QAM256; break;
+#if 0
+    case 512:  q->data.qam.alpha = RQAM512_ALPHA;     break;
+    case 1024: q->data.qam.alpha = RQAM1024_ALPHA;    break;
+    case 2048: q->data.qam.alpha = RQAM2048_ALPHA;    break;
+    case 4096: q->data.qam.alpha = RQAM4096_ALPHA;    break;
+    default:
+        // calculate alpha dynamically
+        // NOTE: this is only an approximation
+        q->data.qam.alpha = sqrtf(2.0f / (T)(q->M) );
+#else
+    default:
+        fprintf(stderr,"error: modem_create_qam(), cannot support QAM with m > 8\n");
+        exit(1);
+#endif
+    }
+
+    unsigned int k;
+    for (k=0; k<(q->m); k++)
+        q->ref[k] = (1<<k) * q->data.qam.alpha;
+
+    q->modulate_func = &MODEM(_modulate_qam);
+    q->demodulate_func = &MODEM(_demodulate_qam);
+
+    // initialize symbol map
+    q->symbol_map = (TC*)malloc(q->M*sizeof(TC));
+    MODEM(_init_map)(q);
+    q->modulate_using_map = 1;
+
+    // initialize soft-demodulation look-up table
+    if      (q->m == 3) MODEM(_demodsoft_gentab)(q, 3);
+    else if (q->m >= 4) MODEM(_demodsoft_gentab)(q, 4);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate QAM
+void MODEM(_modulate_qam)(MODEM()      _q,
+                          unsigned int _sym_in,
+                          TC *         _y)
+{
+    unsigned int s_i;   // in-phase symbol
+    unsigned int s_q;   // quadrature symbol
+    s_i = _sym_in >> _q->data.qam.m_q;
+    s_q = _sym_in & ( (1<<_q->data.qam.m_q)-1 );
+
+    // 'encode' symbols (actually gray decoding)
+    s_i = gray_decode(s_i);
+    s_q = gray_decode(s_q);
+
+    // compute output sample
+    *_y = (2*(int)s_i - (int)(_q->data.qam.M_i) + 1) * _q->data.qam.alpha +
+          (2*(int)s_q - (int)(_q->data.qam.M_q) + 1) * _q->data.qam.alpha * _Complex_I;
+}
+
+// demodulate QAM
+void MODEM(_demodulate_qam)(MODEM()      _q,
+                          TC             _x,
+                          unsigned int * _sym_out)
+{
+    // demodulate in-phase component on linearly-spaced array
+    unsigned int s_i;   // in-phase symbol
+    T res_i;        // in-phase residual
+    MODEM(_demodulate_linear_array_ref)(crealf(_x), _q->data.qam.m_i, _q->ref, &s_i, &res_i);
+
+    // demodulate quadrature component on linearly-spaced array
+    unsigned int s_q;   // quadrature symbol
+    T res_q;        // quadrature residual
+    MODEM(_demodulate_linear_array_ref)(cimagf(_x), _q->data.qam.m_q, _q->ref, &s_q, &res_q);
+
+    // 'decode' output symbol (actually gray encoding)
+    s_i = gray_encode(s_i);
+    s_q = gray_encode(s_q);
+    *_sym_out = ( s_i << _q->data.qam.m_q ) + s_q;
+
+    // re-modulate symbol (subtract residual) and store state
+    _q->x_hat = _x - (res_i + _Complex_I*res_q);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_qpsk.c b/src/modem/src/modem_qpsk.c
new file mode 100644
index 0000000..e5a155f
--- /dev/null
+++ b/src/modem/src/modem_qpsk.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_qpsk.c
+//
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// create a qpsk (quaternary phase-shift keying) modem object
+MODEM() MODEM(_create_qpsk)()
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_QPSK;
+
+    MODEM(_init)(q, 2);
+
+    q->modulate_func   = &MODEM(_modulate_qpsk);
+    q->demodulate_func = &MODEM(_demodulate_qpsk);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+
+// modulate QPSK
+void MODEM(_modulate_qpsk)(MODEM()      _q,
+                           unsigned int _sym_in,
+                           TC *         _y)
+{
+    // compute output sample directly from input
+    *_y  = (_sym_in & 0x01 ? -M_SQRT1_2 : M_SQRT1_2) +
+           (_sym_in & 0x02 ? -M_SQRT1_2 : M_SQRT1_2)*_Complex_I;
+}
+
+// demodulate QPSK
+void MODEM(_demodulate_qpsk)(MODEM() _q,
+                             TC _x,
+                             unsigned int * _sym_out)
+{
+    // slice directly to output symbol
+    *_sym_out  = (crealf(_x) > 0 ? 0 : 1) +
+                    (cimagf(_x) > 0 ? 0 : 2);
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_qpsk)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
+// demodulate QPSK (soft)
+void MODEM(_demodulate_soft_qpsk)(MODEM()         _q,
+                                  TC              _x,
+                                  unsigned int  * _s,
+                                  unsigned char * _soft_bits)
+{
+    // gamma = 1/(2*sigma^2), approximate for constellation size
+    T gamma = 5.8f;
+
+    // approximate log-likelihood ratios
+    T LLR;
+    int soft_bit;
+    
+    // compute soft value for first bit
+    LLR = -2.0f * cimagf(_x) * gamma;
+    soft_bit = LLR*16 + 127;
+    if (soft_bit > 255) soft_bit = 255;
+    if (soft_bit <   0) soft_bit = 0;
+    _soft_bits[0] = (unsigned char) ( soft_bit );
+
+    // compute soft value for first bit
+    LLR = -2.0f * crealf(_x) * gamma;
+    soft_bit = LLR*16 + 127;
+    if (soft_bit > 255) soft_bit = 255;
+    if (soft_bit <   0) soft_bit = 0;
+    _soft_bits[1] = (unsigned char) ( soft_bit );
+
+    // re-modulate symbol and store state
+    *_s  = (crealf(_x) > 0 ? 0 : 1) +
+           (cimagf(_x) > 0 ? 0 : 2);
+    MODEM(_modulate_qpsk)(_q, *_s, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_sqam128.c b/src/modem/src/modem_sqam128.c
new file mode 100644
index 0000000..80b61de
--- /dev/null
+++ b/src/modem/src/modem_sqam128.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_sqam128.c
+//
+
+// create a 'square' 128-QAM modem object
+MODEM() MODEM(_create_sqam128)()
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_SQAM128;
+
+    MODEM(_init)(q, 7);
+
+    // allocate memory for 32-point symbol map
+    q->data.sqam128.map = (TC*) malloc( 32*sizeof(TC) );
+#if T == float
+    memmove(q->data.sqam128.map, modem_arb_sqam128, 32*sizeof(TC));
+#endif
+
+    // set modulation, demodulation functions
+    q->modulate_func   = &MODEM(_modulate_sqam128);
+    q->demodulate_func = &MODEM(_demodulate_sqam128);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate symbol with 'square' 128-QAM
+void MODEM(_modulate_sqam128)(MODEM()      _q,
+                              unsigned int _sym_in,
+                              TC *         _y)
+{
+    // strip off most-significant two bits (quadrant)
+    unsigned int quad = (_sym_in >> 5) & 0x03;
+    
+    // strip off least-significant 5 bits
+    unsigned int s = _sym_in & 0x1f;
+    TC p = _q->data.sqam128.map[s];
+    
+    switch (quad) {
+    case 0: *_y =  p;           return;
+    case 1: *_y =  conjf(p);    return;
+    case 2: *_y = -conjf(p);    return;
+    case 3: *_y = -p;           return;
+    default:
+        // should never get to this point
+        fprintf(stderr,"error: modem_modulate_sqam128(), logic error\n");
+        exit(1);
+    }
+}
+
+
+// demodulate 'square' 128-QAM
+void MODEM(_demodulate_sqam128)(MODEM()        _q,
+                                TC             _x,
+                                unsigned int * _sym_out)
+{
+    // determine quadrant and de-rotate to first quadrant
+    // 10 | 00
+    // ---+---
+    // 11 | 01
+    unsigned int quad = 2*(crealf(_x) < 0.0f) + (cimagf(_x) < 0.0f);
+    
+    TC x_prime = _x;
+    switch (quad) {
+    case 0: x_prime = _x;           break;
+    case 1: x_prime =  conjf(_x);   break;
+    case 2: x_prime = -conjf(_x);   break;
+    case 3: x_prime = -_x;          break;
+    default:
+        // should never get to this point
+        fprintf(stderr,"error: modem_demodulate_sqam128(), logic error\n");
+        exit(1);
+    }
+    //printf(" x = %12.8f +j*%12.8f, quad = %1u, r = %12.8f + j*%12.8f\n",
+    //        crealf(_x), cimagf(_x), quad, crealf(r), cimagf(r));
+    assert(crealf(x_prime) >= 0.0f);
+    assert(cimagf(x_prime) >= 0.0f);
+
+    // find symbol in map closest to x_prime
+    T dmin = 0.0f;
+    T d = 0.0f;
+    unsigned int i;
+    for (i=0; i<32; i++) {
+        d = cabsf(x_prime - _q->data.sqam128.map[i]);
+        if (i==0 || d < dmin) {
+            dmin = d;
+            *_sym_out = i;
+        }
+    }
+
+    // add quadrant bits
+    *_sym_out |= (quad << 5);
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_sqam128)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_sqam32.c b/src/modem/src/modem_sqam32.c
new file mode 100644
index 0000000..b22e59c
--- /dev/null
+++ b/src/modem/src/modem_sqam32.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_sqam32.c
+//
+
+// create a 'square' 32-QAM modem object
+MODEM() MODEM(_create_sqam32)()
+{
+    MODEM() q = (MODEM()) malloc( sizeof(struct MODEM(_s)) );
+    q->scheme = LIQUID_MODEM_SQAM32;
+
+    MODEM(_init)(q, 5);
+
+    // allocate memory for 8-point symbol map
+    q->data.sqam32.map = (TC*) malloc( 8*sizeof(TC) );
+#if T == float
+    memmove(q->data.sqam32.map, modem_arb_sqam32, 8*sizeof(TC));
+#endif
+
+    // set modulation, demodulation functions
+    q->modulate_func   = &MODEM(_modulate_sqam32);
+    q->demodulate_func = &MODEM(_demodulate_sqam32);
+
+    // reset and return
+    MODEM(_reset)(q);
+    return q;
+}
+
+// modulate symbol with 'square' 32-QAM
+void MODEM(_modulate_sqam32)(MODEM()      _q,
+                             unsigned int _sym_in,
+                             TC *         _y)
+{
+    // strip off most-significant two bits (quadrant)
+    unsigned int quad = (_sym_in >> 3) & 0x03;
+    
+    // strip off least-significant 3 bits
+    unsigned int s = _sym_in & 0x07;
+    TC p = _q->data.sqam32.map[s];
+    
+    switch (quad) {
+    case 0: *_y =  p;           return;
+    case 1: *_y =  conjf(p);    return;
+    case 2: *_y = -conjf(p);    return;
+    case 3: *_y = -p;           return;
+    default:
+        // should never get to this point
+        fprintf(stderr,"error: modem_modulate_sqam32(), logic error\n");
+        exit(1);
+    }
+}
+
+// demodulate 'square' 32-QAM
+void MODEM(_demodulate_sqam32)(MODEM()        _q,
+                               TC             _x,
+                               unsigned int * _sym_out)
+{
+    // determine quadrant and de-rotate to first quadrant
+    // 10 | 00
+    // ---+---
+    // 11 | 01
+    unsigned int quad = 2*(crealf(_x) < 0.0f) + (cimagf(_x) < 0.0f);
+    
+    TC x_prime = _x;
+    switch (quad) {
+    case 0: x_prime = _x;           break;
+    case 1: x_prime =  conjf(_x);   break;
+    case 2: x_prime = -conjf(_x);   break;
+    case 3: x_prime = -_x;          break;
+    default:
+        // should never get to this point
+        fprintf(stderr,"error: modem_demodulate_sqam32(), logic error\n");
+        exit(1);
+    }
+    //printf(" x = %12.8f +j*%12.8f, quad = %1u, r = %12.8f + j*%12.8f\n",
+    //        crealf(_x), cimagf(_x), quad, crealf(r), cimagf(r));
+    assert(crealf(x_prime) >= 0.0f);
+    assert(cimagf(x_prime) >= 0.0f);
+
+    // find symbol in map closest to x_prime
+    T dmin = 0.0f;
+    T d = 0.0f;
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        d = cabsf(x_prime - _q->data.sqam32.map[i]);
+        if (i==0 || d < dmin) {
+            dmin = d;
+            *_sym_out = i;
+        }
+    }
+
+    // add quadrant bits
+    *_sym_out |= (quad << 3);
+
+    // re-modulate symbol and store state
+    MODEM(_modulate_sqam32)(_q, *_sym_out, &_q->x_hat);
+    _q->r = _x;
+}
+
diff --git a/src/modem/src/modem_utilities.c b/src/modem/src/modem_utilities.c
new file mode 100644
index 0000000..4b4238a
--- /dev/null
+++ b/src/modem/src/modem_utilities.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modem_utilities.c : common utilities not specific to precision
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+// full modulation type descriptor
+const struct modulation_type_s modulation_types[LIQUID_MODEM_NUM_SCHEMES] = {
+    // name       fullname        scheme          bps
+
+    // unknown
+    {"unknown",   "unkown",       LIQUID_MODEM_UNKNOWN, 0},
+
+    // phase-shift keying
+    {"psk2",      "phase-shift keying (2)",   LIQUID_MODEM_PSK2,  1},
+    {"psk4",      "phase-shift keying (4)",   LIQUID_MODEM_PSK4,  2},
+    {"psk8",      "phase-shift keying (8)",   LIQUID_MODEM_PSK8,  3},
+    {"psk16",     "phase-shift keying (16)",  LIQUID_MODEM_PSK16, 4},
+    {"psk32",     "phase-shift keying (32)",  LIQUID_MODEM_PSK32, 5},
+    {"psk64",     "phase-shift keying (64)",  LIQUID_MODEM_PSK64, 6},
+    {"psk128",    "phase-shift keying (128)", LIQUID_MODEM_PSK128, 7},
+    {"psk256",    "phase-shift keying (256)", LIQUID_MODEM_PSK256, 8},
+
+    // differential phase-shift keying
+    {"dpsk2",     "differential phase-shift keying (2)",   LIQUID_MODEM_DPSK2,  1},
+    {"dpsk4",     "differential phase-shift keying (4)",   LIQUID_MODEM_DPSK4,  2},
+    {"dpsk8",     "differential phase-shift keying (8)",   LIQUID_MODEM_DPSK8,  3},
+    {"dpsk16",    "differential phase-shift keying (16)",  LIQUID_MODEM_DPSK16, 4},
+    {"dpsk32",    "differential phase-shift keying (32)",  LIQUID_MODEM_DPSK32, 5},
+    {"dpsk64",    "differential phase-shift keying (64)",  LIQUID_MODEM_DPSK64, 6},
+    {"dpsk128",   "differential phase-shift keying (128)", LIQUID_MODEM_DPSK128, 7},
+    {"dpsk256",   "differential phase-shift keying (256)", LIQUID_MODEM_DPSK256, 8},
+
+    // amplitude-shift keying
+    {"ask2",      "amplitude-shift keying (2)",   LIQUID_MODEM_ASK2,  1},
+    {"ask4",      "amplitude-shift keying (4)",   LIQUID_MODEM_ASK4,  2},
+    {"ask8",      "amplitude-shift keying (8)",   LIQUID_MODEM_ASK8,  3},
+    {"ask16",     "amplitude-shift keying (16)",  LIQUID_MODEM_ASK16, 4},
+    {"ask32",     "amplitude-shift keying (32)",  LIQUID_MODEM_ASK32, 5},
+    {"ask64",     "amplitude-shift keying (64)",  LIQUID_MODEM_ASK64, 6},
+    {"ask128",    "amplitude-shift keying (128)", LIQUID_MODEM_ASK128, 7},
+    {"ask256",    "amplitude-shift keying (256)", LIQUID_MODEM_ASK256, 8},
+
+    // quadrature amplitude-shift keying
+    {"qam4",      "quadrature amplitude-shift keying (4)",   LIQUID_MODEM_QAM4,   2},
+    {"qam8",      "quadrature amplitude-shift keying (8)",   LIQUID_MODEM_QAM8,   3},
+    {"qam16",     "quadrature amplitude-shift keying (16)",  LIQUID_MODEM_QAM16,  4},
+    {"qam32",     "quadrature amplitude-shift keying (32)",  LIQUID_MODEM_QAM32,  5},
+    {"qam64",     "quadrature amplitude-shift keying (64)",  LIQUID_MODEM_QAM64,  6},
+    {"qam128",    "quadrature amplitude-shift keying (128)", LIQUID_MODEM_QAM128, 7},
+    {"qam256",    "quadrature amplitude-shift keying (256)", LIQUID_MODEM_QAM256, 8},
+
+    // amplitude/phase-shift keying
+    {"apsk4",     "amplitude/phase-shift keying (4)",   LIQUID_MODEM_APSK4,   2},
+    {"apsk8",     "amplitude/phase-shift keying (8)",   LIQUID_MODEM_APSK8,   3},
+    {"apsk16",    "amplitude/phase-shift keying (16)",  LIQUID_MODEM_APSK16,  4},
+    {"apsk32",    "amplitude/phase-shift keying (32)",  LIQUID_MODEM_APSK32,  5},
+    {"apsk64",    "amplitude/phase-shift keying (64)",  LIQUID_MODEM_APSK64,  6},
+    {"apsk128",   "amplitude/phase-shift keying (128)", LIQUID_MODEM_APSK128, 7},
+    {"apsk256",   "amplitude/phase-shift keying (256)", LIQUID_MODEM_APSK256, 8},
+
+    // specific modem types
+    {"bpsk",      "binary phase-shift keying",     LIQUID_MODEM_BPSK,      1},
+    {"qpsk",      "quaternary phase-shift keying", LIQUID_MODEM_QPSK,      2},
+    {"ook",       "ook (on/off keying)",           LIQUID_MODEM_OOK,       1},
+    {"sqam32",    "'square' 32-QAM",               LIQUID_MODEM_SQAM32,    5},
+    {"sqam128",   "'square' 128-QAM",              LIQUID_MODEM_SQAM128,   7},
+    {"V29",       "V.29",                          LIQUID_MODEM_V29,       4},
+    {"arb16opt",  "arb16opt (optimal 16-qam)",     LIQUID_MODEM_ARB16OPT,  4},
+    {"arb32opt",  "arb32opt (optimal 32-qam)",     LIQUID_MODEM_ARB32OPT,  5},
+    {"arb64opt",  "arb64opt (optimal 64-qam)",     LIQUID_MODEM_ARB64OPT,  6},
+    {"arb128opt", "arb128opt (optimal 128-qam)",   LIQUID_MODEM_ARB128OPT, 7},
+    {"arb256opt", "arb256opt (optimal 256-qam)",   LIQUID_MODEM_ARB256OPT, 8},
+    {"arb64vt",   "arb64vt (64-qam vt logo)",      LIQUID_MODEM_ARB64VT,   6},
+
+    // arbitrary modem type
+    {"arb",       "arbitrary constellation",       LIQUID_MODEM_ARB,       0},
+};
+
+
+
+modulation_scheme liquid_getopt_str2mod(const char * _str)
+{
+    // compare each string to short name
+    unsigned int i;
+    for (i=0; i<LIQUID_MODEM_NUM_SCHEMES; i++) {
+        if (strcmp(_str,modulation_types[i].name)==0)
+            return i;
+    }
+    fprintf(stderr,"warning: liquid_getopt_str2mod(), unknown/unsupported mod scheme : %s\n", _str);
+    return LIQUID_MODEM_UNKNOWN;
+}
+
+// Print compact list of existing and available modulation schemes
+void liquid_print_modulation_schemes()
+{
+    unsigned int i;
+    unsigned int len = 10;
+
+    // print all available modem schemes
+    printf("          ");
+    for (i=1; i<LIQUID_MODEM_NUM_SCHEMES; i++) {
+        printf("%s", modulation_types[i].name);
+
+        if (i != LIQUID_MODEM_NUM_SCHEMES-1)
+            printf(", ");
+
+        len += strlen(modulation_types[i].name);
+        if (len > 48 && i != LIQUID_MODEM_NUM_SCHEMES-1) {
+            len = 10;
+            printf("\n          ");
+        }
+    }
+    printf("\n");
+}
+
+// query basic modulation types
+int liquid_modem_is_psk(modulation_scheme _ms)
+{
+    switch (_ms) {
+    // Phase-shift keying (PSK)
+    case LIQUID_MODEM_PSK2:
+    case LIQUID_MODEM_PSK4:
+    case LIQUID_MODEM_PSK8:
+    case LIQUID_MODEM_PSK16:
+    case LIQUID_MODEM_PSK32:
+    case LIQUID_MODEM_PSK64:
+    case LIQUID_MODEM_PSK128:
+    case LIQUID_MODEM_PSK256:
+        return 1;
+    default:
+        return 0;
+    }
+
+    return 0;
+}
+
+int liquid_modem_is_dpsk(modulation_scheme _ms)
+{
+    switch (_ms) {
+    // Differential phase-shift keying (DPSK)
+    case LIQUID_MODEM_DPSK2:
+    case LIQUID_MODEM_DPSK4:
+    case LIQUID_MODEM_DPSK8:
+    case LIQUID_MODEM_DPSK16:
+    case LIQUID_MODEM_DPSK32:
+    case LIQUID_MODEM_DPSK64:
+    case LIQUID_MODEM_DPSK128:
+    case LIQUID_MODEM_DPSK256:
+        return 1;
+    default:
+        return 0;
+    }
+
+    return 0;
+}
+
+int liquid_modem_is_ask(modulation_scheme _ms)
+{
+    switch (_ms) {
+    // amplitude-shift keying (ASK)
+    case LIQUID_MODEM_ASK2:
+    case LIQUID_MODEM_ASK4:
+    case LIQUID_MODEM_ASK8:
+    case LIQUID_MODEM_ASK16:
+    case LIQUID_MODEM_ASK32:
+    case LIQUID_MODEM_ASK64:
+    case LIQUID_MODEM_ASK128:
+    case LIQUID_MODEM_ASK256:
+        return 1;
+    default:
+        return 0;
+    }
+
+    return 0;
+}
+
+int liquid_modem_is_qam(modulation_scheme _ms)
+{
+    switch (_ms) {
+    // rectangular quadrature amplitude-shift keying (QAM)
+    case LIQUID_MODEM_QAM4:
+    case LIQUID_MODEM_QAM8:
+    case LIQUID_MODEM_QAM16:
+    case LIQUID_MODEM_QAM32:
+    case LIQUID_MODEM_QAM64:
+    case LIQUID_MODEM_QAM128:
+    case LIQUID_MODEM_QAM256:
+        return 1;
+    default:
+        return 0;
+    }
+
+    return 0;
+}
+
+int liquid_modem_is_apsk(modulation_scheme _ms)
+{
+    switch (_ms) {
+    // amplitude phase-shift keying (APSK)
+    case LIQUID_MODEM_APSK4:
+    case LIQUID_MODEM_APSK8:
+    case LIQUID_MODEM_APSK16:
+    case LIQUID_MODEM_APSK32:
+    case LIQUID_MODEM_APSK64:
+    case LIQUID_MODEM_APSK128:
+    case LIQUID_MODEM_APSK256:
+        return 1;
+    default:
+        return 0;
+    }
+
+    return 0;
+}
+
+
+// gray encoding
+unsigned int gray_encode(unsigned int symbol_in)
+{
+    return symbol_in ^ (symbol_in >> 1);
+}
+
+// gray decoding
+unsigned int gray_decode(unsigned int symbol_in)
+{
+    unsigned int mask = symbol_in;
+    unsigned int symbol_out = symbol_in;
+    unsigned int i;
+
+    // Run loop in blocks of 4 to reduce number of comparisons. Running
+    // loop more times than MAX_MOD_BITS_PER_SYMBOL will not result in
+    // decoding errors.
+    for (i=0; i<MAX_MOD_BITS_PER_SYMBOL; i+=4) {
+        symbol_out ^= (mask >> 1);
+        symbol_out ^= (mask >> 2);
+        symbol_out ^= (mask >> 3);
+        symbol_out ^= (mask >> 4);
+        mask >>= 4;
+    }
+
+    return symbol_out;
+}
+
+// pack soft bits into symbol
+//  _soft_bits  :   soft input bits [size: _bps x 1]
+//  _bps        :   bits per symbol
+//  _sym_out    :   output symbol, value in [0,2^_bps)
+void liquid_pack_soft_bits(unsigned char * _soft_bits,
+                           unsigned int _bps,
+                           unsigned int * _sym_out)
+{
+    // validate input
+    if (_bps > MAX_MOD_BITS_PER_SYMBOL) {
+        fprintf(stderr,"error: liquid_unpack_soft_bits(), bits/symbol exceeds maximum (%u)\n", MAX_MOD_BITS_PER_SYMBOL);
+        exit(1);
+    }
+
+    unsigned int i;
+    unsigned int s=0;
+    for (i=0; i<_bps; i++) {
+        s <<= 1;
+        s |= _soft_bits[i] > LIQUID_SOFTBIT_ERASURE ? 1 : 0;
+    }
+    *_sym_out = s;
+}
+
+// unpack soft bits into symbol
+//  _sym_in     :   input symbol, value in [0,2^_bps)
+//  _bps        :   bits per symbol
+//  _soft_bits  :   soft output bits [size: _bps x 1]
+void liquid_unpack_soft_bits(unsigned int _sym_in,
+                             unsigned int _bps,
+                             unsigned char * _soft_bits)
+{
+    // validate input
+    if (_bps > MAX_MOD_BITS_PER_SYMBOL) {
+        fprintf(stderr,"error: liquid_unpack_soft_bits(), bits/symbol exceeds maximum (%u)\n", MAX_MOD_BITS_PER_SYMBOL);
+        exit(1);
+    }
+
+    unsigned int i;
+    for (i=0; i<_bps; i++)
+        _soft_bits[i] = ((_sym_in >> (_bps-i-1)) & 0x0001) ? LIQUID_SOFTBIT_1 : LIQUID_SOFTBIT_0;
+}
+
+
diff --git a/src/modem/src/modemf.c b/src/modem/src/modemf.c
new file mode 100644
index 0000000..4d8aeb5
--- /dev/null
+++ b/src/modem/src/modemf.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// modemf.c : linear modem API, floating-point (single precision)
+//
+
+#include <math.h>
+#include <stdint.h>
+#include "liquid.internal.h"
+
+// Macro definitions
+#define MODEM(name)         LIQUID_CONCAT(modem,name)
+#define FREQMOD(name)       LIQUID_CONCAT(freqmod,name)
+#define FREQDEM(name)       LIQUID_CONCAT(freqdem,name)
+
+#define T                   float           /* primitive type */
+#define TC                  float complex   /* primitive type (complex) */
+
+#define PRINTVAL_T(X,F)     PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// common source must come first (object definition)
+#include "modem_common.c"
+
+// generic modem specifications
+#include "modem_psk.c"
+#include "modem_dpsk.c"
+#include "modem_ask.c"
+#include "modem_qam.c"
+#include "modem_apsk.c"
+
+// specific modems
+#include "modem_bpsk.c"
+#include "modem_qpsk.c"
+#include "modem_ook.c"
+#include "modem_sqam32.c"
+#include "modem_sqam128.c"
+
+// arbitary modems
+#include "modem_arb.c"
+
+// analog modems
+#include "freqmod.c"
+#include "freqdem.c"
diff --git a/src/modem/tests/cpfskmodem_autotest.c b/src/modem/tests/cpfskmodem_autotest.c
new file mode 100644
index 0000000..b8f0330
--- /dev/null
+++ b/src/modem/tests/cpfskmodem_autotest.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+void cpfskmodem_test_mod_demod(unsigned int _bps,
+                               float        _h,
+                               unsigned int _k,
+                               unsigned int _m,
+                               float        _beta,
+                               int          _filter_type)
+{
+    // create modulator/demodulator pair
+    cpfskmod mod = cpfskmod_create(_bps, _h, _k, _m, _beta, _filter_type);
+    cpfskdem dem = cpfskdem_create(_bps, _h, _k, _m, _beta, _filter_type);
+
+    // derived values
+    unsigned int delay = cpfskmod_get_delay(mod) + cpfskdem_get_delay(dem);
+    //unsigned int  M = 1 << _m;      // constellation size
+    
+    unsigned int  num_symbols = 80 + delay; // number of symbols to test
+
+    msequence ms = msequence_create_default(7);
+
+    float complex buf[_k];      // sample buffer
+    unsigned int  sym_in [num_symbols]; // symbol buffer
+    unsigned int  sym_out[num_symbols]; // symbol buffer
+
+    // modulate, demodulate, count errors
+    unsigned int i;
+    for (i=0; i<num_symbols; i++) {
+        // generate random symbol
+        sym_in[i] = msequence_generate_symbol(ms, _bps);
+
+        // modulate
+        cpfskmod_modulate(mod, sym_in[i], buf);
+
+        // demodulate
+        sym_out[i] = cpfskdem_demodulate(dem, buf);
+    }
+
+    // count errors
+    for (i=0; i<num_symbols; i++) {
+        if (i >= delay) {
+            // print results
+            if (liquid_autotest_verbose) {
+                printf("  %3u : input = %2u, output = %2u %s\n",
+                        i, sym_in[i-delay], sym_out[i],
+                        (sym_in[i-delay] == sym_out[i]) ? "" : "*");
+            }
+
+            // check result
+            CONTEND_EQUALITY(sym_in[i-delay], sym_out[i]);
+        }
+    }
+
+    // clean it up
+    msequence_destroy(ms);
+    cpfskmod_destroy(mod);
+    cpfskdem_destroy(dem);
+}
+
+//
+// AUTOTESTS: check different modulation indices
+//
+
+// square pulse shape
+void autotest_cpfskmodem_bps1_h0p5000_k4_m3_square()    { cpfskmodem_test_mod_demod( 1, 0.5000f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+void autotest_cpfskmodem_bps1_h0p0250_k4_m3_square()    { cpfskmodem_test_mod_demod( 1, 0.2500f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+void autotest_cpfskmodem_bps1_h0p1250_k4_m3_square()    { cpfskmodem_test_mod_demod( 1, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+void autotest_cpfskmodem_bps1_h0p0625_k4_m3_square()    { cpfskmodem_test_mod_demod( 1, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+
+// raised-cosine pulse shape (full)
+void autotest_cpfskmodem_bps1_h0p5000_k4_m3_rcosfull()  { cpfskmodem_test_mod_demod( 1, 0.5000f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_FULL ); }
+void autotest_cpfskmodem_bps1_h0p0250_k4_m3_rcosfull()  { cpfskmodem_test_mod_demod( 1, 0.2500f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_FULL ); }
+void autotest_cpfskmodem_bps1_h0p1250_k4_m3_rcosfull()  { cpfskmodem_test_mod_demod( 1, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_FULL ); }
+void autotest_cpfskmodem_bps1_h0p0625_k4_m3_rcosfull()  { cpfskmodem_test_mod_demod( 1, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_FULL ); }
+
+// raised-cosine pulse shape (partial)
+void autotest_cpfskmodem_bps1_h0p5000_k4_m3_rcospart()  { cpfskmodem_test_mod_demod( 1, 0.5000f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_PARTIAL ); }
+void autotest_cpfskmodem_bps1_h0p0250_k4_m3_rcospart()  { cpfskmodem_test_mod_demod( 1, 0.2500f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_PARTIAL ); }
+void autotest_cpfskmodem_bps1_h0p1250_k4_m3_rcospart()  { cpfskmodem_test_mod_demod( 1, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_PARTIAL ); }
+void autotest_cpfskmodem_bps1_h0p0625_k4_m3_rcospart()  { cpfskmodem_test_mod_demod( 1, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_RCOS_PARTIAL ); }
+
+// Gauss minimum-shift keying
+void autotest_cpfskmodem_bps1_h0p5000_k4_m3_gmsk()      { cpfskmodem_test_mod_demod( 1, 0.5000f, 4, 3, 0.25f, LIQUID_CPFSK_GMSK ); }
+void autotest_cpfskmodem_bps1_h0p0250_k4_m3_gmsk()      { cpfskmodem_test_mod_demod( 1, 0.2500f, 4, 3, 0.25f, LIQUID_CPFSK_GMSK ); }
+void autotest_cpfskmodem_bps1_h0p1250_k4_m3_gmsk()      { cpfskmodem_test_mod_demod( 1, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_GMSK ); }
+void autotest_cpfskmodem_bps1_h0p0625_k4_m3_gmsk()      { cpfskmodem_test_mod_demod( 1, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_GMSK ); }
+
+//
+// AUTOTESTS: check different bits per symbol
+//
+
+// square pulse shape
+void autotest_cpfskmodem_bps2_h0p0250_k4_m3_square()    { cpfskmodem_test_mod_demod( 2, 0.2500f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+void autotest_cpfskmodem_bps3_h0p1250_k4_m3_square()    { cpfskmodem_test_mod_demod( 3, 0.1250f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+void autotest_cpfskmodem_bps4_h0p0625_k4_m3_square()    { cpfskmodem_test_mod_demod( 4, 0.0625f, 4, 3, 0.25f, LIQUID_CPFSK_SQUARE ); }
+
diff --git a/src/modem/tests/freqmodem_autotest.c b/src/modem/tests/freqmodem_autotest.c
new file mode 100644
index 0000000..ff7efb0
--- /dev/null
+++ b/src/modem/tests/freqmodem_autotest.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+//  _kf     :   modulation factor
+void freqmodem_test(float _kf)
+{
+    // options
+    unsigned int num_samples = 1024;
+    float tol = 5e-2f;
+
+    unsigned int i;
+
+    // create mod/demod objects
+    freqmod mod = freqmod_create(_kf);  // modulator
+    freqdem dem = freqdem_create(_kf);  // demodulator
+
+    // allocate arrays
+    float         m[num_samples];       // message signal
+    float complex r[num_samples];       // received signal (complex baseband)
+    float         y[num_samples];       // demodulator output
+
+    // generate message signal (sum of sines)
+    for (i=0; i<num_samples; i++) {
+        m[i] = 0.3f*cosf(2*M_PI*0.013f*i + 0.0f) +
+               0.2f*cosf(2*M_PI*0.021f*i + 0.4f) +
+               0.4f*cosf(2*M_PI*0.037f*i + 1.7f);
+    }
+
+    // modulate signal
+    freqmod_modulate_block(mod, m, num_samples, r);
+
+    // demodulate signal
+    freqdem_demodulate_block(dem, r, num_samples, y);
+
+    // delete modem objects
+    freqmod_destroy(mod);
+    freqdem_destroy(dem);
+
+    // compare demodulated signal to original, skipping first sample
+    for (i=1; i<num_samples; i++)
+        CONTEND_DELTA( y[i], m[i], tol );
+}
+
+// AUTOTESTS: generic PSK
+void autotest_freqmodem_kf_0_02() { freqmodem_test(0.02f); }
+void autotest_freqmodem_kf_0_04() { freqmodem_test(0.04f); }
+void autotest_freqmodem_kf_0_08() { freqmodem_test(0.08f); }
+
diff --git a/src/modem/tests/fskmodem_autotest.c b/src/modem/tests/fskmodem_autotest.c
new file mode 100644
index 0000000..0a4707e
--- /dev/null
+++ b/src/modem/tests/fskmodem_autotest.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+void fskmodem_test_mod_demod(unsigned int _m,
+                             unsigned int _k,
+                             float        _bandwidth)
+{
+    // create modulator/demodulator pair
+    fskmod mod = fskmod_create(_m,_k,_bandwidth);
+    fskdem dem = fskdem_create(_m,_k,_bandwidth);
+
+    unsigned int M = 1 << _m;   // constellation size
+    float complex buf[_k];      // transmit buffer
+    
+    // modulate, demodulate, count errors
+    unsigned int i;
+    for (i=0; i<M; i++) {
+        // generate random symbol
+        unsigned int sym_in = i;
+
+        // modulate
+        fskmod_modulate(mod, sym_in, buf);
+
+        // demodulate
+        unsigned int sym_out = fskdem_demodulate(dem, buf);
+
+        // count errors
+        CONTEND_EQUALITY(sym_in, sym_out);
+    }
+
+    // clean it up
+    fskmod_destroy(mod);
+    fskdem_destroy(dem);
+}
+
+// AUTOTESTS: basic properties: M=2^m, k = 2*M, bandwidth = 0.25
+void autotest_fskmodem_norm_M2()    { fskmodem_test_mod_demod( 1,    4, 0.25f    ); }
+void autotest_fskmodem_norm_M4()    { fskmodem_test_mod_demod( 2,    8, 0.25f    ); }
+void autotest_fskmodem_norm_M8()    { fskmodem_test_mod_demod( 3,   16, 0.25f    ); }
+void autotest_fskmodem_norm_M16()   { fskmodem_test_mod_demod( 4,   32, 0.25f    ); }
+void autotest_fskmodem_norm_M32()   { fskmodem_test_mod_demod( 5,   64, 0.25f    ); }
+void autotest_fskmodem_norm_M64()   { fskmodem_test_mod_demod( 6,  128, 0.25f    ); }
+void autotest_fskmodem_norm_M128()  { fskmodem_test_mod_demod( 7,  256, 0.25f    ); }
+void autotest_fskmodem_norm_M256()  { fskmodem_test_mod_demod( 8,  512, 0.25f    ); }
+void autotest_fskmodem_norm_M512()  { fskmodem_test_mod_demod( 9, 1024, 0.25f    ); }
+void autotest_fskmodem_norm_M1024() { fskmodem_test_mod_demod(10, 2048, 0.25f    ); }
+
+// AUTOTESTS: obscure properties: M=2^m, k not relative to M, bandwidth basically irrational
+void autotest_fskmodem_misc_M2()    { fskmodem_test_mod_demod( 1,    5, 0.3721451); }
+void autotest_fskmodem_misc_M4()    { fskmodem_test_mod_demod( 2,   10, 0.3721451); }
+void autotest_fskmodem_misc_M8()    { fskmodem_test_mod_demod( 3,   20, 0.3721451); }
+void autotest_fskmodem_misc_M16()   { fskmodem_test_mod_demod( 4,   30, 0.3721451); }
+void autotest_fskmodem_misc_M32()   { fskmodem_test_mod_demod( 5,   60, 0.3721451); }
+void autotest_fskmodem_misc_M64()   { fskmodem_test_mod_demod( 6,  100, 0.3721451); }
+void autotest_fskmodem_misc_M128()  { fskmodem_test_mod_demod( 7,  200, 0.3721451); }
+void autotest_fskmodem_misc_M256()  { fskmodem_test_mod_demod( 8,  500, 0.3721451); }
+void autotest_fskmodem_misc_M512()  { fskmodem_test_mod_demod( 9, 1000, 0.3721451); }
+void autotest_fskmodem_misc_M1024() { fskmodem_test_mod_demod(10, 2000, 0.3721451); }
+
diff --git a/src/modem/tests/modem_autotest.c b/src/modem/tests/modem_autotest.c
new file mode 100644
index 0000000..9fbbcb0
--- /dev/null
+++ b/src/modem/tests/modem_autotest.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+void modem_test_mod_demod(modulation_scheme _ms)
+{
+    // generate mod/demod
+    modem mod   = modem_create(_ms);
+    modem demod = modem_create(_ms);
+
+    // run the test
+    unsigned int i, s, M = 1 << modem_get_bps(mod);
+    float complex x;
+    float e = 0.0f;
+    for (i=0; i<M; i++) {
+        modem_modulate(mod, i, &x);
+        modem_demodulate(demod, x, &s);
+        CONTEND_EQUALITY(s, i);
+
+        CONTEND_DELTA( modem_get_demodulator_phase_error(demod), 0.0f, 1e-3f);
+        
+        CONTEND_DELTA( modem_get_demodulator_evm(demod), 0.0f, 1e-3f);
+
+        e += crealf(x*conjf(x));
+    }
+    e = sqrtf(e / (float)M);
+
+    CONTEND_DELTA(e,1.0f,1e-3f);
+
+    // clean it up
+    modem_destroy(mod);
+    modem_destroy(demod);
+}
+
+// AUTOTESTS: generic PSK
+void autotest_mod_demod_psk2()      { modem_test_mod_demod(LIQUID_MODEM_PSK2);      }
+void autotest_mod_demod_psk4()      { modem_test_mod_demod(LIQUID_MODEM_PSK4);      }
+void autotest_mod_demod_psk8()      { modem_test_mod_demod(LIQUID_MODEM_PSK8);      }
+void autotest_mod_demod_psk16()     { modem_test_mod_demod(LIQUID_MODEM_PSK16);     }
+void autotest_mod_demod_psk32()     { modem_test_mod_demod(LIQUID_MODEM_PSK32);     }
+void autotest_mod_demod_psk64()     { modem_test_mod_demod(LIQUID_MODEM_PSK64);     }
+void autotest_mod_demod_psk128()    { modem_test_mod_demod(LIQUID_MODEM_PSK128);    }
+void autotest_mod_demod_psk256()    { modem_test_mod_demod(LIQUID_MODEM_PSK256);    }
+
+// AUTOTESTS: generic DPSK
+void autotest_mod_demod_dpsk2()     { modem_test_mod_demod(LIQUID_MODEM_DPSK2);     }
+void autotest_mod_demod_dpsk4()     { modem_test_mod_demod(LIQUID_MODEM_DPSK4);     }
+void autotest_mod_demod_dpsk8()     { modem_test_mod_demod(LIQUID_MODEM_DPSK8);     }
+void autotest_mod_demod_dpsk16()    { modem_test_mod_demod(LIQUID_MODEM_DPSK16);    }
+void autotest_mod_demod_dpsk32()    { modem_test_mod_demod(LIQUID_MODEM_DPSK32);    }
+void autotest_mod_demod_dpsk64()    { modem_test_mod_demod(LIQUID_MODEM_DPSK64);    }
+void autotest_mod_demod_dpsk128()   { modem_test_mod_demod(LIQUID_MODEM_DPSK128);   }
+void autotest_mod_demod_dpsk256()   { modem_test_mod_demod(LIQUID_MODEM_DPSK256);   }
+
+// AUTOTESTS: generic ASK
+void autotest_mod_demod_ask2()      { modem_test_mod_demod(LIQUID_MODEM_ASK2);      }
+void autotest_mod_demod_ask4()      { modem_test_mod_demod(LIQUID_MODEM_ASK4);      }
+void autotest_mod_demod_ask8()      { modem_test_mod_demod(LIQUID_MODEM_ASK8);      }
+void autotest_mod_demod_ask16()     { modem_test_mod_demod(LIQUID_MODEM_ASK16);     }
+void autotest_mod_demod_ask32()     { modem_test_mod_demod(LIQUID_MODEM_ASK32);     }
+void autotest_mod_demod_ask64()     { modem_test_mod_demod(LIQUID_MODEM_ASK64);     }
+void autotest_mod_demod_ask128()    { modem_test_mod_demod(LIQUID_MODEM_ASK128);    }
+void autotest_mod_demod_ask256()    { modem_test_mod_demod(LIQUID_MODEM_ASK256);    }
+
+// AUTOTESTS: generic QAM
+void autotest_mod_demod_qam4()      { modem_test_mod_demod(LIQUID_MODEM_QAM4);      }
+void autotest_mod_demod_qam8()      { modem_test_mod_demod(LIQUID_MODEM_QAM8);      }
+void autotest_mod_demod_qam16()     { modem_test_mod_demod(LIQUID_MODEM_QAM16);     }
+void autotest_mod_demod_qam32()     { modem_test_mod_demod(LIQUID_MODEM_QAM32);     }
+void autotest_mod_demod_qam64()     { modem_test_mod_demod(LIQUID_MODEM_QAM64);     }
+void autotest_mod_demod_qam128()    { modem_test_mod_demod(LIQUID_MODEM_QAM128);    }
+void autotest_mod_demod_qam256()    { modem_test_mod_demod(LIQUID_MODEM_QAM256);    }
+
+// AUTOTESTS: generic APSK (maps to specific APSK modems internally)
+void autotest_mod_demod_apsk4()     { modem_test_mod_demod(LIQUID_MODEM_APSK4);     }
+void autotest_mod_demod_apsk8()     { modem_test_mod_demod(LIQUID_MODEM_APSK8);     }
+void autotest_mod_demod_apsk16()    { modem_test_mod_demod(LIQUID_MODEM_APSK16);    }
+void autotest_mod_demod_apsk32()    { modem_test_mod_demod(LIQUID_MODEM_APSK32);    }
+void autotest_mod_demod_apsk64()    { modem_test_mod_demod(LIQUID_MODEM_APSK64);    }
+void autotest_mod_demod_apsk128()   { modem_test_mod_demod(LIQUID_MODEM_APSK128);   }
+void autotest_mod_demod_apsk256()   { modem_test_mod_demod(LIQUID_MODEM_APSK256);   }
+
+// AUTOTESTS: Specific modems
+void autotest_mod_demod_bpsk()      { modem_test_mod_demod(LIQUID_MODEM_BPSK);      }
+void autotest_mod_demod_qpsk()      { modem_test_mod_demod(LIQUID_MODEM_QPSK);      }
+void autotest_mod_demod_ook()       { modem_test_mod_demod(LIQUID_MODEM_OOK);       }
+void autotest_mod_demod_sqam32()    { modem_test_mod_demod(LIQUID_MODEM_SQAM32);    }
+void autotest_mod_demod_sqam128()   { modem_test_mod_demod(LIQUID_MODEM_SQAM128);   }
+void autotest_mod_demod_V29()       { modem_test_mod_demod(LIQUID_MODEM_V29);       }
+void autotest_mod_demod_arb16opt()  { modem_test_mod_demod(LIQUID_MODEM_ARB16OPT);  }
+void autotest_mod_demod_arb32opt()  { modem_test_mod_demod(LIQUID_MODEM_ARB32OPT);  }
+void autotest_mod_demod_arb64opt()  { modem_test_mod_demod(LIQUID_MODEM_ARB64OPT);  }
+void autotest_mod_demod_arb128opt() { modem_test_mod_demod(LIQUID_MODEM_ARB128OPT); }
+void autotest_mod_demod_arb256opt() { modem_test_mod_demod(LIQUID_MODEM_ARB256OPT); }
+void autotest_mod_demod_arb64vt()   { modem_test_mod_demod(LIQUID_MODEM_ARB64VT);   }
+
diff --git a/src/modem/tests/modem_demodsoft_autotest.c b/src/modem/tests/modem_demodsoft_autotest.c
new file mode 100644
index 0000000..928a0a8
--- /dev/null
+++ b/src/modem/tests/modem_demodsoft_autotest.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// soft demodulation tests
+//
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Help function to keep code base small
+void modem_test_demodsoft(modulation_scheme _ms)
+{
+    // generate mod/demod
+    modem mod   = modem_create(_ms);
+    modem demod = modem_create(_ms);
+
+    // 
+    unsigned int bps = modem_get_bps(demod);
+
+    // run the test
+    unsigned int i, s, M=1<<bps;
+    unsigned int sym_soft;
+    unsigned char soft_bits[bps];
+    float complex x;
+    
+    for (i=0; i<M; i++) {
+        // modulate symbol
+        modem_modulate(mod, i, &x);
+
+        // demodulate using soft-decision
+        modem_demodulate_soft(demod, x, &s, soft_bits);
+
+        // check hard-decision output
+        CONTEND_EQUALITY(s, i);
+
+        // check soft bits
+        liquid_pack_soft_bits(soft_bits, bps, &sym_soft);
+        CONTEND_EQUALITY(sym_soft, i);
+
+        // check phase error, evm, etc.
+        //CONTEND_DELTA( modem_get_demodulator_phase_error(demod), 0.0f, 1e-3f);
+        //CONTEND_DELTA( modem_get_demodulator_evm(demod), 0.0f, 1e-3f);
+    }
+    // clean it up
+    modem_destroy(mod);
+    modem_destroy(demod);
+}
+
+// AUTOTESTS: generic PSK
+void autotest_demodsoft_psk2()      { modem_test_demodsoft(LIQUID_MODEM_PSK2);      }
+void autotest_demodsoft_psk4()      { modem_test_demodsoft(LIQUID_MODEM_PSK4);      }
+void autotest_demodsoft_psk8()      { modem_test_demodsoft(LIQUID_MODEM_PSK8);      }
+void autotest_demodsoft_psk16()     { modem_test_demodsoft(LIQUID_MODEM_PSK16);     }
+void autotest_demodsoft_psk32()     { modem_test_demodsoft(LIQUID_MODEM_PSK32);     }
+void autotest_demodsoft_psk64()     { modem_test_demodsoft(LIQUID_MODEM_PSK64);     }
+void autotest_demodsoft_psk128()    { modem_test_demodsoft(LIQUID_MODEM_PSK128);    }
+void autotest_demodsoft_psk256()    { modem_test_demodsoft(LIQUID_MODEM_PSK256);    }
+
+// AUTOTESTS: generic DPSK
+void autotest_demodsoft_dpsk2()     { modem_test_demodsoft(LIQUID_MODEM_DPSK2);     }
+void autotest_demodsoft_dpsk4()     { modem_test_demodsoft(LIQUID_MODEM_DPSK4);     }
+void autotest_demodsoft_dpsk8()     { modem_test_demodsoft(LIQUID_MODEM_DPSK8);     }
+void autotest_demodsoft_dpsk16()    { modem_test_demodsoft(LIQUID_MODEM_DPSK16);    }
+void autotest_demodsoft_dpsk32()    { modem_test_demodsoft(LIQUID_MODEM_DPSK32);    }
+void autotest_demodsoft_dpsk64()    { modem_test_demodsoft(LIQUID_MODEM_DPSK64);    }
+void autotest_demodsoft_dpsk128()   { modem_test_demodsoft(LIQUID_MODEM_DPSK128);   }
+void autotest_demodsoft_dpsk256()   { modem_test_demodsoft(LIQUID_MODEM_DPSK256);   }
+
+// AUTOTESTS: generic ASK
+void autotest_demodsoft_ask2()      { modem_test_demodsoft(LIQUID_MODEM_ASK2);      }
+void autotest_demodsoft_ask4()      { modem_test_demodsoft(LIQUID_MODEM_ASK4);      }
+void autotest_demodsoft_ask8()      { modem_test_demodsoft(LIQUID_MODEM_ASK8);      }
+void autotest_demodsoft_ask16()     { modem_test_demodsoft(LIQUID_MODEM_ASK16);     }
+void autotest_demodsoft_ask32()     { modem_test_demodsoft(LIQUID_MODEM_ASK32);     }
+void autotest_demodsoft_ask64()     { modem_test_demodsoft(LIQUID_MODEM_ASK64);     }
+void autotest_demodsoft_ask128()    { modem_test_demodsoft(LIQUID_MODEM_ASK128);    }
+void autotest_demodsoft_ask256()    { modem_test_demodsoft(LIQUID_MODEM_ASK256);    }
+
+// AUTOTESTS: generic QAM
+void autotest_demodsoft_qam4()      { modem_test_demodsoft(LIQUID_MODEM_QAM4);      }
+void autotest_demodsoft_qam8()      { modem_test_demodsoft(LIQUID_MODEM_QAM8);      }
+void autotest_demodsoft_qam16()     { modem_test_demodsoft(LIQUID_MODEM_QAM16);     }
+void autotest_demodsoft_qam32()     { modem_test_demodsoft(LIQUID_MODEM_QAM32);     }
+void autotest_demodsoft_qam64()     { modem_test_demodsoft(LIQUID_MODEM_QAM64);     }
+void autotest_demodsoft_qam128()    { modem_test_demodsoft(LIQUID_MODEM_QAM128);    }
+void autotest_demodsoft_qam256()    { modem_test_demodsoft(LIQUID_MODEM_QAM256);    }
+
+// AUTOTESTS: generic APSK (maps to specific APSK modems internally)
+void autotest_demodsoft_apsk4()     { modem_test_demodsoft(LIQUID_MODEM_APSK4);     }
+void autotest_demodsoft_apsk8()     { modem_test_demodsoft(LIQUID_MODEM_APSK8);     }
+void autotest_demodsoft_apsk16()    { modem_test_demodsoft(LIQUID_MODEM_APSK16);    }
+void autotest_demodsoft_apsk32()    { modem_test_demodsoft(LIQUID_MODEM_APSK32);    }
+void autotest_demodsoft_apsk64()    { modem_test_demodsoft(LIQUID_MODEM_APSK64);    }
+void autotest_demodsoft_apsk128()   { modem_test_demodsoft(LIQUID_MODEM_APSK128);   }
+void autotest_demodsoft_apsk256()   { modem_test_demodsoft(LIQUID_MODEM_APSK256);   }
+
+// AUTOTESTS: Specific modems
+void autotest_demodsoft_bpsk()      { modem_test_demodsoft(LIQUID_MODEM_BPSK);      }
+void autotest_demodsoft_qpsk()      { modem_test_demodsoft(LIQUID_MODEM_QPSK);      }
+void autotest_demodsoft_ook()       { modem_test_demodsoft(LIQUID_MODEM_OOK);       }
+void autotest_demodsoft_sqam32()    { modem_test_demodsoft(LIQUID_MODEM_SQAM32);    }
+void autotest_demodsoft_sqam128()   { modem_test_demodsoft(LIQUID_MODEM_SQAM128);   }
+void autotest_demodsoft_V29()       { modem_test_demodsoft(LIQUID_MODEM_V29);       }
+void autotest_demodsoft_arb16opt()  { modem_test_demodsoft(LIQUID_MODEM_ARB16OPT);  }
+void autotest_demodsoft_arb32opt()  { modem_test_demodsoft(LIQUID_MODEM_ARB32OPT);  }
+void autotest_demodsoft_arb64opt()  { modem_test_demodsoft(LIQUID_MODEM_ARB64OPT);  }
+void autotest_demodsoft_arb128opt() { modem_test_demodsoft(LIQUID_MODEM_ARB128OPT); }
+void autotest_demodsoft_arb256opt() { modem_test_demodsoft(LIQUID_MODEM_ARB256OPT); }
+void autotest_demodsoft_arb64vt()   { modem_test_demodsoft(LIQUID_MODEM_ARB64VT);   }
+
diff --git a/src/modem/tests/modem_demodstats_autotest.c b/src/modem/tests/modem_demodstats_autotest.c
new file mode 100644
index 0000000..bee7abf
--- /dev/null
+++ b/src/modem/tests/modem_demodstats_autotest.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Helper function to keep code base small
+void modem_test_demodstats(modulation_scheme _ms)
+{
+    // generate mod/demod
+    modem mod   = modem_create(_ms);
+    modem demod = modem_create(_ms);
+
+    // run the test
+    unsigned int i, s, M = 1 << modem_get_bps(mod);
+    float complex x;
+    float complex x_hat;    // rotated symbol
+    float demodstats;
+    float phi = 0.01f;
+
+    for (i=0; i<M; i++) {
+        // reset modem objects
+        modem_reset(mod);
+        modem_reset(demod);
+
+        // modulate symbol
+        modem_modulate(mod, i, &x);
+
+        // ignore rare condition where modulated symbol is (0,0)
+        // (e.g. APSK-8)
+        if (cabsf(x) < 1e-3f) continue;
+
+        // add phase offsets
+        x_hat = x * cexpf( phi*_Complex_I);
+
+        // demod positive phase signal, and ensure demodulator
+        // maps to appropriate symbol
+        modem_demodulate(demod, x_hat, &s);
+        if (s != i)
+            AUTOTEST_WARN("modem_test_demodstats(), output symbol does not match");
+
+        demodstats = modem_get_demodulator_phase_error(demod);
+        CONTEND_EXPRESSION(demodstats > 0.0f);
+    }
+
+    // repeat with negative phase error
+    for (i=0; i<M; i++) {
+        // reset modem objects
+        modem_reset(mod);
+        modem_reset(demod);
+
+        // modulate symbol
+        modem_modulate(mod, i, &x);
+
+        // ignore rare condition where modulated symbol is (0,0)
+        // (e.g. APSK-8)
+        if (cabsf(x) < 1e-3f) continue;
+
+        // add phase offsets
+        x_hat = x * cexpf(-phi*_Complex_I);
+
+        // demod positive phase signal, and ensure demodulator
+        // maps to appropriate symbol
+        modem_demodulate(demod, x_hat, &s);
+        if (s != i)
+            AUTOTEST_WARN("modem_test_demodstats(), output symbol does not match");
+
+        demodstats = modem_get_demodulator_phase_error(demod);
+        CONTEND_EXPRESSION(demodstats < 0.0f);
+    }
+
+    // clean up allocated objects up
+    modem_destroy(mod);
+    modem_destroy(demod);
+}
+
+// AUTOTESTS: generic PSK
+void autotest_demodstats_psk2()     { modem_test_demodstats(LIQUID_MODEM_PSK2);     }
+void autotest_demodstats_psk4()     { modem_test_demodstats(LIQUID_MODEM_PSK4);     }
+void autotest_demodstats_psk8()     { modem_test_demodstats(LIQUID_MODEM_PSK8);     }
+void autotest_demodstats_psk16()    { modem_test_demodstats(LIQUID_MODEM_PSK16);    }
+void autotest_demodstats_psk32()    { modem_test_demodstats(LIQUID_MODEM_PSK32);    }
+void autotest_demodstats_psk64()    { modem_test_demodstats(LIQUID_MODEM_PSK64);    }
+void autotest_demodstats_psk128()   { modem_test_demodstats(LIQUID_MODEM_PSK128);   }
+void autotest_demodstats_psk256()   { modem_test_demodstats(LIQUID_MODEM_PSK256);   }
+
+// AUTOTESTS: generic DPSK
+void autotest_demodstats_dpsk2()    { modem_test_demodstats(LIQUID_MODEM_DPSK2);    }
+void autotest_demodstats_dpsk4()    { modem_test_demodstats(LIQUID_MODEM_DPSK4);    }
+void autotest_demodstats_dpsk8()    { modem_test_demodstats(LIQUID_MODEM_DPSK8);    }
+void autotest_demodstats_dpsk16()   { modem_test_demodstats(LIQUID_MODEM_DPSK16);   }
+void autotest_demodstats_dpsk32()   { modem_test_demodstats(LIQUID_MODEM_DPSK32);   }
+void autotest_demodstats_dpsk64()   { modem_test_demodstats(LIQUID_MODEM_DPSK64);   }
+void autotest_demodstats_dpsk128()  { modem_test_demodstats(LIQUID_MODEM_DPSK128);  }
+void autotest_demodstats_dpsk256()  { modem_test_demodstats(LIQUID_MODEM_DPSK256);  }
+
+// AUTOTESTS: generic ASK
+void autotest_demodstats_ask2()     { modem_test_demodstats(LIQUID_MODEM_ASK2);     }
+void autotest_demodstats_ask4()     { modem_test_demodstats(LIQUID_MODEM_ASK4);     }
+void autotest_demodstats_ask8()     { modem_test_demodstats(LIQUID_MODEM_ASK8);     }
+void autotest_demodstats_ask16()    { modem_test_demodstats(LIQUID_MODEM_ASK16);    }
+void autotest_demodstats_ask32()    { modem_test_demodstats(LIQUID_MODEM_ASK32);    }
+void autotest_demodstats_ask64()    { modem_test_demodstats(LIQUID_MODEM_ASK64);    }
+void autotest_demodstats_ask128()   { modem_test_demodstats(LIQUID_MODEM_ASK128);   }
+void autotest_demodstats_ask256()   { modem_test_demodstats(LIQUID_MODEM_ASK256);   }
+
+// AUTOTESTS: generic QAM
+void autotest_demodstats_qam4()     { modem_test_demodstats(LIQUID_MODEM_QAM4);     }
+void autotest_demodstats_qam8()     { modem_test_demodstats(LIQUID_MODEM_QAM8);     }
+void autotest_demodstats_qam16()    { modem_test_demodstats(LIQUID_MODEM_QAM16);    }
+void autotest_demodstats_qam32()    { modem_test_demodstats(LIQUID_MODEM_QAM32);    }
+void autotest_demodstats_qam64()    { modem_test_demodstats(LIQUID_MODEM_QAM64);    }
+void autotest_demodstats_qam128()   { modem_test_demodstats(LIQUID_MODEM_QAM128);   }
+void autotest_demodstats_qam256()   { modem_test_demodstats(LIQUID_MODEM_QAM256);   }
+
+// AUTOTESTS: generic APSK (maps to specific APSK modems internally)
+void autotest_demodstats_apsk4()    { modem_test_demodstats(LIQUID_MODEM_APSK4);    }
+void autotest_demodstats_apsk8()    { modem_test_demodstats(LIQUID_MODEM_APSK8);    }
+void autotest_demodstats_apsk16()   { modem_test_demodstats(LIQUID_MODEM_APSK16);   }
+void autotest_demodstats_apsk32()   { modem_test_demodstats(LIQUID_MODEM_APSK32);   }
+void autotest_demodstats_apsk64()   { modem_test_demodstats(LIQUID_MODEM_APSK64);   }
+void autotest_demodstats_apsk128()  { modem_test_demodstats(LIQUID_MODEM_APSK128);  }
+void autotest_demodstats_apsk256()  { modem_test_demodstats(LIQUID_MODEM_APSK256);  }
+
+// AUTOTESTS: Specific modems
+void autotest_demodstats_bpsk()     { modem_test_demodstats(LIQUID_MODEM_BPSK);     }
+void autotest_demodstats_qpsk()     { modem_test_demodstats(LIQUID_MODEM_QPSK);     }
+void autotest_demodstats_ook()      { modem_test_demodstats(LIQUID_MODEM_OOK);      }
+void autotest_demodstats_sqam32()   { modem_test_demodstats(LIQUID_MODEM_SQAM32);   }
+void autotest_demodstats_sqam128()  { modem_test_demodstats(LIQUID_MODEM_SQAM128);  }
+void autotest_demodstats_V29()      { modem_test_demodstats(LIQUID_MODEM_V29);      }
+void autotest_demodstats_arb16opt() { modem_test_demodstats(LIQUID_MODEM_ARB16OPT); }
+void autotest_demodstats_arb32opt() { modem_test_demodstats(LIQUID_MODEM_ARB32OPT); }
+void autotest_demodstats_arb64opt() { modem_test_demodstats(LIQUID_MODEM_ARB64OPT); }
+void autotest_demodstats_arb128opt(){ modem_test_demodstats(LIQUID_MODEM_ARB128OPT);}
+void autotest_demodstats_arb256opt(){ modem_test_demodstats(LIQUID_MODEM_ARB256OPT);}
+void autotest_demodstats_arb64vt()  { modem_test_demodstats(LIQUID_MODEM_ARB64VT);  }
+
diff --git a/src/multichannel/bench/firpfbch2_crcf_benchmark.c b/src/multichannel/bench/firpfbch2_crcf_benchmark.c
new file mode 100644
index 0000000..28f9f67
--- /dev/null
+++ b/src/multichannel/bench/firpfbch2_crcf_benchmark.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define FIRPFBCH2_EXECUTE_BENCH_API(NUM_CHANNELS,M,TYPE)    \
+(   struct rusage *_start,                                  \
+    struct rusage *_finish,                                 \
+    unsigned long int *_num_iterations)                     \
+{ firpfbch2_crcf_execute_bench(_start, _finish, _num_iterations, NUM_CHANNELS, M, TYPE); }
+
+// Helper function to keep code base small
+void firpfbch2_crcf_execute_bench(struct rusage *     _start,
+                                  struct rusage *     _finish,
+                                  unsigned long int * _num_iterations,
+                                  unsigned int        _num_channels,
+                                  unsigned int        _m,
+                                  int                 _type)
+{
+    // initialize channelizer
+    float As         = 60.0f;
+    firpfbch2_crcf q = firpfbch2_crcf_create_kaiser(_type,_num_channels,_m,As);
+
+    unsigned long int i;
+
+    float complex x[_num_channels];
+    float complex y[_num_channels];
+    for (i=0; i<_num_channels; i++)
+        x[i] = 1.0f + _Complex_I*1.0f;
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    *_num_iterations /= _num_channels;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        firpfbch2_crcf_execute(q, x, y);
+        firpfbch2_crcf_execute(q, x, y);
+        firpfbch2_crcf_execute(q, x, y);
+        firpfbch2_crcf_execute(q, x, y);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firpfbch2_crcf_destroy(q);
+}
+
+// analysis
+void benchmark_firpfbch2_crcf_a4    FIRPFBCH2_EXECUTE_BENCH_API(4,    2,  LIQUID_ANALYZER)
+void benchmark_firpfbch2_crcf_a16   FIRPFBCH2_EXECUTE_BENCH_API(16,   2,  LIQUID_ANALYZER)
+void benchmark_firpfbch2_crcf_a64   FIRPFBCH2_EXECUTE_BENCH_API(64,   2,  LIQUID_ANALYZER)
+void benchmark_firpfbch2_crcf_a256  FIRPFBCH2_EXECUTE_BENCH_API(256,  2,  LIQUID_ANALYZER)
+void benchmark_firpfbch2_crcf_a512  FIRPFBCH2_EXECUTE_BENCH_API(512,  2,  LIQUID_ANALYZER)
+void benchmark_firpfbch2_crcf_a1024 FIRPFBCH2_EXECUTE_BENCH_API(1024, 2,  LIQUID_ANALYZER)
+
+// synthesis
+void benchmark_firpfbch2_crcf_s4    FIRPFBCH2_EXECUTE_BENCH_API(4,    2,  LIQUID_SYNTHESIZER)
+void benchmark_firpfbch2_crcf_s16   FIRPFBCH2_EXECUTE_BENCH_API(16,   2,  LIQUID_SYNTHESIZER)
+void benchmark_firpfbch2_crcf_s64   FIRPFBCH2_EXECUTE_BENCH_API(64,   2,  LIQUID_SYNTHESIZER)
+void benchmark_firpfbch2_crcf_s256  FIRPFBCH2_EXECUTE_BENCH_API(256,  2,  LIQUID_SYNTHESIZER)
+void benchmark_firpfbch2_crcf_s512  FIRPFBCH2_EXECUTE_BENCH_API(512,  2,  LIQUID_SYNTHESIZER)
+void benchmark_firpfbch2_crcf_s1024 FIRPFBCH2_EXECUTE_BENCH_API(1024, 2,  LIQUID_SYNTHESIZER)
+
+
diff --git a/src/multichannel/bench/firpfbch_crcf_benchmark.c b/src/multichannel/bench/firpfbch_crcf_benchmark.c
new file mode 100644
index 0000000..6beca20
--- /dev/null
+++ b/src/multichannel/bench/firpfbch_crcf_benchmark.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define FIRPFBCH_EXECUTE_BENCH_API(NUM_CHANNELS,M,TYPE) \
+(   struct rusage *_start,                              \
+    struct rusage *_finish,                             \
+    unsigned long int *_num_iterations)                 \
+{ firpfbch_crcf_execute_bench(_start, _finish, _num_iterations, NUM_CHANNELS, M, TYPE); }
+
+// Helper function to keep code base small
+void firpfbch_crcf_execute_bench(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations,
+    unsigned int _num_channels,
+    unsigned int _m,
+    int _type)
+{
+    // initialize channelizer
+    float As    = 60.0f;
+    firpfbch_crcf c = firpfbch_crcf_create_kaiser(_type,_num_channels,_m,As);
+
+    unsigned long int i;
+
+    float complex x[_num_channels];
+    float complex y[_num_channels];
+    for (i=0; i<_num_channels; i++)
+        x[i] = 1.0f + _Complex_I*1.0f;
+
+    // scale number of iterations to keep execution time
+    // relatively linear
+    *_num_iterations /= _num_channels;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    if (_type == LIQUID_SYNTHESIZER) {
+        for (i=0; i<(*_num_iterations); i++) {
+            firpfbch_crcf_synthesizer_execute(c,x,y);
+            firpfbch_crcf_synthesizer_execute(c,x,y);
+            firpfbch_crcf_synthesizer_execute(c,x,y);
+            firpfbch_crcf_synthesizer_execute(c,x,y);
+        }
+    } else  {
+        for (i=0; i<(*_num_iterations); i++) {
+            firpfbch_crcf_analyzer_execute(c,x,y);
+            firpfbch_crcf_analyzer_execute(c,x,y);
+            firpfbch_crcf_analyzer_execute(c,x,y);
+            firpfbch_crcf_analyzer_execute(c,x,y);
+        }
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    firpfbch_crcf_destroy(c);
+}
+
+//
+void benchmark_firpfbch_crcf_a4      FIRPFBCH_EXECUTE_BENCH_API(4,    2,  LIQUID_ANALYZER)
+void benchmark_firpfbch_crcf_a16     FIRPFBCH_EXECUTE_BENCH_API(16,   2,  LIQUID_ANALYZER)
+void benchmark_firpfbch_crcf_a64     FIRPFBCH_EXECUTE_BENCH_API(64,   2,  LIQUID_ANALYZER)
+void benchmark_firpfbch_crcf_a256    FIRPFBCH_EXECUTE_BENCH_API(256,  2,  LIQUID_ANALYZER)
+void benchmark_firpfbch_crcf_a512    FIRPFBCH_EXECUTE_BENCH_API(512,  2,  LIQUID_ANALYZER)
+void benchmark_firpfbch_crcf_a1024   FIRPFBCH_EXECUTE_BENCH_API(1024, 2,  LIQUID_ANALYZER)
+
+
diff --git a/src/multichannel/bench/ofdmframesync_acquire_benchmark.c b/src/multichannel/bench/ofdmframesync_acquire_benchmark.c
new file mode 100644
index 0000000..4a0a579
--- /dev/null
+++ b/src/multichannel/bench/ofdmframesync_acquire_benchmark.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define OFDMFRAMESYNC_ACQUIRE_BENCH_API(M,CP_LEN)   \
+(   struct rusage *_start,                          \
+    struct rusage *_finish,                         \
+    unsigned long int *_num_iterations)             \
+{ ofdmframesync_acquire_bench(_start, _finish, _num_iterations, M, CP_LEN); }
+
+// Helper function to keep code base small
+void ofdmframesync_acquire_bench(struct rusage *_start,
+                                 struct rusage *_finish,
+                                 unsigned long int *_num_iterations,
+                                 unsigned int _num_subcarriers,
+                                 unsigned int _cp_len)
+{
+    // options
+    unsigned int M         = _num_subcarriers;
+    unsigned int cp_len    = _cp_len;
+    unsigned int taper_len = 0;
+
+    // derived values
+    unsigned int num_samples = 3*(M + cp_len);
+
+    // create synthesizer/analyzer objects
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, NULL);
+    //ofdmframegen_print(fg);
+
+    ofdmframesync fs = ofdmframesync_create(M,cp_len,taper_len,NULL,NULL,NULL);
+
+    unsigned int i;
+    float complex y[num_samples];   // frame samples
+
+    // assemble full frame
+    unsigned int n=0;
+
+    // write first S0 symbol
+    ofdmframegen_write_S0a(fg, &y[n]);
+    n += M + cp_len;
+
+    // write second S0 symbol
+    ofdmframegen_write_S0b(fg, &y[n]);
+    n += M + cp_len;
+
+    // write S1 symbol
+    ofdmframegen_write_S1( fg, &y[n]);
+    n += M + cp_len;
+
+    assert(n == num_samples);
+
+    // add noise
+    for (i=0; i<num_samples; i++)
+        y[i] += 0.02f*randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+    // start trials
+    *_num_iterations /= M*sqrtf(M);
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        //
+        ofdmframesync_execute(fs,y,num_samples);
+
+        //
+        ofdmframesync_reset(fs);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    //*_num_iterations *= 4;
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    ofdmframesync_destroy(fs);
+}
+
+//
+void benchmark_ofdmframesync_acquire_n64    OFDMFRAMESYNC_ACQUIRE_BENCH_API(64, 8)
+void benchmark_ofdmframesync_acquire_n128   OFDMFRAMESYNC_ACQUIRE_BENCH_API(128,16)
+void benchmark_ofdmframesync_acquire_n256   OFDMFRAMESYNC_ACQUIRE_BENCH_API(256,32)
+void benchmark_ofdmframesync_acquire_n512   OFDMFRAMESYNC_ACQUIRE_BENCH_API(512,64)
+
diff --git a/src/multichannel/bench/ofdmframesync_rxsymbol_benchmark.c b/src/multichannel/bench/ofdmframesync_rxsymbol_benchmark.c
new file mode 100644
index 0000000..8e144c6
--- /dev/null
+++ b/src/multichannel/bench/ofdmframesync_rxsymbol_benchmark.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <sys/resource.h>
+#include "liquid.h"
+
+#define OFDMFRAMESYNC_RXSYMBOL_BENCH_API(M,CP_LEN)  \
+(   struct rusage *_start,                          \
+    struct rusage *_finish,                         \
+    unsigned long int *_num_iterations)             \
+{ ofdmframesync_rxsymbol_bench(_start, _finish, _num_iterations, M, CP_LEN); }
+
+// Helper function to keep code base small
+void ofdmframesync_rxsymbol_bench(struct rusage *_start,
+                                 struct rusage *_finish,
+                                 unsigned long int *_num_iterations,
+                                 unsigned int _num_subcarriers,
+                                 unsigned int _cp_len)
+{
+    // options
+    modulation_scheme ms = LIQUID_MODEM_QPSK;
+    unsigned int M         = _num_subcarriers;
+    unsigned int cp_len    = _cp_len;
+    unsigned int taper_len = 0;
+
+    // create synthesizer/analyzer objects
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, NULL);
+    //ofdmframegen_print(fg);
+
+    modem mod = modem_create(ms);
+
+    ofdmframesync fs = ofdmframesync_create(M,cp_len,taper_len,NULL,NULL,NULL);
+
+    unsigned int i;
+    float complex X[M];         // channelized symbol
+    float complex x[M+cp_len];  // time-domain symbol
+
+    // synchronize short sequence (first)
+    ofdmframegen_write_S0a(fg, x);
+    ofdmframesync_execute(fs, x, M+cp_len);
+
+    // synchronize short sequence (second)
+    ofdmframegen_write_S0b(fg, x);
+    ofdmframesync_execute(fs, x, M+cp_len);
+
+    // synchronize long sequence
+    ofdmframegen_write_S1(fg, x);
+    ofdmframesync_execute(fs, x, M+cp_len);
+
+    // modulate data symbols (use same symbol, ignore pilot phase)
+    unsigned int s;
+    for (i=0; i<M; i++) {
+        s = modem_gen_rand_sym(mod);
+        modem_modulate(mod,s,&X[i]);
+    }
+
+    ofdmframegen_writesymbol(fg, X, x);
+
+    // add noise
+    for (i=0; i<M+cp_len; i++)
+        x[i] += 0.02f*randnf()*cexpf(_Complex_I*2*M_PI*randf());
+
+    // normalize number of iterations
+    *_num_iterations /= M;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        // receive data symbols (ignoring pilots)
+        ofdmframesync_execute(fs, x, M+cp_len);
+        ofdmframesync_execute(fs, x, M+cp_len);
+        ofdmframesync_execute(fs, x, M+cp_len);
+        ofdmframesync_execute(fs, x, M+cp_len);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    ofdmframesync_destroy(fs);
+    modem_destroy(mod);
+}
+
+//
+void benchmark_ofdmframesync_rxsymbol_n64   OFDMFRAMESYNC_RXSYMBOL_BENCH_API(64, 8)
+void benchmark_ofdmframesync_rxsymbol_n128  OFDMFRAMESYNC_RXSYMBOL_BENCH_API(128,16)
+void benchmark_ofdmframesync_rxsymbol_n256  OFDMFRAMESYNC_RXSYMBOL_BENCH_API(256,32)
+void benchmark_ofdmframesync_rxsymbol_n512  OFDMFRAMESYNC_RXSYMBOL_BENCH_API(512,64)
+
diff --git a/src/multichannel/ofdmoqam.readme.txt b/src/multichannel/ofdmoqam.readme.txt
new file mode 100644
index 0000000..17eccaa
--- /dev/null
+++ b/src/multichannel/ofdmoqam.readme.txt
@@ -0,0 +1,51 @@
+
+##
+##  Synthesis channelizer
+##
+
+                               {X0} +-----------+
+    X[0] --+-------------> Re{-} -->|           |
+    X[1] ----+----------->jIm{-} -->|           |
+    X[2] ------+---------> Re{-} -->|           | {x0}
+           | | | .                  | firpfbch  |---------->(+)--->
+           | | |   .                |   [c0]    |            |
+           | | |     .              |           |            |
+  X[M-1] --------------+->jIm{-} -->|           |            |
+           | | |       |            +-----------+            |
+           | | |       |                                     |
+           | | |       |       {X1} +-----------+            |
+           +------------->jIm{-} -->|           |            |
+             +-----------> Re{-} -->|           |            |
+               +--------->jIm{-}    |           | {x1}       |
+                 .     |            | firpfbch  |--[z^-M/2]--+
+                   .   |            |   [c1]    |        {x1_prime}
+                     . |            |           |
+                       +-> Re{-} -->|           |
+                                    +-----------+
+
+
+##
+##  Analysis channelizer
+##
+
+                    +-----------+ {X0}
+                    |           | --> Re{-} -->(+)--------------> X[0]
+                    |           | -->jIm{-} ---->(+)------------> X[1]
+               {x0} |           | --> Re{-} ------>(+)----------> X[2]
+  --+--[z^-M] ----> | firpfbch  |     .         | | | .
+    | {x0_prime}    |   [c0]    |     .         | | |   .
+    |               |           |     .         | | |     .
+    |               |           | -->jIm{-}----------------(+)--> X[M-1]
+    |               +-----------+               | | |       |
+    |                                           | | |       |
+    |               +-----------+ {X1}          | | | . . . |
+    |               |           | -->jIm{-} ----+ | |       |
+    |               |           | --> Re{-} ------+ |       |
+    |          {x1} |           | -->jIm{-} --------+       |
+    +--[z^-M/2] --> | firpfbch  |     .               .     |
+  {x1_prime}        |   [c1]    |     .                 .   |
+                    |           |     .                   . |
+                    |           | --> Re{-} ----------------+
+                    +-----------+
+
+                        
diff --git a/src/multichannel/readme.ofdm.txt b/src/multichannel/readme.ofdm.txt
new file mode 100644
index 0000000..3d59d1f
--- /dev/null
+++ b/src/multichannel/readme.ofdm.txt
@@ -0,0 +1,116 @@
+=========================================================================
+
+  OFDM framing structure
+
+=========================================================================
+
+signal           preamble                       payload
+^       |<-------------------->|<---------------------------------->|
+|       |                      |                                    |
+|       +----+----+----+--+----+--+----+--+----+--+----+-//-+--+----+
+|       |    |    |    |//|    |//|    |//|    |//|    |    |//|    |
+|       |    |    |    |//|    |//|    |//|    |//|    |    |//|    |
+|       | S0 | S0 | .. |//| S1 |//| P0 |//| P1 |//| P2 | .. |//| Px |
+|       |    |    |    |//|    |//|    |//|    |//|    |    |//|    |
+|_______|    |    |    |//|    |//|    |//|    |//|    |    |//|    |
++----------------------------------------------------------------------->
+                                                                       time
+
+
+               spectral null      ^    pilot subcarrier
+                    /             |    /
+     *----*----*        *----*----+---*----*----*----*----*----*
+     |         |        |         |                            |
+     |         |        |         |          ...               |
+  ___/         \________/         |                            \___
+  +-------------------------------+--------------------------------+ freq
+-Fs/2                             0                             +Fs/2
+
+
+Overview:
+    The ofdmframe family of objects (generator and synchronizer)
+    realize a simple way to load data onto an OFDM physical layer
+    system. OFDM has several benefits over traditional 'narrowband'
+    communications systems.
+    These objects allow the user to abstractly specify the number of
+    subcarriers, their assignment (null/pilot/data), forward
+    error-correction and modulation scheme.
+    Furthermore, the framing structure includes a provision for a brief
+    user-defined header which can be used for source/destination
+    address, packet identifier, etc.
+
+Operational description:
+    The structure of the frame consists of three main components: the
+    preamble, the header, and the payload.
+    
+  Preamble
+    The preamble consists of two types of phasing symbols: the S0 and S1
+    sequences. The S0 symbols are necessary for coarse carrier frequency
+    and timing offsets while the S1 sequence is used for fine timing
+    acquisition and equalizer gain estimation.  The transmitter
+    generates multiple S0 symbols (minimally 2, but usually 3 or more)
+    and just a single S1 symbol. This aligns the receiver's timing to
+    that of the transmitter, signalling the start of the header.
+
+OFDM frame gen/sync parameters (must match on both TX and RX side):
+    M       :   Total number of subcarriers
+    p*      :   subcarrier allocation
+    cplen   :   cyclic prefix length (samples)
+
+    M0      :   number of null subcarriers
+    Mp      :   number of pilot subcarriers
+    Md      :   number of data subcarriers
+
+    num_S0  :   number of S0 symbols
+    num_S1  :   number of S1 symbols (must be 1, non-negotiable)
+
+Notes:
+    * The length of each data symbol is equal to the number of
+      subcarriers plus the cyclic prefix length (e.g. if N=64 and
+      cplen=16, each data symbol is 80 samples long) with the exception
+      of the S0 training symbols which are simply the number of
+      subcarriers.
+    * The S0 symbols do NOT have a cyclic prefix; this is
+      necessary to preserve continuity between symbols. Unlike 802.11a
+      (and derivative protocols) in which a fixed cyclic prefix has
+      been designed into the system, the flexibility of liquid-dsp's
+      OFDM framing structure requires that no prefix is added to
+      maintain temporal continuity.
+
+Details:
+    section num samples     Description/purpose
+
+    S0      M*num_S0        Initial preamble for coarse carrier
+                            frequency, phase, and timing offset
+                            estimates
+
+    S1      M+cplen         Secondary preamble for timing disambiguation
+                            and equalization
+
+    payload M+cplen         payload symbols
+
+
+=========================================================================
+  Synchronizer: Description of Operation
+=========================================================================
+
+
+signal                              FFT input
+^                  |<---------------------------------------->|
+|                  |                                          |
+|       +------------+------------------------------------------+-------
+|       |////////////|                                          |//////
+|       |//        //|                                          |//
+|       |// cyclic //|                  OFDM                    |// ...
+|       |// prefix //|                 symbol                   |//
+|       |//        //|                                          |//
+|_______|////////////|                                          |//////
++------------------+-+------------------------------------------+------->
+                   | |                                          |      time
+                   | +- true start of symbol                    |
+                   |                                            +- start of
+                   +- timing backoff (e.g. 2 samples)             next symbol
+
+Talking points:
+    * FFT input backoff by one or two samples to ensure no overlap
+      with next symbol
diff --git a/src/multichannel/src/firpfbch.c b/src/multichannel/src/firpfbch.c
new file mode 100644
index 0000000..1abe731
--- /dev/null
+++ b/src/multichannel/src/firpfbch.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firpfbch.c
+//
+// finite impulse response polyphase filterbank channelizer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// firpfbch object structure definition
+struct FIRPFBCH(_s) {
+    int type;                   // synthesis/analysis
+    unsigned int num_channels;  // number of channels
+    unsigned int p;             // filter length (symbols)
+
+    // filter
+    unsigned int h_len;         // filter length
+    TC * h;                     // filter coefficients
+    
+    // create separate bank of dotprod and window objects
+    DOTPROD() * dp;             // dot product object array
+    WINDOW() * w;               // window buffer object array
+    unsigned int filter_index;  // running filter index (analysis)
+
+    // fft plan
+    FFT_PLAN fft;               // fft|ifft object
+    TO * x;                     // fft|ifft transform input array
+    TO * X;                     // fft|ifft transform output array
+};
+
+// 
+// forward declaration of internal methods
+//
+
+void FIRPFBCH(_analyzer_push)(FIRPFBCH() _q, TI _x);
+
+void FIRPFBCH(_analyzer_run)(FIRPFBCH()   _q,
+                             unsigned int _k,
+                             TO *         _X);
+
+
+// create FIR polyphase filterbank channelizer object
+//  _type   : channelizer type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER)
+//  _M      : number of channels
+//  _p      : filter length (symbols)
+//  _h      : filter coefficients, [size: _M*_p x 1]
+FIRPFBCH() FIRPFBCH(_create)(int          _type,
+                             unsigned int _M,
+                             unsigned int _p,
+                             TC *         _h)
+{
+    // validate input
+    if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) {
+        fprintf(stderr,"error: firpfbch_%s_create(), invalid type %d\n", EXTENSION_FULL, _type);
+        exit(1);
+    } else if (_M == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create(), number of channels must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_p == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create(), invalid filter size (must be greater than 0)\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create main object
+    FIRPFBCH() q = (FIRPFBCH()) malloc(sizeof(struct FIRPFBCH(_s)));
+
+    // set user-defined properties
+    q->type         = _type;
+    q->num_channels = _M;
+    q->p            = _p;
+
+    // derived values
+    q->h_len = q->num_channels * q->p;
+
+    // create bank of filters
+    q->dp = (DOTPROD()*) malloc((q->num_channels)*sizeof(DOTPROD()));
+    q->w  = (WINDOW()*)  malloc((q->num_channels)*sizeof(WINDOW()));
+
+    // copy filter coefficients
+    q->h = (TC*) malloc((q->h_len)*sizeof(TC));
+    unsigned int i;
+    for (i=0; i<q->h_len; i++)
+        q->h[i] = _h[i];
+
+    // generate bank of sub-samped filters
+    unsigned int n;
+    unsigned int h_sub_len = q->p;
+    TC h_sub[h_sub_len];
+    for (i=0; i<q->num_channels; i++) {
+        // sub-sample prototype filter, loading coefficients in reverse order
+        for (n=0; n<h_sub_len; n++) {
+            h_sub[h_sub_len-n-1] = q->h[i + n*(q->num_channels)];
+        }
+        // create window buffer and dotprod object (coefficients
+        // loaded in reverse order)
+        q->dp[i] = DOTPROD(_create)(h_sub,h_sub_len);
+        q->w[i]  = WINDOW(_create)(h_sub_len);
+    }
+
+    // allocate memory for buffers
+    // TODO : use fftw_malloc if HAVE_FFTW3_H
+    q->x = (T*) malloc((q->num_channels)*sizeof(T));
+    q->X = (T*) malloc((q->num_channels)*sizeof(T));
+
+    // create fft plan
+    if (q->type == LIQUID_ANALYZER)
+        q->fft = FFT_CREATE_PLAN(q->num_channels, q->X, q->x, FFT_DIR_FORWARD, FFT_METHOD);
+    else
+        q->fft = FFT_CREATE_PLAN(q->num_channels, q->X, q->x, FFT_DIR_BACKWARD, FFT_METHOD);
+
+    // reset filterbank object
+    FIRPFBCH(_reset)(q);
+
+    // return filterbank object
+    return q;
+}
+
+// create FIR polyphase filterbank channelizer object with
+// prototype filter based on windowed Kaiser design
+//  _type   : channelizer type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER)
+//  _M      : number of channels
+//  _m      : filter delay (symbols)
+//  _As     : stop-band attentuation [dB]
+FIRPFBCH() FIRPFBCH(_create_kaiser)(int          _type,
+                                    unsigned int _M,
+                                    unsigned int _m,
+                                    float        _As)
+{
+    // validate input
+    if (_M == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create_kaiser(), number of channels must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create_kaiser(), invalid filter size (must be greater than 0)\n", EXTENSION_FULL);
+        exit(1);
+    }
+    
+    _As = fabsf(_As);
+
+    // design filter
+    unsigned int h_len = 2*_M*_m + 1;
+    float h[h_len];
+    float fc = 0.5f / (float)_M; // TODO : check this value
+    liquid_firdes_kaiser(h_len, fc, _As, 0.0f, h);
+
+    // copy coefficients to type-specfic array
+    TC hc[h_len];
+    unsigned int i;
+    for (i=0; i<h_len; i++)
+        hc[i] = h[i];
+
+    // create filterbank object
+    unsigned int p = 2*_m;
+    FIRPFBCH() q = FIRPFBCH(_create)(_type, _M, p, hc);
+
+    // return filterbank object
+    return q;
+}
+
+// create FIR polyphase filterbank channelizer object with
+// prototype root-Nyquist filter
+//  _type   : channelizer type (LIQUID_ANALYZER | LIQUID_SYNTHESIZER)
+//  _M      : number of channels
+//  _m      : filter delay (symbols)
+//  _beta   : filter excess bandwidth factor, in [0,1]
+//  _ftype  : filter prototype (rrcos, rkaiser, etc.)
+FIRPFBCH() FIRPFBCH(_create_rnyquist)(int          _type,
+                                      unsigned int _M,
+                                      unsigned int _m,
+                                      float        _beta,
+                                      int          _ftype)
+{
+    // validate input
+    if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) {
+        fprintf(stderr,"error: firpfbch_%s_create_rnyquist(), invalid type %d\n", EXTENSION_FULL, _type);
+        exit(1);
+    } else if (_M == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create_rnyquist(), number of channels must be greater than 0\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m == 0) {
+        fprintf(stderr,"error: firpfbch_%s_create_rnyquist(), invalid filter size (must be greater than 0)\n", EXTENSION_FULL);
+        exit(1);
+    }
+    
+    // design filter
+    unsigned int h_len = 2*_M*_m + 1;
+    float h[h_len];
+    // TODO : actually design based on requested filter prototype
+    switch (_ftype) {
+    case LIQUID_FIRFILT_ARKAISER:
+        // root-Nyquist Kaiser (approximate optimum)
+        liquid_firdes_arkaiser(_M, _m, _beta, 0.0f, h);
+        break;
+    case LIQUID_FIRFILT_RKAISER:
+        // root-Nyquist Kaiser (true optimum)
+        liquid_firdes_rkaiser(_M, _m, _beta, 0.0f, h);
+        break;
+    case LIQUID_FIRFILT_RRC:
+        // root raised-cosine
+        liquid_firdes_rrcos(_M, _m, _beta, 0.0f, h);
+        break;
+    case LIQUID_FIRFILT_hM3:
+        // harris-Moerder-3 filter
+        liquid_firdes_hM3(_M, _m, _beta, 0.0f, h);
+        break;
+    default:
+        fprintf(stderr,"error: firpfbch_%s_create_rnyquist(), unknown/invalid prototype (%d)\n", EXTENSION_FULL, _ftype);
+        exit(1);
+    }
+
+    // copy coefficients to type-specfic array, reversing order if
+    // channelizer is an analyzer, matched filter: g(-t)
+    unsigned int g_len = 2*_M*_m;
+    TC gc[g_len];
+    unsigned int i;
+    if (_type == LIQUID_SYNTHESIZER) {
+        for (i=0; i<g_len; i++)
+            gc[i] = h[i];
+    } else {
+        for (i=0; i<g_len; i++)
+            gc[i] = h[g_len-i-1];
+    }
+
+    // create filterbank object
+    unsigned int p = 2*_m;
+    FIRPFBCH() q = FIRPFBCH(_create)(_type, _M, p, gc);
+
+    // return filterbank object
+    return q;
+}
+
+// destroy firpfbch object
+void FIRPFBCH(_destroy)(FIRPFBCH() _q)
+{
+    unsigned int i;
+
+    // free dot product, window objects and arrays
+    for (i=0; i<_q->num_channels; i++) {
+        DOTPROD(_destroy)(_q->dp[i]);
+        WINDOW(_destroy)(_q->w[i]);
+    }
+    free(_q->dp);
+    free(_q->w);
+
+    // free transform object
+    FFT_DESTROY_PLAN(_q->fft);
+
+    // free additional arrays
+    free(_q->h);
+    free(_q->x);
+    free(_q->X);
+
+    // free main object memory
+    free(_q);
+}
+
+// clear/reset firpfbch object internals
+void FIRPFBCH(_reset)(FIRPFBCH() _q)
+{
+    unsigned int i;
+    for (i=0; i<_q->num_channels; i++) {
+        WINDOW(_clear)(_q->w[i]);
+        _q->x[i] = 0;
+        _q->X[i] = 0;
+    }
+    _q->filter_index = _q->num_channels-1;
+}
+
+// print firpfbch object
+void FIRPFBCH(_print)(FIRPFBCH() _q)
+{
+    unsigned int i;
+    printf("firpfbch (%s) [%u channels]:\n",
+            _q->type == LIQUID_ANALYZER ? "analyzer" : "synthesizer",
+            _q->num_channels);
+    for (i=0; i<_q->h_len; i++)
+        printf("  h[%3u] = %12.8f + %12.8f*j\n", i, crealf(_q->h[i]), cimagf(_q->h[i]));
+}
+
+// 
+// SYNTHESIZER
+//
+
+// execute filterbank as synthesizer on block of samples
+//  _q      :   filterbank channelizer object
+//  _x      :   channelized input, [size: num_channels x 1]
+//  _y      :   output time series, [size: num_channels x 1]
+void FIRPFBCH(_synthesizer_execute)(FIRPFBCH() _q,
+                                    TI * _x,
+                                    TO * _y)
+{
+    unsigned int i;
+
+    // copy channelized symbols to transform input
+    memmove(_q->X, _x, _q->num_channels*sizeof(TI));
+
+    // execute inverse DFT, store result in buffer 'x'
+    FFT_EXECUTE(_q->fft);
+
+    // push samples into filter bank and execute
+    T * r;      // read pointer
+    for (i=0; i<_q->num_channels; i++) {
+        WINDOW(_push)(_q->w[i], _q->x[i]);
+        WINDOW(_read)(_q->w[i], &r);
+        DOTPROD(_execute)(_q->dp[i], r, &_y[i]);
+
+        // normalize by DFT scaling factor
+        //_y[i] /= (float) (_q->num_channels);
+    }
+}
+
+// 
+// ANALYZER
+//
+
+// execute filterbank as analyzer on block of samples
+//  _q      :   filterbank channelizer object
+//  _x      :   input time series, [size: num_channels x 1]
+//  _y      :   channelized output, [size: num_channels x 1]
+void FIRPFBCH(_analyzer_execute)(FIRPFBCH() _q,
+                                 TI * _x,
+                                 TO * _y)
+{
+    unsigned int i;
+
+    // push samples into buffers
+    for (i=0; i<_q->num_channels; i++)
+        FIRPFBCH(_analyzer_push)(_q, _x[i]);
+
+    // execute analysis filters on the given input starting
+    // with filterbank at index zero
+    FIRPFBCH(_analyzer_run)(_q, 0, _y);
+}
+
+// 
+// internal methods
+//
+
+// push single sample into analysis filterbank, updating index
+// counter appropriately
+//  _q      :   filterbank channelizer object
+//  _x      :   input sample
+void FIRPFBCH(_analyzer_push)(FIRPFBCH() _q,
+                              TI _x)
+{
+    // push sample into filter
+    WINDOW(_push)(_q->w[_q->filter_index], _x);
+
+    // decrement filter index
+    _q->filter_index = (_q->filter_index + _q->num_channels - 1) % _q->num_channels;
+}
+
+// run filterbank analyzer dot products, DFT
+//  _q      :   filterbank channelizer object
+//  _k      :   filterbank alignment index
+//  _y      :   output array, [size: num_channels x 1]
+void FIRPFBCH(_analyzer_run)(FIRPFBCH() _q,
+                             unsigned int _k,
+                             TO * _y)
+{
+    unsigned int i;
+
+    // execute filter outputs, reversing order of output (not
+    // sure why this is necessary)
+    T * r;  // read pointer
+    unsigned int index;
+    for (i=0; i<_q->num_channels; i++) {
+        // compute appropriate index
+        index = (i+_k) % _q->num_channels;
+
+        // read buffer at specified index
+        WINDOW(_read)(_q->w[index], &r);
+
+        // compute dot product
+        DOTPROD(_execute)(_q->dp[i], r, &_q->X[_q->num_channels-i-1]);
+    }
+
+    // execute DFT, store result in buffer 'x'
+    FFT_EXECUTE(_q->fft);
+
+    // move to output array
+    memmove(_y, _q->x, _q->num_channels*sizeof(TO));
+}
+
+
diff --git a/src/multichannel/src/firpfbch2.c b/src/multichannel/src/firpfbch2.c
new file mode 100644
index 0000000..f810aa5
--- /dev/null
+++ b/src/multichannel/src/firpfbch2.c
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// firpfbch2.c
+//
+// finite impulse response polyphase filterbank channelizer with output
+// rate 2 Fs / M
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+// firpfbch2 object structure definition
+struct FIRPFBCH2(_s) {
+    int type;           // synthesis/analysis
+    unsigned int M;     // number of channels
+    unsigned int M2;    // number of channels/2
+    unsigned int m;     // filter semi-length
+
+    // filter
+    unsigned int h_len; // prototype filter length: 2*M*m
+    
+    // create separate bank of dotprod and window objects
+    DOTPROD() * dp;     // dot product object array
+
+    // inverse FFT plan
+    FFT_PLAN ifft;      // inverse FFT object
+    TO * X;             // IFFT input array  [size: M x 1]
+    TO * x;             // IFFT output array [size: M x 1]
+
+    // common data structures shared between analysis and
+    // synthesis algorithms
+    WINDOW() * w0;      // window buffer object array
+    WINDOW() * w1;      // window buffer object array (synthesizer only)
+    int flag;           // flag indicating filter/buffer alignment
+};
+
+// create firpfbch2 object
+//  _type   :   channelizer type (e.g. LIQUID_ANALYZER)
+//  _M      :   number of channels (must be even)
+//  _m      :   prototype filter semi-lenth, length=2*M*m
+//  _h      :   prototype filter coefficient array
+//  _h_len  :   number of coefficients
+FIRPFBCH2() FIRPFBCH2(_create)(int          _type,
+                               unsigned int _M,
+                               unsigned int _m,
+                               TC *         _h)
+{
+    // validate input
+    if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) {
+        fprintf(stderr,"error: firpfbch2_%s_create(), invalid type %d\n", EXTENSION_FULL, _type);
+        exit(1);
+    } else if (_M < 2 || _M % 2) {
+        fprintf(stderr,"error: firpfbch2_%s_create(), number of channels must be greater than 2 and even\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: firpfbch2_%s_create(), filter semi-length must be at least 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // create object
+    FIRPFBCH2() q = (FIRPFBCH2()) malloc(sizeof(struct FIRPFBCH2(_s)));
+
+    // set input parameters
+    q->type     = _type;        // channelizer type (e.g. LIQUID_ANALYZER)
+    q->M        = _M;           // number of channels
+    q->m        = _m;           // prototype filter semi-length
+
+    // compute derived values
+    q->h_len    = 2*q->M*q->m;  // prototype filter length
+    q->M2       = q->M / 2;     // number of channels / 2
+
+    // generate bank of sub-samped filters
+    q->dp = (DOTPROD()*) malloc((q->M)*sizeof(DOTPROD()));
+    unsigned int i;
+    unsigned int n;
+    unsigned int h_sub_len = 2 * q->m;
+    TC h_sub[h_sub_len];
+    for (i=0; i<q->M; i++) {
+        // sub-sample prototype filter, loading coefficients
+        // in reverse order
+        for (n=0; n<h_sub_len; n++)
+            h_sub[h_sub_len-n-1] = _h[i + n*(q->M)];
+
+        // create dotprod object
+        q->dp[i] = DOTPROD(_create)(h_sub,h_sub_len);
+    }
+
+    // create FFT plan (inverse transform)
+    // TODO : use fftw_malloc if HAVE_FFTW3_H
+    q->X = (T*) malloc((q->M)*sizeof(T));   // IFFT input
+    q->x = (T*) malloc((q->M)*sizeof(T));   // IFFT output
+    q->ifft = FFT_CREATE_PLAN(q->M, q->X, q->x, FFT_DIR_BACKWARD, FFT_METHOD);
+
+    // create buffer objects
+    q->w0 = (WINDOW()*) malloc((q->M)*sizeof(WINDOW()));
+    q->w1 = (WINDOW()*) malloc((q->M)*sizeof(WINDOW()));
+    for (i=0; i<q->M; i++) {
+        q->w0[i] = WINDOW(_create)(h_sub_len);
+        q->w1[i] = WINDOW(_create)(h_sub_len);
+    }
+
+    // reset filterbank object and return
+    FIRPFBCH2(_reset)(q);
+    return q;
+}
+
+// create firpfbch2 object using Kaiser window prototype
+//  _type   :   channelizer type (e.g. LIQUID_ANALYZER)
+//  _M      :   number of channels (must be even)
+//  _m      :   prototype filter semi-lenth, length=2*M*m+1
+//  _As     :   filter stop-band attenuation [dB]
+FIRPFBCH2() FIRPFBCH2(_create_kaiser)(int          _type,
+                                      unsigned int _M,
+                                      unsigned int _m,
+                                      float        _As)
+{
+    // validate input
+    if (_type != LIQUID_ANALYZER && _type != LIQUID_SYNTHESIZER) {
+        fprintf(stderr,"error: firpfbch2_%s_create_kaiser(), invalid type %d\n", EXTENSION_FULL, _type);
+        exit(1);
+    } else if (_M < 2 || _M % 2) {
+        fprintf(stderr,"error: firpfbch2_%s_create_kaiser(), number of channels must be greater than 2 and even\n", EXTENSION_FULL);
+        exit(1);
+    } else if (_m < 1) {
+        fprintf(stderr,"error: firpfbch2_%s_create_kaiser(), filter semi-length must be at least 1\n", EXTENSION_FULL);
+        exit(1);
+    }
+
+    // design prototype filter
+    unsigned int h_len = 2*_M*_m+1;
+    float * hf = (float*)malloc(h_len*sizeof(float));
+
+    // filter cut-off frequency (analyzer has twice the
+    // bandwidth of the synthesizer)
+    float fc = (_type == LIQUID_ANALYZER) ? 1.0f/(float)_M : 0.5f/(float)_M;
+
+    // compute filter coefficients (floating point precision)
+    liquid_firdes_kaiser(h_len, fc, _As, 0.0f, hf);
+
+    // normalize to unit average and scale by number of channels
+    float hf_sum = 0.0f;
+    unsigned int i;
+    for (i=0; i<h_len; i++) hf_sum += hf[i];
+    for (i=0; i<h_len; i++) hf[i] = hf[i] * (float)_M / hf_sum;
+
+    // convert to type-specific array
+    TC * h = (TC*) malloc(h_len * sizeof(TC));
+    for (i=0; i<h_len; i++)
+        h[i] = (TC) hf[i];
+
+    // create filterbank channelizer object
+    FIRPFBCH2() q = FIRPFBCH2(_create)(_type, _M, _m, h);
+
+    // free prototype filter coefficients
+    free(hf);
+    free(h);
+
+    // return object
+    return q;
+}
+
+// destroy firpfbch2 object, freeing internal memory
+void FIRPFBCH2(_destroy)(FIRPFBCH2() _q)
+{
+    unsigned int i;
+
+    // free dotprod objects
+    for (i=0; i<_q->M; i++)
+        DOTPROD(_destroy)(_q->dp[i]);
+    free(_q->dp);
+
+    // free transform object and arrays
+    FFT_DESTROY_PLAN(_q->ifft);
+    free(_q->X);
+    free(_q->x);
+    
+    // free window objects (buffers)
+    for (i=0; i<_q->M; i++) {
+        WINDOW(_destroy)(_q->w0[i]);
+        WINDOW(_destroy)(_q->w1[i]);
+    }
+    free(_q->w0);
+    free(_q->w1);
+
+    // free main object memory
+    free(_q);
+}
+
+// reset firpfbch2 object internals
+void FIRPFBCH2(_reset)(FIRPFBCH2() _q)
+{
+    unsigned int i;
+
+    // clear window buffers
+    for (i=0; i<_q->M; i++) {
+        WINDOW(_clear)(_q->w0[i]);
+        WINDOW(_clear)(_q->w1[i]);
+    }
+
+    // reset filter/buffer alignment flag
+    _q->flag = 0;
+}
+
+// print firpfbch2 object internals
+void FIRPFBCH2(_print)(FIRPFBCH2() _q)
+{
+    printf("firpfbch2_%s:\n", EXTENSION_FULL);
+    printf("    channels    :   %u\n", _q->M);
+    printf("    h_len       :   %u\n", _q->h_len);
+    printf("    semi-length :   %u\n", _q->m);
+
+    // TODO: print filter coefficients...
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        DOTPROD(_print)(_q->dp[i]);
+}
+
+// execute filterbank channelizer (analyzer)
+//  _x      :   channelizer input,  [size: M/2 x 1]
+//  _y      :   channelizer output, [size: M   x 1]
+void FIRPFBCH2(_execute_analyzer)(FIRPFBCH2() _q,
+                                  TI *        _x,
+                                  TO *        _y)
+{
+    unsigned int i;
+
+    // load buffers in blocks of num_channels/2 starting
+    // in the middle of the filter bank and moving in the
+    // negative direction
+    unsigned int base_index = _q->flag ? _q->M : _q->M2;
+    for (i=0; i<_q->M2; i++) {
+        // push sample into buffer at filter index
+        WINDOW(_push)(_q->w0[base_index-i-1], _x[i]);
+    }
+
+    // execute filter outputs
+    unsigned int offset = _q->flag ? _q->M2 : 0;
+    TI * r;      // buffer read pointer
+    for (i=0; i<_q->M; i++) {
+        // compute buffer index
+        unsigned int buffer_index  = (offset+i)%(_q->M);
+
+        // read buffer at index
+        WINDOW(_read)(_q->w0[buffer_index], &r);
+
+        // run dot product storing result in IFFT input buffer
+        DOTPROD(_execute)(_q->dp[i], r, &_q->X[buffer_index]);
+    }
+
+    // execute IFFT, store result in buffer 'x'
+    FFT_EXECUTE(_q->ifft);
+
+    // scale result by 1/num_channels (C transform)
+    for (i=0; i<_q->M; i++)
+        _y[i] = _q->x[i] / (float)(_q->M);
+
+    // update flag
+    _q->flag = 1 - _q->flag;
+}
+
+// execute filterbank channelizer (synthesizer)
+//  _x      :   channelizer input,  [size: M   x 1]
+//  _y      :   channelizer output, [size: M/2 x 1]
+void FIRPFBCH2(_execute_synthesizer)(FIRPFBCH2() _q,
+                                     TI *        _x,
+                                     TO *        _y)
+{
+    unsigned int i;
+
+    // copy input array to internal IFFT input buffer
+    memmove(_q->X, _x, _q->M * sizeof(TI));
+
+    // execute IFFT, store result in buffer 'x'
+    FFT_EXECUTE(_q->ifft);
+
+    // TODO: ignore this scaling
+    // scale result by 1/num_channels (C transform)
+    for (i=0; i<_q->M; i++)
+        _q->x[i] *= 1.0f / (float)(_q->M);
+    // scale result by num_channels/2
+    for (i=0; i<_q->M; i++)
+        _q->x[i] *= (float)(_q->M2);
+
+    // push samples into appropriate buffer
+    WINDOW() * buffer = (_q->flag == 0 ? _q->w1 : _q->w0);
+    for (i=0; i<_q->M; i++)
+        WINDOW(_push)(buffer[i], _q->x[i]);
+
+    // compute filter outputs
+    TO * r0, * r1;  // buffer read pointers
+    TO   y0,   y1;  // dotprod outputs
+    for (i=0; i<_q->M2; i++) {
+        // buffer index
+        unsigned int b = (_q->flag == 0) ? i : i+_q->M2;
+
+        // read buffer with index offset
+        WINDOW(_read)(_q->w0[b], &r0);
+        WINDOW(_read)(_q->w1[b], &r1);
+
+        // swap buffer outputs on alternating runs
+        TO * p0 = _q->flag ? r0 : r1;
+        TO * p1 = _q->flag ? r1 : r0;
+
+        // run dot products
+        DOTPROD(_execute)(_q->dp[i],        p0, &y0);
+        DOTPROD(_execute)(_q->dp[i+_q->M2], p1, &y1);
+
+        // save output
+        _y[i] = y0 + y1;
+    }
+    _q->flag = 1 - _q->flag;
+}
+
+// execute filterbank channelizer
+// LIQUID_ANALYZER:     input: M/2, output: M
+// LIQUID_SYNTHESIZER:  input: M,   output: M/2
+//  _x      :   channelizer input
+//  _y      :   channelizer output
+void FIRPFBCH2(_execute)(FIRPFBCH2() _q,
+                         TI *        _x,
+                         TO *        _y)
+{
+    switch (_q->type) {
+    case LIQUID_ANALYZER:
+        FIRPFBCH2(_execute_analyzer)(_q, _x, _y);
+        return;
+    case LIQUID_SYNTHESIZER:
+        FIRPFBCH2(_execute_synthesizer)(_q, _x, _y);
+        return;
+    default:
+        fprintf(stderr,"error: firpfbch2_%s_execute(), invalid type\n", EXTENSION_FULL);
+        exit(1);
+    }
+}
+
diff --git a/src/multichannel/src/firpfbch_cccf.c b/src/multichannel/src/firpfbch_cccf.c
new file mode 100644
index 0000000..d0f18ae
--- /dev/null
+++ b/src/multichannel/src/firpfbch_cccf.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// multichannel API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "cccf"
+
+// 
+#define FIRPFBCH(name)      LIQUID_CONCAT(firpfbch_cccf,name)
+
+#define T                   float complex   // general
+#define TO                  float complex   // output
+#define TC                  float complex   // coefficients
+#define TI                  float complex   // input
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_cccf,name)
+
+#define TO_COMPLEX          1
+#define TC_COMPLEX          1
+#define TI_COMPLEX          1
+
+#define PRINTVAL_TO(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// source files
+#include "firpfbch.c"
+
diff --git a/src/multichannel/src/firpfbch_crcf.c b/src/multichannel/src/firpfbch_crcf.c
new file mode 100644
index 0000000..74c8e0e
--- /dev/null
+++ b/src/multichannel/src/firpfbch_crcf.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// multichannel API: complex floating-point
+//
+
+#include "liquid.internal.h"
+
+// naming extensions (useful for print statements)
+#define EXTENSION_SHORT     "f"
+#define EXTENSION_FULL      "crcf"
+
+// 
+#define FIRPFBCH(name)      LIQUID_CONCAT(firpfbch_crcf,name)
+#define FIRPFBCH2(name)     LIQUID_CONCAT(firpfbch2_crcf,name)
+
+#define T                   float complex   // general
+#define TO                  float complex   // output
+#define TC                  float           // coefficients
+#define TI                  float complex   // input
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_crcf,name)
+
+#define TO_COMPLEX          1
+#define TC_COMPLEX          0
+#define TI_COMPLEX          1
+
+#define PRINTVAL_TO(X,F)    PRINTVAL_CFLOAT(X,F)
+#define PRINTVAL_TC(X,F)    PRINTVAL_FLOAT(X,F)
+#define PRINTVAL_TI(X,F)    PRINTVAL_CFLOAT(X,F)
+
+// source files
+#include "firpfbch.c"       // maximally-decimated polyphase filterbank
+#include "firpfbch2.c"      // polyphase filterbank w/ output rate 2 Fs / M
+
diff --git a/src/multichannel/src/firpfbch_old.c b/src/multichannel/src/firpfbch_old.c
new file mode 100644
index 0000000..c62326e
--- /dev/null
+++ b/src/multichannel/src/firpfbch_old.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_FIRPFBCH_PRINT    0
+
+#define FIRFILT(name)       LIQUID_CONCAT(firfilt_crcf,name)
+#define DOTPROD(name)       LIQUID_CONCAT(dotprod_crcf,name)
+#define WINDOW(name)        LIQUID_CONCAT(windowcf,name)
+
+struct firpfbch_s {
+    unsigned int num_channels;
+    unsigned int m;
+    float beta;
+    float dt;
+    float complex * x;  // time-domain buffer
+    float complex * X;  // freq-domain buffer
+
+    // run|state buffers
+    float complex * X_prime;    // freq-domain buffer (analysis
+                                // filter bank)
+
+    // filter
+    unsigned int h_len;
+    float * h;
+    unsigned int filter_index;
+    
+    // create separate bank of dotprod and window objects
+    DOTPROD() * dp;
+    WINDOW() * w;
+
+    // fft plan
+    FFT_PLAN fft;
+
+    int nyquist;    // nyquist/root-nyquist
+    //int type;       // synthesis/analysis
+};
+
+firpfbch firpfbch_create(unsigned int _num_channels,
+                         unsigned int _m,
+                         float _beta,
+                         float _dt,
+                         int _nyquist,
+                         int _gradient)
+{
+    firpfbch c = (firpfbch) malloc(sizeof(struct firpfbch_s));
+    c->num_channels = _num_channels;
+    c->m            = _m;
+    c->beta         = _beta;
+    c->dt           = _dt;
+    c->nyquist      = _nyquist;
+
+    // validate inputs
+    if (_m < 1) {
+        printf("error: firpfbch_create(), invalid filter delay (must be greater than 0)\n");
+        exit(1);
+    }
+
+    // create bank of filters
+    c->dp   = (DOTPROD()*) malloc((c->num_channels)*sizeof(DOTPROD()));
+    c->w    = (WINDOW()*) malloc((c->num_channels)*sizeof(WINDOW()));
+
+    // design filter
+    // TODO: use filter prototype object
+    c->h_len = 2*(c->m)*(c->num_channels);
+    c->h = (float*) malloc((c->h_len+1)*sizeof(float));
+
+    if (c->nyquist == FIRPFBCH_NYQUIST) {
+        float fc = 0.5f/(float)(c->num_channels);  // cutoff frequency
+        liquid_firdes_kaiser(c->h_len+1, fc, c->beta, 0.0f, c->h);
+    } else if (c->nyquist == FIRPFBCH_ROOTNYQUIST) {
+        design_rkaiser_filter(c->num_channels, c->m, c->beta, c->dt, c->h);
+    } else {
+        printf("error: firpfbch_create(), unsupported nyquist flag: %d\n", _nyquist);
+        exit(1);
+    }
+    
+    unsigned int i;
+    if (_gradient) {
+        float dh[c->h_len];
+        for (i=0; i<c->h_len; i++) {
+            if (i==0) {
+                dh[i] = c->h[i+1] - c->h[c->h_len-1];
+            } else if (i==c->h_len-1) {
+                dh[i] = c->h[0]   - c->h[i-1];
+            } else {
+                dh[i] = c->h[i+1] - c->h[i-1];
+            }
+        }
+        memmove(c->h, dh, (c->h_len)*sizeof(float));
+    }
+
+    // generate bank of sub-samped filters
+    unsigned int n;
+    unsigned int h_sub_len = 2*(c->m);  // length of each sub-sampled filter
+    float h_sub[h_sub_len];
+    for (i=0; i<c->num_channels; i++) {
+        // sub-sample prototype filter, loading coefficients in reverse order
+        for (n=0; n<h_sub_len; n++) {
+            h_sub[h_sub_len-n-1] = c->h[i + n*(c->num_channels)];
+        }
+        // create window buffer and dotprod object (coefficients
+        // loaded in reverse order)
+        c->dp[i] = DOTPROD(_create)(h_sub,h_sub_len);
+        c->w[i]  = WINDOW(_create)(h_sub_len);
+
+#if DEBUG_FIRPFBCH_PRINT
+        printf("h_sub[%u] :\n", i);
+        for (n=0; n<h_sub_len; n++)
+            printf("  h[%3u] = %8.4f\n", n, h_sub[n]);
+#endif
+    }
+
+#if DEBUG_FIRPFBCH_PRINT
+    for (i=0; i<c->h_len+1; i++)
+        printf("h(%4u) = %12.4e;\n", i+1, c->h[i]);
+#endif
+
+    // allocate memory for buffers
+    // TODO : use fftw_malloc if HAVE_FFTW3_H
+    c->x = (float complex*) malloc((c->num_channels)*sizeof(float complex));
+    c->X = (float complex*) malloc((c->num_channels)*sizeof(float complex));
+    c->X_prime = (float complex*) malloc((c->num_channels)*sizeof(float complex));
+    firpfbch_clear(c);
+
+    // create fft plan
+    c->fft = FFT_CREATE_PLAN(c->num_channels, c->X, c->x, FFT_DIR_BACKWARD, FFT_METHOD);
+
+    return c;
+}
+
+void firpfbch_destroy(firpfbch _c)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_channels; i++) {
+        DOTPROD(_destroy)(_c->dp[i]);
+        WINDOW(_destroy)(_c->w[i]);
+    }
+    free(_c->dp);
+    free(_c->w);
+
+    FFT_DESTROY_PLAN(_c->fft);
+    free(_c->h);
+    free(_c->x);
+    free(_c->X);
+    free(_c->X_prime);
+    free(_c);
+}
+
+void firpfbch_clear(firpfbch _c)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_channels; i++) {
+        WINDOW(_clear)(_c->w[i]);
+        _c->x[i] = 0;
+        _c->X[i] = 0;
+        _c->X_prime[i] = 0;
+    }
+    _c->filter_index = 0;
+}
+
+void firpfbch_print(firpfbch _c)
+{
+    printf("firpfbch: [%u channels]\n", _c->num_channels);
+    unsigned int i;
+    for (i=0; i<_c->num_channels; i++) {
+        DOTPROD(_print)(_c->dp[i]);
+    }
+}
+
+void firpfbch_get_filter_taps(firpfbch _c, 
+                              float * _h)
+{
+    memmove(_h, _c->h, (_c->h_len)*sizeof(float));
+}
+
+// 
+// SYNTHESIZER
+//
+
+void firpfbch_synthesizer_execute(firpfbch _c, float complex * _x, float complex * _y)
+{
+    unsigned int i;
+
+    // copy samples into ifft input buffer _c->X
+    memmove(_c->X, _x, (_c->num_channels)*sizeof(float complex));
+
+    // execute inverse fft, store in buffer _c->x
+    FFT_EXECUTE(_c->fft);
+
+    // push samples into filter bank and execute, putting
+    // samples into output buffer _y
+    float complex * r;
+    for (i=0; i<_c->num_channels; i++) {
+        WINDOW(_push)(_c->w[i], _c->x[i]);
+        WINDOW(_read)(_c->w[i], &r);
+        DOTPROD(_execute)(_c->dp[i], r, &(_y[i]));
+
+        // invoke scaling factor
+        _y[i] /= (float)(_c->num_channels);
+    }
+}
+
+// 
+// ANALYZER
+//
+
+void firpfbch_analyzer_execute(firpfbch _c, float complex * _x, float complex * _y)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_channels; i++)
+        firpfbch_analyzer_push(_c,_x[i]);
+
+    // run the analysis filters on the given input
+    firpfbch_analyzer_run(_c,_y);
+
+    // save the run state : IDFT input X -> X_prime
+    firpfbch_analyzer_saverunstate(_c);
+}
+
+void firpfbch_analyzer_push(firpfbch _c, float complex _x)
+{
+    // push sample into the buffer at filter_index
+    WINDOW(_push)(_c->w[_c->filter_index], _x);
+
+    // decrement filter index
+    _c->filter_index = (_c->filter_index+_c->num_channels-1) % _c->num_channels;
+}
+
+void firpfbch_analyzer_run(firpfbch _c, float complex * _y)
+{
+    // NOTE: The analyzer is different from the synthesizer in
+    //       that the invocation of the commutator results in a
+    //       delay from the first input sample to the resulting
+    //       partitions.  As a result, the inverse DFT is
+    //       invoked after the first filter is run, after which
+    //       the remaining filters are executed.
+
+    // restore saved IDFT input state X from X_prime
+    memmove(_c->X, _c->X_prime, (_c->num_channels)*sizeof(float complex));
+
+    unsigned int i, b;
+    unsigned int k = _c->filter_index;
+
+    // push first value and compute output
+    float complex * r;
+    WINDOW(_read)(_c->w[k], &r);
+    DOTPROD(_execute)(_c->dp[0], r, &(_c->X[0]));
+
+    // execute inverse fft, store in buffer _c->x
+    FFT_EXECUTE(_c->fft);
+
+    // copy results to output buffer
+    memmove(_y, _c->x, (_c->num_channels)*sizeof(float complex));
+
+    // push remaining samples into filter bank and execute in
+    // *reverse* order, putting result into the inverse DFT
+    // input buffer _c->X
+    //for (i=1; i<_c->num_channels; i++) {
+    // NOTE : the filter window buffers have already been loaded
+    //        in the proper reverse order, so there is no need
+    //        to execute the dot products in any particular order,
+    //        so long as they are aligned with the proper input
+    //        buffer.
+    for (i=1; i<_c->num_channels; i++) {
+        b = (k+i) % _c->num_channels;
+        WINDOW(_read)(_c->w[b], &r);
+        DOTPROD(_execute)(_c->dp[i], r, &(_c->X[i]));
+    }
+}
+
+// save the run state of the filter bank by dumping the
+// IDFT input buffer X into the temporary buffer X_prime
+void firpfbch_analyzer_saverunstate(firpfbch _c)
+{
+    memmove(_c->X_prime, _c->X, (_c->num_channels)*sizeof(float complex));
+}
+
+// clear the run state of the filter bank
+void firpfbch_analyzer_clearrunstate(firpfbch _c)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_channels; i++)
+        _c->X_prime[i] = 0;
+}
diff --git a/src/multichannel/src/ofdmframe.common.c b/src/multichannel/src/ofdmframe.common.c
new file mode 100644
index 0000000..e5a4112
--- /dev/null
+++ b/src/multichannel/src/ofdmframe.common.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// ofdmframe data, methods common to both generator/synchronizer
+// objects
+//  - physical layer convergence procedure (PLCP)
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// generate short sequence symbols
+//  _p      :   subcarrier allocation array
+//  _M      :   total number of subcarriers
+//  _S0     :   output symbol (freq)
+//  _s0     :   output symbol (time)
+//  _M_S0   :   total number of enabled subcarriers in S0
+void ofdmframe_init_S0(unsigned char * _p,
+                       unsigned int    _M,
+                       float complex * _S0,
+                       float complex * _s0,
+                       unsigned int *  _M_S0)
+{
+    unsigned int i;
+
+    // compute m-sequence length
+    unsigned int m = liquid_nextpow2(_M);
+    if (m < 4)      m = 4;
+    else if (m > 8) m = 8;
+
+    // generate m-sequence generator object
+    msequence ms = msequence_create_default(m);
+
+    unsigned int s;
+    unsigned int M_S0 = 0;
+
+    // short sequence
+    for (i=0; i<_M; i++) {
+        // generate symbol
+        //s = msequence_generate_symbol(ms,1);
+        s = msequence_generate_symbol(ms,3) & 0x01;
+
+        if (_p[i] == OFDMFRAME_SCTYPE_NULL) {
+            // NULL subcarrier
+            _S0[i] = 0.0f;
+        } else {
+            if ( (i%2) == 0 ) {
+                // even subcarrer
+                _S0[i] = s ? 1.0f : -1.0f;
+                M_S0++;
+            } else {
+                // odd subcarrer (ignore)
+                _S0[i] = 0.0f;
+            }
+        }
+    }
+
+    // destroy objects
+    msequence_destroy(ms);
+
+    // ensure at least one subcarrier was enabled
+    if (M_S0 == 0) {
+        fprintf(stderr,"error: ofdmframe_init_S0(), no subcarriers enabled; check allocation\n");
+        exit(1);
+    }
+
+    // set return value(s)
+    *_M_S0 = M_S0;
+
+    // run inverse fft to get time-domain sequence
+    fft_run(_M, _S0, _s0, LIQUID_FFT_BACKWARD, 0);
+
+    // normalize time-domain sequence level
+    float g = 1.0f / sqrtf(M_S0);
+    for (i=0; i<_M; i++)
+        _s0[i] *= g;
+}
+
+
+// generate long sequence symbols
+//  _p      :   subcarrier allocation array
+//  _M      :   total number of subcarriers
+//  _S1     :   output symbol (freq)
+//  _s1     :   output symbol (time)
+//  _M_S1   :   total number of enabled subcarriers in S1
+void ofdmframe_init_S1(unsigned char * _p,
+                       unsigned int    _M,
+                       float complex * _S1,
+                       float complex * _s1,
+                       unsigned int *  _M_S1)
+{
+    unsigned int i;
+
+    // compute m-sequence length
+    unsigned int m = liquid_nextpow2(_M);
+    if (m < 4)      m = 4;
+    else if (m > 8) m = 8;
+
+    // increase m such that the resulting S1 sequence will
+    // differ significantly from S0 with the same subcarrier
+    // allocation array
+    m++;
+
+    // generate m-sequence generator object
+    msequence ms = msequence_create_default(m);
+
+    unsigned int s;
+    unsigned int M_S1 = 0;
+
+    // long sequence
+    for (i=0; i<_M; i++) {
+        // generate symbol
+        //s = msequence_generate_symbol(ms,1);
+        s = msequence_generate_symbol(ms,3) & 0x01;
+
+        if (_p[i] == OFDMFRAME_SCTYPE_NULL) {
+            // NULL subcarrier
+            _S1[i] = 0.0f;
+        } else {
+            _S1[i] = s ? 1.0f : -1.0f;
+            M_S1++;
+        }
+    }
+
+    // destroy objects
+    msequence_destroy(ms);
+
+    // ensure at least one subcarrier was enabled
+    if (M_S1 == 0) {
+        fprintf(stderr,"error: ofdmframe_init_S1(), no subcarriers enabled; check allocation\n");
+        exit(1);
+    }
+
+    // set return value(s)
+    *_M_S1 = M_S1;
+
+    // run inverse fft to get time-domain sequence
+    fft_run(_M, _S1, _s1, LIQUID_FFT_BACKWARD, 0);
+
+    // normalize time-domain sequence level
+    float g = 1.0f / sqrtf(M_S1);
+    for (i=0; i<_M; i++)
+        _s1[i] *= g;
+}
+
+// initialize default subcarrier allocation
+//  _M      :   number of subcarriers
+//  _p      :   output subcarrier allocation array, [size: _M x 1]
+//
+// key: '.' (null), 'P' (pilot), '+' (data)
+// .+++P+++++++P.........P+++++++P+++
+//
+void ofdmframe_init_default_sctype(unsigned int _M,
+                                   unsigned char * _p)
+{
+    // validate input
+    if (_M < 6) {
+        fprintf(stderr,"warning: ofdmframe_init_default_sctype(), less than 4 subcarriers\n");
+    }
+
+    unsigned int i;
+    unsigned int M2 = _M/2;
+
+    // compute guard band
+    unsigned int G = _M / 10;
+    if (G < 2) G = 2;
+
+    // designate pilot spacing
+    unsigned int P = (_M > 34) ? 8 : 4;
+    unsigned int P2 = P/2;
+
+    // initialize as NULL
+    for (i=0; i<_M; i++)
+        _p[i] = OFDMFRAME_SCTYPE_NULL;
+
+    // upper band
+    for (i=1; i<M2-G; i++) {
+        if ( ((i+P2)%P) == 0 )
+            _p[i] = OFDMFRAME_SCTYPE_PILOT;
+        else
+            _p[i] = OFDMFRAME_SCTYPE_DATA;
+    }
+
+    // lower band
+    for (i=1; i<M2-G; i++) {
+        unsigned int k = _M - i;
+        if ( ((i+P2)%P) == 0 )
+            _p[k] = OFDMFRAME_SCTYPE_PILOT;
+        else
+            _p[k] = OFDMFRAME_SCTYPE_DATA;
+    }
+}
+
+// validate subcarrier type (count number of null, pilot, and data
+// subcarriers in the allocation)
+//  _p          :   subcarrier allocation array, [size: _M x 1]
+//  _M          :   number of subcarriers
+//  _M_null     :   output number of null subcarriers
+//  _M_pilot    :   output number of pilot subcarriers
+//  _M_data     :   output number of data subcarriers
+void ofdmframe_validate_sctype(unsigned char * _p,
+                               unsigned int _M,
+                               unsigned int * _M_null,
+                               unsigned int * _M_pilot,
+                               unsigned int * _M_data)
+{
+    // clear counters
+    unsigned int M_null  = 0;
+    unsigned int M_pilot = 0;
+    unsigned int M_data  = 0;
+
+    unsigned int i;
+    for (i=0; i<_M; i++) {
+        // update appropriate counters
+        if (_p[i] == OFDMFRAME_SCTYPE_NULL)
+            M_null++;
+        else if (_p[i] == OFDMFRAME_SCTYPE_PILOT)
+            M_pilot++;
+        else if (_p[i] == OFDMFRAME_SCTYPE_DATA)
+            M_data++;
+        else {
+            fprintf(stderr,"error: ofdmframe_validate_sctype(), invalid subcarrier type (%u)\n", _p[i]);
+            exit(1);
+        }
+    }
+
+    // set outputs
+    *_M_null  = M_null;
+    *_M_pilot = M_pilot;
+    *_M_data  = M_data;
+}
+
+// print subcarrier allocation to screen
+//
+// key: '.' (null), 'P' (pilot), '+' (data)
+// .+++P+++++++P.........P+++++++P+++
+//
+void ofdmframe_print_sctype(unsigned char * _p,
+                            unsigned int    _M)
+{
+    unsigned int i;
+
+    printf("[");
+    for (i=0; i<_M; i++) {
+        unsigned int k = (i + _M/2) % _M;
+
+        switch (_p[k]) {
+        case OFDMFRAME_SCTYPE_NULL:     printf(".");    break;
+        case OFDMFRAME_SCTYPE_PILOT:    printf("|");    break;
+        case OFDMFRAME_SCTYPE_DATA:     printf("+");    break;
+        default:
+            fprintf(stderr,"error: ofdmframe_print_default_sctype(), invalid subcarrier type\n");
+            exit(1);
+        }
+    }
+
+    printf("]\n");
+}
+
diff --git a/src/multichannel/src/ofdmframegen.c b/src/multichannel/src/ofdmframegen.c
new file mode 100644
index 0000000..db2d99c
--- /dev/null
+++ b/src/multichannel/src/ofdmframegen.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// ofdmframegen.c
+//
+// OFDM frame generator
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#if HAVE_FFTW3_H
+#   include <fftw3.h>
+#endif
+
+#define DEBUG_OFDMFRAMEGEN            1
+
+struct ofdmframegen_s {
+    unsigned int M;         // number of subcarriers
+    unsigned int cp_len;    // cyclic prefix length
+    unsigned char * p;      // subcarrier allocation (null, pilot, data)
+
+    // tapering/trasition
+    unsigned int taper_len; // number of samples in tapering window/overlap
+    float * taper;          // tapering window
+    float complex *postfix; // overlapping symbol buffer
+
+    // constants
+    unsigned int M_null;    // number of null subcarriers
+    unsigned int M_pilot;   // number of pilot subcarriers
+    unsigned int M_data;    // number of data subcarriers
+    unsigned int M_S0;      // number of enabled subcarriers in S0
+    unsigned int M_S1;      // number of enabled subcarriers in S1
+
+    // scaling factors
+    float g_data;           //
+
+    // transform object
+    FFT_PLAN ifft;          // ifft object
+    float complex * X;      // frequency-domain buffer
+    float complex * x;      // time-domain buffer
+
+    // PLCP short
+    float complex * S0;     // short sequence (frequency)
+    float complex * s0;     // short sequence (time)
+
+    // PLCP long
+    float complex * S1;     // long sequence (frequency)
+    float complex * s1;     // long sequence (time)
+
+    // pilot sequence
+    msequence ms_pilot;
+};
+
+// create OFDM framing generator object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+ofdmframegen ofdmframegen_create(unsigned int    _M,
+                                 unsigned int    _cp_len,
+                                 unsigned int    _taper_len,
+                                 unsigned char * _p)
+{
+    // validate input
+    if (_M < 2) {
+        fprintf(stderr,"error: ofdmframegen_create(), number of subcarriers must be at least 2\n");
+        exit(1);
+    } else if (_M % 2) {
+        fprintf(stderr,"error: ofdmframegen_create(), number of subcarriers must be even\n");
+        exit(1);
+    } else if (_cp_len > _M) {
+        fprintf(stderr,"error: ofdmframegen_create(), cyclic prefix cannot exceed symbol length\n");
+        exit(1);
+    } else if (_taper_len > _cp_len) {
+        fprintf(stderr,"error: ofdmframegen_create(), taper length cannot exceed cyclic prefix\n");
+        exit(1);
+    }
+
+    ofdmframegen q = (ofdmframegen) malloc(sizeof(struct ofdmframegen_s));
+    q->M         = _M;
+    q->cp_len    = _cp_len;
+    q->taper_len = _taper_len;
+
+    // allocate memory for subcarrier allocation IDs
+    q->p = (unsigned char*) malloc((q->M)*sizeof(unsigned char));
+    if (_p == NULL) {
+        // initialize default subcarrier allocation
+        ofdmframe_init_default_sctype(q->M, q->p);
+    } else {
+        // copy user-defined subcarrier allocation
+        memmove(q->p, _p, q->M*sizeof(unsigned char));
+    }
+
+    // validate and count subcarrier allocation
+    ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data);
+    if ( (q->M_pilot + q->M_data) == 0) {
+        fprintf(stderr,"error: ofdmframegen_create(), must have at least one enabled subcarrier\n");
+        exit(1);
+    } else if (q->M_data == 0) {
+        fprintf(stderr,"error: ofdmframegen_create(), must have at least one data subcarriers\n");
+        exit(1);
+    } else if (q->M_pilot < 2) {
+        fprintf(stderr,"error: ofdmframegen_create(), must have at least two pilot subcarriers\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // allocate memory for transform objects
+    q->X = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->x = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->ifft = FFT_CREATE_PLAN(q->M, q->X, q->x, FFT_DIR_BACKWARD, FFT_METHOD);
+
+    // allocate memory for PLCP arrays
+    q->S0 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->s0 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->S1 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->s1 = (float complex*) malloc((q->M)*sizeof(float complex));
+    ofdmframe_init_S0(q->p, q->M, q->S0, q->s0, &q->M_S0);
+    ofdmframe_init_S1(q->p, q->M, q->S1, q->s1, &q->M_S1);
+
+    // create tapering window and transition buffer
+    q->taper   = (float*)         malloc(q->taper_len * sizeof(float));
+    q->postfix = (float complex*) malloc(q->taper_len * sizeof(float complex));
+    for (i=0; i<q->taper_len; i++) {
+        float t = ((float)i + 0.5f) / (float)(q->taper_len);
+        float g = sinf(M_PI_2*t);
+        q->taper[i] = g*g;
+    }
+#if 0
+    // validate window symmetry
+    for (i=0; i<q->taper_len; i++) {
+        printf("    taper[%2u] = %12.8f (%12.8f)\n", i, q->taper[i],
+            q->taper[i] + q->taper[q->taper_len - i - 1]);
+    }
+#endif
+
+    // compute scaling factor
+    q->g_data = 1.0f / sqrtf(q->M_pilot + q->M_data);
+
+    // set pilot sequence
+    q->ms_pilot = msequence_create_default(8);
+
+    return q;
+}
+
+// free transform object memory
+void ofdmframegen_destroy(ofdmframegen _q)
+{
+    // free subcarrier type array memory
+    free(_q->p);
+
+    // free transform array memory
+    free(_q->X);
+    free(_q->x);
+    FFT_DESTROY_PLAN(_q->ifft);
+
+    // free tapering window and transition buffer
+    free(_q->taper);
+    free(_q->postfix);
+
+    // free PLCP memory arrays
+    free(_q->S0);
+    free(_q->s0);
+    free(_q->S1);
+    free(_q->s1);
+
+    // free pilot msequence object memory
+    msequence_destroy(_q->ms_pilot);
+
+    // free main object memory
+    free(_q);
+}
+
+void ofdmframegen_print(ofdmframegen _q)
+{
+    printf("ofdmframegen:\n");
+    printf("    num subcarriers     :   %-u\n", _q->M);
+    printf("      - NULL            :   %-u\n", _q->M_null);
+    printf("      - pilot           :   %-u\n", _q->M_pilot);
+    printf("      - data            :   %-u\n", _q->M_data);
+    printf("    cyclic prefix len   :   %-u\n", _q->cp_len);
+    printf("    taper len           :   %-u\n", _q->taper_len);
+    printf("    ");
+    ofdmframe_print_sctype(_q->p, _q->M);
+}
+
+void ofdmframegen_reset(ofdmframegen _q)
+{
+    msequence_reset(_q->ms_pilot);
+
+    // clear internal postfix buffer
+    unsigned int i;
+    for (i=0; i<_q->taper_len; i++)
+        _q->postfix[i] = 0.0f;
+}
+
+// write first PLCP short sequence 'symbol' to buffer
+//
+//  |<- 2*cp->|<-       M       ->|<-       M       ->|
+//  |         |                   |                   |
+//      +-----+-------------------+-------------------+
+//     /      |                   |                   |
+//    /  ..s0 |        s0         |        s0         |
+//   /        |                   |                   |
+//  +---------+-------------------+-------------------+-----> time
+//  |                        |                        |
+//  |<-        s0[a]       ->|<-        s0[b]       ->|
+//  |        M + cp_len      |        M + cp_len      |
+//
+void ofdmframegen_write_S0a(ofdmframegen    _q,
+                            float complex * _y)
+{
+    unsigned int i;
+    unsigned int k;
+    for (i=0; i<_q->M + _q->cp_len; i++) {
+        k = (i + _q->M - 2*_q->cp_len) % _q->M;
+        _y[i] = _q->s0[k];
+    }
+
+    // apply tapering window
+    for (i=0; i<_q->taper_len; i++)
+        _y[i] *= _q->taper[i];
+}
+
+void ofdmframegen_write_S0b(ofdmframegen _q,
+                            float complex * _y)
+{
+    unsigned int i;
+    unsigned int k;
+    for (i=0; i<_q->M + _q->cp_len; i++) {
+        k = (i + _q->M - _q->cp_len) % _q->M;
+        _y[i] = _q->s0[k];
+    }
+
+    // copy postfix (first 'taper_len' samples of s0 symbol)
+    memmove(_q->postfix, _q->s0, _q->taper_len*sizeof(float complex));
+}
+
+void ofdmframegen_write_S1(ofdmframegen _q,
+                           float complex * _y)
+{
+    // copy S1 symbol to output, adding cyclic prefix and tapering window
+    memmove(_q->x, _q->s1, (_q->M)*sizeof(float complex));
+    ofdmframegen_gensymbol(_q, _y);
+}
+
+
+// write OFDM symbol
+//  _q      :   framing generator object
+//  _x      :   input symbols, [size: _M x 1]
+//  _y      :   output samples, [size: _M x 1]
+void ofdmframegen_writesymbol(ofdmframegen    _q,
+                              float complex * _x,
+                              float complex * _y)
+{
+    // move frequency data to internal buffer
+    unsigned int i;
+    unsigned int k;
+    int sctype;
+    for (i=0; i<_q->M; i++) {
+        // start at mid-point (effective fftshift)
+        k = (i + _q->M/2) % _q->M;
+
+        sctype = _q->p[k];
+        if (sctype==OFDMFRAME_SCTYPE_NULL) {
+            // disabled subcarrier
+            _q->X[k] = 0.0f;
+        } else if (sctype==OFDMFRAME_SCTYPE_PILOT) {
+            // pilot subcarrier
+            _q->X[k] = (msequence_advance(_q->ms_pilot) ? 1.0f : -1.0f) * _q->g_data;
+        } else {
+            // data subcarrier
+            _q->X[k] = _x[k] * _q->g_data;
+        }
+
+        //printf("X[%3u] = %12.8f + j*%12.8f;\n",i+1,crealf(_q->X[i]),cimagf(_q->X[i]));
+    }
+
+    // execute transform
+    FFT_EXECUTE(_q->ifft);
+
+    // copy result to output, adding cyclic prefix and tapering window
+    ofdmframegen_gensymbol(_q, _y);
+}
+
+// write tail to output
+void ofdmframegen_writetail(ofdmframegen    _q,
+                            float complex * _buffer)
+{
+    // write tail to output, applying tapering window
+    unsigned int i;
+    for (i=0; i<_q->taper_len; i++)
+        _buffer[i] = _q->postfix[i] * _q->taper[_q->taper_len-i-1];
+}
+
+// 
+// internal methods
+//
+
+// generate symbol (add cyclic prefix/postfix, overlap)
+//
+//  ->|   |<- taper_len
+//    +   +-----+-------------------+
+//     \ /      |                   |
+//      X       |      symbol       |
+//     / \      |                   |
+//    +---+-----+-------------------+----> time
+//    |         |                   |
+//    |<- cp  ->|<-       M       ->|
+//
+//  _q->x           :   input time-domain symbol [size: _q->M x 1]
+//  _q->postfix     :   input:  post-fix from previous symbol [size: _q->taper_len x 1]
+//                      output: post-fix from this new symbol
+//  _q->taper       :   tapering window
+//  _q->taper_len   :   tapering window length
+//
+//  _buffer         :   output sample buffer [size: (_q->M + _q->cp_len) x 1]
+void ofdmframegen_gensymbol(ofdmframegen    _q,
+                            float complex * _buffer)
+{
+    // copy input symbol with cyclic prefix to output symbol
+    memmove( &_buffer[0],          &_q->x[_q->M-_q->cp_len], _q->cp_len*sizeof(float complex));
+    memmove( &_buffer[_q->cp_len], &_q->x[               0], _q->M    * sizeof(float complex));
+    
+    // apply tapering window to over-lapping regions
+    unsigned int i;
+    for (i=0; i<_q->taper_len; i++) {
+        _buffer[i] *= _q->taper[i];
+        _buffer[i] += _q->postfix[i] * _q->taper[_q->taper_len-i-1];
+    }
+
+    // copy post-fix to output (first 'taper_len' samples of input symbol)
+    memmove(_q->postfix, _q->x, _q->taper_len*sizeof(float complex));
+}
+
diff --git a/src/multichannel/src/ofdmframesync.c b/src/multichannel/src/ofdmframesync.c
new file mode 100644
index 0000000..d1e54d0
--- /dev/null
+++ b/src/multichannel/src/ofdmframesync.c
@@ -0,0 +1,1303 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// ofdmframesync.c
+//
+// OFDM frame synchronizer
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "liquid.internal.h"
+
+#define DEBUG_OFDMFRAMESYNC             1
+#define DEBUG_OFDMFRAMESYNC_PRINT       0
+#define DEBUG_OFDMFRAMESYNC_FILENAME    "ofdmframesync_internal_debug.m"
+#define DEBUG_OFDMFRAMESYNC_BUFFER_LEN  (2048)
+
+#define OFDMFRAMESYNC_ENABLE_SQUELCH    0
+
+struct ofdmframesync_s {
+    unsigned int M;         // number of subcarriers
+    unsigned int M2;        // number of subcarriers (divided by 2)
+    unsigned int cp_len;    // cyclic prefix length
+    unsigned char * p;      // subcarrier allocation (null, pilot, data)
+
+    // constants
+    unsigned int M_null;    // number of null subcarriers
+    unsigned int M_pilot;   // number of pilot subcarriers
+    unsigned int M_data;    // number of data subcarriers
+    unsigned int M_S0;      // number of enabled subcarriers in S0
+    unsigned int M_S1;      // number of enabled subcarriers in S1
+
+    // scaling factors
+    float g_data;           // data symbols gain
+    float g_S0;             // S0 training symbols gain
+    float g_S1;             // S1 training symbols gain
+
+    // transform object
+    FFT_PLAN fft;           // ifft object
+    float complex * X;      // frequency-domain buffer
+    float complex * x;      // time-domain buffer
+    windowcf input_buffer;  // input sequence buffer
+
+    // PLCP sequences
+    float complex * S0;     // short sequence (freq)
+    float complex * s0;     // short sequence (time)
+    float complex * S1;     // long sequence (freq)
+    float complex * s1;     // long sequence (time)
+
+    // gain
+    float g0;               // nominal gain (coarse initial estimate)
+    float complex * G0;     // complex subcarrier gain estimate, S0[0]
+    float complex * G1;     // complex subcarrier gain estimate, S0[1]
+    float complex * G;      // complex subcarrier gain estimate
+    float complex * B;      // subcarrier phase rotation due to backoff
+    float complex * R;      // 
+
+    // receiver state
+    enum {
+        OFDMFRAMESYNC_STATE_SEEKPLCP=0,   // seek initial PLCP
+        OFDMFRAMESYNC_STATE_PLCPSHORT0,   // seek first PLCP short sequence
+        OFDMFRAMESYNC_STATE_PLCPSHORT1,   // seek second PLCP short sequence
+        OFDMFRAMESYNC_STATE_PLCPLONG,     // seek PLCP long sequence
+        OFDMFRAMESYNC_STATE_RXSYMBOLS     // receive payload symbols
+    } state;
+
+    // synchronizer objects
+    nco_crcf nco_rx;        // numerically-controlled oscillator
+    msequence ms_pilot;     // pilot sequence generator
+    float phi_prime;        // ...
+    float p1_prime;         // filtered pilot phase slope
+
+#if OFDMFRAMESYNC_ENABLE_SQUELCH
+    // coarse signal detection
+    float squelch_threshold;
+    int squelch_enabled;
+#endif
+
+    // timing
+    unsigned int timer;         // input sample timer
+    unsigned int num_symbols;   // symbol counter
+    unsigned int backoff;       // sample timing backoff
+    float complex s_hat_0;      // first S0 symbol metrics estimate
+    float complex s_hat_1;      // second S0 symbol metrics estimate
+
+    // detection thresholds
+    float plcp_detect_thresh;   // plcp detection threshold, nominally 0.35
+    float plcp_sync_thresh;     // long symbol threshold, nominally 0.30
+
+    // callback
+    ofdmframesync_callback callback;
+    void * userdata;
+
+#if DEBUG_OFDMFRAMESYNC
+    int debug_enabled;
+    int debug_objects_created;
+    windowcf debug_x;
+    windowf  debug_rssi;
+    windowcf debug_framesyms;
+    float complex * G_hat;  // complex subcarrier gain estimate, S1
+    float * px;             // pilot x-value
+    float * py;             // pilot y-value
+    float p_phase[2];       // pilot polyfit
+    windowf debug_pilot_0;  // pilot polyfit history, p[0]
+    windowf debug_pilot_1;  // pilot polyfit history, p[1]
+#endif
+};
+
+// create OFDM framing synchronizer object
+//  _M          :   number of subcarriers, >10 typical
+//  _cp_len     :   cyclic prefix length
+//  _taper_len  :   taper length (OFDM symbol overlap)
+//  _p          :   subcarrier allocation (null, pilot, data), [size: _M x 1]
+//  _callback   :   user-defined callback function
+//  _userdata   :   user-defined data pointer
+ofdmframesync ofdmframesync_create(unsigned int           _M,
+                                   unsigned int           _cp_len,
+                                   unsigned int           _taper_len,
+                                   unsigned char *        _p,
+                                   ofdmframesync_callback _callback,
+                                   void *                 _userdata)
+{
+    ofdmframesync q = (ofdmframesync) malloc(sizeof(struct ofdmframesync_s));
+
+    // validate input
+    if (_M < 8) {
+        fprintf(stderr,"warning: ofdmframesync_create(), less than 8 subcarriers\n");
+    } else if (_M % 2) {
+        fprintf(stderr,"error: ofdmframesync_create(), number of subcarriers must be even\n");
+        exit(1);
+    } else if (_cp_len > _M) {
+        fprintf(stderr,"error: ofdmframesync_create(), cyclic prefix length cannot exceed number of subcarriers\n");
+        exit(1);
+    }
+    q->M = _M;
+    q->cp_len = _cp_len;
+
+    // derived values
+    q->M2 = _M/2;
+
+    // subcarrier allocation
+    q->p = (unsigned char*) malloc((q->M)*sizeof(unsigned char));
+    if (_p == NULL) {
+        ofdmframe_init_default_sctype(q->M, q->p);
+    } else {
+        memmove(q->p, _p, q->M*sizeof(unsigned char));
+    }
+
+    // validate and count subcarrier allocation
+    ofdmframe_validate_sctype(q->p, q->M, &q->M_null, &q->M_pilot, &q->M_data);
+    if ( (q->M_pilot + q->M_data) == 0) {
+        fprintf(stderr,"error: ofdmframesync_create(), must have at least one enabled subcarrier\n");
+        exit(1);
+    } else if (q->M_data == 0) {
+        fprintf(stderr,"error: ofdmframesync_create(), must have at least one data subcarriers\n");
+        exit(1);
+    } else if (q->M_pilot < 2) {
+        fprintf(stderr,"error: ofdmframesync_create(), must have at least two pilot subcarriers\n");
+        exit(1);
+    }
+
+    // create transform object
+    q->X = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->x = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->fft = FFT_CREATE_PLAN(q->M, q->x, q->X, FFT_DIR_FORWARD, FFT_METHOD);
+ 
+    // create input buffer the length of the transform
+    q->input_buffer = windowcf_create(q->M + q->cp_len);
+
+    // allocate memory for PLCP arrays
+    q->S0 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->s0 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->S1 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->s1 = (float complex*) malloc((q->M)*sizeof(float complex));
+    ofdmframe_init_S0(q->p, q->M, q->S0, q->s0, &q->M_S0);
+    ofdmframe_init_S1(q->p, q->M, q->S1, q->s1, &q->M_S1);
+
+    // compute scaling factor
+    q->g_data = sqrtf(q->M) / sqrtf(q->M_pilot + q->M_data);
+    q->g_S0   = sqrtf(q->M) / sqrtf(q->M_S0);
+    q->g_S1   = sqrtf(q->M) / sqrtf(q->M_S1);
+
+    // gain
+    q->g0 = 1.0f;
+    q->G0 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->G1 = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->G  = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->B  = (float complex*) malloc((q->M)*sizeof(float complex));
+    q->R  = (float complex*) malloc((q->M)*sizeof(float complex));
+
+#if 1
+    memset(q->G0, 0x00, q->M*sizeof(float complex));
+    memset(q->G1, 0x00, q->M*sizeof(float complex));
+    memset(q->G , 0x00, q->M*sizeof(float complex));
+    memset(q->B,  0x00, q->M*sizeof(float complex));
+#endif
+
+    // timing backoff
+    q->backoff = q->cp_len < 2 ? q->cp_len : 2;
+    float phi = (float)(q->backoff)*2.0f*M_PI/(float)(q->M);
+    unsigned int i;
+    for (i=0; i<q->M; i++)
+        q->B[i] = liquid_cexpjf(i*phi);
+
+    // set callback data
+    q->callback = _callback;
+    q->userdata = _userdata;
+
+    // 
+    // synchronizer objects
+    //
+
+    // numerically-controlled oscillator
+    q->nco_rx = nco_crcf_create(LIQUID_NCO);
+
+    // set pilot sequence
+    q->ms_pilot = msequence_create_default(8);
+
+#if OFDMFRAMESYNC_ENABLE_SQUELCH
+    // coarse detection
+    q->squelch_threshold = -25.0f;
+    q->squelch_enabled = 0;
+#endif
+
+    // reset object
+    ofdmframesync_reset(q);
+
+#if DEBUG_OFDMFRAMESYNC
+    q->debug_enabled = 0;
+    q->debug_objects_created = 0;
+
+    q->debug_x =        NULL;
+    q->debug_rssi =     NULL;
+    q->debug_framesyms =NULL;
+    
+    q->G_hat = NULL;
+    q->px    = NULL;
+    q->py    = NULL;
+    
+    q->debug_pilot_0 = NULL;
+    q->debug_pilot_1 = NULL;
+#endif
+
+    // return object
+    return q;
+}
+
+void ofdmframesync_destroy(ofdmframesync _q)
+{
+#if DEBUG_OFDMFRAMESYNC
+    // destroy debugging objects
+    if (_q->debug_x         != NULL) windowcf_destroy(_q->debug_x);
+    if (_q->debug_rssi      != NULL) windowf_destroy(_q->debug_rssi);
+    if (_q->debug_framesyms != NULL) windowcf_destroy(_q->debug_framesyms);
+    if (_q->G_hat           != NULL) free(_q->G_hat);
+    if (_q->px              != NULL) free(_q->px);
+    if (_q->py              != NULL) free(_q->py);
+    if (_q->debug_pilot_0   != NULL) windowf_destroy(_q->debug_pilot_0);
+    if (_q->debug_pilot_1   != NULL) windowf_destroy(_q->debug_pilot_1);
+#endif
+
+    // free subcarrier type array memory
+    free(_q->p);
+
+    // free transform object
+    windowcf_destroy(_q->input_buffer);
+    free(_q->X);
+    free(_q->x);
+    FFT_DESTROY_PLAN(_q->fft);
+
+    // clean up PLCP arrays
+    free(_q->S0);
+    free(_q->s0);
+    free(_q->S1);
+    free(_q->s1);
+
+    // free gain arrays
+    free(_q->G0);
+    free(_q->G1);
+    free(_q->G);
+    free(_q->B);
+    free(_q->R);
+
+    // destroy synchronizer objects
+    nco_crcf_destroy(_q->nco_rx);           // numerically-controlled oscillator
+    msequence_destroy(_q->ms_pilot);
+
+    // free main object memory
+    free(_q);
+}
+
+void ofdmframesync_print(ofdmframesync _q)
+{
+    printf("ofdmframesync:\n");
+    printf("    num subcarriers     :   %-u\n", _q->M);
+    printf("    cyclic prefix len   :   %-u\n", _q->cp_len);
+    //printf("    taper len           :   %-u\n", _q->taper_len);
+}
+
+void ofdmframesync_reset(ofdmframesync _q)
+{
+#if 0
+    // reset gain parameters
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        _q->G[i] = 1.0f;
+#endif
+
+    // reset synchronizer objects
+    nco_crcf_reset(_q->nco_rx);
+    msequence_reset(_q->ms_pilot);
+
+    // reset timers
+    _q->timer = 0;
+    _q->num_symbols = 0;
+    _q->s_hat_0 = 0.0f;
+    _q->s_hat_1 = 0.0f;
+    _q->phi_prime = 0.0f;
+    _q->p1_prime = 0.0f;
+
+    // set thresholds (increase for small number of subcarriers)
+    _q->plcp_detect_thresh = (_q->M > 44) ? 0.35f : 0.35f + 0.01f*(44 - _q->M);
+    _q->plcp_sync_thresh   = (_q->M > 44) ? 0.30f : 0.30f + 0.01f*(44 - _q->M);
+
+    // reset state
+    _q->state = OFDMFRAMESYNC_STATE_SEEKPLCP;
+}
+
+void ofdmframesync_execute(ofdmframesync _q,
+                           float complex * _x,
+                           unsigned int _n)
+{
+    unsigned int i;
+    float complex x;
+    for (i=0; i<_n; i++) {
+        x = _x[i];
+
+        // correct for carrier frequency offset
+        if (_q->state != OFDMFRAMESYNC_STATE_SEEKPLCP) {
+            nco_crcf_mix_down(_q->nco_rx, x, &x);
+            nco_crcf_step(_q->nco_rx);
+        }
+
+        // save input sample to buffer
+        windowcf_push(_q->input_buffer,x);
+
+#if DEBUG_OFDMFRAMESYNC
+        if (_q->debug_enabled) {
+            windowcf_push(_q->debug_x, x);
+            windowf_push(_q->debug_rssi, crealf(x)*crealf(x) + cimagf(x)*cimagf(x));
+        }
+#endif
+
+        switch (_q->state) {
+        case OFDMFRAMESYNC_STATE_SEEKPLCP:
+            ofdmframesync_execute_seekplcp(_q);
+            break;
+        case OFDMFRAMESYNC_STATE_PLCPSHORT0:
+            ofdmframesync_execute_S0a(_q);
+            break;
+        case OFDMFRAMESYNC_STATE_PLCPSHORT1:
+            ofdmframesync_execute_S0b(_q);
+            break;
+        case OFDMFRAMESYNC_STATE_PLCPLONG:
+            ofdmframesync_execute_S1(_q);
+            break;
+        case OFDMFRAMESYNC_STATE_RXSYMBOLS:
+            ofdmframesync_execute_rxsymbols(_q);
+            break;
+        default:;
+        }
+
+    } // for (i=0; i<_n; i++)
+} // ofdmframesync_execute()
+
+// get receiver RSSI
+float ofdmframesync_get_rssi(ofdmframesync _q)
+{
+    return -10.0f*log10(_q->g0);
+}
+
+// get receiver carrier frequency offset estimate
+float ofdmframesync_get_cfo(ofdmframesync _q)
+{
+    return nco_crcf_get_frequency(_q->nco_rx);
+}
+
+
+//
+// internal methods
+//
+
+// frame detection
+void ofdmframesync_execute_seekplcp(ofdmframesync _q)
+{
+    _q->timer++;
+
+    if (_q->timer < _q->M)
+        return;
+
+    // reset timer
+    _q->timer = 0;
+
+    //
+    float complex * rc;
+    windowcf_read(_q->input_buffer, &rc);
+
+    // estimate gain
+    unsigned int i;
+    float g = 0.0f;
+    for (i=_q->cp_len; i<_q->M + _q->cp_len; i++) {
+        // compute |rc[i]|^2 efficiently
+        g += crealf(rc[i])*crealf(rc[i]) + cimagf(rc[i])*cimagf(rc[i]);
+    }
+    g = (float)(_q->M) / g;
+
+#if OFDMFRAMESYNC_ENABLE_SQUELCH
+    // TODO : squelch here
+    if ( -10*log10f( sqrtf(g) ) < _q->squelch_threshold &&
+         _q->squelch_enabled)
+    {
+        printf("squelch\n");
+        return;
+    }
+#endif
+
+    // estimate S0 gain
+    ofdmframesync_estimate_gain_S0(_q, &rc[_q->cp_len], _q->G0);
+
+    float complex s_hat;
+    ofdmframesync_S0_metrics(_q, _q->G0, &s_hat);
+    s_hat *= g;
+
+    float tau_hat  = cargf(s_hat) * (float)(_q->M2) / (2*M_PI);
+#if DEBUG_OFDMFRAMESYNC_PRINT
+    printf(" - gain=%12.3f, rssi=%12.8f, s_hat=%12.4f <%12.8f>, tau_hat=%8.3f\n",
+            sqrt(g),
+            -10*log10(g),
+            cabsf(s_hat), cargf(s_hat),
+            tau_hat);
+#endif
+
+    // save gain (permits dynamic invocation of get_rssi() method)
+    _q->g0 = g;
+
+    // 
+    if (cabsf(s_hat) > _q->plcp_detect_thresh) {
+
+        int dt = (int)roundf(tau_hat);
+        // set timer appropriately...
+        _q->timer = (_q->M + dt) % (_q->M2);
+        _q->timer += _q->M; // add delay to help ensure good S0 estimate
+        _q->state = OFDMFRAMESYNC_STATE_PLCPSHORT0;
+
+#if DEBUG_OFDMFRAMESYNC_PRINT
+        printf("********** frame detected! ************\n");
+        printf("    s_hat   :   %12.8f <%12.8f>\n", cabsf(s_hat), cargf(s_hat));
+        printf("  tau_hat   :   %12.8f\n", tau_hat);
+        printf("    dt      :   %12d\n", dt);
+        printf("    timer   :   %12u\n", _q->timer);
+#endif
+        //printf("exiting prematurely\n");
+        //ofdmframesync_destroy(_q);
+        //exit(1);
+    }
+
+}
+
+// frame detection
+void ofdmframesync_execute_S0a(ofdmframesync _q)
+{
+    //printf("t : %u\n", _q->timer);
+    _q->timer++;
+
+    if (_q->timer < _q->M2)
+        return;
+
+    // reset timer
+    _q->timer = 0;
+
+    //
+    float complex * rc;
+    windowcf_read(_q->input_buffer, &rc);
+
+    // TODO : re-estimate nominal gain
+
+    // estimate S0 gain
+    ofdmframesync_estimate_gain_S0(_q, &rc[_q->cp_len], _q->G0);
+
+    float complex s_hat;
+    ofdmframesync_S0_metrics(_q, _q->G0, &s_hat);
+    s_hat *= _q->g0;
+
+    _q->s_hat_0 = s_hat;
+
+#if DEBUG_OFDMFRAMESYNC_PRINT
+    float tau_hat  = cargf(s_hat) * (float)(_q->M2) / (2*M_PI);
+    printf("********** S0[0] received ************\n");
+    printf("    s_hat   :   %12.8f <%12.8f>\n", cabsf(s_hat), cargf(s_hat));
+    printf("  tau_hat   :   %12.8f\n", tau_hat);
+#endif
+
+#if 0
+    // TODO : also check for phase of s_hat (should be small)
+    if (cabsf(s_hat) < 0.3f) {
+        // false alarm
+#if DEBUG_OFDMFRAMESYNC_PRINT
+        printf("false alarm S0[0]\n");
+#endif
+        ofdmframesync_reset(_q);
+        return;
+    }
+#endif
+    _q->state = OFDMFRAMESYNC_STATE_PLCPSHORT1;
+}
+
+// frame detection
+void ofdmframesync_execute_S0b(ofdmframesync _q)
+{
+    //printf("t = %u\n", _q->timer);
+    _q->timer++;
+
+    if (_q->timer < _q->M2)
+        return;
+
+    // reset timer
+    _q->timer = _q->M + _q->cp_len - _q->backoff;
+
+    //
+    float complex * rc;
+    windowcf_read(_q->input_buffer, &rc);
+
+    // estimate S0 gain
+    ofdmframesync_estimate_gain_S0(_q, &rc[_q->cp_len], _q->G1);
+
+    float complex s_hat;
+    ofdmframesync_S0_metrics(_q, _q->G1, &s_hat);
+    s_hat *= _q->g0;
+
+    _q->s_hat_1 = s_hat;
+
+#if DEBUG_OFDMFRAMESYNC_PRINT
+    float tau_hat  = cargf(s_hat) * (float)(_q->M2) / (2*M_PI);
+    printf("********** S0[1] received ************\n");
+    printf("    s_hat   :   %12.8f <%12.8f>\n", cabsf(s_hat), cargf(s_hat));
+    printf("  tau_hat   :   %12.8f\n", tau_hat);
+
+    // new timing offset estimate
+    tau_hat  = cargf(_q->s_hat_0 + _q->s_hat_1) * (float)(_q->M2) / (2*M_PI);
+    printf("  tau_hat * :   %12.8f\n", tau_hat);
+
+    printf("**********\n");
+#endif
+
+    // re-adjust timer accordingly
+    float tau_prime = cargf(_q->s_hat_0 + _q->s_hat_1) * (float)(_q->M2) / (2*M_PI);
+    _q->timer -= (int)roundf(tau_prime);
+
+#if 0
+    if (cabsf(s_hat) < 0.3f) {
+#if DEBUG_OFDMFRAMESYNC_PRINT
+        printf("false alarm S0[1]\n");
+#endif
+        // false alarm
+        ofdmframesync_reset(_q);
+        return;
+    }
+#endif
+
+    float complex g_hat = 0.0f;
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        g_hat += _q->G1[i] * conjf(_q->G0[i]);
+
+#if 0
+    // compute carrier frequency offset estimate using freq. domain method
+    float nu_hat = 2.0f * cargf(g_hat) / (float)(_q->M);
+#else
+    // compute carrier frequency offset estimate using ML method
+    float complex t0 = 0.0f;
+    for (i=0; i<_q->M2; i++) {
+        t0 += conjf(rc[i])       *       _q->s0[i] * 
+                    rc[i+_q->M2] * conjf(_q->s0[i+_q->M2]);
+    }
+    float nu_hat = cargf(t0) / (float)(_q->M2);
+#endif
+
+#if DEBUG_OFDMFRAMESYNC_PRINT
+    printf("   nu_hat   :   %12.8f\n", nu_hat);
+#endif
+
+    // set NCO frequency
+    nco_crcf_set_frequency(_q->nco_rx, nu_hat);
+
+    _q->state = OFDMFRAMESYNC_STATE_PLCPLONG;
+}
+
+void ofdmframesync_execute_S1(ofdmframesync _q)
+{
+    _q->timer--;
+
+    if (_q->timer > 0)
+        return;
+
+    // increment number of symbols observed
+    _q->num_symbols++;
+
+    // run fft
+    float complex * rc;
+    windowcf_read(_q->input_buffer, &rc);
+
+    // estimate S1 gain
+    // TODO : add backoff in gain estimation
+    ofdmframesync_estimate_gain_S1(_q, &rc[_q->cp_len], _q->G);
+
+    // compute detector output
+    float complex g_hat = 0.0f;
+    unsigned int i;
+    for (i=0; i<_q->M; i++) {
+        //g_hat += _q->G[(i+1+_q->M)%_q->M]*conjf(_q->G[(i+_q->M)%_q->M]);
+        g_hat += _q->G[(i+1)%_q->M]*conjf(_q->G[i]);
+    }
+    g_hat /= _q->M_S1; // normalize output
+    g_hat *= _q->g0;
+
+    // rotate by complex phasor relative to timing backoff
+    g_hat *= liquid_cexpjf((float)(_q->backoff)*2.0f*M_PI/(float)(_q->M));
+
+#if DEBUG_OFDMFRAMESYNC_PRINT
+    printf("    g_hat   :   %12.4f <%12.8f>\n", cabsf(g_hat), cargf(g_hat));
+#endif
+
+    // check conditions for g_hat:
+    //  1. magnitude should be large (near unity) when aligned
+    //  2. phase should be very near zero (time aligned)
+    if (cabsf(g_hat) > _q->plcp_sync_thresh && fabsf(cargf(g_hat)) < 0.1f*M_PI ) {
+        //printf("    acquisition\n");
+        _q->state = OFDMFRAMESYNC_STATE_RXSYMBOLS;
+        // reset timer
+        _q->timer = _q->M + _q->cp_len + _q->backoff;
+        _q->num_symbols = 0;
+
+        // normalize gain by subcarriers, apply timing backoff correction
+        float g = (float)(_q->M) / sqrtf(_q->M_pilot + _q->M_data);
+        for (i=0; i<_q->M; i++) {
+            _q->G[i] *= g;          // gain due to relative subcarrier allocation
+            _q->G[i] *= _q->B[i];   // timing backoff correction
+        }
+
+#if 0
+        // TODO : choose number of taps more appropriately
+        //unsigned int ntaps = _q->M / 4;
+        unsigned int ntaps = (_q->M < 8) ? 2 : 8;
+        // FIXME : this is by far the most computationally complex part of synchronization
+        ofdmframesync_estimate_eqgain(_q, ntaps);
+#else
+        unsigned int poly_order = 4;
+        if (poly_order >= _q->M_pilot + _q->M_data)
+            poly_order = _q->M_pilot + _q->M_data - 1;
+        ofdmframesync_estimate_eqgain_poly(_q, poly_order);
+#endif
+
+#if 1
+        // compute composite gain
+        unsigned int i;
+        for (i=0; i<_q->M; i++)
+            _q->R[i] = _q->B[i] / _q->G[i];
+#endif
+
+        return;
+#if 0
+        printf("exiting prematurely\n");
+        ofdmframesync_destroy(_q);
+        exit(1);
+#endif
+    }
+
+    // check if we are stuck searching for the S1 symbol
+    if (_q->num_symbols == 16) {
+#if DEBUG_OFDMFRAMESYNC_PRINT
+        printf("could not find S1 symbol. bailing...\n");
+#endif
+        ofdmframesync_reset(_q);
+    }
+
+    // 'reset' timer (wait another half symbol)
+    _q->timer = _q->M2;
+}
+
+void ofdmframesync_execute_rxsymbols(ofdmframesync _q)
+{
+    // wait for timeout
+    _q->timer--;
+
+    if (_q->timer == 0) {
+
+        // run fft
+        float complex * rc;
+        windowcf_read(_q->input_buffer, &rc);
+        memmove(_q->x, &rc[_q->cp_len-_q->backoff], (_q->M)*sizeof(float complex));
+        FFT_EXECUTE(_q->fft);
+
+        // recover symbol in internal _q->X buffer
+        ofdmframesync_rxsymbol(_q);
+
+#if DEBUG_OFDMFRAMESYNC
+        if (_q->debug_enabled) {
+            unsigned int i;
+            for (i=0; i<_q->M; i++) {
+                if (_q->p[i] == OFDMFRAME_SCTYPE_DATA)
+                    windowcf_push(_q->debug_framesyms, _q->X[i]);
+            }
+        }
+#endif
+        // invoke callback
+        if (_q->callback != NULL) {
+            int retval = _q->callback(_q->X, _q->p, _q->M, _q->userdata);
+
+            if (retval != 0)
+                ofdmframesync_reset(_q);
+        }
+
+        // reset timer
+        _q->timer = _q->M + _q->cp_len;
+    }
+
+}
+
+// compute S0 metrics
+void ofdmframesync_S0_metrics(ofdmframesync _q,
+                              float complex * _G,
+                              float complex * _s_hat)
+{
+    // timing, carrier offset correction
+    unsigned int i;
+    float complex s_hat = 0.0f;
+
+    // compute timing estimate, accumulate phase difference across
+    // gains on subsequent pilot subcarriers (note that all the odd
+    // subcarriers are NULL)
+    for (i=0; i<_q->M; i+=2) {
+        s_hat += _G[(i+2)%_q->M]*conjf(_G[i]);
+    }
+    s_hat /= _q->M_S0; // normalize output
+
+    // set output values
+    *_s_hat = s_hat;
+}
+
+// estimate short sequence gain
+//  _q      :   ofdmframesync object
+//  _x      :   input array (time), [size: M x 1]
+//  _G      :   output gain (freq)
+void ofdmframesync_estimate_gain_S0(ofdmframesync   _q,
+                                    float complex * _x,
+                                    float complex * _G)
+{
+    // move input array into fft input buffer
+    memmove(_q->x, _x, (_q->M)*sizeof(float complex));
+
+    // compute fft, storing result into _q->X
+    FFT_EXECUTE(_q->fft);
+    
+    // compute gain, ignoring NULL subcarriers
+    unsigned int i;
+    float gain = sqrtf(_q->M_S0) / (float)(_q->M);
+
+    for (i=0; i<_q->M; i++) {
+        if (_q->p[i] != OFDMFRAME_SCTYPE_NULL && (i%2)==0) {
+            // NOTE : if cabsf(_q->S0[i]) == 0 then we can multiply by conjugate
+            //        rather than compute division
+            //_G[i] = _q->X[i] / _q->S0[i];
+            _G[i] = _q->X[i] * conjf(_q->S0[i]);
+        } else {
+            _G[i] = 0.0f;
+        }
+
+        // normalize gain
+        _G[i] *= gain;
+    }
+}
+
+// estimate long sequence gain
+//  _q      :   ofdmframesync object
+//  _x      :   input array (time), [size: M x 1]
+//  _G      :   output gain (freq)
+void ofdmframesync_estimate_gain_S1(ofdmframesync _q,
+                                    float complex * _x,
+                                    float complex * _G)
+{
+    // move input array into fft input buffer
+    memmove(_q->x, _x, (_q->M)*sizeof(float complex));
+
+    // compute fft, storing result into _q->X
+    FFT_EXECUTE(_q->fft);
+    
+    // compute gain, ignoring NULL subcarriers
+    unsigned int i;
+    float gain = sqrtf(_q->M_S1) / (float)(_q->M);
+    for (i=0; i<_q->M; i++) {
+        if (_q->p[i] != OFDMFRAME_SCTYPE_NULL) {
+            // NOTE : if cabsf(_q->S1[i]) == 0 then we can multiply by conjugate
+            //        rather than compute division
+            //_G[i] = _q->X[i] / _q->S1[i];
+            _G[i] = _q->X[i] * conjf(_q->S1[i]);
+        } else {
+            _G[i] = 0.0f;
+        }
+
+        // normalize gain
+        _G[i] *= gain;
+    }   
+}
+
+// estimate complex equalizer gain from G0 and G1
+//  _q      :   ofdmframesync object
+//  _ntaps  :   number of time-domain taps for smoothing
+void ofdmframesync_estimate_eqgain(ofdmframesync _q,
+                                   unsigned int _ntaps)
+{
+#if DEBUG_OFDMFRAMESYNC
+    if (_q->debug_enabled) {
+        // copy pre-smoothed gain
+        memmove(_q->G_hat, _q->G, _q->M*sizeof(float complex));
+    }
+#endif
+
+    // validate input
+    if (_ntaps == 0 || _ntaps > _q->M) {
+        fprintf(stderr, "error: ofdmframesync_estimate_eqgain(), ntaps must be in [1,M]\n");
+        exit(1);
+    }
+
+    unsigned int i;
+
+    // generate smoothing window (fft of temporal window)
+    for (i=0; i<_q->M; i++)
+        _q->x[i] = (i < _ntaps) ? 1.0f : 0.0f;
+    FFT_EXECUTE(_q->fft);
+
+    memmove(_q->G0, _q->G, _q->M*sizeof(float complex));
+
+    // smooth complex equalizer gains
+    for (i=0; i<_q->M; i++) {
+        // set gain to zero for null subcarriers
+        if (_q->p[i] == OFDMFRAME_SCTYPE_NULL) {
+            _q->G[i] = 0.0f;
+            continue;
+        }
+
+        float complex w;
+        float complex w0 = 0.0f;
+        float complex G_hat = 0.0f;
+
+        unsigned int j;
+        for (j=0; j<_q->M; j++) {
+            if (_q->p[j] == OFDMFRAME_SCTYPE_NULL) continue;
+
+            // select window sample from array
+            w = _q->X[(i + _q->M - j) % _q->M];
+
+            // accumulate gain
+            //G_hat += w * 0.5f * (_q->G0[j] + _q->G1[j]);
+            G_hat += w * _q->G0[j];
+            w0 += w;
+        }
+
+        // eliminate divide-by-zero issues
+        if (cabsf(w0) < 1e-4f) {
+            fprintf(stderr,"error: ofdmframesync_estimate_eqgain(), weighting factor is zero\n");
+            w0 = 1.0f;
+        }
+        _q->G[i] = G_hat / w0;
+    }
+}
+
+// estimate complex equalizer gain from G0 and G1 using polynomial fit
+//  _q      :   ofdmframesync object
+//  _order  :   polynomial order
+void ofdmframesync_estimate_eqgain_poly(ofdmframesync _q,
+                                        unsigned int _order)
+{
+#if DEBUG_OFDMFRAMESYNC
+    if (_q->debug_enabled) {
+        // copy pre-smoothed gain
+        memmove(_q->G_hat, _q->G, _q->M*sizeof(float complex));
+    }
+#endif
+
+    // polynomial interpolation
+    unsigned int i;
+    unsigned int N = _q->M_pilot + _q->M_data;
+    if (_order > N-1) _order = N-1;
+    if (_order > 10)  _order = 10;
+    float x_freq[N];
+    float y_abs[N];
+    float y_arg[N];
+    float p_abs[_order+1];
+    float p_arg[_order+1];
+
+    unsigned int n=0;
+    unsigned int k;
+    for (i=0; i<_q->M; i++) {
+
+        // start at mid-point (effective fftshift)
+        k = (i + _q->M2) % _q->M;
+
+        if (_q->p[k] != OFDMFRAME_SCTYPE_NULL) {
+            if (n == N) {
+                fprintf(stderr, "error: ofdmframesync_estimate_eqgain_poly(), pilot subcarrier mismatch\n");
+                exit(1);
+            }
+            // store resulting...
+            x_freq[n] = (k > _q->M2) ? (float)k - (float)(_q->M) : (float)k;
+            x_freq[n] = x_freq[n] / (float)(_q->M);
+            y_abs[n] = cabsf(_q->G[k]);
+            y_arg[n] = cargf(_q->G[k]);
+
+            // update counter
+            n++;
+        }
+    }
+
+    if (n != N) {
+        fprintf(stderr, "error: ofdmframesync_estimate_eqgain_poly(), pilot subcarrier mismatch\n");
+        exit(1);
+    }
+
+    // try to unwrap phase
+    for (i=1; i<N; i++) {
+        while ((y_arg[i] - y_arg[i-1]) >  M_PI)
+            y_arg[i] -= 2*M_PI;
+        while ((y_arg[i] - y_arg[i-1]) < -M_PI)
+            y_arg[i] += 2*M_PI;
+    }
+
+    // fit to polynomial
+    polyf_fit(x_freq, y_abs, N, p_abs, _order+1);
+    polyf_fit(x_freq, y_arg, N, p_arg, _order+1);
+
+    // compute subcarrier gain
+    for (i=0; i<_q->M; i++) {
+        float freq = (i > _q->M2) ? (float)i - (float)(_q->M) : (float)i;
+        freq = freq / (float)(_q->M);
+        float A     = polyf_val(p_abs, _order+1, freq);
+        float theta = polyf_val(p_arg, _order+1, freq);
+        _q->G[i] = (_q->p[i] == OFDMFRAME_SCTYPE_NULL) ? 0.0f : A * liquid_cexpjf(theta);
+    }
+
+#if 0
+    for (i=0; i<N; i++)
+        printf("x(%3u) = %12.8f; y_abs(%3u) = %12.8f; y_arg(%3u) = %12.8f;\n",
+                i+1, x_freq[i],
+                i+1, y_abs[i],
+                i+1, y_arg[i]);
+
+    for (i=0; i<=_order; i++)
+        printf("p_abs(%3u) = %12.8f;\n", i+1, p_abs[i]);
+    for (i=0; i<=_order; i++)
+        printf("p_arg(%3u) = %12.8f;\n", i+1, p_arg[i]);
+#endif
+}
+
+// recover symbol, correcting for gain, pilot phase, etc.
+void ofdmframesync_rxsymbol(ofdmframesync _q)
+{
+    // apply gain
+    unsigned int i;
+    for (i=0; i<_q->M; i++)
+        _q->X[i] *= _q->R[i];
+
+    // polynomial curve-fit
+    float x_phase[_q->M_pilot];
+    float y_phase[_q->M_pilot];
+    float p_phase[2];
+
+    unsigned int n=0;
+    unsigned int k;
+    float complex pilot = 1.0f;
+    for (i=0; i<_q->M; i++) {
+
+        // start at mid-point (effective fftshift)
+        k = (i + _q->M2) % _q->M;
+
+        if (_q->p[k]==OFDMFRAME_SCTYPE_PILOT) {
+            if (n == _q->M_pilot) {
+                fprintf(stderr,"warning: ofdmframesync_rxsymbol(), pilot subcarrier mismatch\n");
+                return;
+            }
+            pilot = (msequence_advance(_q->ms_pilot) ? 1.0f : -1.0f);
+#if 0
+            printf("pilot[%3u] = %12.4e + j*%12.4e (expected %12.4e + j*%12.4e)\n",
+                    k,
+                    crealf(_q->X[k]), cimagf(_q->X[k]),
+                    crealf(pilot),    cimagf(pilot));
+#endif
+            // store resulting...
+            x_phase[n] = (k > _q->M2) ? (float)k - (float)(_q->M) : (float)k;
+            y_phase[n] = cargf(_q->X[k]*conjf(pilot));
+
+            // update counter
+            n++;
+
+        }
+    }
+
+    if (n != _q->M_pilot) {
+        fprintf(stderr,"warning: ofdmframesync_rxsymbol(), pilot subcarrier mismatch\n");
+        return;
+    }
+
+    // try to unwrap phase
+    for (i=1; i<_q->M_pilot; i++) {
+        while ((y_phase[i] - y_phase[i-1]) >  M_PI)
+            y_phase[i] -= 2*M_PI;
+        while ((y_phase[i] - y_phase[i-1]) < -M_PI)
+            y_phase[i] += 2*M_PI;
+    }
+
+    // fit phase to 1st-order polynomial (2 coefficients)
+    polyf_fit(x_phase, y_phase, _q->M_pilot, p_phase, 2);
+
+    // filter slope estimate (timing offset)
+    float alpha = 0.3f;
+    p_phase[1] = alpha*p_phase[1] + (1-alpha)*_q->p1_prime;
+    _q->p1_prime = p_phase[1];
+
+#if DEBUG_OFDMFRAMESYNC
+    if (_q->debug_enabled) {
+        // save pilots
+        memmove(_q->px, x_phase, _q->M_pilot*sizeof(float));
+        memmove(_q->py, y_phase, _q->M_pilot*sizeof(float));
+
+        // NOTE : swapping values for octave
+        _q->p_phase[0] = p_phase[1];
+        _q->p_phase[1] = p_phase[0];
+
+        windowf_push(_q->debug_pilot_0, p_phase[0]);
+        windowf_push(_q->debug_pilot_1, p_phase[1]);
+    }
+#endif
+
+    // compensate for phase offset
+    // TODO : find more computationally efficient way to do this
+    for (i=0; i<_q->M; i++) {
+        // only apply to data/pilot subcarriers
+        if (_q->p[i] == OFDMFRAME_SCTYPE_NULL) {
+            _q->X[i] = 0.0f;
+        } else {
+            float fx    = (i > _q->M2) ? (float)i - (float)(_q->M) : (float)i;
+            float theta = polyf_val(p_phase, 2, fx);
+            _q->X[i] *= liquid_cexpjf(-theta);
+        }
+    }
+
+    // adjust NCO frequency based on differential phase
+    if (_q->num_symbols > 0) {
+        // compute phase error (unwrapped)
+        float dphi_prime = p_phase[0] - _q->phi_prime;
+        while (dphi_prime >  M_PI) dphi_prime -= M_2_PI;
+        while (dphi_prime < -M_PI) dphi_prime += M_2_PI;
+
+        // adjust NCO proportionally to phase error
+        nco_crcf_adjust_frequency(_q->nco_rx, 1e-3f*dphi_prime);
+    }
+    // set internal phase state
+    _q->phi_prime = p_phase[0];
+    //printf("%3u : theta : %12.8f, nco freq: %12.8f\n", _q->num_symbols, p_phase[0], nco_crcf_get_frequency(_q->nco_rx));
+    
+    // increment symbol counter
+    _q->num_symbols++;
+
+#if 0
+    for (i=0; i<_q->M_pilot; i++)
+        printf("x_phase(%3u) = %12.8f; y_phase(%3u) = %12.8f;\n", i+1, x_phase[i], i+1, y_phase[i]);
+    printf("poly : p0=%12.8f, p1=%12.8f\n", p_phase[0], p_phase[1]);
+#endif
+}
+
+// enable debugging
+void ofdmframesync_debug_enable(ofdmframesync _q)
+{
+    // create debugging objects if necessary
+#if DEBUG_OFDMFRAMESYNC
+    if (_q->debug_objects_created)
+        return;
+
+    _q->debug_x         = windowcf_create(DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    _q->debug_rssi      = windowf_create(DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    _q->debug_framesyms = windowcf_create(DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    _q->G_hat           = (float complex*) malloc((_q->M)*sizeof(float complex));
+
+    _q->px = (float*) malloc((_q->M_pilot)*sizeof(float));
+    _q->py = (float*) malloc((_q->M_pilot)*sizeof(float));
+
+    _q->debug_pilot_0 = windowf_create(DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    _q->debug_pilot_1 = windowf_create(DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+
+    _q->debug_enabled   = 1;
+    _q->debug_objects_created = 1;
+#else
+    fprintf(stderr,"ofdmframesync_debug_enable(): compile-time debugging disabled\n");
+#endif
+}
+
+void ofdmframesync_debug_disable(ofdmframesync _q)
+{
+    // disable debugging
+#if DEBUG_OFDMFRAMESYNC
+    _q->debug_enabled = 0;
+#else
+    fprintf(stderr,"ofdmframesync_debug_disable(): compile-time debugging disabled\n");
+#endif
+}
+
+void ofdmframesync_debug_print(ofdmframesync _q,
+                               const char * _filename)
+{
+#if DEBUG_OFDMFRAMESYNC
+    if (!_q->debug_objects_created) {
+        fprintf(stderr,"error: ofdmframe_debug_print(), debugging objects don't exist; enable debugging first\n");
+        return;
+    }
+
+    FILE * fid = fopen(_filename,"w");
+    if (!fid) {
+        fprintf(stderr,"error: ofdmframe_debug_print(), could not open '%s' for writing\n", _filename);
+        return;
+    }
+    fprintf(fid,"%% %s : auto-generated file\n", DEBUG_OFDMFRAMESYNC_FILENAME);
+    fprintf(fid,"close all;\n");
+    fprintf(fid,"clear all;\n");
+    fprintf(fid,"n = %u;\n", DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    fprintf(fid,"M = %u;\n", _q->M);
+    fprintf(fid,"M_null  = %u;\n", _q->M_null);
+    fprintf(fid,"M_pilot = %u;\n", _q->M_pilot);
+    fprintf(fid,"M_data  = %u;\n", _q->M_data);
+    unsigned int i;
+    float complex * rc;
+    float * r;
+
+    // save subcarrier allocation
+    fprintf(fid,"p = zeros(1,M);\n");
+    for (i=0; i<_q->M; i++)
+        fprintf(fid,"p(%4u) = %d;\n", i+1, _q->p[i]);
+    fprintf(fid,"i_null  = find(p==%d);\n", OFDMFRAME_SCTYPE_NULL);
+    fprintf(fid,"i_pilot = find(p==%d);\n", OFDMFRAME_SCTYPE_PILOT);
+    fprintf(fid,"i_data  = find(p==%d);\n", OFDMFRAME_SCTYPE_DATA);
+
+    // short, long, training sequences
+    for (i=0; i<_q->M; i++) {
+        fprintf(fid,"S0(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(_q->S0[i]), cimagf(_q->S0[i]));
+        fprintf(fid,"S1(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(_q->S1[i]), cimagf(_q->S1[i]));
+    }
+
+    fprintf(fid,"x = zeros(1,n);\n");
+    windowcf_read(_q->debug_x, &rc);
+    for (i=0; i<DEBUG_OFDMFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"x(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(0:(n-1),real(x),0:(n-1),imag(x));\n");
+    fprintf(fid,"xlabel('sample index');\n");
+    fprintf(fid,"ylabel('received signal, x');\n");
+
+
+    fprintf(fid,"s1 = [];\n");
+    for (i=0; i<_q->M; i++)
+        fprintf(fid,"s1(%3u) = %12.4e + j*%12.4e;\n", i+1, crealf(_q->s1[i]), cimagf(_q->s1[i]));
+
+
+    // write agc_rssi
+    fprintf(fid,"\n\n");
+    fprintf(fid,"agc_rssi = zeros(1,%u);\n", DEBUG_OFDMFRAMESYNC_BUFFER_LEN);
+    windowf_read(_q->debug_rssi, &r);
+    for (i=0; i<DEBUG_OFDMFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"agc_rssi(%4u) = %12.4e;\n", i+1, r[i]);
+    fprintf(fid,"agc_rssi = filter([0.00362168 0.00724336 0.00362168],[1 -1.82269490 0.83718163],agc_rssi);\n");
+    fprintf(fid,"agc_rssi = 10*log10( agc_rssi );\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(agc_rssi)\n");
+    fprintf(fid,"ylabel('RSSI [dB]');\n");
+
+    // write short, long symbols
+    fprintf(fid,"\n\n");
+    fprintf(fid,"S0 = zeros(1,M);\n");
+    fprintf(fid,"S1 = zeros(1,M);\n");
+    for (i=0; i<_q->M; i++) {
+        fprintf(fid,"S0(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(_q->S0[i]), cimagf(_q->S0[i]));
+        fprintf(fid,"S1(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(_q->S1[i]), cimagf(_q->S1[i]));
+    }
+
+
+    // write gain arrays
+    fprintf(fid,"\n\n");
+    fprintf(fid,"G0     = zeros(1,M);\n");
+    fprintf(fid,"G1     = zeros(1,M);\n");
+    fprintf(fid,"G_hat  = zeros(1,M);\n");
+    fprintf(fid,"G      = zeros(1,M);\n");
+    for (i=0; i<_q->M; i++) {
+        fprintf(fid,"G0(%3u)    = %12.8f + j*%12.8f;\n", i+1, crealf(_q->G0[i]),   cimagf(_q->G0[i]));
+        fprintf(fid,"G1(%3u)    = %12.8f + j*%12.8f;\n", i+1, crealf(_q->G1[i]),   cimagf(_q->G1[i]));
+        fprintf(fid,"G_hat(%3u) = %12.8f + j*%12.8f;\n", i+1, crealf(_q->G_hat[i]),cimagf(_q->G_hat[i]));
+        fprintf(fid,"G(%3u)     = %12.8f + j*%12.8f;\n", i+1, crealf(_q->G[i]),    cimagf(_q->G[i]));
+    }
+    fprintf(fid,"f = [0:(M-1)];\n");
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"subplot(2,1,1);\n");
+    fprintf(fid,"  plot(f, fftshift(abs(G_hat)),'sb',...\n");
+    fprintf(fid,"       f, fftshift(abs(G)),'-k','LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('subcarrier index');\n");
+    fprintf(fid,"  ylabel('gain estimate (mag)');\n");
+    fprintf(fid,"subplot(2,1,2);\n");
+    fprintf(fid,"  plot(f, fftshift(arg(G_hat).*[abs(G0) > 1e-3]),'sb',...\n");
+    fprintf(fid,"       f, fftshift(arg(G)),'-k','LineWidth',2);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  xlabel('subcarrier index');\n");
+    fprintf(fid,"  ylabel('gain estimate (phase)');\n");
+
+    // write pilot response
+    fprintf(fid,"\n\n");
+    fprintf(fid,"px = zeros(1,M_pilot);\n");
+    fprintf(fid,"py = zeros(1,M_pilot);\n");
+    for (i=0; i<_q->M_pilot; i++) {
+        fprintf(fid,"px(%3u) = %12.8f;\n", i+1, _q->px[i]);
+        fprintf(fid,"py(%3u) = %12.8f;\n", i+1, _q->py[i]);
+    }
+    fprintf(fid,"p_phase(1) = %12.8f;\n", _q->p_phase[0]);
+    fprintf(fid,"p_phase(2) = %12.8f;\n", _q->p_phase[1]);
+
+    // save pilot history
+    fprintf(fid,"p0 = zeros(1,M);\n");
+    windowf_read(_q->debug_pilot_0, &r);
+    for (i=0; i<DEBUG_OFDMFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"p0(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"p1 = zeros(1,M);\n");
+    windowf_read(_q->debug_pilot_1, &r);
+    for (i=0; i<DEBUG_OFDMFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"p1(%4u) = %12.4e;\n", i+1, r[i]);
+
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"fp = (-M/2):(M/2);\n");
+    fprintf(fid,"subplot(3,1,1);\n");
+    fprintf(fid,"  plot(px, py, 'sb',...\n");
+    fprintf(fid,"       fp, polyval(p_phase, fp), '-k');\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  legend('pilots','polyfit',0);\n");
+    fprintf(fid,"  xlabel('subcarrier');\n");
+    fprintf(fid,"  ylabel('phase');\n");
+    fprintf(fid,"subplot(3,1,2);\n");
+    fprintf(fid,"  plot(1:length(p0), p0);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('p0 (phase offset)');\n");
+    fprintf(fid,"subplot(3,1,3);\n");
+    fprintf(fid,"  plot(1:length(p1), p1);\n");
+    fprintf(fid,"  grid on;\n");
+    fprintf(fid,"  ylabel('p1 (phase slope)');\n");
+
+    // write frame symbols
+    fprintf(fid,"framesyms = zeros(1,n);\n");
+    windowcf_read(_q->debug_framesyms, &rc);
+    for (i=0; i<DEBUG_OFDMFRAMESYNC_BUFFER_LEN; i++)
+        fprintf(fid,"framesyms(%4u) = %12.4e + j*%12.4e;\n", i+1, crealf(rc[i]), cimagf(rc[i]));
+    fprintf(fid,"figure;\n");
+    fprintf(fid,"plot(real(framesyms), imag(framesyms), 'x');\n");
+    fprintf(fid,"xlabel('I');\n");
+    fprintf(fid,"ylabel('Q');\n");
+    fprintf(fid,"axis([-1 1 -1 1]*1.6);\n");
+    fprintf(fid,"axis square;\n");
+    fprintf(fid,"grid on;\n");
+
+    fclose(fid);
+    printf("ofdmframesync/debug: results written to '%s'\n", _filename);
+#else
+    fprintf(stderr,"ofdmframesync_debug_print(): compile-time debugging disabled\n");
+#endif
+}
+
diff --git a/src/multichannel/tests/firpfbch2_crcf_autotest.c b/src/multichannel/tests/firpfbch2_crcf_autotest.c
new file mode 100644
index 0000000..a41242b
--- /dev/null
+++ b/src/multichannel/tests/firpfbch2_crcf_autotest.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// Helper function to keep code base small
+void firpfbch2_crcf_runtest(unsigned int _M,
+                            unsigned int _m,
+                            float        _As)
+{
+    float tol = 1e-3f;
+    unsigned int i;
+
+    // derived values
+    unsigned int num_symbols = 8*_m;    // number of symbols
+    unsigned int num_samples = _M * num_symbols;
+
+    // allocate arrays
+    float complex x[num_samples];
+    float complex y[num_samples];
+
+    // generate pseudo-random sequence
+    unsigned int s = 1;         // seed
+    unsigned int p = 524287;    // large prime number
+    unsigned int g =   1031;    // another large prime number
+    for (i=0; i<num_samples; i++) {
+        s = (s * p) % g;
+        x[i] = (float)s / (float)g - 0.5f;
+        //x[i] = (i==0) ? 1.0f : 0.0f;  // impulse response
+    }
+
+    // create filterbank objects from prototype
+    firpfbch2_crcf qa = firpfbch2_crcf_create_kaiser(LIQUID_ANALYZER,    _M, _m, _As);
+    firpfbch2_crcf qs = firpfbch2_crcf_create_kaiser(LIQUID_SYNTHESIZER, _M, _m, _As);
+
+    // run channelizer
+    float complex Y[_M];
+    for (i=0; i<num_samples; i+=_M/2) {
+        // run analysis filterbank
+        firpfbch2_crcf_execute(qa, &x[i], Y);
+
+        // run synthesis filterbank
+        firpfbch2_crcf_execute(qs, Y, &y[i]);
+    }
+
+    // destroy filterbank objects
+    firpfbch2_crcf_destroy(qa); // analysis fitlerbank
+    firpfbch2_crcf_destroy(qs); // synthesis filterbank
+
+    // validate output
+    unsigned int delay = 2*_M*_m - _M/2 + 1;
+    float rmse = 0.0f;
+    for (i=0; i<num_samples; i++) {
+        //printf("%3u : %12.8f + %12.8fj\n", i, crealf(y[i]), cimagf(y[i]));
+        if (i < delay) {
+            CONTEND_DELTA( crealf(y[i]), 0.0f, tol );
+            CONTEND_DELTA( cimagf(y[i]), 0.0f, tol );
+        } else {
+            CONTEND_DELTA( crealf(y[i]), crealf(x[i-delay]), tol );
+            CONTEND_DELTA( cimagf(y[i]), cimagf(x[i-delay]), tol );
+        }
+
+        // compute rmse
+        float complex err = y[i] - (i < delay ? 0.0f : x[i-delay]);
+        rmse += crealf(err * conjf(err));
+    }
+
+    rmse = sqrtf(rmse / (float)num_samples);
+    if (liquid_autotest_verbose)
+        printf("firpfbch2:  M=%3u, m=%2u, As=%8.2f dB, rmse=%12.4e\n", _M, _m, _As, rmse);
+}
+
+// analysis
+void autotest_firpfbch2_crcf_n8()    { firpfbch2_crcf_runtest(   8, 5, 60.0f); }
+void autotest_firpfbch2_crcf_n16()   { firpfbch2_crcf_runtest(  16, 5, 60.0f); }
+void autotest_firpfbch2_crcf_n32()   { firpfbch2_crcf_runtest(  32, 5, 60.0f); }
+void autotest_firpfbch2_crcf_n64()   { firpfbch2_crcf_runtest(  64, 5, 60.0f); }
+
diff --git a/src/multichannel/tests/firpfbch_crcf_analyzer_autotest.c b/src/multichannel/tests/firpfbch_crcf_analyzer_autotest.c
new file mode 100644
index 0000000..7786236
--- /dev/null
+++ b/src/multichannel/tests/firpfbch_crcf_analyzer_autotest.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: validate analysis correctness
+//
+void autotest_firpfbch_crcf_analysis()
+{
+    float tol = 1e-4f;              // error tolerance
+    unsigned int num_channels=4;    // number of channels
+    unsigned int p=5;               // filter length (symbols)
+    unsigned int num_symbols=40;    // number of symbols
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence.
+    //        For the sake of consistency, use pseudo-random values
+    //        chosen from m-sequences
+    unsigned int h_len = p*num_channels;
+    float h[h_len];
+    msequence ms = msequence_create_default(6);
+    for (i=0; i<h_len; i++)
+        h[i] = (float)msequence_generate_symbol(ms, 2) - 1.5f; // (-1.5, -0.5, 0.5, 1.5)
+    msequence_destroy(ms);
+
+    // create filterbank object
+    firpfbch_crcf q = firpfbch_crcf_create(LIQUID_ANALYZER, num_channels, p, h);
+
+    // generate filter object
+    firfilt_crcf f = firfilt_crcf_create(h, h_len);
+
+    // allocate memory for arrays
+    float complex y[num_samples];                   // time-domain input
+    float complex Y0[num_symbols][num_channels];    // channelized output
+    float complex Y1[num_symbols][num_channels];    // channelized output
+
+    // generate input sequence (complex noise)
+    ms = msequence_create_default(7);
+    for (i=0; i<num_samples; i++) {
+        y[i] = 0.1f * M_SQRT1_2 * ((float)msequence_generate_symbol(ms,2) - 1.5f) +
+               0.1f * M_SQRT1_2 * ((float)msequence_generate_symbol(ms,2) - 1.5f)*_Complex_I;
+    }
+    msequence_destroy(ms);
+
+    // 
+    // run analysis filter bank
+    //
+
+    for (i=0; i<num_symbols; i++)
+        firpfbch_crcf_analyzer_execute(q, &y[i*num_channels], &Y0[i][0]);
+
+
+    // 
+    // run traditional down-converter (inefficient)
+    //
+
+    float dphi; // carrier frequency
+    unsigned int n=0;
+    for (i=0; i<num_channels; i++) {
+
+        // reset filter
+        firfilt_crcf_reset(f);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+            // push down-converted sample into filter
+            firfilt_crcf_push(f, y[j]*cexpf(-_Complex_I*j*dphi));
+
+            // compute output at the appropriate sample time
+            assert(n<num_symbols);
+            if ( ((j+1)%num_channels)==0 ) {
+                firfilt_crcf_execute(f, &Y1[n][i]);
+                n++;
+            }
+        }
+        assert(n==num_symbols);
+
+    }
+
+    // destroy objects
+    firfilt_crcf_destroy(f);
+    firpfbch_crcf_destroy(q);
+
+    if (liquid_autotest_verbose) {
+        // print filterbank channelizer
+        printf("\n");
+        printf("filterbank channelizer:\n");
+        for (i=0; i<num_symbols; i++) {
+            printf("%3u: ", i);
+            for (j=0; j<num_channels; j++) {
+                printf("  %8.5f+j%8.5f, ", crealf(Y0[i][j]), cimagf(Y0[i][j]));
+            }
+            printf("\n");
+        }
+
+        // print traditional channelizer
+        printf("\n");
+        printf("traditional channelizer:\n");
+        for (i=0; i<num_symbols; i++) {
+            printf("%3u: ", i);
+            for (j=0; j<num_channels; j++) {
+                printf("  %8.5f+j%8.5f, ", crealf(Y1[i][j]), cimagf(Y1[i][j]));
+            }
+            printf("\n");
+        }
+    }
+
+    // compare results
+    for (i=0; i<num_symbols; i++) {
+        for (j=0; j<num_channels; j++) {
+            CONTEND_DELTA( crealf(Y0[i][j]), crealf(Y1[i][j]), tol );
+            CONTEND_DELTA( cimagf(Y0[i][j]), cimagf(Y1[i][j]), tol );
+        }
+    }
+
+}
+
+
diff --git a/src/multichannel/tests/firpfbch_crcf_synthesizer_autotest.c b/src/multichannel/tests/firpfbch_crcf_synthesizer_autotest.c
new file mode 100644
index 0000000..038b238
--- /dev/null
+++ b/src/multichannel/tests/firpfbch_crcf_synthesizer_autotest.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: validate synthesis correctness
+//
+void autotest_firpfbch_crcf_synthesis()
+{
+    // options
+    float tol = 1e-4f;              // error tolerance
+    unsigned int num_channels=4;    // number of channels
+    unsigned int p=5;               // filter length (symbols)
+    unsigned int num_symbols=40;    // number of symbols
+
+    // derived values
+    unsigned int num_samples = num_channels * num_symbols;
+
+    unsigned int i;
+    unsigned int j;
+
+    // generate filter
+    // NOTE : these coefficients can be random; the purpose of this
+    //        exercise is to demonstrate mathematical equivalence.
+    //        For the sake of consistency, use pseudo-random values
+    //        chosen from m-sequences
+    unsigned int h_len = p*num_channels;
+    float h[h_len];
+    msequence ms = msequence_create_default(6);
+    for (i=0; i<h_len; i++)
+        h[i] = (float)msequence_generate_symbol(ms, 2) - 1.5f; // (-1.5, -0.5, 0.5, 1.5)
+    msequence_destroy(ms);
+
+    // create filter object
+    firfilt_crcf f = firfilt_crcf_create(h, h_len);
+
+    // create filterbank channelizer object
+    firpfbch_crcf q = firpfbch_crcf_create(LIQUID_SYNTHESIZER, num_channels, p, h);
+
+    float complex Y[num_symbols][num_channels];     // channelized input
+    float complex y0[num_samples];                  // time-domain output
+    float complex y1[num_samples];                  // time-domain output
+
+    // generate input sequence (complex noise)
+    ms = msequence_create_default(7);
+    for (i=0; i<num_symbols; i++) {
+        for (j=0; j<num_channels; j++) {
+            Y[i][j] = 0.1f * M_SQRT1_2 * ((float)msequence_generate_symbol(ms,2) - 1.5f) +
+                      0.1f * M_SQRT1_2 * ((float)msequence_generate_symbol(ms,2) - 1.5f)*_Complex_I;
+        }
+    }
+    msequence_destroy(ms);
+
+    // 
+    // run synthesis filter bank
+    //
+
+    for (i=0; i<num_symbols; i++)
+        firpfbch_crcf_synthesizer_execute(q, &Y[i][0], &y0[i*num_channels]);
+
+    // 
+    // run traditional up-converter (inefficient)
+    //
+
+    // clear output array
+    for (i=0; i<num_samples; i++)
+        y1[i] = 0.0f;
+
+    unsigned int n;
+    float dphi; // carrier frequency
+    float complex y_hat;
+    for (i=0; i<num_channels; i++) {
+        // reset filter
+        firfilt_crcf_reset(f);
+
+        // set center frequency
+        dphi = 2.0f * M_PI * (float)i / (float)num_channels;
+
+        // reset input symbol counter
+        n=0;
+
+        for (j=0; j<num_samples; j++) {
+
+            // interpolate sequence
+            if ( (j%num_channels)==0 ) {
+                assert(n<num_symbols);
+                firfilt_crcf_push(f, Y[n][i]);
+                n++;
+            } else {
+                firfilt_crcf_push(f, 0);
+            }
+            firfilt_crcf_execute(f, &y_hat);
+
+            // accumulate up-converted sample
+            y1[j] += y_hat * cexpf(_Complex_I*j*dphi);
+        }
+        assert(n==num_symbols);
+    }
+
+    // destroy objects
+    firfilt_crcf_destroy(f);
+    firpfbch_crcf_destroy(q);
+
+    // 
+    // compare results
+    // 
+    for (i=0; i<num_samples; i++) {
+
+        // print channelizer outputs
+        if (liquid_autotest_verbose) {
+            printf("%3u: old:%8.5f+j%8.5f, firpfbch:%8.5f+j%8.5f, error:%12.4e+j%12.4e\n",
+                i,
+                crealf(y0[i]),       cimagf(y0[i]),
+                crealf(y1[i]),       cimagf(y1[i]),
+                crealf(y1[i]-y0[i]), cimagf(y1[i]-y0[i]));
+        }
+
+
+        CONTEND_DELTA( crealf(y0[i]), crealf(y1[i]), tol );
+        CONTEND_DELTA( cimagf(y0[i]), cimagf(y1[i]), tol );
+    }
+}
+
diff --git a/src/multichannel/tests/ofdmframesync_autotest.c b/src/multichannel/tests/ofdmframesync_autotest.c
new file mode 100644
index 0000000..780e9f4
--- /dev/null
+++ b/src/multichannel/tests/ofdmframesync_autotest.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <complex.h>
+#include <math.h>
+#include <assert.h>
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+
+// internal callback
+//  _X          :   subcarrier symbols
+//  _p          :   subcarrier allocation
+//  _M          :   number of subcarriers
+//  _userdata   :   user-defined data structure
+int ofdmframesync_autotest_callback(float complex * _X,
+                                    unsigned char * _p,
+                                    unsigned int    _M,
+                                    void * _userdata)
+{
+    printf("******** callback invoked!\n");
+
+    // type cast _userdata as complex float array
+    float complex * X = (float complex *)_userdata;
+
+    // copy values and return
+    memmove(X, _X, _M*sizeof(float complex));
+
+    // return
+    return 0;
+}
+
+
+// Helper function to keep code base small
+//  _num_subcarriers    :   number of subcarriers
+//  _cp_len             :   cyclic prefix lenght
+//  _taper_len          :   taper length
+void ofdmframesync_acquire_test(unsigned int _num_subcarriers,
+                                unsigned int _cp_len,
+                                unsigned int _taper_len)
+{
+    // options
+    unsigned int M         = _num_subcarriers;  // number of subcarriers
+    unsigned int cp_len    = _cp_len;           // cyclic prefix lenght
+    unsigned int taper_len = _taper_len;        // taper length
+    float tol              = 1e-2f;             // error tolerance
+
+    //
+    float dphi = 1.0f / (float)M;       // carrier frequency offset
+
+    // subcarrier allocation (initialize to default)
+    unsigned char p[M];
+    ofdmframe_init_default_sctype(M, p);
+
+    // derived values
+    unsigned int num_samples = (3 + 1)*(M + cp_len);
+
+    // create synthesizer/analyzer objects
+    ofdmframegen fg = ofdmframegen_create(M, cp_len, taper_len, p);
+    //ofdmframegen_print(fg);
+
+    float complex X[M];         // original data sequence
+    float complex X_test[M];    // recovered data sequence
+    ofdmframesync fs = ofdmframesync_create(M,cp_len,taper_len,p,ofdmframesync_autotest_callback,(void*)X_test);
+
+    unsigned int i;
+    float complex y[num_samples];   // frame samples
+
+    // assemble full frame
+    unsigned int n=0;
+
+    // write first S0 symbol
+    ofdmframegen_write_S0a(fg, &y[n]);
+    n += M + cp_len;
+
+    // write second S0 symbol
+    ofdmframegen_write_S0b(fg, &y[n]);
+    n += M + cp_len;
+
+    // write S1 symbol
+    ofdmframegen_write_S1( fg, &y[n]);
+    n += M + cp_len;
+
+    // generate data symbol (random)
+    for (i=0; i<M; i++) {
+        X[i]      = cexpf(_Complex_I*2*M_PI*randf());
+        X_test[i] = 0.0f;
+    }
+
+    // write data symbol
+    ofdmframegen_writesymbol(fg, X, &y[n]);
+    n += M + cp_len;
+
+    // validate frame length
+    assert(n == num_samples);
+
+    // add carrier offset
+    for (i=0; i<num_samples; i++)
+        y[i] *= cexpf(_Complex_I*dphi*i);
+
+    // run receiver
+    ofdmframesync_execute(fs,y,num_samples);
+
+    // check output
+    for (i=0; i<M; i++) {
+        if (p[i] == OFDMFRAME_SCTYPE_DATA) {
+            float e = crealf( (X[i] - X_test[i])*conjf(X[i] - X_test[i]) );
+            CONTEND_DELTA( cabsf(e), 0.0f, tol );
+        }
+    }
+
+    // destroy objects
+    ofdmframegen_destroy(fg);
+    ofdmframesync_destroy(fs);
+}
+
+//
+void autotest_ofdmframesync_acquire_n64()   { ofdmframesync_acquire_test(64,  8,  0); }
+void autotest_ofdmframesync_acquire_n128()  { ofdmframesync_acquire_test(128, 16, 0); }
+void autotest_ofdmframesync_acquire_n256()  { ofdmframesync_acquire_test(256, 32, 0); }
+void autotest_ofdmframesync_acquire_n512()  { ofdmframesync_acquire_test(512, 64, 0); }
+
diff --git a/src/nco/bench/nco_benchmark.c b/src/nco/bench/nco_benchmark.c
new file mode 100644
index 0000000..e23b64e
--- /dev/null
+++ b/src/nco/bench/nco_benchmark.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include <string.h>
+
+#include "liquid.h"
+
+void benchmark_nco_sincos(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations)
+{
+    float s, c;
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i;
+
+    // increase number of iterations for NCO
+    *_num_iterations *= 100;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        nco_crcf_sincos(p, &s, &c);
+        nco_crcf_step(p);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    nco_crcf_destroy(p);
+}
+
+void benchmark_nco_mix_up(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations)
+{
+    float complex x[16],  y[16];
+    memset(x, 0, 16*sizeof(float complex));
+
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i, j;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        for (j=0; j<16; j++) {
+            nco_crcf_mix_up(p, x[j], &y[j]);
+            nco_crcf_step(p);
+        }
+
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    *_num_iterations *= 16;
+    nco_crcf_destroy(p);
+}
+
+void benchmark_nco_mix_block_up(struct rusage *_start,
+                                struct rusage *_finish,
+                                unsigned long int *_num_iterations)
+{
+    float complex x[16], y[16];
+    memset(x, 0, 16*sizeof(float complex));
+
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        nco_crcf_mix_block_up(p, x, y, 16);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    *_num_iterations *= 16;
+    nco_crcf_destroy(p);
+}
+
diff --git a/src/nco/bench/vco_benchmark.c b/src/nco/bench/vco_benchmark.c
new file mode 100644
index 0000000..094abfa
--- /dev/null
+++ b/src/nco/bench/vco_benchmark.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include <string.h>
+
+#include "liquid.h"
+
+void benchmark_vco_sincos(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations)
+{
+    float s, c;
+    nco_crcf p = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        nco_crcf_sincos(p, &s, &c);
+        nco_crcf_step(p);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    nco_crcf_destroy(p);
+}
+
+void benchmark_vco_mix_up(struct rusage *_start,
+                          struct rusage *_finish,
+                          unsigned long int *_num_iterations)
+{
+    float complex x[16],  y[16];
+    memset(x, 0, 16*sizeof(float complex));
+
+    nco_crcf p = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i, j;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        for (j=0; j<16; j++) {
+            nco_crcf_mix_up(p, x[j], &y[j]);
+            nco_crcf_step(p);
+        }
+
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    *_num_iterations *= 16;
+    nco_crcf_destroy(p);
+}
+
+void benchmark_vco_mix_block_up(struct rusage *_start,
+                                struct rusage *_finish,
+                                unsigned long int *_num_iterations)
+{
+    float complex x[16], y[16];
+    memset(x, 0, 16*sizeof(float complex));
+
+    nco_crcf p = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_set_phase(p, 0.0f);
+    nco_crcf_set_frequency(p, 0.1f);
+
+    unsigned int i;
+
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        nco_crcf_mix_block_up(p, x, y, 16);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+
+    *_num_iterations *= 16;
+    nco_crcf_destroy(p);
+}
+
diff --git a/src/nco/src/nco.c b/src/nco/src/nco.c
new file mode 100644
index 0000000..546edb0
--- /dev/null
+++ b/src/nco/src/nco.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Numerically-controlled oscillator (nco) with internal phase-locked
+// loop (pll) implementation
+//
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#define NCO_PLL_BANDWIDTH_DEFAULT   (0.1)
+#define NCO_PLL_GAIN_DEFAULT        (1000)
+
+#define LIQUID_DEBUG_NCO            (0)
+
+struct NCO(_s) {
+    liquid_ncotype type;
+    T theta;            // NCO phase
+    T d_theta;          // NCO frequency
+    T sintab[256];      // sine table
+    unsigned int index; // table index
+    T sine;
+    T cosine;
+    void (*compute_sincos)(NCO() _q);
+
+    // phase-locked loop
+    T alpha;
+    T beta;
+};
+
+// create nco/vco object
+NCO() NCO(_create)(liquid_ncotype _type)
+{
+    NCO() q = (NCO()) malloc(sizeof(struct NCO(_s)));
+    q->type = _type;
+
+    // initialize sine table
+    unsigned int i;
+    for (i=0; i<256; i++)
+        q->sintab[i] = SIN(2.0f*M_PI*(float)(i)/256.0f);
+
+    // set default pll bandwidth
+    NCO(_pll_set_bandwidth)(q, NCO_PLL_BANDWIDTH_DEFAULT);
+
+    // set internal method
+    if (q->type == LIQUID_NCO) {
+        q->compute_sincos = &NCO(_compute_sincos_nco);
+    } else if (q->type == LIQUID_VCO) {
+        q->compute_sincos = &NCO(_compute_sincos_vco);
+    } else {
+        fprintf(stderr,"error: NCO(_create)(), unknown type : %u\n", q->type);
+        exit(1);
+    }
+
+    // reset object and return
+    NCO(_reset)(q);
+    return q;
+}
+
+// destroy nco object
+void NCO(_destroy)(NCO() _q)
+{
+    free(_q);
+}
+
+// reset internal state of nco object
+void NCO(_reset)(NCO() _q)
+{
+    _q->theta = 0;
+    _q->d_theta = 0;
+
+    // reset sine table index
+    _q->index = 0;
+
+    // set internal sine, cosine values
+    _q->sine = 0;
+    _q->cosine = 1;
+
+    // reset pll filter state
+    NCO(_pll_reset)(_q);
+}
+
+// set frequency of nco object
+void NCO(_set_frequency)(NCO() _q,
+                         T _f)
+{
+    _q->d_theta = _f;
+}
+
+// adjust frequency of nco object
+void NCO(_adjust_frequency)(NCO() _q,
+                            T _df)
+{
+    _q->d_theta += _df;
+}
+
+// set phase of nco object, constraining phase
+void NCO(_set_phase)(NCO() _q, T _phi)
+{
+    _q->theta = _phi;
+    NCO(_constrain_phase)(_q);
+}
+
+// adjust phase of nco object, constraining phase
+void NCO(_adjust_phase)(NCO() _q, T _dphi)
+{
+    _q->theta += _dphi;
+    NCO(_constrain_phase)(_q);
+}
+
+// increment internal phase of nco object
+void NCO(_step)(NCO() _q)
+{
+    _q->theta += _q->d_theta;
+    NCO(_constrain_phase)(_q);
+}
+
+// get phase
+T NCO(_get_phase)(NCO() _q)
+{
+    return _q->theta;
+}
+
+// ge frequency
+T NCO(_get_frequency)(NCO() _q)
+{
+    return _q->d_theta;
+}
+
+
+// TODO : compute sine, cosine internally
+T NCO(_sin)(NCO() _q)
+{
+    // compute internal sin, cos
+    _q->compute_sincos(_q);
+
+    // return resulting cosine component
+    return _q->sine;
+}
+
+T NCO(_cos)(NCO() _q)
+{
+    // compute internal sin, cos
+    _q->compute_sincos(_q);
+
+    // return resulting cosine component
+    return _q->cosine;
+}
+
+// compute sin, cos of internal phase
+void NCO(_sincos)(NCO() _q, T* _s, T* _c)
+{
+    // compute sine, cosine internally, calling implementation-
+    // specific function (nco, vco)
+    _q->compute_sincos(_q);
+
+    // return result
+    *_s = _q->sine;
+    *_c = _q->cosine;
+}
+
+// compute complex exponential of internal phase
+void NCO(_cexpf)(NCO() _q,
+                 TC * _y)
+{
+    // compute sine, cosine internally, calling implementation-
+    // specific function (nco, vco)
+    _q->compute_sincos(_q);
+
+    // set _y[0] to [cos(theta) + _Complex_I*sin(theta)]
+    *_y = _q->cosine + _Complex_I*(_q->sine);
+}
+
+// pll methods
+
+// reset pll state, retaining base frequency
+void NCO(_pll_reset)(NCO() _q)
+{
+}
+
+// set pll bandwidth
+void NCO(_pll_set_bandwidth)(NCO() _q,
+                             T     _bandwidth)
+{
+    // validate input
+    if (_bandwidth < 0.0f) {
+        fprintf(stderr,"error: nco_pll_set_bandwidth(), bandwidth must be positive\n");
+        exit(1);
+    }
+
+    _q->alpha = _bandwidth;         // frequency proportion
+    _q->beta  = sqrtf(_q->alpha);   // phase proportion
+}
+
+// advance pll phase
+//  _q      :   nco object
+//  _dphi   :   phase error
+void NCO(_pll_step)(NCO() _q,
+                    T     _dphi)
+{
+    // increase frequency proportional to error
+    NCO(_adjust_frequency)(_q, _dphi*_q->alpha);
+
+    // increase phase proportional to error
+    NCO(_adjust_phase)(_q, _dphi*_q->beta);
+
+    // constrain frequency
+    //NCO(_constrain_frequency)(_q);
+}
+
+// mixing functions
+
+// Rotate input vector up by NCO angle, y = x exp{+j theta}
+//  _q      :   nco object
+//  _x      :   input sample
+//  _y      :   output sample
+void NCO(_mix_up)(NCO() _q,
+                  TC _x,
+                  TC *_y)
+{
+    // compute sine, cosine internally, calling implementation-
+    // specific function (nco, vco)
+    _q->compute_sincos(_q);
+
+    // multiply _x by [cos(theta) + _Complex_I*sin(theta)]
+    *_y = _x * (_q->cosine + _Complex_I*(_q->sine));
+}
+
+// Rotate input vector down by NCO angle, y = x exp{-j theta}
+//  _q      :   nco object
+//  _x      :   input sample
+//  _y      :   output sample
+void NCO(_mix_down)(NCO() _q,
+                    TC _x,
+                    TC *_y)
+{
+    // compute sine, cosine internally
+    _q->compute_sincos(_q);
+
+    // multiply _x by [cos(-theta) + _Complex_I*sin(-theta)]
+    *_y = _x * (_q->cosine - _Complex_I*(_q->sine));
+}
+
+
+// Rotate input vector array up by NCO angle:
+//      y(t) = x(t) exp{+j (f*t + theta)}
+// TODO : implement NCO/VCO-specific versions
+//  _q      :   nco object
+//  _x      :   input array [size: _n x 1]
+//  _y      :   output sample [size: _n x 1]
+//  _n      :   number of input, output samples
+void NCO(_mix_block_up)(NCO() _q,
+                        TC *_x,
+                        TC *_y,
+                        unsigned int _n)
+{
+    unsigned int i;
+    // FIXME: this method should be more efficient but is causing occasional
+    //        errors so instead favor slower but more reliable algorithm
+#if 0
+    T theta =   _q->theta;
+    T d_theta = _q->d_theta;
+    for (i=0; i<_n; i++) {
+        // multiply _x[i] by [cos(theta) + _Complex_I*sin(theta)]
+        _y[i] = _x[i] * liquid_cexpjf(theta);
+        
+        theta += d_theta;
+    }
+
+    NCO(_set_phase)(_q, theta);
+#else
+    for (i=0; i<_n; i++) {
+        // mix single sample up
+        NCO(_mix_up)(_q, _x[i], &_y[i]);
+
+        // step NCO phase
+        NCO(_step)(_q);
+    }
+#endif
+}
+
+// Rotate input vector array down by NCO angle:
+//      y(t) = x(t) exp{-j (f*t + theta)}
+// TODO : implement NCO/VCO-specific versions
+//  _q      :   nco object
+//  _x      :   input array [size: _n x 1]
+//  _y      :   output sample [size: _n x 1]
+//  _n      :   number of input, output samples
+void NCO(_mix_block_down)(NCO() _q,
+                          TC *_x,
+                          TC *_y,
+                          unsigned int _n)
+{
+    unsigned int i;
+    // FIXME: this method should be more efficient but is causing occasional
+    //        errors so instead favor slower but more reliable algorithm
+#if 0
+    T theta =   _q->theta;
+    T d_theta = _q->d_theta;
+    for (i=0; i<_n; i++) {
+        // multiply _x[i] by [cos(-theta) + _Complex_I*sin(-theta)]
+        _y[i] = _x[i] * liquid_cexpjf(-theta);
+        
+        theta += d_theta;
+    }
+
+    NCO(_set_phase)(_q, theta);
+#else
+    for (i=0; i<_n; i++) {
+        // mix single sample down
+        NCO(_mix_down)(_q, _x[i], &_y[i]);
+
+        // step NCO phase
+        NCO(_step)(_q);
+    }
+#endif
+}
+
+//
+// internal methods
+//
+
+// constrain frequency of NCO object to be in (-pi,pi)
+void NCO(_constrain_frequency)(NCO() _q)
+{
+    if (_q->d_theta > M_PI)
+        _q->d_theta -= 2*M_PI;
+    else if (_q->d_theta < -M_PI)
+        _q->d_theta += 2*M_PI;
+}
+
+// constrain phase of NCO object to be in (-pi,pi)
+void NCO(_constrain_phase)(NCO() _q)
+{
+    if (_q->theta > M_PI)
+        _q->theta -= 2*M_PI;
+    else if (_q->theta < -M_PI)
+        _q->theta += 2*M_PI;
+}
+
+// compute sin, cos of internal phase of nco
+void NCO(_compute_sincos_nco)(NCO() _q)
+{
+    // assume phase is constrained to be in (-pi,pi)
+
+    // compute index
+    // NOTE : 40.743665 ~ 256 / (2*pi)
+    // NOTE : add 512 to ensure positive value, add 0.5 for rounding precision
+    // TODO : move away from floating-point specific code
+    _q->index = ((unsigned int)((_q->theta)*40.743665f + 512.0f + 0.5f))&0xff;
+    assert(_q->index < 256);
+    
+    _q->sine = _q->sintab[_q->index];
+    _q->cosine = _q->sintab[(_q->index+64)&0xff];
+}
+
+// compute sin, cos of internal phase of vco
+void NCO(_compute_sincos_vco)(NCO() _q)
+{
+    _q->sine   = SIN(_q->theta);
+    _q->cosine = COS(_q->theta);
+}
+
diff --git a/src/nco/src/nco.utilities.c b/src/nco/src/nco.utilities.c
new file mode 100644
index 0000000..036d752
--- /dev/null
+++ b/src/nco/src/nco.utilities.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// nco.utilities.c
+//
+// Numerically-controlled oscillator (nco) utilities
+//
+
+#include <math.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+// unwrap phase of array (basic)
+void liquid_unwrap_phase(float * _theta,
+                         unsigned int _n)
+{
+    unsigned int i;
+    for (i=1; i<_n; i++) {
+        while ( (_theta[i] - _theta[i-1]) >  M_PI ) _theta[i] -= 2*M_PI;
+        while ( (_theta[i] - _theta[i-1]) < -M_PI ) _theta[i] += 2*M_PI;
+    }
+}
+
+// unwrap phase of array (advanced)
+void liquid_unwrap_phase2(float * _theta,
+                          unsigned int _n)
+{
+    fprintf(stderr,"warning: liquid_unwrap_phase2() has not yet been tested!\n");
+
+    unsigned int i;
+
+    // make an initial estimate of phase difference
+    float dphi = 0.0f;
+    for (i=1; i<_n; i++)
+        dphi += _theta[i] - _theta[i-1];
+
+    dphi /= (float)(_n-1);
+
+    for (i=1; i<_n; i++) {
+        while ( (_theta[i] - _theta[i-1]) >  M_PI+dphi ) _theta[i] -= 2*M_PI;
+        while ( (_theta[i] - _theta[i-1]) < -M_PI+dphi ) _theta[i] += 2*M_PI;
+    }
+}
+
+
+
diff --git a/src/nco/src/nco_crcf.c b/src/nco/src/nco_crcf.c
new file mode 100644
index 0000000..5b9a292
--- /dev/null
+++ b/src/nco/src/nco_crcf.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// numerically-controlled oscillator (nco) API, floating point precision
+//
+
+#include "liquid.internal.h"
+
+#define NCO(name)   LIQUID_CONCAT(nco_crcf,name)
+#define T           float
+#define TC          float complex
+
+#define SIN         sinf
+#define COS         cosf
+
+#include "nco.c"
diff --git a/src/nco/tests/data/nco_sincos_fsqrt1_2.c b/src/nco/tests/data/nco_sincos_fsqrt1_2.c
new file mode 100644
index 0000000..c05c898
--- /dev/null
+++ b/src/nco/tests/data/nco_sincos_fsqrt1_2.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest nco sincos data for f=0.707106781187
+//
+
+#include <complex.h>
+
+float complex nco_sincos_fsqrt1_2[256] = {
+    1.000000000000 + _Complex_I*  0.000000000000,
+    0.760244597076 + _Complex_I*  0.649636939080,
+    0.155943694765 + _Complex_I*  0.987765945993,
+   -0.523133894289 + _Complex_I*  0.852250508152,
+   -0.951363128126 + _Complex_I*  0.308071742363,
+   -0.923403461740 + _Complex_I* -0.383830752866,
+   -0.452661857292 + _Complex_I* -0.891682254479,
+    0.235135999123 + _Complex_I* -0.971962479686,
+    0.810183603115 + _Complex_I* -0.586176193003,
+    0.996739414692 + _Complex_I*  0.080687912355,
+    0.705347906308 + _Complex_I*  0.708861291838,
+    0.075734454968 + _Complex_I*  0.997128022037,
+   -0.590194485905 + _Complex_I*  0.807261090854,
+   -0.973118793234 + _Complex_I*  0.230303743466,
+   -0.889422123833 + _Complex_I* -0.457086737542,
+   -0.379237935093 + _Complex_I* -0.925299188688,
+    0.312794941512 + _Complex_I* -0.949820680215,
+    0.854839263647 + _Complex_I* -0.518892891961,
+    0.986978921600 + _Complex_I*  0.160849645067,
+    0.645851521500 + _Complex_I*  0.763463039168,
+   -0.004968662133 + _Complex_I*  0.999987656122,
+   -0.653406318582 + _Complex_I*  0.757007386250,
+   -0.988528584662 + _Complex_I*  0.151033894564,
+   -0.849640712505 + _Complex_I* -0.527361981615,
+   -0.303340937614 + _Complex_I* -0.952882089016,
+    0.388414094720 + _Complex_I* -0.921484938034,
+    0.893920371491 + _Complex_I* -0.448225801838,
+    0.970782170565 + _Complex_I*  0.239962449800,
+    0.582143428727 + _Complex_I*  0.813086113761,
+   -0.085639377739 + _Complex_I*  0.996326200087,
+   -0.712357177173 + _Complex_I*  0.701817107322,
+   -0.997492012529 + _Complex_I*  0.070779127866,
+   -0.804318649129 + _Complex_I* -0.594198208230,
+   -0.225465802126 + _Complex_I* -0.974251082664,
+    0.461500333346 + _Complex_I* -0.887140035351,
+    0.927172072075 + _Complex_I* -0.374635754786,
+    0.948254783364 + _Complex_I*  0.317510418456,
+    0.514639079331 + _Complex_I*  0.857406915079,
+   -0.165751624352 + _Complex_I*  0.986167530912,
+   -0.766662633072 + _Complex_I*  0.642050159295,
+   -0.999950624793 + _Complex_I* -0.009937201600,
+   -0.753751486611 + _Complex_I* -0.657159566948,
+   -0.146120365674 + _Complex_I* -0.989266818778,
+    0.531577049558 + _Complex_I* -0.847009941136,
+    0.954377525386 + _Complex_I* -0.298602644058,
+    0.919543664932 + _Complex_I*  0.392987847501,
+    0.443778680694 + _Complex_I*  0.896136419616,
+   -0.244782976343 + _Complex_I*  0.969577895010,
+   -0.815968551135 + _Complex_I*  0.578096292635,
+   -0.995888388425 + _Complex_I* -0.090588728879,
+   -0.698268982046 + _Complex_I* -0.715835476008,
+   -0.065822053387 + _Complex_I* -0.997831377181,
+    0.598187261134 + _Complex_I* -0.801356350581,
+    0.975359320021 + _Complex_I* -0.220622294542,
+    0.884836045372 + _Complex_I*  0.465902535742,
+    0.370024325563 + _Complex_I*  0.929022065665,
+   -0.322218056780 + _Complex_I*  0.946665476229,
+   -0.859953399058 + _Complex_I*  0.510372561418,
+   -0.985331793961 + _Complex_I* -0.170649511602,
+   -0.638232946313 + _Complex_I* -0.769843299796,
+    0.014905495740 + _Complex_I* -0.999888906928,
+    0.660896591520 + _Complex_I* -0.750476978539,
+    0.989980630117 + _Complex_I* -0.141203229401,
+    0.844358258992 + _Complex_I*  0.535778994056,
+    0.293856978673 + _Complex_I*  0.955849400316,
+   -0.397551898294 + _Complex_I*  0.917579690361,
+   -0.898330344143 + _Complex_I*  0.439320603650,
+   -0.968349682754 + _Complex_I* -0.249597459743,
+   -0.574034884644 + _Complex_I* -0.818830844077,
+    0.095535843587 + _Complex_I* -0.995425990514,
+    0.719296102472 + _Complex_I* -0.694703618076,
+    0.998146107617 + _Complex_I* -0.060863353909,
+    0.798374268344 + _Complex_I*  0.602161546138,
+    0.215773340288 + _Complex_I*  0.976443477945,
+   -0.470293236050 + _Complex_I*  0.882510210777,
+   -0.930849123784 + _Complex_I*  0.365403761270,
+   -0.945052798049 + _Complex_I* -0.326917740264,
+   -0.506093443552 + _Complex_I* -0.862478652718,
+    0.175543185898 + _Complex_I* -0.984471731379,
+    0.773004960816 + _Complex_I* -0.634399976792,
+    0.999802504049 + _Complex_I*  0.019873421897,
+    0.747183942875 + _Complex_I*  0.664617300038,
+    0.136282607136 + _Complex_I*  0.990670001056,
+   -0.539967711374 + _Complex_I*  0.841685731538,
+   -0.957297677470 + _Complex_I*  0.289104058618,
+   -0.915593062806 + _Complex_I* -0.402106134423,
+   -0.434851680767 + _Complex_I* -0.900502090911,
+    0.254405781142 + _Complex_I* -0.967097564117,
+    0.821672921923 + _Complex_I* -0.569959305019,
+    0.994939017768 + _Complex_I*  0.100480599729,
+    0.691121103433 + _Complex_I*  0.722738971129,
+    0.055903151852 + _Complex_I*  0.998436196065,
+   -0.606120965124 + _Complex_I*  0.795372476037,
+   -0.977503529671 + _Complex_I*  0.210919059075,
+   -0.880162588986 + _Complex_I* -0.474672325874,
+   -0.360774175978 + _Complex_I* -0.932653201328,
+    0.331609352882 + _Complex_I* -0.943416788636,
+    0.864982613715 + _Complex_I* -0.501801831374,
+    0.983587364400 + _Complex_I*  0.180432526426,
+    0.630551345359 + _Complex_I*  0.776147538079,
+   -0.024840857424 + _Complex_I*  0.999691418290,
+   -0.668321600646 + _Complex_I*  0.743872460917,
+   -0.991334914576 + _Complex_I*  0.131358620360,
+   -0.838992424751 + _Complex_I* -0.544143098100,
+   -0.284344001234 + _Complex_I* -0.958722321093,
+    0.406650443454 + _Complex_I* -0.913583831314,
+    0.902651606302 + _Complex_I* -0.430372022371,
+    0.965821570012 + _Complex_I*  0.259207821834,
+    0.565869654379 + _Complex_I*  0.824494714509,
+   -0.105422875231 + _Complex_I*  0.994427482212,
+   -0.726163996983 + _Complex_I*  0.687521526561,
+   -0.998701635364 + _Complex_I*  0.050941569671,
+   -0.792351047769 + _Complex_I* -0.610065420344,
+   -0.206059570743 + _Complex_I* -0.978539449029,
+    0.479039697103 + _Complex_I* -0.877793237955,
+    0.934434253757 + _Complex_I* -0.356135683981,
+    0.941757488380 + _Complex_I*  0.336292778810,
+    0.497497830836 + _Complex_I*  0.867465220233,
+   -0.185317412481 + _Complex_I*  0.982678714856,
+   -0.779270954001 + _Complex_I*  0.626687147028,
+   -0.999555652393 + _Complex_I* -0.029807679686,
+   -0.740542614416 + _Complex_I* -0.672009401894,
+   -0.126431390635 + _Complex_I* -0.991975354261,
+    0.548305051154 + _Complex_I* -0.836278405125,
+    0.960123296014 + _Complex_I* -0.279576924033,
+    0.911552045487 + _Complex_I*  0.411184713198,
+    0.425881739056 + _Complex_I*  0.904778837252,
+   -0.264003463266 + _Complex_I*  0.964521731940,
+   -0.827296152171 + _Complex_I*  0.561766033686,
+   -0.993891396472 + _Complex_I* -0.110362548078,
+   -0.683904976326 + _Complex_I* -0.729571095478,
+   -0.045978729857 + _Complex_I* -0.998942418962,
+    0.613994814417 + _Complex_I* -0.789310058132,
+    0.979551210443 + _Complex_I* -0.201194995263,
+    0.875402216180 + _Complex_I*  0.483395241917,
+    0.351488399794 + _Complex_I*  0.936192237102,
+   -0.340967902424 + _Complex_I*  0.940074938245,
+   -0.869926410981 + _Complex_I*  0.493181548192,
+   -0.981745805180 + _Complex_I* -0.190197723464,
+   -0.622807477199 + _Complex_I* -0.782375131472,
+    0.034773766063 + _Complex_I* -0.999395209711,
+    0.675680612738 + _Complex_I* -0.737194485581,
+    0.992591304302 + _Complex_I* -0.121501039602,
+    0.833543739662 + _Complex_I*  0.552453467788,
+    0.274802944706 + _Complex_I*  0.961500567644,
+   -0.415708831715 + _Complex_I*  0.909497755486,
+   -0.906883731242 + _Complex_I*  0.421380941677,
+   -0.963198081990 + _Complex_I* -0.268792587045,
+   -0.557648544251 + _Complex_I* -0.830077165747,
+    0.115299496322 + _Complex_I* -0.993330773785,
+    0.732960182500 + _Complex_I* -0.680271542011,
+    0.999158540912 + _Complex_I* -0.041014754931,
+    0.786249582201 + _Complex_I*  0.617909050337,
+    0.196325452731 + _Complex_I*  0.980538788937,
+   -0.487738852787 + _Complex_I*  0.872989582688,
+   -0.937927107962 + _Complex_I*  0.346832438147,
+   -0.938369179770 + _Complex_I* -0.345634608305,
+   -0.488853090003 + _Complex_I* -0.872366125199,
+    0.195073338893 + _Complex_I* -0.980788658404,
+    0.785459993857 + _Complex_I* -0.618912431650,
+    0.999210094204 + _Complex_I*  0.039738993954,
+    0.733828157068 + _Complex_I*  0.679335142543,
+    0.116567688981 + _Complex_I*  0.993182749491,
+   -0.556588245585 + _Complex_I*  0.830788495874,
+   -0.962854101984 + _Complex_I*  0.270022181112,
+   -0.907421012027 + _Complex_I* -0.420222687313,
+   -0.416869741348 + _Complex_I* -0.908966236308,
+    0.273575074938 + _Complex_I* -0.961850652842,
+    0.832837686581 + _Complex_I* -0.553517287725,
+    0.992745627990 + _Complex_I*  0.120233598080,
+    0.676621313319 + _Complex_I*  0.736331174379,
+    0.036049767444 + _Complex_I*  0.999349995881,
+   -0.621808031469 + _Complex_I*  0.783169695533,
+   -0.981502160129 + _Complex_I*  0.191451063363,
+   -0.870555397043 + _Complex_I* -0.492070422480,
+   -0.342167913985 + _Complex_I* -0.939638823506,
+    0.350292781243 + _Complex_I* -0.936640255065,
+    0.874784302655 + _Complex_I* -0.484512563128,
+    0.979807298156 + _Complex_I*  0.199944138398,
+    0.615002106542 + _Complex_I*  0.788525464997,
+   -0.044703240778 + _Complex_I*  0.999000310442,
+   -0.682972901089 + _Complex_I*  0.730443711985,
+   -0.993749675226 + _Complex_I*  0.111631460566,
+   -0.828012741783 + _Complex_I* -0.560709282467,
+   -0.265234751275 + _Complex_I* -0.964183865617,
+    0.424726168556 + _Complex_I* -0.905321866379,
+    0.911026301038 + _Complex_I* -0.412348249441,
+    0.960479477760 + _Complex_I*  0.278350808877,
+    0.549372366100 + _Complex_I*  0.835577646521,
+   -0.125164731540 + _Complex_I*  0.992135973533,
+   -0.739683987895 + _Complex_I*  0.672954380365,
+   -0.999516779141 + _Complex_I*  0.031083889968,
+   -0.780070474162 + _Complex_I* -0.625691661556,
+   -0.186571947499 + _Complex_I* -0.982441300235,
+    0.496389844059 + _Complex_I* -0.868099719338,
+    0.941327341476 + _Complex_I* -0.337494942465,
+    0.934888206815 + _Complex_I*  0.354942306239,
+    0.480160074725 + _Complex_I*  0.877180883649,
+   -0.204810001732 + _Complex_I*  0.978801748665,
+   -0.791571469213 + _Complex_I*  0.611076598413,
+   -0.998765863605 + _Complex_I* -0.049666383980,
+   -0.727041233885 + _Complex_I* -0.686593798567,
+   -0.106692476220 + _Complex_I* -0.994292067512,
+    0.564816476695 + _Complex_I* -0.825216545916,
+    0.965489825714 + _Complex_I* -0.260440773388,
+    0.903200370366 + _Complex_I*  0.429219164264,
+    0.407816577581 + _Complex_I*  0.913063874573,
+   -0.283119670959 + _Complex_I*  0.959084590595,
+   -0.838296977925 + _Complex_I*  0.545213881703,
+   -0.991501825466 + _Complex_I* -0.130092774964,
+   -0.669270833677 + _Complex_I* -0.743018540273,
+   -0.026117245101 + _Complex_I* -0.999658886575,
+    0.629559844720 + _Complex_I* -0.776951994602,
+    0.983356186070 + _Complex_I* -0.181688225591,
+    0.865622610200 + _Complex_I*  0.500697010886,
+    0.332813638953 + _Complex_I*  0.942992620187,
+   -0.359583068506 + _Complex_I*  0.933113078273,
+   -0.879555809016 + _Complex_I*  0.475795732248,
+   -0.977772034756 + _Complex_I* -0.209670808766,
+   -0.607136004174 + _Complex_I* -0.794597931306,
+    0.054628301030 + _Complex_I* -0.998506759480,
+    0.690197745585 + _Complex_I* -0.723620806770,
+    0.994809912959 + _Complex_I* -0.101750857877,
+    0.822399977304 + _Complex_I*  0.568909726873,
+    0.255640365802 + _Complex_I*  0.966771950034,
+   -0.433701563513 + _Complex_I*  0.901056576363,
+   -0.915078906610 + _Complex_I*  0.403274837644,
+   -0.957666025784 + _Complex_I* -0.287881543451,
+   -0.541041937199 + _Complex_I* -0.840995613658,
+    0.135017606689 + _Complex_I* -0.990843199444,
+    0.746334749190 + _Complex_I* -0.665570764195,
+    0.999776314675 + _Complex_I* -0.021149955457,
+    0.773814333841 + _Complex_I*  0.633412485465,
+    0.176800018209 + _Complex_I*  0.984246795047,
+   -0.504991816627 + _Complex_I*  0.863124130783,
+   -0.944634618526 + _Complex_I*  0.328124119019,
+   -0.931314913263 + _Complex_I* -0.364214953474,
+   -0.471419643442 + _Complex_I* -0.881909020125,
+    0.214526439499 + _Complex_I* -0.976718181850,
+    0.797604776560 + _Complex_I* -0.603180421109,
+    0.998223004463 + _Complex_I*  0.059588869430,
+    0.720182515080 + _Complex_I*  0.693784653169,
+    0.096806727533 + _Complex_I*  0.995303198781,
+   -0.572988931946 + _Complex_I*  0.819563105482,
+   -0.968030206924 + _Complex_I*  0.250833647030,
+   -0.898890537295 + _Complex_I* -0.438173255644,
+   -0.398723141757 + _Complex_I* -0.917071347403,
+    0.292636308795 + _Complex_I* -0.956223818347,
+    0.843673487096 + _Complex_I* -0.536856635585,
+    0.990160111727 + _Complex_I*  0.139939105133,
+    0.661854263264 + _Complex_I*  0.749632532778,
+    0.016182143669 + _Complex_I*  0.999869060541,
+   -0.637249488677 + _Complex_I*  0.770657569340,
+   -0.985113105181 + _Complex_I*  0.171907446032,
+   -0.860604342767 + _Complex_I* -0.509274155254,
+   -0.323426498437 + _Complex_I* -0.946253295957};
+
diff --git a/src/nco/tests/data/nco_sincos_fsqrt1_3.c b/src/nco/tests/data/nco_sincos_fsqrt1_3.c
new file mode 100644
index 0000000..5b94c0f
--- /dev/null
+++ b/src/nco/tests/data/nco_sincos_fsqrt1_3.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest nco sincos data for f=0.577350269190
+//
+
+#include <complex.h>
+
+float complex nco_sincos_fsqrt1_3[256] = {
+    1.000000000000 + _Complex_I*  0.000000000000,
+    0.837911827695 + _Complex_I*  0.545805614673,
+    0.404192461982 + _Complex_I*  0.914673960314,
+   -0.160556538575 + _Complex_I*  0.987026644990,
+   -0.673256907353 + _Complex_I*  0.739408639861,
+   -0.967703312923 + _Complex_I*  0.252091844688,
+   -0.948443195842 + _Complex_I* -0.316947163201,
+   -0.621720230463 + _Complex_I* -0.783239398290,
+   -0.093450273402 + _Complex_I* -0.995623948286,
+    0.465114051693 + _Complex_I* -0.885250766121,
+    0.872899403683 + _Complex_I* -0.487900226531,
+    0.997711417775 + _Complex_I*  0.067616025029,
+    0.799088991477 + _Complex_I*  0.601212760759,
+    0.341420816904 + _Complex_I*  0.939910541373,
+   -0.226927910067 + _Complex_I*  0.973911558424,
+   -0.721711976662 + _Complex_I*  0.692193486492,
+   -0.982534092802 + _Complex_I*  0.186082660346,
+   -0.924841898283 + _Complex_I* -0.380351762426,
+   -0.567337837836 + _Complex_I* -0.823485141190,
+   -0.025916270960 + _Complex_I* -0.999664117041,
+    0.523906737901 + _Complex_I* -0.851775633592,
+    0.903891575553 + _Complex_I* -0.427761638818,
+    0.990856146318 + _Complex_I*  0.134922560393,
+    0.756608593536 + _Complex_I*  0.653868057170,
+    0.277086432600 + _Complex_I*  0.960844997317,
+   -0.292260595196 + _Complex_I*  0.956338718496,
+   -0.766863651569 + _Complex_I*  0.641810049705,
+   -0.992867652562 + _Complex_I*  0.119221745066,
+   -0.897007447265 + _Complex_I* -0.442015429086,
+   -0.510358646627 + _Complex_I* -0.859961657176,
+    0.041736354516 + _Complex_I* -0.999128658738,
+    0.580301416814 + _Complex_I* -0.814401783915,
+    0.930746487037 + _Complex_I* -0.365665115738,
+    0.979465563334 + _Complex_I*  0.201611533010,
+    0.710665073638 + _Complex_I*  0.703530491955,
+    0.211483778128 + _Complex_I*  0.977381507697,
+   -0.356255555520 + _Complex_I*  0.934388558985,
+   -0.808505265432 + _Complex_I*  0.588488942775,
+   -0.998656693799 + _Complex_I*  0.051815132253,
+   -0.865067245650 + _Complex_I* -0.501655918438,
+   -0.451043459964 + _Complex_I* -0.892501987238,
+    0.109197945833 + _Complex_I* -0.994020024258,
+    0.634039960711 + _Complex_I* -0.773300283345,
+    0.953341218789 + _Complex_I* -0.301894883291,
+    0.963591805394 + _Complex_I*  0.267377696485,
+    0.661468722830 + _Complex_I*  0.749972751984,
+    0.144913127625 + _Complex_I*  0.989444382187,
+   -0.418619875580 + _Complex_I*  0.908161549379,
+   -0.846446217737 + _Complex_I*  0.532474225177,
+   -0.999874719120 + _Complex_I* -0.015828646941,
+   -0.829167488991 + _Complex_I* -0.559000246154,
+   -0.389663773211 + _Complex_I* -0.920957188933,
+    0.176159720196 + _Complex_I* -0.984361596661,
+    0.684876399442 + _Complex_I* -0.728659260209,
+    0.971572351007 + _Complex_I* -0.236742828316,
+    0.943307529299 + _Complex_I*  0.331920028273,
+    0.609244720899 + _Complex_I*  0.792982263393,
+    0.077679185906 + _Complex_I*  0.996978407026,
+   -0.479068103627 + _Complex_I*  0.877777735015,
+   -0.880512846507 + _Complex_I*  0.474022285486,
+   -0.996516153423 + _Complex_I* -0.083399975816,
+   -0.789472496379 + _Complex_I* -0.613785937817,
+   -0.326500531288 + _Complex_I* -0.945197018123,
+    0.242315182549 + _Complex_I* -0.970197584158,
+    0.732578046264 + _Complex_I* -0.680683043811,
+    0.985356436799 + _Complex_I* -0.170507162484,
+    0.918705579515 + _Complex_I*  0.394943107508,
+    0.554232105691 + _Complex_I*  0.832362164578,
+    0.010089693778 + _Complex_I*  0.999949097744,
+   -0.537323558182 + _Complex_I*  0.843376187607,
+   -0.910549223177 + _Complex_I*  0.413400667841,
+   -0.988596369416 + _Complex_I* -0.150589569286,
+   -0.746163958323 + _Complex_I* -0.665762230305,
+   -0.261842842740 + _Complex_I* -0.965110525124,
+    0.307361528464 + _Complex_I* -0.951592817764,
+    0.776926562896 + _Complex_I* -0.629591229184,
+    0.994630384139 + _Complex_I* -0.103491057329,
+    0.889898563214 + _Complex_I*  0.456158467191,
+    0.496682678992 + _Complex_I*  0.867932207254,
+   -0.057545980537 + _Complex_I*  0.998342857001,
+   -0.593119594448 + _Complex_I*  0.805114368697,
+   -0.936417866314 + _Complex_I*  0.350886847356,
+   -0.976151617251 + _Complex_I* -0.217089889533,
+   -0.699440105123 + _Complex_I* -0.714691219581,
+   -0.195986656442 + _Complex_I* -0.980606562540,
+    0.371001030117 + _Complex_I* -0.928632454555,
+    0.817718958886 + _Complex_I* -0.575617671966,
+    0.999351744645 + _Complex_I* -0.036001256586,
+    0.857018334845 + _Complex_I*  0.515285914556,
+    0.436859853992 + _Complex_I*  0.899529581487,
+   -0.124918257436 + _Complex_I*  0.992167036824,
+   -0.646200824792 + _Complex_I*  0.763167408920,
+   -0.958000370884 + _Complex_I*  0.286766960067,
+   -0.959238858607 + _Complex_I* -0.282596553655,
+   -0.649514799540 + _Complex_I* -0.760348949614,
+   -0.129233406987 + _Complex_I* -0.991614202459,
+    0.432942399044 + _Complex_I* -0.901421587888,
+    0.854768520727 + _Complex_I* -0.519009418002,
+    0.999498907872 + _Complex_I*  0.031653327829,
+    0.820215392622 + _Complex_I*  0.572054813550,
+    0.375037449599 + _Complex_I*  0.927009660898,
+   -0.191718762927 + _Complex_I*  0.981449904958,
+   -0.696324287694 + _Complex_I*  0.717727306410,
+   -0.975197950213 + _Complex_I*  0.221334493244,
+   -0.937935505961 + _Complex_I* -0.346809726878,
+   -0.596616557906 + _Complex_I* -0.802526437466,
+   -0.061888634976 + _Complex_I* -0.998083061103,
+    0.492902119414 + _Complex_I* -0.870084766375,
+    0.887905666482 + _Complex_I* -0.460025572582,
+    0.995071200231 + _Complex_I*  0.099163029757,
+    0.779658189663 + _Complex_I*  0.626205323590,
+    0.311498437124 + _Complex_I*  0.950246664645,
+   -0.257641740113 + _Complex_I*  0.966240515478,
+   -0.743260559821 + _Complex_I*  0.669002047989,
+   -0.987931888154 + _Complex_I*  0.154888942047,
+   -0.912339068261 + _Complex_I* -0.409435494949,
+   -0.540987504175 + _Complex_I* -0.841030629839,
+    0.005739411495 + _Complex_I* -0.999983529442,
+    0.550605745726 + _Complex_I* -0.834765423801,
+    0.916978721986 + _Complex_I* -0.398936114465,
+    0.986088888068 + _Complex_I*  0.166218846191,
+    0.735532362955 + _Complex_I*  0.677489588884,
+    0.246533645077 + _Complex_I*  0.969134233141,
+   -0.322385448685 + _Complex_I*  0.946608484262,
+   -0.786794806137 + _Complex_I*  0.617214657178,
+   -0.996143899378 + _Complex_I*  0.087734438690,
+   -0.882566704612 + _Complex_I* -0.470187209428,
+   -0.482882261671 + _Complex_I* -0.875685286712,
+    0.073341187736 + _Complex_I* -0.997306908720,
+    0.605789158993 + _Complex_I* -0.795625222606,
+    0.941854615084 + _Complex_I* -0.336020660147,
+    0.972593084902 + _Complex_I*  0.232513851631,
+    0.688039883664 + _Complex_I*  0.725672872917,
+    0.180440427994 + _Complex_I*  0.983585914877,
+   -0.385653546043 + _Complex_I*  0.922643670343,
+   -0.826727763238 + _Complex_I*  0.562602173380,
+   -0.999796396159 + _Complex_I*  0.020178360380,
+   -0.848754688019 + _Complex_I* -0.528786799727,
+   -0.422566787646 + _Complex_I* -0.906331788022,
+    0.140607269299 + _Complex_I* -0.990065450271,
+    0.658199775658 + _Complex_I* -0.752843313927,
+    0.962419484720 + _Complex_I* -0.271567184010,
+    0.954645563245 + _Complex_I*  0.297744602936,
+    0.637398132678 + _Complex_I*  0.770534632874,
+    0.113521305399 + _Complex_I*  0.993535562132,
+   -0.447156443700 + _Complex_I*  0.894455764618,
+   -0.862876651411 + _Complex_I*  0.505414566915,
+   -0.998872660419 + _Complex_I* -0.047470077604,
+   -0.811057781641 + _Complex_I* -0.584966045886,
+   -0.360317155943 + _Complex_I* -0.932829859692,
+    0.207229768268 + _Complex_I* -0.978292299440,
+    0.707597703708 + _Complex_I* -0.706615517595,
+    0.978579202106 + _Complex_I* -0.205870700212,
+    0.932328471853 + _Complex_I*  0.361612528228,
+    0.583838905619 + _Complex_I*  0.811869529102,
+    0.046082577121 + _Complex_I*  0.998937633732,
+   -0.506612632779 + _Complex_I*  0.862173787765,
+   -0.895076011251 + _Complex_I*  0.445913594861,
+   -0.993376920248 + _Complex_I* -0.114901237236,
+   -0.769648530419 + _Complex_I* -0.638467806255,
+   -0.296418293364 + _Complex_I* -0.955058215691,
+    0.272903742509 + _Complex_I* -0.962041343875,
+    0.753756840705 + _Complex_I* -0.657153425837,
+    0.990259801557 + _Complex_I* -0.139231912364,
+    0.905743959725 + _Complex_I*  0.423825293512,
+    0.527607351878 + _Complex_I*  0.849488364985,
+   -0.021567078691 + _Complex_I*  0.999767403508,
+   -0.563749972526 + _Complex_I*  0.825945499701,
+   -0.923178460993 + _Complex_I*  0.384371602954,
+   -0.983334330553 + _Complex_I* -0.181806475010,
+   -0.724716471305 + _Complex_I* -0.689047194479,
+   -0.231162675510 + _Complex_I* -0.972915113178,
+    0.337328591442 + _Complex_I* -0.941386966872,
+    0.796465908688 + _Complex_I* -0.604683434781,
+    0.997407819049 + _Complex_I* -0.071955837157,
+    0.875013708545 + _Complex_I*  0.484098140730,
+    0.468960852521 + _Complex_I*  0.883218952923,
+   -0.089118018438 + _Complex_I*  0.996021073467,
+   -0.618306935941 + _Complex_I*  0.785936723259,
+   -0.947055371103 + _Complex_I*  0.321070279011,
+   -0.968790857919 + _Complex_I* -0.247879554650,
+   -0.676467265722 + _Complex_I* -0.736472700381,
+   -0.164848988076 + _Complex_I* -0.986318818197,
+    0.400209431938 + _Complex_I* -0.916423706911,
+    0.835529421227 + _Complex_I* -0.549445708204,
+    0.999990536929 + _Complex_I* -0.004350408249,
+    0.840278375725 + _Complex_I*  0.542155191149,
+    0.408167842223 + _Complex_I*  0.912906902469,
+   -0.156261050358 + _Complex_I*  0.987715791177,
+   -0.670033806829 + _Complex_I*  0.742330585188,
+   -0.966597453037 + _Complex_I*  0.256299363601,
+   -0.949813070210 + _Complex_I* -0.312818048805,
+   -0.625121758220 + _Complex_I* -0.780527249621,
+   -0.097780759713 + _Complex_I* -0.995207979786,
+    0.461258448050 + _Complex_I* -0.887265824938,
+    0.870768578204 + _Complex_I* -0.491693078264,
+    0.997996133675 + _Complex_I*  0.063274933192,
+    0.801696950596 + _Complex_I*  0.597730708100,
+    0.345506580588 + _Complex_I*  0.938416326995,
+   -0.222688849754 + _Complex_I*  0.974889571282,
+   -0.718693822797 + _Complex_I*  0.695326677953,
+   -0.981715259472 + _Complex_I*  0.190355323855,
+   -0.926487831883 + _Complex_I* -0.376324723307,
+   -0.570914965629 + _Complex_I* -0.821009197281,
+   -0.030264972734 + _Complex_I* -0.999541910790,
+    0.520196208392 + _Complex_I* -0.854046781374,
+    0.902022084201 + _Complex_I* -0.431689888246,
+    0.991433737996 + _Complex_I*  0.130610654858,
+    0.759446026685 + _Complex_I*  0.650570313304,
+    0.281263878515 + _Complex_I*  0.959630465670,
+   -0.288097365664 + _Complex_I*  0.957601121500,
+   -0.764064258949 + _Complex_I*  0.645140146167,
+   -0.992339593722 + _Complex_I*  0.123539996488,
+   -0.898921906390 + _Complex_I* -0.438108897665,
+   -0.514095001354 + _Complex_I* -0.857733250832,
+    0.037389342002 + _Complex_I* -0.999300774094,
+    0.576752945141 + _Complex_I* -0.816918625244,
+    0.929146886781 + _Complex_I* -0.369710782619,
+    0.980333387059 + _Complex_I*  0.197348550079,
+    0.713718993420 + _Complex_I*  0.700432151198,
+    0.215733785416 + _Complex_I*  0.976452217894,
+   -0.352187212553 + _Complex_I*  0.935929573907,
+   -0.805937447338 + _Complex_I*  0.592000701839,
+   -0.998421826461 + _Complex_I*  0.056159206242,
+   -0.867241467503 + _Complex_I* -0.497887775551,
+   -0.454921939715 + _Complex_I* -0.890531318240,
+    0.104872519572 + _Complex_I* -0.994485673420,
+    0.630669788815 + _Complex_I* -0.776051298225,
+    0.952018831263 + _Complex_I* -0.306039449941,
+    0.964745888993 + _Complex_I*  0.263183148531,
+    0.664725150952 + _Complex_I*  0.747087995950,
+    0.149216243304 + _Complex_I*  0.988804587739,
+   -0.414665040654 + _Complex_I*  0.909974122742,
+   -0.844121727495 + _Complex_I*  0.536151572944,
+   -0.999934118311 + _Complex_I* -0.011478633927,
+   -0.831591521802 + _Complex_I* -0.555387739211,
+   -0.393666625547 + _Complex_I* -0.919253277356,
+    0.171875678373 + _Complex_I* -0.985118648277,
+    0.681699953151 + _Complex_I* -0.731631856793,
+    0.970533228996 + _Complex_I* -0.240967324373,
+    0.944742590342 + _Complex_I*  0.327813114433,
+    0.612688752154 + _Complex_I*  0.790324296086,
+    0.082015713908 + _Complex_I*  0.996631036378,
+   -0.475244878672 + _Complex_I*  0.879853570372,
+   -0.878442323690 + _Complex_I*  0.477848390130,
+   -0.996869547263 + _Complex_I* -0.079063934501,
+   -0.792135244952 + _Complex_I* -0.610345601856,
+   -0.330609434495 + _Complex_I* -0.943767663052,
+    0.238092133930 + _Complex_I* -0.971242573079,
+    0.729609864697 + _Complex_I* -0.683863616035,
+    0.984605336535 + _Complex_I* -0.174792251734,
+    0.920415049492 + _Complex_I*  0.390942625801,
+    0.557847976181 + _Complex_I*  0.829943151952,
+    0.014439785103 + _Complex_I*  0.999895740868,
+   -0.533649442727 + _Complex_I*  0.845705783519,
+   -0.908742144910 + _Complex_I*  0.417358016653};
+
diff --git a/src/nco/tests/data/nco_sincos_fsqrt1_5.c b/src/nco/tests/data/nco_sincos_fsqrt1_5.c
new file mode 100644
index 0000000..64f8dfa
--- /dev/null
+++ b/src/nco/tests/data/nco_sincos_fsqrt1_5.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest nco sincos data for f=0.447213595500
+//
+
+#include <complex.h>
+
+float complex nco_sincos_fsqrt1_5[256] = {
+    1.000000000000 + _Complex_I*  0.000000000000,
+    0.901655595150 + _Complex_I*  0.432454838954,
+    0.625965624531 + _Complex_I*  0.779850650385,
+    0.227155220309 + _Complex_I*  0.973858565648,
+   -0.216334073812 + _Complex_I*  0.976319398818,
+   -0.617272876457 + _Complex_I*  0.786749131547,
+   -0.896801011772 + _Complex_I*  0.442434114060,
+   -0.999938423544 + _Complex_I*  0.011097257308,
+   -0.906399137016 + _Complex_I* -0.422422305775,
+   -0.634581283115 + _Complex_I* -0.772856128345,
+   -0.237948391981 + _Complex_I* -0.971277798961,
+    0.205486285143 + _Complex_I* -0.978659995411,
+    0.608504109432 + _Complex_I* -0.793550722263,
+    0.891835984739 + _Complex_I* -0.452358902117,
+    0.999753701760 + _Complex_I* -0.022193147956,
+    0.911031053190 + _Complex_I*  0.412337750060,
+    0.643118791167 + _Complex_I*  0.765766426822,
+    0.248712259614 + _Complex_I*  0.968577416584,
+   -0.194613190240 + _Complex_I*  0.980880067177,
+   -0.599660403354 + _Complex_I*  0.800254584898,
+   -0.886761125508 + _Complex_I*  0.462227980858,
+   -0.999445857398 + _Complex_I*  0.033286305454,
+   -0.915550773236 + _Complex_I* -0.402202413750,
+   -0.651577097268 + _Complex_I* -0.758582418934,
+   -0.259445497609 + _Complex_I* -0.965757751080,
+    0.183716128155 + _Complex_I* -0.982979340706,
+    0.590742847351 + _Complex_I* -0.806859893850,
+    0.881577059062 + _Complex_I* -0.472040134878,
+    0.999014928368 + _Complex_I* -0.044375363646,
+    0.919957740540 + _Complex_I*  0.392017545042,
+    0.659955159752 + _Complex_I*  0.751304989413,
+    0.270146784136 + _Complex_I*  0.962819149696,
+   -0.172796440895 + _Complex_I*  0.984957557468,
+   -0.581752539647 + _Complex_I*  0.813365835657,
+   -0.876284423835 + _Complex_I*  0.481794155781,
+   -0.998460967741 + _Complex_I*  0.055458956883,
+   -0.924251412370 + _Complex_I* -0.381784398231,
+   -0.668251946836 + _Complex_I* -0.743935034495,
+   -0.280814801300 + _Complex_I* -0.959761974331,
+    0.161855473250 + _Complex_I* -0.986814473839,
+    0.572690587423 + _Complex_I* -0.819771609094,
+    0.870883871630 + _Complex_I* -0.491488842330,
+    0.997784043738 + _Complex_I* -0.066535720188,
+    0.928431259947 + _Complex_I*  0.371504233561,
+    0.676466436748 + _Complex_I*  0.736473461812,
+    0.291448235303 + _Complex_I*  0.956586601484,
+   -0.150894572633 + _Complex_I*  0.988549861135,
+   -0.563558106687 + _Complex_I*  0.826076425270,
+   -0.865376067540 + _Complex_I*  0.501123000598,
+   -0.996984239726 + _Complex_I*  0.077604289425,
+   -0.932496768511 + _Complex_I* -0.361178317063,
+   -0.684597617848 + _Complex_I* -0.728921190278,
+   -0.302045776608 + _Complex_I* -0.953293422212,
+    0.139915088909 + _Complex_I* -0.990163505637,
+    0.554356222129 + _Complex_I* -0.832279507730,
+    0.859761689869 + _Complex_I* -0.510695444109,
+    0.996061654203 + _Complex_I* -0.088663301468,
+    0.936447437384 + _Complex_I*  0.350807920403,
+    0.692644488759 + _Complex_I*  0.721279149977,
+    0.312606120095 + _Complex_I*  0.949882842080,
+   -0.128918374235 + _Complex_I*  0.991655208621,
+   -0.545086066988 + _Complex_I*  0.838380092545,
+   -0.854041430042 + _Complex_I*  0.520204993990,
+   -0.995016400786 + _Complex_I*  0.099711394367,
+   -0.940282780028 + _Complex_I* -0.340394320727,
+   -0.700606058485 + _Complex_I* -0.713548282049,
+   -0.323127965229 + _Complex_I* -0.946355281111,
+    0.117905782887 + _Complex_I* -0.993024786378,
+    0.535748782911 + _Complex_I* -0.844377428410,
+    0.848215992526 + _Complex_I* -0.529650479111,
+    0.993848608203 + _Complex_I* -0.110747207518,
+    0.944002324110 + _Complex_I*  0.329938800499,
+    0.708481346534 + _Complex_I*  0.705729538572,
+    0.333610016214 + _Complex_I*  0.942711173733,
+   -0.106878671100 + _Complex_I*  0.994272070242,
+   -0.526345519812 + _Complex_I*  0.850270776737,
+   -0.842286094742 + _Complex_I*  0.539030736233,
+   -0.992558420270 + _Complex_I*  0.121769381828,
+   -0.947605611558 + _Complex_I* -0.319442647347,
+   -0.716269383043 + _Complex_I* -0.697823882448,
+   -0.344050982153 + _Complex_I* -0.938950968730,
+    0.095838396892 + _Complex_I* -0.995396906606,
+    0.516877435730 + _Complex_I* -0.856059411743,
+    0.836252466973 + _Complex_I* -0.548344610151,
+    0.991145995878 + _Complex_I* -0.132776559884,
+    0.951092198616 + _Complex_I*  0.308907153903,
+    0.723969208892 + _Complex_I*  0.689832287281,
+    0.354449577213 + _Complex_I*  0.935075129181,
+   -0.084786319907 + _Complex_I*  0.996399156943,
+   -0.507345696686 + _Complex_I*  0.861742620539,
+   -0.830115852277 + _Complex_I*  0.557590953834,
+   -0.989611508971 + _Complex_I*  0.143767386120,
+   -0.954461655900 + _Complex_I* -0.298333617644,
+   -0.731579875826 + _Complex_I* -0.681755737260,
+   -0.364804520775 + _Complex_I* -0.931084132408,
+    0.073723801240 + _Complex_I* -0.997278697823,
+    0.497751476543 + _Complex_I* -0.867319703223,
+    0.823877006398 + _Complex_I* -0.566768628568,
+    0.987955148526 + _Complex_I* -0.154740506984,
+    0.957713568453 + _Complex_I*  0.287723340731,
+    0.739100446567 + _Complex_I*  0.673595227035,
+    0.375114537598 + _Complex_I*  0.926978469913,
+   -0.062652203273 + _Complex_I*  0.998035420927,
+   -0.488095956857 + _Complex_I*  0.872789972960,
+   -0.817536697667 + _Complex_I*  0.575876504094,
+   -0.986177118527 + _Complex_I*  0.165694571104,
+   -0.960847535791 + _Complex_I* -0.277077629851,
+   -0.746529994937 + _Complex_I* -0.665351761596,
+   -0.385378357974 + _Complex_I* -0.922758647321,
+    0.051572889504 + _Complex_I* -0.998669233064,
+    0.478380326732 + _Complex_I* -0.878152756072,
+    0.811095706911 + _Complex_I* -0.584913458753,
+    0.984277637946 + _Complex_I* -0.176628229455,
+    0.963863171958 + _Complex_I*  0.266397796054,
+    0.753867605965 + _Complex_I*  0.657026356151,
+    0.395594717883 + _Complex_I*  0.918425184315,
+   -0.040487224383 + _Complex_I*  0.999180056177,
+   -0.468605782677 + _Complex_I*  0.883407392114,
+   -0.804554827358 + _Complex_I*  0.593878379616,
+   -0.982256940707 + _Complex_I*  0.187540135525,
+   -0.966760105569 + _Complex_I* -0.255685154594,
+   -0.761112376002 + _Complex_I* -0.648620035997,
+   -0.405762359151 + _Complex_I* -0.913978614573,
+    0.029396573143 + _Complex_I* -0.999567827357,
+    0.458773528456 + _Complex_I* -0.888553233964,
+    0.797914864535 + _Complex_I* -0.602770162628,
+    0.980115275667 + _Complex_I* -0.198428945482,
+    0.969537979859 + _Complex_I*  0.244941024761,
+    0.768263412834 + _Complex_I*  0.640133836397,
+    0.415880029602 + _Complex_I*  0.909419485704,
+   -0.018302301629 + _Complex_I*  0.999832498849,
+   -0.448884774939 + _Complex_I*  0.893589647897,
+   -0.791176636173 + _Complex_I*  0.611587712739,
+   -0.977852906576 + _Complex_I*  0.209293318336,
+   -0.972196452723 + _Complex_I* -0.234166729729,
+   -0.775319835790 + _Complex_I* -0.631568802452,
+   -0.425946483218 + _Complex_I* -0.904748359177,
+    0.007205776134 + _Complex_I* -0.999974038058,
+    0.438940739955 + _Complex_I* -0.898516013662,
+    0.784340972106 + _Complex_I* -0.620329944043,
+    0.975470112054 + _Complex_I* -0.220131916109,
+    0.974735196764 + _Complex_I*  0.223363596382,
+    0.782280775851 + _Complex_I*  0.622925988970,
+    0.435960480284 + _Complex_I*  0.899965810257,
+    0.003891636774 + _Complex_I*  0.999992427553,
+   -0.428942648141 + _Complex_I*  0.903331724565,
+   -0.777408714164 + _Complex_I*  0.628995779908,
+   -0.972967185547 + _Complex_I*  0.230943403995,
+   -0.977153899328 + _Complex_I* -0.212532955158,
+   -0.789145375757 + _Complex_I* -0.614206460339,
+   -0.445920787547 + _Complex_I* -0.895072427926,
+   -0.014988570415 + _Complex_I* -0.999887665069,
+    0.418891730791 + _Complex_I* -0.908036187536,
+    0.770380716075 + _Complex_I* -0.637584153112,
+    0.970344435298 + _Complex_I* -0.241726450529,
+    0.979452262544 + _Complex_I*  0.201676139882,
+    0.795912790112 + _Complex_I*  0.605411290394,
+    0.455826178368 + _Complex_I*  0.890068814820,
+    0.026083658170 + _Complex_I*  0.999659763508,
+   -0.408789225705 + _Complex_I*  0.912628823207,
+   -0.763257843359 + _Complex_I*  0.646094005971,
+   -0.967602184308 + _Complex_I*  0.252479727747,
+   -0.981630003362 + _Complex_I* -0.190794487601,
+   -0.802582185490 + _Complex_I* -0.596541562286,
+   -0.465675432867 + _Complex_I* -0.884955587148,
+   -0.037175533647 + _Complex_I* -0.999308750936,
+    0.398636377036 + _Complex_I* -0.917109065981,
+    0.756040973217 + _Complex_I* -0.654524290472,
+    0.964740770291 + _Complex_I* -0.263201911351,
+    0.983686853588 + _Complex_I*  0.179889338425,
+    0.809152740535 + _Complex_I*  0.587598368348,
+    0.475467338081 + _Complex_I*  0.879733374619,
+    0.048262830849 + _Complex_I*  0.998834670583,
+   -0.388434435136 + _Complex_I*  0.921476364103,
+   -0.748730994427 + _Complex_I*  0.662873968401,
+   -0.961760545639 + _Complex_I*  0.273891680873,
+   -0.985622559913 + _Complex_I* -0.168962035352,
+   -0.815623646064 + _Complex_I* -0.578582809960,
+   -0.485200688108 + _Complex_I* -0.874402820364,
+   -0.059344184342 + _Complex_I* -0.998237580832,
+    0.378184656405 + _Complex_I* -0.925730179728,
+    0.741328807236 + _Complex_I* -0.671142011471,
+    0.958661877377 + _Complex_I* -0.284547719838,
+    0.987436883951 + _Complex_I*  0.158013924113,
+    0.821994105167 + _Complex_I*  0.569495997414,
+    0.494874284257 + _Complex_I*  0.868964580855,
+    0.070418229426 + _Complex_I*  0.997517555216,
+   -0.367888303132 + _Complex_I*  0.929869988987,
+   -0.733835323245 + _Complex_I*  0.679327401448,
+   -0.955445147112 + _Complex_I*  0.295168715922,
+   -0.989129602261 + _Complex_I* -0.147046352999,
+   -0.828263333302 + _Complex_I* -0.560339049779,
+   -0.504486935198 + _Complex_I* -0.863419325829,
+   -0.081483602301 + _Complex_I* -0.996674682410,
+    0.357546643343 + _Complex_I* -0.933895282049,
+    0.726251465296 + _Complex_I* -0.687429130278,
+    0.952110750997 + _Complex_I* -0.305753361120,
+    0.990700506382 + _Complex_I*  0.136060672699,
+    0.834430558397 + _Complex_I*  0.551113094758,
+    0.514037457104 + _Complex_I*  0.857767738198,
+    0.092538940232 + _Complex_I*  0.995709066214,
+   -0.347160950645 + _Complex_I*  0.937805563189,
+   -0.718578167365 + _Complex_I*  0.695446200210,
+   -0.948659099670 + _Complex_I*  0.316300351901,
+   -0.992149402850 + _Complex_I* -0.125058236130,
+   -0.840495020939 + _Complex_I* -0.541819268554,
+   -0.523524673801 + _Complex_I* -0.852010513974,
+   -0.103582881724 + _Complex_I* -0.994620825548,
+    0.336732504064 + _Complex_I* -0.941600350842,
+    0.710816374441 + _Complex_I* -0.703377623917,
+    0.945090618214 + _Complex_I* -0.326808389373,
+    0.993476113231 + _Complex_I*  0.114040398276,
+    0.846455974072 + _Complex_I*  0.532458715731,
+    0.532947416909 + _Complex_I*  0.846148362174,
+    0.114614066682 + _Complex_I*  0.993410094432,
+   -0.326262587896 + _Complex_I*  0.945279177672,
+   -0.702967042411 + _Complex_I*  0.711222424621,
+   -0.941405746096 + _Complex_I*  0.337276179440,
+   -0.994680474136 + _Complex_I* -0.103008516015,
+   -0.852312683688 + _Complex_I* -0.523032589066,
+   -0.542304525992 + _Complex_I* -0.840182004740,
+   -0.125631136584 + _Complex_I* -0.992077021970,
+    0.315752491540 + _Complex_I* -0.948841590618,
+    0.695031137943 + _Complex_I* -0.718979636214,
+    0.937604937119 + _Complex_I* -0.347702432964,
+    0.995762337245 + _Complex_I*  0.091963947955,
+    0.858064428515 + _Complex_I*  0.513542049416,
+    0.551594848693 + _Complex_I*  0.834112176446,
+    0.136632734646 + _Complex_I*  0.990621772334,
+   -0.305203509345 + _Complex_I*  0.952287150960,
+   -0.687009638366 + _Complex_I*  0.726648303371,
+   -0.933688659365 + _Complex_I*  0.358085865921,
+   -0.996721569324 + _Complex_I* -0.080908054267,
+   -0.863710500209 + _Complex_I* -0.503988265566,
+   -0.560817240883 + _Complex_I* -0.827939624808,
+   -0.147617505989 + _Complex_I* -0.989044524744,
+    0.294616940449 + _Complex_I* -0.955615434367,
+    0.678903531553 + _Complex_I* -0.734227481674,
+    0.929657395135 + _Complex_I* -0.368425199561,
+    0.997558052238 + _Complex_I*  0.069842196517,
+    0.869250203441 + _Complex_I*  0.494372414095,
+    0.569970566797 + _Complex_I*  0.821665109996,
+    0.158584097806 + _Complex_I*  0.987345473440,
+   -0.283994088620 + _Complex_I*  0.958826030951,
+   -0.670713815793 + _Complex_I*  0.741716237725,
+   -0.925511640889 + _Complex_I*  0.378719160565,
+   -0.998271682975 + _Complex_I* -0.058767737497,
+   -0.874682855979 + _Complex_I* -0.484695679221,
+   -0.579053699176 + _Complex_I* -0.815289404733,
+   -0.169531159530 + _Complex_I* -0.985524827667,
+    0.273336262091 + _Complex_I* -0.961918545318,
+    0.662441499674 + _Complex_I* -0.749113649261,
+    0.921251907190 + _Complex_I* -0.388966481202,
+    0.998862373647 + _Complex_I*  0.047686041058,
+    0.880007788778 + _Complex_I*  0.474959252664,
+    0.588065519407 + _Complex_I*  0.808813294206};
+
diff --git a/src/nco/tests/data/nco_sincos_fsqrt1_7.c b/src/nco/tests/data/nco_sincos_fsqrt1_7.c
new file mode 100644
index 0000000..b11da3d
--- /dev/null
+++ b/src/nco/tests/data/nco_sincos_fsqrt1_7.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// autotest nco sincos data for f=0.377964473009
+//
+
+#include <complex.h>
+
+float complex nco_sincos_fsqrt1_7[256] = {
+    1.000000000000 + _Complex_I*  0.000000000000,
+    0.929417729782 + _Complex_I*  0.369029380357,
+    0.727634632866 + _Complex_I*  0.685964897829,
+    0.423135327397 + _Complex_I*  0.906066495743,
+    0.058904317893 + _Complex_I*  0.998263633182,
+   -0.313641892575 + _Complex_I*  0.949541343608,
+   -0.641912989417 + _Complex_I*  0.766777486640,
+   -0.879568734108 + _Complex_I*  0.475771838153,
+   -0.993060562667 + _Complex_I*  0.117604076781,
+   -0.966367453272 + _Complex_I* -0.257165210044,
+   -0.803257526443 + _Complex_I* -0.595631888176,
+   -0.526756120043 + _Complex_I* -0.850016464545,
+   -0.175895428035 + _Complex_I* -0.984408857333,
+    0.199795461237 + _Complex_I* -0.979837626175,
+    0.547282316042 + _Complex_I* -0.836948066817,
+    0.817512314214 + _Complex_I* -0.575911118237,
+    0.972338562249 + _Complex_I* -0.233575941320,
+    0.989905083997 + _Complex_I*  0.141731876011,
+    0.867732109487 + _Complex_I*  0.497032178200,
+    0.623066130519 + _Complex_I*  0.782169161371,
+    0.290445307576 + _Complex_I*  0.956891594334,
+   -0.083176093733 + _Complex_I*  0.996534865136,
+   -0.445055979995 + _Complex_I*  0.895502749673,
+   -0.744109743372 + _Complex_I*  0.668057400093,
+   -0.938121596793 + _Complex_I*  0.346306034644,
+   -0.999703946129 + _Complex_I* -0.024331463036,
+   -0.920163547338 + _Complex_I* -0.391534220918,
+   -0.710728684262 + _Complex_I* -0.703466230439,
+   -0.400964133096 + _Complex_I* -0.916093752828,
+   -0.034597664351 + _Complex_I* -0.999401321603,
+    0.336652767782 + _Complex_I* -0.941628862103,
+    0.660379766665 + _Complex_I* -0.750931797023,
+    0.890884559273 + _Complex_I* -0.454229789917,
+    0.995628042491 + _Complex_I* -0.093406643266,
+    0.959824150645 + _Complex_I*  0.280602209256,
+    0.788527123674 + _Complex_I*  0.614999979863,
+    0.505918027668 + _Complex_I*  0.862581560944,
+    0.151891245788 + _Complex_I*  0.988397212386,
+   -0.223577193999 + _Complex_I*  0.974686225574,
+   -0.567484461944 + _Complex_I*  0.823384105659,
+   -0.831283046614 + _Complex_I*  0.555849346867,
+   -0.977733942036 + _Complex_I*  0.209848370473,
+   -0.986163474863 + _Complex_I* -0.165775754701,
+   -0.855381693965 + _Complex_I* -0.517998221646,
+   -0.603850349342 + _Complex_I* -0.797097707687,
+   -0.267076747662 + _Complex_I* -0.963675262139,
+    0.107398620363 + _Complex_I* -0.994216041082,
+    0.466713111501 + _Complex_I* -0.884408769491,
+    0.760144260739 + _Complex_I* -0.649754340398,
+    0.946269994744 + _Complex_I* -0.323377638447,
+    0.998815959813 + _Complex_I*  0.048648519224,
+    0.910364528935 + _Complex_I*  0.413807231035,
+    0.693401907701 + _Complex_I*  0.720551035248,
+    0.378555524829 + _Complex_I*  0.925578583709,
+    0.010270525265 + _Complex_I*  0.999947256764,
+   -0.359464308278 + _Complex_I*  0.933158834858,
+   -0.678455527940 + _Complex_I*  0.734641474875,
+   -0.901672884794 + _Complex_I*  0.432418788707,
+   -0.997606003243 + _Complex_I*  0.069153902954,
+   -0.952712528707 + _Complex_I* -0.303873061728,
+   -0.773329827890 + _Complex_I* -0.634003925300,
+   -0.484780377312 + _Complex_I* -0.874635916123,
+   -0.127797127559 + _Complex_I* -0.991800329798,
+    0.247226544974 + _Complex_I* -0.968957705713,
+    0.587350595903 + _Complex_I* -0.809332612399,
+    0.844561569887 + _Complex_I* -0.535458452795,
+    0.982550397988 + _Complex_I* -0.185996546781,
+    0.981837950701 + _Complex_I*  0.189721476283,
+    0.842524800322 + _Complex_I*  0.538657554336,
+    0.584277023699 + _Complex_I*  0.811554286279,
+    0.243550049538 + _Complex_I*  0.969888330361,
+   -0.131557555439 + _Complex_I*  0.991308534013,
+   -0.488093898561 + _Complex_I*  0.872791124031,
+   -0.775728690804 + _Complex_I*  0.631066556129,
+   -0.953858098906 + _Complex_I*  0.300257767846,
+   -0.997336566835 + _Complex_I* -0.072936770247,
+   -0.900026476647 + _Complex_I* -0.435835222687,
+   -0.675664562503 + _Complex_I* -0.737209196211,
+   -0.355922770904 + _Complex_I* -0.934515372346,
+    0.014062695080 + _Complex_I* -0.999901115414,
+    0.382063007175 + _Complex_I* -0.924136277044,
+    0.696129570446 + _Complex_I* -0.717916165824,
+    0.911927322820 + _Complex_I* -0.410351748984,
+    0.998993273758 + _Complex_I* -0.044860216082,
+    0.945036798307 + _Complex_I*  0.326963988607,
+    0.757674637528 + _Complex_I*  0.652632472106,
+    0.463355684742 + _Complex_I*  0.886172392606,
+    0.103627339662 + _Complex_I*  0.994616194557,
+   -0.270729511199 + _Complex_I*  0.962655458493,
+   -0.606868955028 + _Complex_I*  0.794801907033,
+   -0.857340021717 + _Complex_I*  0.514750509629,
+   -0.986785078242 + _Complex_I*  0.162034593094,
+   -0.976931072688 + _Complex_I* -0.213554862310,
+   -0.829169041221 + _Complex_I* -0.558997943718,
+   -0.564357743106 + _Complex_I* -0.825530337296,
+   -0.219879143544 + _Complex_I* -0.975527120194,
+    0.155638594267 + _Complex_I* -0.987814065487,
+    0.509185681445 + _Complex_I* -0.860656692190,
+    0.790853805905 + _Complex_I* -0.612005112466,
+    0.960881416303 + _Complex_I* -0.276960112297,
+    0.995266643155 + _Complex_I*  0.097181834844,
+    0.889155511714 + _Complex_I*  0.457605152930,
+    0.657527151087 + _Complex_I*  0.753430849902,
+    0.333079272352 + _Complex_I*  0.942898827197,
+   -0.038387588793 + _Complex_I*  0.999262924873,
+   -0.404435483608 + _Complex_I*  0.914566530985,
+   -0.713391429243 + _Complex_I*  0.700765773053,
+   -0.921641801618 + _Complex_I*  0.388041736814,
+   -0.999789032622 + _Complex_I*  0.020539967128,
+   -0.936801504302 + _Complex_I* -0.349861317578,
+   -0.741570822148 + _Complex_I* -0.670874590172,
+   -0.441656635685 + _Complex_I* -0.897184159555,
+   -0.079396193214 + _Complex_I* -0.996843139367,
+    0.294072176383 + _Complex_I* -0.955783215524,
+    0.626027982347 + _Complex_I* -0.779800593305,
+    0.869610835882 + _Complex_I* -0.493737778701,
+    0.990435475412 + _Complex_I* -0.137976697470,
+    0.971445746224 + _Complex_I*  0.237261800850,
+    0.815322324712 + _Complex_I*  0.579007346089,
+    0.544104301925 + _Complex_I*  0.839017585410,
+    0.196078045407 + _Complex_I*  0.980588292868,
+   -0.179627478280 + _Complex_I*  0.983734704606,
+   -0.529975971546 + _Complex_I*  0.848012658858,
+   -0.805510650347 + _Complex_I*  0.592581295839,
+   -0.967335788375 + _Complex_I*  0.253498466521,
+   -0.992607414390 + _Complex_I* -0.121369357324,
+   -0.877758070920 + _Complex_I* -0.479104131619,
+   -0.639000412754 + _Complex_I* -0.769206391354,
+   -0.310038554984 + _Complex_I* -0.950723984353,
+    0.062689752919 + _Complex_I* -0.998033063019,
+    0.426568490660 + _Complex_I* -0.904455263004,
+    0.730230883453 + _Complex_I* -0.683200451443,
+    0.930810569171 + _Complex_I* -0.365501962128,
+    0.999992808660 + _Complex_I*  0.003792443700,
+    0.928011522874 + _Complex_I*  0.372551490955,
+    0.725027916943 + _Complex_I*  0.688719478201,
+    0.419696078314 + _Complex_I*  0.907664696817,
+    0.055118035666 + _Complex_I*  0.998479845638,
+   -0.317240719156 + _Complex_I*  0.948345045914,
+   -0.644816333651 + _Complex_I*  0.764337553609,
+   -0.881366746740 + _Complex_I*  0.472432701811,
+   -0.993499428070 + _Complex_I*  0.113837104776,
+   -0.965385219214 + _Complex_I* -0.260828254841,
+   -0.800992849544 + _Complex_I* -0.598673913729,
+   -0.523528692375 + _Complex_I* -0.852008044716,
+   -0.172160847942 + _Complex_I* -0.985068851622,
+    0.203510003471 + _Complex_I* -0.979072866791,
+    0.550452458771 + _Complex_I* -0.834866510667,
+    0.819690545696 + _Complex_I* -0.572806607239,
+    0.973217393439 + _Complex_I* -0.229886722341,
+    0.989360455092 + _Complex_I*  0.145485016068,
+    0.865840902777 + _Complex_I*  0.500319429044,
+    0.620095317331 + _Complex_I*  0.784526479747,
+    0.286814261387 + _Complex_I*  0.957986210477,
+   -0.086954797956 + _Complex_I*  0.996212258062,
+   -0.448448923206 + _Complex_I*  0.893808460060,
+   -0.746637962304 + _Complex_I*  0.665230601556,
+   -0.939428196580 + _Complex_I*  0.342745770900,
+   -0.999604481214 + _Complex_I* -0.028122608992,
+   -0.918672058640 + _Complex_I* -0.395021073709,
+   -0.708055717096 + _Complex_I* -0.706156570094,
+   -0.397487015646 + _Complex_I* -0.917607798786,
+   -0.030807242302 + _Complex_I* -0.999525344262,
+    0.340221421243 + _Complex_I* -0.940345353861,
+    0.663222884212 + _Complex_I* -0.748421943731,
+    0.892600793524 + _Complex_I* -0.450847893862,
+    0.995975122026 + _Complex_I* -0.089630108250,
+    0.958753080142 + _Complex_I*  0.284240270403,
+    0.786189100308 + _Complex_I*  0.617986001910,
+    0.502643097433 + _Complex_I*  0.864494023463,
+    0.148141712706 + _Complex_I*  0.988966143483,
+   -0.227272028815 + _Complex_I*  0.973831312353,
+   -0.570603018834 + _Complex_I*  0.821226031551,
+   -0.833385095928 + _Complex_I*  0.552692755412,
+   -0.978522748949 + _Complex_I*  0.206138860453,
+   -0.985527687809 + _Complex_I* -0.169514532008,
+   -0.853411063532 + _Complex_I* -0.521238483461,
+   -0.600823058669 + _Complex_I* -0.799382043938,
+   -0.263420142846 + _Complex_I* -0.964681205551,
+    0.111168356384 + _Complex_I* -0.993801588114,
+    0.470063825674 + _Complex_I* -0.882632426207,
+    0.762602951037 + _Complex_I* -0.646866863481,
+    0.947489581282 + _Complex_I* -0.319786637248,
+    0.998624280217 + _Complex_I*  0.052436122669,
+    0.908788641568 + _Complex_I*  0.417256761428,
+    0.690664271978 + _Complex_I*  0.723175541216,
+    0.375042597839 + _Complex_I*  0.927007578074,
+    0.006478207732 + _Complex_I*  0.999979016192,
+   -0.363000675592 + _Complex_I*  0.931788876044,
+   -0.681236735369 + _Complex_I*  0.732063187426,
+   -0.903306324468 + _Complex_I*  0.428996135385,
+   -0.997861091402 + _Complex_I*  0.065370041044,
+   -0.951553255949 + _Complex_I* -0.307483985100,
+   -0.770919842419 + _Complex_I* -0.636932175796,
+   -0.481459883622 + _Complex_I* -0.876468128606,
+   -0.124034861615 + _Complex_I* -0.992277860835,
+    0.250899484630 + _Complex_I* -0.968013144855,
+    0.590415720432 + _Complex_I* -0.807099298145,
+    0.846586192393 + _Complex_I* -0.532251649927,
+    0.983248713565 + _Complex_I* -0.182268942150,
+    0.981111381953 + _Complex_I*  0.193443677081,
+    0.840475912991 + _Complex_I*  0.541848908537,
+    0.581195048024 + _Complex_I*  0.813764287833,
+    0.239870051199 + _Complex_I*  0.970805005414,
+   -0.135316091168 + _Complex_I*  0.990802480554,
+   -0.491400399711 + _Complex_I*  0.870933778863,
+   -0.778116396659 + _Complex_I*  0.628120110528,
+   -0.954989950067 + _Complex_I*  0.296638155453,
+   -0.997052786053 + _Complex_I* -0.076718588513,
+   -0.898367123706 + _Complex_I* -0.439245388188,
+   -0.672863879197 + _Complex_I* -0.739766314502,
+   -0.352376114406 + _Complex_I* -0.935858468998,
+    0.017854662635 + _Complex_I* -0.999840592806,
+    0.385564994432 + _Complex_I* -0.922680678821,
+    0.698847220981 + _Complex_I* -0.715270970841,
+    0.913477000745 + _Complex_I* -0.406890364975,
+    0.999156219501 + _Complex_I* -0.041071267730,
+    0.943790009707 + _Complex_I*  0.330545636150,
+    0.755194116925 + _Complex_I*  0.655501217209,
+    0.459991593687 + _Complex_I*  0.887923270186,
+    0.099854568522 + _Complex_I*  0.995002042784,
+   -0.274378380919 + _Complex_I*  0.961621809281,
+   -0.609878832312 + _Complex_I*  0.792494674997,
+   -0.859286018620 + _Complex_I*  0.511495394119,
+   -0.987392489006 + _Complex_I*  0.158291100995,
+   -0.976114152452 + _Complex_I* -0.217258282656,
+   -0.827043110154 + _Complex_I* -0.562138500679,
+   -0.561222907291 + _Complex_I* -0.827664695593,
+   -0.216177930638 + _Complex_I* -0.976353984119,
+    0.159383704246 + _Complex_I* -0.987216711174,
+    0.512446011768 + _Complex_I* -0.858719444885,
+    0.793169113539 + _Complex_I* -0.609001442796,
+    0.961924861910 + _Complex_I* -0.273314031909,
+    0.994890929216 + _Complex_I*  0.100955628686,
+    0.887413675715 + _Complex_I*  0.460973934354,
+    0.654665078505 + _Complex_I*  0.755919066426,
+    0.329500986349 + _Complex_I*  0.944155230878,
+   -0.042176961119 + _Complex_I*  0.999110156064,
+   -0.407901017253 + _Complex_I*  0.913026155224,
+   -0.716043913743 + _Complex_I*  0.698055236776,
+   -0.923106800218 + _Complex_I*  0.384543671630,
+   -0.999859739467 + _Complex_I*  0.016748175800,
+   -0.935467938094 + _Complex_I* -0.353411568569,
+   -0.739021235147 + _Complex_I* -0.673682131277,
+   -0.438250939168 + _Complex_I* -0.898852665523,
+   -0.075615150766 + _Complex_I* -0.997137076321,
+    0.297694815644 + _Complex_I* -0.954661089989,
+    0.628980830213 + _Complex_I* -0.777420809616,
+    0.871477054943 + _Complex_I* -0.490436277929,
+    0.990951621711 + _Complex_I* -0.134219534454,
+    0.970538958206 + _Complex_I*  0.240944247919,
+    0.813120608690 + _Complex_I*  0.582095246264,
+    0.540918462130 + _Complex_I*  0.841075036680,
+    0.192357809450 + _Complex_I*  0.981324856071,
+   -0.183356945000 + _Complex_I*  0.983046403137,
+   -0.533188200573 + _Complex_I*  0.845996656476};
+
diff --git a/src/nco/tests/gen_nco_data.m b/src/nco/tests/gen_nco_data.m
new file mode 100644
index 0000000..d43b368
--- /dev/null
+++ b/src/nco/tests/gen_nco_data.m
@@ -0,0 +1,61 @@
+% generate nco data for autotests
+
+% options
+p = 7;          % base frequency integer
+f = 1/sqrt(p);  % frequency
+n = 256;        % number of points
+
+% compute sincos data
+v = exp(j*[0:(n-1)]*f);
+
+% print results
+
+basename = ['nco_sincos_fsqrt1_' num2str(p)];
+filename = ['data/' basename '.c'];
+fid      = fopen(filename,'w');
+
+fprintf(fid,'/* Copyright (c) 2007 - 2015 Joseph Gaeddert\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * Permission is hereby granted, free of charge, to any person obtaining a copy\n');
+fprintf(fid,' * of this software and associated documentation files (the "Software"), to deal\n');
+fprintf(fid,' * in the Software without restriction, including without limitation the rights\n');
+fprintf(fid,' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n');
+fprintf(fid,' * copies of the Software, and to permit persons to whom the Software is\n');
+fprintf(fid,' * furnished to do so, subject to the following conditions:\n');
+fprintf(fid,' * \n');
+fprintf(fid,' * The above copyright notice and this permission notice shall be included in\n');
+fprintf(fid,' * all copies or substantial portions of the Software.\n');
+fprintf(fid,' *\n');
+fprintf(fid,' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n');
+fprintf(fid,' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n');
+fprintf(fid,' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n');
+fprintf(fid,' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n');
+fprintf(fid,' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n');
+fprintf(fid,' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n');
+fprintf(fid,' * THE SOFTWARE.\n');
+fprintf(fid,' */\n');
+fprintf(fid,'\n');
+
+fprintf(fid,'//\n');
+fprintf(fid,'// autotest nco sincos data for f=%.12f\n', f);
+fprintf(fid,'//\n');
+
+fprintf(fid,'\n');
+fprintf(fid,'#include <complex.h>\n');
+fprintf(fid,'\n');
+
+fprintf(fid,'float complex %s[%u] = {\n', basename, n);
+for i=1:n,
+    %fprintf(fid,'  {%16.12f, %16.12f}', real(v(i)), imag(v(i)));
+    fprintf(fid,'  %16.12f + _Complex_I*%16.12f', real(v(i)), imag(v(i)));
+    if i==n,
+        fprintf(fid,'};\n');
+    else,
+        fprintf(fid,',\n');
+    end;
+end;
+
+fprintf(fid,'\n');
+
+fclose(fid);
+printf('results written to %s\n', filename);
diff --git a/src/nco/tests/nco_crcf_frequency_autotest.c b/src/nco/tests/nco_crcf_frequency_autotest.c
new file mode 100644
index 0000000..3c62b71
--- /dev/null
+++ b/src/nco/tests/nco_crcf_frequency_autotest.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <complex.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// declare external arrays
+extern float complex nco_sincos_fsqrt1_2[];
+extern float complex nco_sincos_fsqrt1_3[];
+extern float complex nco_sincos_fsqrt1_5[];
+extern float complex nco_sincos_fsqrt1_7[];
+
+// autotest helper function
+//  _type       :   NCO type (e.g. LIQUID_NCO)
+//  _phase      :   initial phase
+//  _frequency  :   initial frequency
+//  _sincos     :   external sin/cosine data
+//  _num_samples:   number of samples to test
+//  _tol        :   error tolerance
+void nco_crcf_frequency_test(int             _type,
+                             float           _phase,
+                             float           _frequency,
+                             float complex * _sincos,
+                             unsigned int    _num_samples,
+                             float           _tol)
+{
+    // create object
+    nco_crcf nco = nco_crcf_create(_type);
+
+    // set phase and frequency
+    nco_crcf_set_phase(nco, _phase);
+    nco_crcf_set_frequency(nco, _frequency);
+
+    // run trials
+    unsigned int i;
+    for (i=0; i<_num_samples; i++) {
+        // compute complex output
+        float complex y_test;
+        nco_crcf_cexpf(nco, &y_test);
+
+        // compare to expected output
+        float complex y = _sincos[i];
+
+        // run tests
+        CONTEND_DELTA( crealf(y_test), crealf(y), _tol );
+        CONTEND_DELTA( cimagf(y_test), cimagf(y), _tol );
+
+        // step oscillator
+        nco_crcf_step(nco);
+    }
+
+    // destroy object
+    nco_crcf_destroy(nco);
+}
+
+
+// test floating point precision nco phase
+void autotest_nco_crcf_frequency()
+{
+    // error tolerance (higher for NCO)
+    float tol = 0.04f;
+
+    // test frequencies with irrational values
+    nco_crcf_frequency_test(LIQUID_NCO, 0.0f, 0.707106781186547, nco_sincos_fsqrt1_2, 256, tol); // 1/sqrt(2)
+    nco_crcf_frequency_test(LIQUID_NCO, 0.0f, 0.577350269189626, nco_sincos_fsqrt1_3, 256, tol); // 1/sqrt(3)
+    nco_crcf_frequency_test(LIQUID_NCO, 0.0f, 0.447213595499958, nco_sincos_fsqrt1_5, 256, tol); // 1/sqrt(5)
+    nco_crcf_frequency_test(LIQUID_NCO, 0.0f, 0.377964473009227, nco_sincos_fsqrt1_7, 256, tol); // 1/sqrt(7)
+}
+
+// test floating point precision vco phase
+void autotest_vco_crcf_frequency()
+{
+    // error tolerance
+    float tol = 0.0001f;
+
+    // test frequencies with irrational values
+    nco_crcf_frequency_test(LIQUID_VCO, 0.0f, 0.707106781186547, nco_sincos_fsqrt1_2, 256, tol); // 1/sqrt(2)
+    nco_crcf_frequency_test(LIQUID_VCO, 0.0f, 0.577350269189626, nco_sincos_fsqrt1_3, 256, tol); // 1/sqrt(3)
+    nco_crcf_frequency_test(LIQUID_VCO, 0.0f, 0.447213595499958, nco_sincos_fsqrt1_5, 256, tol); // 1/sqrt(5)
+    nco_crcf_frequency_test(LIQUID_VCO, 0.0f, 0.377964473009227, nco_sincos_fsqrt1_7, 256, tol); // 1/sqrt(7)
+}
+
diff --git a/src/nco/tests/nco_crcf_phase_autotest.c b/src/nco/tests/nco_crcf_phase_autotest.c
new file mode 100644
index 0000000..a9e16b2
--- /dev/null
+++ b/src/nco/tests/nco_crcf_phase_autotest.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <complex.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// autotest helper function
+//  _theta  :   input phase
+//  _cos    :   expected output: cos(_theta)
+//  _sin    :   expected output: sin(_theta)
+//  _type   :   NCO type (e.g. LIQUID_NCO)
+//  _tol    :   error tolerance
+void nco_crcf_phase_test(float _theta,
+                         float _cos,
+                         float _sin,
+                         int   _type,
+                         float _tol)
+{
+    // create object
+    nco_crcf nco = nco_crcf_create(_type);
+
+    // set phase
+    nco_crcf_set_phase(nco, _theta);
+
+    // compute cosine and sine outputs
+    float c = nco_crcf_cos(nco);
+    float s = nco_crcf_sin(nco);
+
+    // run tests
+    CONTEND_DELTA( c, _cos, _tol );
+    CONTEND_DELTA( s, _sin, _tol );
+
+    // destroy object
+    nco_crcf_destroy(nco);
+}
+
+
+// test floating point precision nco phase
+void autotest_nco_crcf_phase()
+{
+    // error tolerance (higher for NCO)
+    float tol = 0.02f;
+
+    nco_crcf_phase_test(-6.283185307f,  1.000000000f,  0.000000000f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-6.195739393f,  0.996179042f,  0.087334510f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-5.951041106f,  0.945345356f,  0.326070787f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-5.131745978f,  0.407173250f,  0.913350943f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-4.748043551f,  0.035647016f,  0.999364443f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-3.041191113f, -0.994963998f, -0.100232943f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-1.947799864f, -0.368136099f, -0.929771914f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-1.143752030f,  0.414182352f, -0.910193924f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-1.029377689f,  0.515352252f, -0.856978446f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-0.174356887f,  0.984838307f, -0.173474811f, LIQUID_NCO, tol);
+    nco_crcf_phase_test(-0.114520496f,  0.993449692f, -0.114270338f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 0.000000000f,  1.000000000f,  0.000000000f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 1.436080000f,  0.134309213f,  0.990939471f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 2.016119855f, -0.430749878f,  0.902471353f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 2.996498473f, -0.989492293f,  0.144585621f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 3.403689755f, -0.965848729f, -0.259106603f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 3.591162483f, -0.900634128f, -0.434578148f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 5.111428476f,  0.388533479f, -0.921434607f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 5.727585681f,  0.849584319f, -0.527452828f, LIQUID_NCO, tol);
+    nco_crcf_phase_test( 6.283185307f,  1.000000000f, -0.000000000f, LIQUID_NCO, tol);
+}
+
+// test floating point precision vco phase
+void autotest_vco_crcf_phase()
+{
+    // error tolerance
+    float tol = 0.0001f;
+
+    nco_crcf_phase_test(-6.283185307f,  1.000000000f,  0.000000000f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-6.195739393f,  0.996179042f,  0.087334510f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-5.951041106f,  0.945345356f,  0.326070787f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-5.131745978f,  0.407173250f,  0.913350943f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-4.748043551f,  0.035647016f,  0.999364443f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-3.041191113f, -0.994963998f, -0.100232943f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-1.947799864f, -0.368136099f, -0.929771914f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-1.143752030f,  0.414182352f, -0.910193924f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-1.029377689f,  0.515352252f, -0.856978446f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-0.174356887f,  0.984838307f, -0.173474811f, LIQUID_VCO, tol);
+    nco_crcf_phase_test(-0.114520496f,  0.993449692f, -0.114270338f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 0.000000000f,  1.000000000f,  0.000000000f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 1.436080000f,  0.134309213f,  0.990939471f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 2.016119855f, -0.430749878f,  0.902471353f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 2.996498473f, -0.989492293f,  0.144585621f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 3.403689755f, -0.965848729f, -0.259106603f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 3.591162483f, -0.900634128f, -0.434578148f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 5.111428476f,  0.388533479f, -0.921434607f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 5.727585681f,  0.849584319f, -0.527452828f, LIQUID_VCO, tol);
+    nco_crcf_phase_test( 6.283185307f,  1.000000000f, -0.000000000f, LIQUID_VCO, tol);
+}
+
+//
+// test floating point precision nco
+//
+void autotest_nco_basic() {
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+
+    unsigned int i;     // loop index
+    float s, c;         // sine/cosine result
+    float tol=1e-4f;    // error tolerance
+    float f=0.0f;       // frequency to test
+
+    nco_crcf_set_phase( p, 0.0f);
+    CONTEND_DELTA( nco_crcf_cos(p), 1.0f, tol );
+    CONTEND_DELTA( nco_crcf_sin(p), 0.0f, tol );
+    nco_crcf_sincos(p, &s, &c);
+    CONTEND_DELTA( s, 0.0f, tol );
+    CONTEND_DELTA( c, 1.0f, tol );
+
+    nco_crcf_set_phase(p, M_PI/2);
+    CONTEND_DELTA( nco_crcf_cos(p), 0.0f, tol );
+    CONTEND_DELTA( nco_crcf_sin(p), 1.0f, tol );
+    nco_crcf_sincos(p, &s, &c);
+    CONTEND_DELTA( s, 1.0f, tol );
+    CONTEND_DELTA( c, 0.0f, tol );
+
+    // cycle through one full period in 64 steps
+    nco_crcf_set_phase(p, 0.0f);
+    f = 2.0f * M_PI / 64.0f;
+    nco_crcf_set_frequency(p, f);
+    for (i=0; i<128; i++) {
+        nco_crcf_sincos(p, &s, &c);
+        CONTEND_DELTA( s, sinf(i*f), tol );
+        CONTEND_DELTA( c, cosf(i*f), tol );
+        nco_crcf_step(p);
+    }
+
+    // double frequency: cycle through one full period in 32 steps
+    nco_crcf_set_phase(p, 0.0f);
+    f = 2.0f * M_PI / 32.0f;
+    nco_crcf_set_frequency(p, f);
+    for (i=0; i<128; i++) {
+        nco_crcf_sincos(p, &s, &c);
+        CONTEND_DELTA( s, sinf(i*f), tol );
+        CONTEND_DELTA( c, cosf(i*f), tol );
+        nco_crcf_step(p);
+    }
+
+    // destroy NCO object
+    nco_crcf_destroy(p);
+}
+
+//
+// test nco mixing
+//
+void autotest_nco_mixing() {
+    // frequency, phase
+    float f = 0.1f;
+    float phi = M_PI;
+
+    // error tolerance (high for NCO)
+    float tol = 0.05f;
+
+    // initialize nco object
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_frequency(p, f);
+    nco_crcf_set_phase(p, phi);
+
+    unsigned int i;
+    float nco_i, nco_q;
+    for (i=0; i<64; i++) {
+        // generate sin/cos
+        nco_crcf_sincos(p, &nco_q, &nco_i);
+
+        // mix back to zero phase
+        complex float nco_cplx_in = nco_i + _Complex_I*nco_q;
+        complex float nco_cplx_out;
+        nco_crcf_mix_down(p, nco_cplx_in, &nco_cplx_out);
+
+        // assert mixer output is correct
+        CONTEND_DELTA(crealf(nco_cplx_out), 1.0f, tol);
+        CONTEND_DELTA(cimagf(nco_cplx_out), 0.0f, tol);
+        //printf("%3u : %12.8f + j*%12.8f\n", i, crealf(nco_cplx_out), cimagf(nco_cplx_out));
+
+        // step nco
+        nco_crcf_step(p);
+    }
+
+    // destroy NCO object
+    nco_crcf_destroy(p);
+}
+
+//
+// test nco block mixing
+//
+void autotest_nco_block_mixing()
+{
+    // frequency, phase
+    float f = 0.1f;
+    float phi = M_PI;
+
+    // error tolerance (high for NCO)
+    float tol = 0.05f;
+
+    unsigned int i;
+
+    // number of samples
+    unsigned int num_samples = 1024;
+
+    // store samples
+    float complex * x = (float complex*)malloc(num_samples*sizeof(float complex));
+    float complex * y = (float complex*)malloc(num_samples*sizeof(float complex));
+
+    // generate complex sin/cos
+    for (i=0; i<num_samples; i++)
+        x[i] = cexpf(_Complex_I*(f*i + phi));
+
+    // initialize nco object
+    nco_crcf p = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_frequency(p, f);
+    nco_crcf_set_phase(p, phi);
+
+    // mix signal back to zero phase (in pieces)
+    unsigned int num_remaining = num_samples;
+    i = 0;
+    while (num_remaining > 0) {
+        unsigned int n = 7 < num_remaining ? 7 : num_remaining;
+        nco_crcf_mix_block_down(p, &x[i], &y[i], n);
+
+        i += n;
+        num_remaining -= n;
+    }
+
+    // assert mixer output is correct
+    for (i=0; i<num_samples; i++) {
+        CONTEND_DELTA( crealf(y[i]), 1.0f, tol );
+        CONTEND_DELTA( cimagf(y[i]), 0.0f, tol );
+    }
+
+    // free those buffers
+    free(x);
+    free(y);
+
+    // destroy NCO object
+    nco_crcf_destroy(p);
+}
+
diff --git a/src/nco/tests/nco_crcf_pll_autotest.c b/src/nco/tests/nco_crcf_pll_autotest.c
new file mode 100644
index 0000000..481fb68
--- /dev/null
+++ b/src/nco/tests/nco_crcf_pll_autotest.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <complex.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// test phase-locked loop
+//  _type           :   NCO type (e.g. LIQUID_NCO)
+//  _phase_offset   :   initial phase offset
+//  _freq_offset    :   initial frequency offset
+//  _pll_bandwidth  :   bandwidth of phase-locked loop
+//  _num_iterations :   number of iterations to run
+//  _tol            :   error tolerance
+void nco_crcf_pll_test(int          _type,
+                       float        _phase_offset,
+                       float        _freq_offset,
+                       float        _pll_bandwidth,
+                       unsigned int _num_iterations,
+                       float        _tol)
+{
+    // objects
+    nco_crcf nco_tx = nco_crcf_create(_type);
+    nco_crcf nco_rx = nco_crcf_create(_type);
+
+    // initialize objects
+    nco_crcf_set_phase(nco_tx, _phase_offset);
+    nco_crcf_set_frequency(nco_tx, _freq_offset);
+    nco_crcf_pll_set_bandwidth(nco_rx, _pll_bandwidth);
+
+    // run loop
+    unsigned int i;
+    float phase_error;
+    float complex r, v;
+    for (i=0; i<_num_iterations; i++) {
+        // received complex signal
+        nco_crcf_cexpf(nco_tx,&r);
+        nco_crcf_cexpf(nco_rx,&v);
+
+        // error estimation
+        phase_error = cargf(r*conjf(v));
+
+        // update pll
+        nco_crcf_pll_step(nco_rx, phase_error);
+
+        // update nco objects
+        nco_crcf_step(nco_tx);
+        nco_crcf_step(nco_rx);
+    }
+
+    // ensure phase of oscillators is locked
+    float nco_tx_phase = nco_crcf_get_phase(nco_tx);
+    float nco_rx_phase = nco_crcf_get_phase(nco_rx);
+    CONTEND_DELTA(nco_tx_phase, nco_rx_phase, _tol);
+
+    // ensure frequency of oscillators is locked
+    float nco_tx_freq = nco_crcf_get_frequency(nco_tx);
+    float nco_rx_freq = nco_crcf_get_frequency(nco_rx);
+    CONTEND_DELTA(nco_tx_freq, nco_rx_freq, _tol);
+
+    if (liquid_autotest_verbose) {
+        printf("  phase error : %12.4e, frequency error : %12.4e\n",
+                cargf( cexpf(_Complex_I*(nco_tx_phase-nco_rx_phase)) ),
+                nco_tx_freq-nco_rx_freq);
+    }
+
+    // clean it up
+    nco_crcf_destroy(nco_tx);
+    nco_crcf_destroy(nco_rx);
+}
+
+//
+// AUTOTEST: test frequency and phase offsets
+//
+void autotest_vco_crcf_pll_phase()
+{
+    float tol = 0.01f;
+
+    // test various phase offsets
+    nco_crcf_pll_test(LIQUID_NCO, -M_PI/1.1f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO, -M_PI/2.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO, -M_PI/4.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO, -M_PI/8.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  M_PI/8.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  M_PI/4.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  M_PI/2.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  M_PI/1.1f,  0.0f, 0.1f, 256, tol);
+    
+    // test various frequency offsets
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,      -1.6f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,      -0.8f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,      -0.4f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,      -0.2f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,       0.2f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,       0.4f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,       0.8f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_NCO,  0.0f,       1.6f, 0.1f, 256, tol);
+}
+
+//
+// AUTOTEST: test frequency and phase offsets
+//
+void autotest_nco_crcf_pll_phase()
+{
+    float tol = 1e-4f;
+
+    // test various phase offsets
+    nco_crcf_pll_test(LIQUID_VCO, -M_PI/1.1f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO, -M_PI/2.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO, -M_PI/4.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO, -M_PI/8.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  M_PI/8.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  M_PI/4.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  M_PI/2.0f,  0.0f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  M_PI/1.1f,  0.0f, 0.1f, 256, tol);
+    
+    // test various frequency offsets
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,      -1.6f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,      -0.8f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,      -0.4f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,      -0.2f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,       0.2f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,       0.4f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,       0.8f, 0.1f, 256, tol);
+    nco_crcf_pll_test(LIQUID_VCO,  0.0f,       1.6f, 0.1f, 256, tol);
+}
+
diff --git a/src/nco/tests/unwrap_phase_autotest.c b/src/nco/tests/unwrap_phase_autotest.c
new file mode 100644
index 0000000..499cd22
--- /dev/null
+++ b/src/nco/tests/unwrap_phase_autotest.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <complex.h>
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+//
+// AUTOTEST: regular phase-unwrapping
+//
+void autotest_nco_unwrap_phase()
+{
+    unsigned int n=32;  // number of steps
+    float tol = 1e-6f;  // error tolerance
+    
+    // initialize data arrays
+    float phi[n];       // original array
+    float theta[n];     // wrapped array
+    float phi_hat[n];   // unwrapped array
+
+    float phi0 = 3.0f;  // initial phase
+    float dphi = 0.1f;  // phase step
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        // phase input
+        phi[i] = phi0 + i*dphi;
+
+        // wrapped array
+        theta[i] = phi[i];
+        while (theta[i] >  M_PI) theta[i] -= 2*M_PI;
+        while (theta[i] < -M_PI) theta[i] += 2*M_PI;
+
+        // initialize output
+        phi_hat[i] = theta[i];
+    }
+
+    // unwrap phase
+    liquid_unwrap_phase(phi_hat, n);
+
+    // compare input to output
+    for (i=0; i<n; i++)
+        CONTEND_DELTA( phi[i], phi_hat[i], tol );
+}
+
diff --git a/src/optim/src/chromosome.c b/src/optim/src/chromosome.c
new file mode 100644
index 0000000..0d2bae9
--- /dev/null
+++ b/src/optim/src/chromosome.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// chromosome.c
+//
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_CHROMOSOME_MAX_SIZE (32)
+
+// create chromosome with varying bits/trait
+//  _bits_per_trait     :   array of bits/trait [size: _num_traits x 1]
+//  _num_traits         :   number of traits in this chromosome
+chromosome chromosome_create(unsigned int * _bits_per_trait,
+                             unsigned int _num_traits)
+{
+    chromosome q;
+    q = (chromosome) malloc( sizeof(struct chromosome_s) );
+    q->num_traits = _num_traits;
+
+    // validate input
+    if (q->num_traits < 1) {
+        fprintf(stderr,"error: chromosome_create(), must have at least one trait\n");
+        exit(1);
+    }
+
+    // initialize internal arrays
+    q->bits_per_trait = (unsigned int *) malloc(q->num_traits*sizeof(unsigned int));
+    q->max_value =      (unsigned long*) malloc(q->num_traits*sizeof(unsigned long));
+    q->traits =         (unsigned long*) malloc(q->num_traits*sizeof(unsigned long));
+
+    // copy/initialize values
+    unsigned int i;
+    q->num_bits = 0;
+    for (i=0; i<q->num_traits; i++) {
+        q->bits_per_trait[i] = _bits_per_trait[i];
+
+        if (q->bits_per_trait[i] > LIQUID_CHROMOSOME_MAX_SIZE) {
+            fprintf(stderr,"error: chromosome_create(), bits/trait cannot exceed %u\n", LIQUID_CHROMOSOME_MAX_SIZE);
+            exit(1);
+        }
+
+        q->max_value[i] = 1 << q->bits_per_trait[i];
+        q->traits[i] = 0;
+
+        q->num_bits += q->bits_per_trait[i];
+    }
+
+    return q;
+}
+
+// create basic chromosome
+//  _num_traits         :   number of traits in this chromosome
+//  _bits_per_trait     :   number of bits/trait for all traits
+chromosome chromosome_create_basic(unsigned int _num_traits,
+                                   unsigned int _bits_per_trait)
+{
+    // validate input
+    if (_num_traits == 0) {
+        fprintf(stderr,"error: chromosome_create_basic(), must have at least one trait\n");
+        exit(1);
+    }
+
+    unsigned int * bpt = (unsigned int *) malloc(_num_traits*sizeof(unsigned int));
+    unsigned int i;
+    for (i=0; i<_num_traits; i++)
+        bpt[i] = _bits_per_trait;
+
+    // create chromosome
+    chromosome q = chromosome_create(bpt, _num_traits);
+
+    // free bits/trait array
+    free(bpt);
+
+    return q;
+}
+
+// create chromosome cloning a parent
+chromosome chromosome_create_clone(chromosome _parent)
+{
+    // create chromosome
+    chromosome q = chromosome_create(_parent->bits_per_trait,
+                                     _parent->num_traits);
+
+    // copy internal values
+    chromosome_copy(_parent, q);
+
+    return q;
+}
+
+// copy chromosome
+void chromosome_copy(chromosome _parent,
+                     chromosome _child)
+{
+    // copy internal values
+    unsigned int i;
+    for (i=0; i<_parent->num_traits; i++)
+        _child->traits[i] = _parent->traits[i];
+}
+
+void chromosome_destroy(chromosome _q)
+{
+    free(_q->bits_per_trait);
+    free(_q->max_value);
+    free(_q->traits);
+
+    free(_q);
+}
+
+unsigned int chromosome_get_num_traits(chromosome _q)
+{
+    return _q->num_traits;
+}
+
+void chromosome_print(chromosome _q)
+{
+    unsigned int i,j;
+    printf("chromosome: ");
+    // print one bit at a time
+    for (i=0; i<_q->num_traits; i++) {
+        for (j=0; j<_q->bits_per_trait[i]; j++) {
+            unsigned int bit = (_q->traits[i] >> (_q->bits_per_trait[i]-j-1) ) & 1;
+            printf("%c", bit ? '1' : '0');
+        }
+        
+        if (i != _q->num_traits-1)
+            printf(".");
+    }
+    printf("\n");
+}
+
+void chromosome_printf(chromosome _q)
+{
+    unsigned int i;
+    printf("chromosome: ");
+    for (i=0; i<_q->num_traits; i++)
+        printf("%6.3f", chromosome_valuef(_q,i));
+    printf("\n");
+}
+
+// clear chromosome (set traits to zero)
+void chromosome_clear(chromosome _q)
+{
+    unsigned int i;
+    for (i=0; i<_q->num_traits; i++)
+        _q->traits[i] = 0;
+}
+
+// initialize chromosome on integer values
+void chromosome_init(chromosome _c,
+                     unsigned int * _v)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_traits; i++) {
+        if (_v[i] >= _c->max_value[i]) {
+            fprintf(stderr,"error: chromosome_init(), value exceeds maximum\n");
+            exit(1);
+        }
+        _c->traits[i] = _v[i];
+    }
+}
+
+// initialize chromosome on floating-point values
+void chromosome_initf(chromosome _c,
+                      float * _v)
+{
+    unsigned int i;
+    for (i=0; i<_c->num_traits; i++) {
+        if (_v[i] > 1.0f || _v[i] < 0.0f) {
+            fprintf(stderr,"error: chromosome_initf(), value must be in [0,1]\n");
+            exit(1);
+        }
+
+        // quantize sample
+        unsigned int N = 1 << _c->bits_per_trait[i];
+        _c->traits[i] = (unsigned int) floorf( _v[i] * N );
+    }
+}
+
+// mutate bit at _index
+void chromosome_mutate(chromosome _q,
+                       unsigned int _index)
+{
+    if (_index >= _q->num_bits) {
+        fprintf(stderr,"error: chromosome_mutate(), maximum index exceeded\n");
+        exit(1);
+    }
+
+    // search for
+    unsigned int i;
+    unsigned int t=0;
+    for (i=0; i<_q->num_traits; i++) {
+        unsigned int b = _q->bits_per_trait[i];
+        if (t == _index) {
+            _q->traits[i] ^= (unsigned long)(1 << (b-1));
+            return;
+        } else if (t > _index) {
+            _q->traits[i-1] ^= (unsigned long)(1 << (t-_index-1));
+            return;
+        } else {
+            t += b;
+        }
+    }
+
+    _q->traits[i-1] ^= (unsigned long)(1 << (t-_index-1));
+}
+
+// crossover parent chromosomes and store in child
+//  _p1         :   first parent chromosome
+//  _p2         :   second parent chromosome
+//  _q          :   child chromosome
+//  _threshold  :   crossover point
+void chromosome_crossover(chromosome _p1,
+                          chromosome _p2,
+                          chromosome _q,
+                          unsigned int _threshold)
+{
+    if (_threshold > _q->num_bits) {
+        fprintf(stderr,"error: chromosome_crossover(), maximum index exceeded\n");
+        exit(1);
+    }
+
+    // TODO : validate input on all properties of _p1, _p2, and _q
+
+    // find crossover point
+    unsigned int i;
+    unsigned int t=0;
+    for (i=0; i<_q->num_traits; i++) {
+        if (t >= _threshold)
+            break;
+        else
+            t += _q->bits_per_trait[i];
+
+        // child gets first parent's traits up until
+        // threshold is reached
+        _q->traits[i] = _p1->traits[i];
+    }
+
+#if 0
+    printf("  crossover point   : %u\n", i);
+    printf("  accumulator       : %u\n", t);
+    printf("  remainder         : %u\n", t - _threshold);
+#endif
+
+    // determine if trait is split
+    unsigned int rem = t - _threshold;
+    if (rem > 0) {
+        // split trait on remainder
+        unsigned int b = _q->bits_per_trait[i-1];
+        unsigned int mask1 = ((1 << (b-rem)) - 1) << rem;
+        unsigned int mask2 = ((1 << rem    ) - 1);
+        _q->traits[i-1] = (_p1->traits[i-1] & mask1) |
+                          (_p2->traits[i-1] & mask2);
+#if 0
+        printf("  b                 : %u\n", b);
+        printf("  mask1             : %.8x\n", mask1);
+        printf("  mask2             : %.8x\n", mask2);
+#endif
+    }
+
+    // finish crossover
+    for ( ; i<_q->num_traits; i++) {
+        // child gets second parent's traits beyond threshold
+        _q->traits[i] = _p2->traits[i];
+    }
+
+}
+    
+void chromosome_init_random(chromosome _q)
+{
+    unsigned int i;
+    for (i=0; i<_q->num_traits; i++)
+        _q->traits[i] = rand() & (_q->max_value[i]-1);
+}
+
+float chromosome_valuef(chromosome _q,
+                        unsigned int _index)
+{
+    if (_index > _q->num_traits) {
+        fprintf(stderr,"error: chromosome_valuef(), trait index exceeded\n");
+        exit(1);
+    }
+
+    return (float) (_q->traits[_index]) / (float)(_q->max_value[_index] - 1);
+}
+
+unsigned int chromosome_value(chromosome _q,
+                              unsigned int _index)
+{
+    if (_index > _q->num_traits) {
+        fprintf(stderr,"error: chromosome_value(), trait index exceeded\n");
+        exit(1);
+    }
+
+    return _q->traits[_index];
+}
+
diff --git a/src/optim/src/gasearch.c b/src/optim/src/gasearch.c
new file mode 100644
index 0000000..012e70c
--- /dev/null
+++ b/src/optim/src/gasearch.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// gasearch.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_GA_SEARCH_MAX_POPULATION_SIZE (1024)
+#define LIQUID_GA_SEARCH_MAX_CHROMOSOME_SIZE (32)
+
+#define LIQUID_DEBUG_GA_SEARCH 0
+
+// Create a simple gasearch object; parameters are specified internally
+//  _utility            :   chromosome fitness utility function
+//  _userdata           :   user data, void pointer passed to _utility() callback
+//  _parent             :   initial population parent chromosome, governs precision, etc.
+//  _minmax             :   search direction
+gasearch gasearch_create(gasearch_utility _utility,
+                         void * _userdata,
+                         chromosome _parent,
+                         int _minmax)
+{
+    return gasearch_create_advanced(_utility,
+                                    _userdata,
+                                    _parent,
+                                    _minmax,
+                                    16,        // population size
+                                    0.02f);    // mutation rate
+}
+
+// Create a gasearch object, specifying search parameters
+//  _utility            :   chromosome fitness utility function
+//  _userdata           :   user data, void pointer passed to _utility() callback
+//  _parent             :   initial population parent chromosome, governs precision, etc.
+//  _minmax             :   search direction
+//  _population_size    :   number of chromosomes in population
+//  _mutation_rate      :   probability of mutating chromosomes
+gasearch gasearch_create_advanced(gasearch_utility _utility,
+                                  void * _userdata,
+                                  chromosome _parent,
+                                  int _minmax,
+                                  unsigned int _population_size,
+                                  float _mutation_rate)
+{
+    gasearch ga;
+    ga = (gasearch) malloc( sizeof(struct gasearch_s) );
+
+    if (_population_size > LIQUID_GA_SEARCH_MAX_POPULATION_SIZE) {
+        fprintf(stderr,"error: gasearch_create(), population size exceeds maximum\n");
+        exit(1);
+    } else if (_mutation_rate < 0.0f || _mutation_rate > 1.0f) {
+        fprintf(stderr,"error: gasearch_create(), mutation rate must be in [0,1]\n");
+        exit(1);
+    }
+
+    // initialize public values
+    ga->userdata        = _userdata;
+    ga->num_parameters  = _parent->num_traits;
+    ga->population_size = _population_size;
+    ga->mutation_rate   = _mutation_rate;
+    ga->get_utility     = _utility;
+    ga->minimize        = ( _minmax==LIQUID_OPTIM_MINIMIZE ) ? 1 : 0;
+
+    ga->bits_per_chromosome = _parent->num_bits;
+
+    // initialize selection size be be 25% of population, minimum of 2
+    ga->selection_size = ( ga->population_size >> 2 ) < 2 ? 2 : ga->population_size >> 2;
+
+    // allocate internal arrays
+    ga->population = (chromosome*) malloc( sizeof(chromosome)*(ga->population_size) );
+    ga->utility = (float*) calloc( sizeof(float), ga->population_size );
+
+    // create optimum chromosome (clone)
+    ga->c = chromosome_create_clone(_parent);
+
+    //printf("num_parameters: %d\n", ga->num_parameters);
+    //printf("population_size: %d\n", ga->population_size);
+    //printf("\nbits_per_chromosome: %d\n", ga->bits_per_chromosome);
+
+    // create population
+    unsigned int i;
+    for (i=0; i<ga->population_size; i++)
+        ga->population[i] = chromosome_create_clone(_parent);
+
+    // initialize population to random, preserving first chromosome
+    for (i=1; i<ga->population_size; i++)
+        chromosome_init_random( ga->population[i] );
+
+    // evaluate population
+    gasearch_evaluate(ga);
+
+    // rank chromosomes
+    gasearch_rank(ga);
+
+    // set global utility optimum
+    ga->utility_opt = ga->utility[0];
+
+    // return object
+    return ga;
+}
+
+// destroy a gasearch object
+void gasearch_destroy(gasearch _g)
+{
+    unsigned int i;
+    for (i=0; i<_g->population_size; i++)
+        chromosome_destroy( _g->population[i] );
+    free(_g->population);
+
+    // destroy optimum chromosome
+    chromosome_destroy(_g->c);
+
+    free(_g->utility);
+    free(_g);
+}
+
+// print search parameter internals
+void gasearch_print(gasearch _g)
+{
+    printf("ga search :\n");
+    printf("    num traits      :   %u\n", _g->num_parameters);
+    printf("    bits/chromosome :   %u\n", _g->bits_per_chromosome);
+    printf("    population size :   %u\n", _g->population_size);
+    printf("    selection size  :   %u\n", _g->selection_size);
+    printf("    mutation rate   :   %12.8f\n", _g->mutation_rate);
+    printf("population:\n");
+    unsigned int i;
+    for (i=0; i<_g->population_size; i++) {
+        printf("%4u: [%8.4f] ", i, _g->utility[i]);
+        chromosome_printf( _g->population[i] );
+    }
+}
+
+// set population/selection size
+//  _q                  :   ga search object
+//  _population_size    :   new population size (number of chromosomes)
+//  _selection_size     :   selection size (number of parents for new generation)
+void gasearch_set_population_size(gasearch _g,
+                                  unsigned int _population_size,
+                                  unsigned int _selection_size)
+{
+    // validate input
+    if (_population_size < 2) {
+        fprintf(stderr,"error: gasearch_set_population_size(), population must be at least 2\n");
+        exit(1);
+    } else if (_selection_size == 0) {
+        fprintf(stderr,"error: gasearch_set_population_size(), selection size must be greater than zero\n");
+        exit(1);
+    } else if (_selection_size >= _population_size) {
+        fprintf(stderr,"error: gasearch_set_population_size(), selection size must be less than population\n");
+        exit(1);
+    }
+
+    // re-size arrays
+    _g->population = (chromosome*) realloc( _g->population, _population_size*sizeof(chromosome) );
+    _g->utility = (float*) realloc( _g->utility, _population_size*sizeof(float) );
+
+    // initialize new chromosomes (copies)
+    if (_population_size > _g->population_size) {
+
+        unsigned int i;
+        unsigned int k = _g->population_size-1; // least optimal
+
+        for (i=_g->population_size; i<_population_size; i++) {
+            // clone chromosome, copying internal values
+            _g->population[i] = chromosome_create_clone(_g->population[k]);
+
+            // copy utility
+            _g->utility[i] = _g->utility[k];
+        }
+    }
+
+    // set internal variables
+    _g->population_size = _population_size;
+    _g->selection_size  = _selection_size;
+}
+
+// set mutation rate
+void gasearch_set_mutation_rate(gasearch _g,
+                                float _mutation_rate)
+{
+    if (_mutation_rate < 0.0f || _mutation_rate > 1.0f) {
+        fprintf(stderr,"error: gasearch_set_mutation_rate(), mutation rate must be in [0,1]\n");
+        exit(1);
+    }
+
+    _g->mutation_rate = _mutation_rate;
+}
+
+// Execute the search
+//  _g              :   ga search object
+//  _max_iterations :   maximum number of iterations to run before bailing
+//  _tarutility :   target utility
+float gasearch_run(gasearch _g,
+                    unsigned int _max_iterations,
+                    float _tarutility)
+{
+    unsigned int i=0;
+    do {
+        i++;
+        gasearch_evolve(_g);
+    } while (
+        optim_threshold_switch(_g->utility[0], _tarutility, _g->minimize) &&
+        i < _max_iterations);
+
+    // return optimum utility
+    return _g->utility_opt;
+}
+
+// iterate over one evolution of the search algorithm
+void gasearch_evolve(gasearch _g)
+{
+    // Inject random chromosome at end
+    chromosome_init_random(_g->population[_g->population_size-1]);
+
+    // Crossover
+    gasearch_crossover(_g);
+
+    // Mutation
+    gasearch_mutate(_g);
+
+    // Evaluation
+    gasearch_evaluate(_g);
+
+    // Rank
+    gasearch_rank(_g);
+
+    if ( optim_threshold_switch(_g->utility_opt,
+                                _g->utility[0],
+                                _g->minimize) )
+    {
+        // update optimum
+        _g->utility_opt = _g->utility[0];
+
+        // copy optimum chromosome
+        chromosome_copy(_g->population[0], _g->c);
+
+#if LIQUID_DEBUG_GA_SEARCH
+        printf("  utility: %0.2E", _g->utility_opt);
+        chromosome_printf(_g->c);
+#endif
+    }
+}
+
+// get optimal chromosome
+//  _g              :   ga search object
+//  _c              :   output optimal chromosome
+//  _utility_opt    :   fitness of _c
+void gasearch_getopt(gasearch _g,
+                      chromosome _c,
+                      float * _utility_opt)
+{
+    // copy optimum chromosome
+    chromosome_copy(_g->c, _c);
+
+    // copy optimum utility
+    *_utility_opt = _g->utility_opt;
+}
+
+// evaluate fitness of entire population
+void gasearch_evaluate(gasearch _g)
+{
+    unsigned int i;
+    for (i=0; i<_g->population_size; i++)
+        _g->utility[i] = _g->get_utility(_g->userdata, _g->population[i]);
+}
+
+// crossover population
+void gasearch_crossover(gasearch _g)
+{
+    chromosome p1, p2;      // parental chromosomes
+    chromosome c;           // child chromosome
+    unsigned int threshold;
+
+    unsigned int i;
+    for (i=_g->selection_size; i<_g->population_size; i++) {
+        // ensure fittest member is used at least once as parent
+        p1 = (i==_g->selection_size) ? _g->population[0] : _g->population[rand() % _g->selection_size];
+        p2 = _g->population[rand() % _g->selection_size];
+        threshold = rand() % _g->bits_per_chromosome;
+
+        c = _g->population[i];
+
+        //printf("  gasearch_crossover, p1: %d, p2: %d, c: %d\n", p1, p2, c);
+        chromosome_crossover(p1, p2, c, threshold);
+    }
+}
+
+// mutate population
+void gasearch_mutate(gasearch _g)
+{
+    unsigned int i;
+    unsigned int index;
+
+    // mutate all but first (best) chromosome
+    //for (i=_g->selection_size; i<_g->population_size; i++) {
+    for (i=1; i<_g->population_size; i++) {
+        // generate random number and mutate if within mutation_rate range
+        unsigned int num_mutations = 0;
+        // force at least one mutation (otherwise nothing has changed)
+        while ( randf() < _g->mutation_rate || num_mutations == 0) {
+            // generate random mutation index
+            index = rand() % _g->bits_per_chromosome;
+
+            // mutate chromosome at index
+            chromosome_mutate( _g->population[i], index );
+
+            //
+            num_mutations++;
+
+            if (num_mutations == _g->bits_per_chromosome)
+                break;
+        }
+    }
+}
+
+// rank population by fitness
+void gasearch_rank(gasearch _g)
+{
+    unsigned int i, j;
+    float u_tmp;        // temporary utility placeholder
+    chromosome c_tmp;   // temporary chromosome placeholder (pointer)
+
+    for (i=0; i<_g->population_size; i++) {
+        for (j=_g->population_size-1; j>i; j--) {
+            if ( optim_threshold_switch(_g->utility[j], _g->utility[j-1], !(_g->minimize)) ) {
+                // swap chromosome pointers
+                c_tmp = _g->population[j];
+                _g->population[j] = _g->population[j-1];
+                _g->population[j-1] = c_tmp;
+
+                // swap utility values
+                u_tmp = _g->utility[j];
+                _g->utility[j] = _g->utility[j-1];
+                _g->utility[j-1] = u_tmp;
+            }
+        }
+    }
+}
+
diff --git a/src/optim/src/gradsearch.c b/src/optim/src/gradsearch.c
new file mode 100644
index 0000000..f2467a1
--- /dev/null
+++ b/src/optim/src/gradsearch.c
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+// gradient search algorithm (steepest descent) object
+struct gradsearch_s {
+    float * v;                  // vector to optimize (externally allocated)
+    unsigned int num_parameters;// ...
+    float u;                    // utility at current position
+
+    // properties
+    float delta;                // gradient approximation step size
+    float alpha;                // line search step size
+
+    float * p;                  // gradient estimate
+    float pnorm;                // L2-norm of gradient estimate
+
+    utility_function utility;   // utility function pointer
+    void * userdata;            // object to optimize (user data)
+    int direction;              // search direction (minimize/maximimze utility)
+};
+
+// create a gradient search object
+//   _userdata          :   user data object pointer
+//   _v                 :   array of parameters to optimize
+//   _num_parameters    :   array length (number of parameters to optimize)
+//   _u                 :   utility function pointer
+//   _direction         :   search direction (e.g. LIQUID_OPTIM_MAXIMIZE)
+gradsearch gradsearch_create(void *           _userdata,
+                             float *          _v,
+                             unsigned int     _num_parameters,
+                             utility_function _utility,
+                             int              _direction)
+{
+    gradsearch q = (gradsearch) malloc( sizeof(struct gradsearch_s) );
+
+    // set user-defined properties
+    q->userdata       = _userdata;
+    q->v              = _v;
+    q->num_parameters = _num_parameters;
+    q->utility        = _utility;
+    q->direction      = _direction;
+
+    // set internal properties
+    // TODO : make these user-configurable properties
+    q->delta = 1e-6f;       // gradient approximation step size
+    q->alpha = q->delta;    // line search step size
+
+    // allocate array for gradient estimate
+    q->p = (float*) malloc(q->num_parameters*sizeof(float));
+    q->pnorm = 0.0f;
+    q->u = 0.0f;
+
+    return q;
+}
+
+void gradsearch_destroy(gradsearch _q)
+{
+    // free gradient estimate array
+    free(_q->p);
+
+    // free main object memory
+    free(_q);
+}
+
+// print status
+void gradsearch_print(gradsearch _q)
+{
+    //printf("gradient search:\n");
+    printf("u=%12.4e ",   _q->u);       // utility
+#if 0
+    // enable more verbose output
+    printf("|p|=%7.1e ",  _q->pnorm);   // norm(p)
+    printf("del=%7.1e ",  _q->delta);   // delta
+#endif
+    printf("step=%7.1e ", _q->alpha);   // alpha (step size)
+
+    unsigned int i;
+    printf("{");
+    for (i=0; i<_q->num_parameters; i++)
+        printf("%8.4f", _q->v[i]);
+    printf("}\n");
+}
+
+float gradsearch_step(gradsearch _q)
+{
+    unsigned int i;
+
+    // ensure norm(p) > 0, otherwise increase delta
+    unsigned int n=20;
+    for (i=0; i<n; i++) {
+        // compute gradient
+        gradsearch_gradient(_q->utility, _q->userdata, _q->v, _q->num_parameters, _q->delta, _q->p);
+
+        // normalize gradient vector
+        _q->pnorm = gradsearch_norm(_q->p, _q->num_parameters);
+
+        if (_q->pnorm > 0.0f) {
+            // try to keep delta about 1e-4 * pnorm
+            if (1e-4f*_q->pnorm < _q->delta)
+                _q->delta *= 0.90f;
+            else if ( 1e-5f*_q->pnorm > _q->delta)
+                _q->delta *= 1.10f;
+
+            break;
+        } else {
+            // step size is too small to approximate gradient
+            _q->delta *= 10.0f;
+        }
+    }
+    
+    if (i == n) {
+        fprintf(stderr,"warning: gradsearch_step(), function ill-conditioned\n");
+        return _q->utility(_q->userdata, _q->v, _q->num_parameters);
+    }
+
+    // run line search
+    _q->alpha = gradsearch_linesearch(_q->utility,
+                                      _q->userdata,
+                                      _q->direction,
+                                      _q->num_parameters,
+                                      _q->v,
+                                      _q->p,
+                                      _q->delta);
+
+    // step in the negative direction of the gradient
+    float dir = _q->direction == LIQUID_OPTIM_MINIMIZE ? 1.0f : -1.0f;
+    for (i=0; i<_q->num_parameters; i++)
+        _q->v[i] = _q->v[i] - dir*_q->alpha*_q->p[i];
+
+    // evaluate utility at current position
+    _q->u = _q->utility(_q->userdata, _q->v, _q->num_parameters);
+
+    // return utility
+    return _q->u;
+}
+
+// batch execution of gradient search : run many steps and stop
+// when criteria are met
+float gradsearch_execute(gradsearch   _q,
+                         unsigned int _max_iterations,
+                         float        _target_utility)
+{
+    int continue_running = 1;
+    unsigned int num_iterations = 0;
+
+    float u = 0.0f;
+    while (continue_running) {
+        // increment number of iterations
+        num_iterations++;
+
+        // step gradient search algorithm
+        u = gradsearch_step(_q);
+
+        // check exit criteria (any one of the following)
+        //  * maximum number of iterations met
+        //  * maximize utility and target exceeded
+        //  * minimize utility and target exceeded
+        if ( (num_iterations >= _max_iterations                            ) ||
+             (_q->direction == LIQUID_OPTIM_MINIMIZE && u < _target_utility) ||
+             (_q->direction == LIQUID_OPTIM_MAXIMIZE && u > _target_utility) )
+        {
+            continue_running = 0;
+        }
+
+    }
+
+    // return final utility
+    return u;
+}
+
+
+// 
+// internal (generic functions)
+//
+
+// compute the gradient of a function at a particular point
+//  _utility    :   user-defined function
+//  _userdata   :   user-defined data object
+//  _x          :   operating point, [size: _n x 1]
+//  _n          :   dimensionality of search
+//  _delta      :   step value for which to compute gradient
+//  _gradient   :   resulting gradient
+void gradsearch_gradient(utility_function _utility,
+                         void  *          _userdata,
+                         float *          _x,
+                         unsigned int     _n,
+                         float            _delta,
+                         float *          _gradient)
+{
+    // operating point for evaluation
+    float x_prime[_n];
+    float u_prime;
+
+    // evaluate function at current operating point
+    float u0 = _utility(_userdata, _x, _n);
+        
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        // copy operating point
+        memmove(x_prime, _x, _n*sizeof(float));
+        
+        // increment test vector by delta along dimension 'i'
+        x_prime[i] += _delta;
+
+        // evaluate new utility
+        u_prime = _utility(_userdata, x_prime, _n);
+
+        // compute gradient estimate
+        _gradient[i] = (u_prime - u0) / _delta;
+    }
+}
+
+// execute line search; loosely solve:
+//
+//    min|max phi(alpha) := f(_x - alpha*_p)
+//
+// and return best guess at alpha that achieves this
+//
+//  _utility    :   user-defined function
+//  _userdata   :   user-defined data object
+//  _direction  :   search direction (e.g. LIQUID_OPTIM_MINIMIZE)
+//  _n          :   dimensionality of search
+//  _x          :   operating point, [size: _n x 1]
+//  _p          :   normalized gradient, [size: _n x 1]
+//  _alpha      :   initial step size
+float gradsearch_linesearch(utility_function _utility,
+                            void  *          _userdata,
+                            int              _direction,
+                            unsigned int     _n,
+                            float *          _x,
+                            float *          _p,
+                            float            _alpha)
+{
+    // evaluate utility at base point
+    float u0 = _utility(_userdata, _x, _n);
+
+    // initialize step size estimate
+    float alpha = _alpha;
+
+    // step direction
+    float dir = _direction == LIQUID_OPTIM_MINIMIZE ? 1.0f : -1.0f;
+
+    // test vector, TODO : dynamic allocation?
+    float x_prime[_n];
+
+    // run line search
+    int continue_running = 1;
+    unsigned int num_iterations = 0;
+    while (continue_running) {
+        // increment iteration counter
+        num_iterations++;
+
+        // update evaluation point
+        unsigned int i;
+        for (i=0; i<_n; i++)
+            x_prime[i] = _x[i] - dir*alpha*_p[i];
+        
+        // compute utility for line search step
+        float uls = _utility(_userdata, x_prime, _n);
+        //printf("  linesearch %6u : alpha=%12.6f, u0=%12.8f, uls=%12.8f\n", num_iterations, alpha, u0, uls);
+
+        // check exit criteria
+        if ( (_direction == LIQUID_OPTIM_MINIMIZE && uls > u0) ||
+             (_direction == LIQUID_OPTIM_MAXIMIZE && uls < u0) )
+        {
+            // compared this utility to previous; went too far.
+            // backtrack step size and stop line search
+            alpha *= 0.5f;
+            continue_running = 0;
+        } else if ( num_iterations >= 20 ) {
+            // maximum number of iterations met: stop line search
+            continue_running = 0;
+        } else {
+            // save new best estimate, increase step size, and continue
+            u0 = uls;
+            alpha *= 2.0f;
+        }
+    }
+
+    return alpha;
+}
+
+// normalize vector, returning its l2-norm
+float gradsearch_norm(float *      _v,
+                      unsigned int _n)
+{
+    float vnorm = 0.0f;
+
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        vnorm += _v[i]*_v[i];
+
+    vnorm = sqrtf(vnorm);
+
+    for (i=0; i<_n; i++)
+        _v[i] /= vnorm;
+
+    return vnorm;
+}
+
diff --git a/src/optim/src/optim.common.c b/src/optim/src/optim.common.c
new file mode 100644
index 0000000..9404914
--- /dev/null
+++ b/src/optim/src/optim.common.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// optim.common.c
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+
+// optimization threshold switch
+//  _u0         :   first utility
+//  _u1         :   second utility
+//  _minimize   :   minimize flag
+//
+// returns:
+//  (_u0 > _u1) if (_minimize == 1)
+//  (_u0 < _u1) otherwise
+int optim_threshold_switch(float _u0,
+                           float _u1,
+                           int _minimize)
+{
+    return _minimize ? _u0 > _u1 : _u0 < _u1;
+}
+
+// sort values by index
+//  _v          :   input values [size: _len x 1]
+//  _rank       :   output rank array (indices) [size: _len x 1]
+//  _len        :   length of input array
+//  _descending :   descending/ascending
+void optim_sort(float *_v,
+                unsigned int * _rank,
+                unsigned int _len,
+                int _descending)
+{
+    unsigned int i, j, tmp_index;
+
+    for (i=0; i<_len; i++)
+        _rank[i] = i;
+
+    for (i=0; i<_len; i++) {
+        for (j=_len-1; j>i; j--) {
+            //if (_v[_rank[j]]>_v[_rank[j-1]]) {
+            if ( optim_threshold_switch(_v[_rank[j]], _v[_rank[j-1]], _descending) ) {
+                // swap elements
+                tmp_index = _rank[j];
+                _rank[j] = _rank[j-1];
+                _rank[j-1] = tmp_index;
+            }
+        }
+    }
+}
+
+
diff --git a/src/optim/src/optim.h b/src/optim/src/optim.h
new file mode 100644
index 0000000..ddf71ae
--- /dev/null
+++ b/src/optim/src/optim.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Optimization
+//
+
+#ifndef __LIQUID_OPTIM_H__
+#define __LIQUID_OPTIM_H__
+
+// optim pattern set (struct)
+struct optim_ps_s {
+    float *x, *y;
+    unsigned int nx, ny, np;
+    unsigned int na; // num allocated
+};
+
+typedef struct optim_ps_s * optim_ps;
+
+optim_ps optim_ps_create(unsigned int _nx, unsigned int _ny);
+void optim_ps_destroy(optim_ps _ps);
+void optim_ps_print(optim_ps _ps);
+void optim_ps_append_pattern(optim_ps _ps, float *_x, float *_y);
+void optim_ps_append_patterns(optim_ps _ps, float *_x, float *_y, unsigned int _np);
+void optim_ps_delete_pattern(optim_ps _ps, unsigned int _i);
+void optim_ps_clear(optim_ps _ps);
+void optim_ps_access(optim_ps _ps, unsigned int _i, float **_x, float **_y);
+
+typedef void(*optim_target_function)(float *_x, float *_y, void *_p);
+typedef float(*optim_obj_function)(optim_ps _ps, void *_p, optim_target_function _f);
+
+#endif // __LIQUID_OPTIM_H__
diff --git a/src/optim/src/qnsearch.c b/src/optim/src/qnsearch.c
new file mode 100644
index 0000000..e7b2e56
--- /dev/null
+++ b/src/optim/src/qnsearch.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "liquid.internal.h"
+
+#define LIQUID_qnsearch_GAMMA_MIN 0.000001
+
+qnsearch qnsearch_create(void * _userdata,
+                         float * _v,
+                         unsigned int _num_parameters,
+                         utility_function _u,
+                         int _minmax)
+{
+    qnsearch q = (qnsearch) malloc( sizeof(struct qnsearch_s) );
+
+    // initialize public values
+    q->delta = 1e-6f;   //_delta;
+    q->gamma = 1e-3f;   //_gamma;
+    q->dgamma = 0.99f;
+    q->gamma_hat = q->gamma;
+
+    q->userdata = _userdata;
+    q->v = _v;
+    q->num_parameters = _num_parameters;
+    q->get_utility = _u;
+    q->minimize = ( _minmax == LIQUID_OPTIM_MINIMIZE ) ? 1 : 0;
+
+    // initialize internal memory arrays
+    q->B        = (float*) calloc( q->num_parameters*q->num_parameters, sizeof(float));
+    q->H        = (float*) calloc( q->num_parameters*q->num_parameters, sizeof(float));
+    q->p        = (float*) calloc( q->num_parameters, sizeof(float) );
+    q->gradient = (float*) calloc( q->num_parameters, sizeof(float) );
+    q->gradient0= (float*) calloc( q->num_parameters, sizeof(float) );
+    q->v_prime  = (float*) calloc( q->num_parameters, sizeof(float) );
+    q->dv       = (float*) calloc( q->num_parameters, sizeof(float) );
+    q->utility = q->get_utility(q->userdata, q->v, q->num_parameters);
+
+    qnsearch_reset(q);
+
+    return q;
+}
+
+void qnsearch_destroy(qnsearch _q)
+{
+    free(_q->B);
+    free(_q->H);
+
+    free(_q->p);
+    free(_q->gradient);
+    free(_q->gradient0);
+    free(_q->v_prime);
+    free(_q->dv);
+    free(_q);
+}
+
+void qnsearch_print(qnsearch _q)
+{
+    printf("[%.3f] ", _q->utility);
+    unsigned int i;
+    for (i=0; i<_q->num_parameters; i++)
+        printf("%.3f ", _q->v[i]);
+    printf("\n");
+}
+
+void qnsearch_reset(qnsearch _q)
+{
+    _q->gamma_hat = _q->gamma;
+
+    // set B to identity matrix
+    unsigned int i,j,n=0;
+    for (i=0; i<_q->num_parameters; i++) {
+        for (j=0; j<_q->num_parameters; j++) {
+            _q->B[n++] = (i==j) ? 1.0f : 0.0f;
+        }
+    }
+
+    _q->utility = _q->get_utility(_q->userdata, _q->v, _q->num_parameters);
+}
+
+void qnsearch_step(qnsearch _q)
+{
+    unsigned int i;
+    unsigned int n = _q->num_parameters;
+
+    // compute normalized gradient vector
+    qnsearch_compute_gradient(_q);
+    //qnsearch_normalize_gradient(_q);
+
+    // TODO : perform line search to find optimal gamma
+
+    // compute search direction
+#if 0
+    matrixf_mul(_q->B,        n, n,
+                _q->gradient, n, 1,
+                _q->p,        n, 1);
+    for (i=0; i<_q->num_parameters; i++)
+        _q->p[i] = -_q->p[i];
+#else
+    qnsearch_compute_Hessian(_q);
+    matrixf_inv(_q->H, n, n);
+    matrixf_mul(_q->H, n, n,
+                _q->gradient, n, 1,
+                _q->p, n, 1);
+#endif
+
+    // compute step vector
+    for (i=0; i<_q->num_parameters; i++)
+        _q->dv[i] = -_q->gamma_hat * _q->p[i];
+
+    // apply change
+    for (i=0; i<_q->num_parameters; i++) {
+        _q->v[i] += _q->dv[i];
+    }
+
+    // TODO update inverse Hessian approximation
+
+    // store previous gradient
+    memmove(_q->gradient0, _q->gradient, (_q->num_parameters)*sizeof(float));
+
+    // update utility
+    float u_prime = _q->get_utility(_q->userdata, _q->v, _q->num_parameters);
+
+    if (u_prime > _q->utility) {
+        _q->gamma_hat *= 0.99f;
+    } else {
+        _q->gamma_hat *= 1.001f;
+    }
+
+    _q->utility = u_prime;
+}
+
+float qnsearch_run(qnsearch _q,
+                   unsigned int _max_iterations,
+                   float _target_utility)
+{
+    unsigned int i=0;
+    do {
+        i++;
+        qnsearch_step(_q);
+        _q->utility = _q->get_utility(_q->userdata, _q->v, _q->num_parameters);
+
+    } while (
+        optim_threshold_switch(_q->utility, _target_utility, _q->minimize) &&
+        i < _max_iterations);
+
+    return _q->utility;
+}
+
+// 
+// internal
+//
+
+// compute gradient
+void qnsearch_compute_gradient(qnsearch _q)
+{
+    unsigned int i;
+    float f_prime;
+
+    // reset v_prime
+    memmove(_q->v_prime, _q->v, (_q->num_parameters)*sizeof(float));
+
+    for (i=0; i<_q->num_parameters; i++) {
+        _q->v_prime[i] += _q->delta;
+        f_prime = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+        _q->v_prime[i] -= _q->delta;
+        _q->gradient[i] = (f_prime - _q->utility) / _q->delta;
+    }
+}
+
+// normalize gradient vector to unity
+void qnsearch_normalize_gradient(qnsearch _q)
+{
+    // normalize gradient
+    float sig = 0.0f;
+    unsigned int i;
+    for (i=0; i<_q->num_parameters; i++)
+        sig += _q->gradient[i] * _q->gradient[i];
+
+    sig = 1.0f / sqrtf(sig/(float)(_q->num_parameters));
+
+    for (i=0; i<_q->num_parameters; i++)
+        _q->gradient[i] *= sig;
+}
+
+// compute Hessian
+void qnsearch_compute_Hessian(qnsearch _q)
+{
+    unsigned int i, j;
+    unsigned int n = _q->num_parameters;
+    float f00, f01, f10, f11;
+    float f0, f1, f2;
+    float m0, m1;
+    float delta = 1e-2f;
+
+    // reset v_prime
+    memmove(_q->v_prime, _q->v, (_q->num_parameters)*sizeof(float));
+
+
+    for (i=0; i<_q->num_parameters; i++) {
+        //for (j=0; j<_q->num_parameters; j++) {
+        for (j=0; j<=i; j++) {
+            if (i==j) {
+
+                _q->v_prime[i] = _q->v[i] - delta;
+                f0 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                _q->v_prime[i] = _q->v[i];
+                f1 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                _q->v_prime[i] = _q->v[i] + delta;
+                f2 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+                
+                m0 = (f1 - f0) / delta;
+                m1 = (f2 - f1) / delta;
+                matrix_access(_q->H, n, n, i, j) = (m1 - m0) / delta;
+
+            } else {
+
+                // 0 0
+                _q->v_prime[i] = _q->v[i] - delta;
+                _q->v_prime[j] = _q->v[j] - delta;
+                f00 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                // 0 1
+                _q->v_prime[i] = _q->v[i] - delta;
+                _q->v_prime[j] = _q->v[j] + delta;
+                f01 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                // 1 0
+                _q->v_prime[i] = _q->v[i] + delta;
+                _q->v_prime[j] = _q->v[j] - delta;
+                f10 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                // 1 1
+                _q->v_prime[i] = _q->v[i] + delta;
+                _q->v_prime[j] = _q->v[j] + delta;
+                f11 = _q->get_utility(_q->userdata, _q->v_prime, _q->num_parameters);
+
+                // compute second partial derivative
+                m0 = (f01 - f00) / (2.0f*delta);
+                m1 = (f11 - f10) / (2.0f*delta);
+                matrix_access(_q->H, n, n, i, j) = (m1 - m0) / (2.0f*delta);
+                matrix_access(_q->H, n, n, j, i) = (m1 - m0) / (2.0f*delta);
+            }
+        }
+    }
+    //matrixf_print(_q->H, n, n);
+    //exit(1);
+}
+
+
diff --git a/src/optim/src/utilities.c b/src/optim/src/utilities.c
new file mode 100644
index 0000000..178441c
--- /dev/null
+++ b/src/optim/src/utilities.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// utilities.c : n-dimensional utility functions
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// n-dimensional Rosenbrock utility function, minimum at _v = {1,1,1...}
+float liquid_rosenbrock(void *       _userdata,
+                        float *      _v,
+                        unsigned int _n)
+{
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_rosenbrock(), input vector length cannot be zero\n");
+        exit(1);
+    } else if (_n == 1) {
+        return (1.0f-_v[0])*(1.0f-_v[0]);
+    }
+
+    float u=0.0f;
+    unsigned int i;
+    for (i=0; i<_n-1; i++)
+        u += powf(1-_v[i],2) + 100*powf( _v[i+1] - powf(_v[i],2), 2);
+
+    return u;
+}
+
+// n-dimensional inverse Gauss utility function (minimum at _v = {1,1,1...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_invgauss(void *       _userdata,
+                      float *      _v,
+                      unsigned int _n)
+{
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_invgauss(), input vector length cannot be zero\n");
+        exit(1);
+    }
+
+    float t = 0.0f;
+    float sigma = 1.0f;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        t += (_v[i]-1.0f)*(_v[i]-1.0f) / (sigma*sigma);
+
+        // increase variance along this dimension
+        sigma *= 1.5f;
+    }
+
+    return 1 - expf(-t);
+}
+
+// n-dimensional multimodal utility function (minimum at _v = {0,0,0...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_multimodal(void *       _userdata,
+                        float *      _v,
+                        unsigned int _n)
+{
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_multimodal(), input vector length cannot be zero\n");
+        exit(1);
+    }
+
+    float t0 = 1.0f;
+    float t1 = 0.0f;
+    float sigma = 4.0f;
+
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        t0 *= 0.5f + 0.5f*cosf(2*M_PI*_v[i]);
+        t1 += _v[i]*_v[i] / (sigma*sigma);
+    }
+    //t0 = powf(t0, 1.0f / (float)_n);
+
+    return 1.0f - t0*expf(-t1);
+}
+
+// n-dimensional spiral utility function (minimum at _v = {0,0,0...}
+//  _userdata   :   user-defined data structure (convenience)
+//  _v          :   input vector [size: _n x 1]
+//  _n          :   input vector size
+float liquid_spiral(void *       _userdata,
+                    float *      _v,
+                    unsigned int _n)
+{
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_rosenbrock(), input vector length cannot be zero\n");
+        exit(1);
+    } else if (_n == 1) {
+        return _v[0]*_v[0];
+    }
+
+    // n is at least 2
+    float r_hat     = sqrtf(_v[0]*_v[0] + _v[1]*_v[1]);
+    float theta_hat = atan2f(_v[1], _v[0]);
+
+    float delta = theta_hat - r_hat * 10;
+    while (delta >  M_PI) delta -= 2*M_PI;
+    while (delta < -M_PI) delta += 2*M_PI;
+
+    delta = delta / M_PI;
+
+    float u = 1 - delta*delta*expf(-r_hat*r_hat/10);
+
+    // additional error...
+    unsigned int i;
+    for (i=2; i<_n; i++)
+        u += _v[i]*_v[i];
+
+    return u;
+}
+
diff --git a/src/optim/tests/gradsearch_autotest.c b/src/optim/tests/gradsearch_autotest.c
new file mode 100644
index 0000000..716c247
--- /dev/null
+++ b/src/optim/tests/gradsearch_autotest.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <getopt.h>
+
+#include "liquid.h"
+
+//
+// AUTOTEST: Find minimum of Rosenbrock function, should be [1 1 1 ...]
+//
+void autotest_gradsearch_rosenbrock()
+{
+    float tol = 1e-2f;                  // error tolerance
+    unsigned int num_parameters = 6;    // dimensionality of search (minimum 2)
+    unsigned int num_iterations = 4000; // number of iterations to run
+
+    // initialize vector for optimization
+    float v_opt[num_parameters];
+    unsigned int i;
+    for (i=0; i<num_parameters; i++)
+        v_opt[i] = 0.0f;
+
+    // create gradsearch object
+    gradsearch gs = gradsearch_create(NULL,
+                                      v_opt,
+                                      num_parameters,
+                                      liquid_rosenbrock,
+                                      LIQUID_OPTIM_MINIMIZE);
+
+#if 0
+    // execute search
+    float u_opt = gradsearch_execute(gs, num_iterations, -1e-6f);
+#else
+    // execute search one iteration at a time
+    unsigned int d=1;
+    for (i=0; i<num_iterations; i++) {
+        gradsearch_step(gs);
+
+        // periodically print updates
+        if (liquid_autotest_verbose) {
+            if (((i+1)%d)==0 || i==0 || i == num_iterations-1) {
+                printf("%5u: ", i+1);
+                gradsearch_print(gs);
+
+                if ((i+1)==10*d) d*=10;
+            }
+        }
+    }
+#endif
+
+    // destroy gradient descent search object
+    gradsearch_destroy(gs);
+
+    // test results, optimum at [1, 1, 1, ... 1];
+    for (i=0; i<num_parameters; i++)
+        CONTEND_DELTA(v_opt[i], 1.0f, tol);
+
+    // test value of utility (should be nearly 0)
+    CONTEND_DELTA( liquid_rosenbrock(NULL, v_opt, num_parameters), 0.0f, tol );
+}
+
+//
+// AUTOTEST: Find maximum of: exp{ -sum{ (v[i]-1)^2/sigma_i^2 } }, should be [1 1 1 ...]
+//
+
+// test utility function
+float utility_max_autotest(void *       _userdata,
+                           float *      _v,
+                           unsigned int _n)
+{
+    if (_n == 0) {
+        fprintf(stderr,"error: liquid_invgauss(), input vector length cannot be zero\n");
+        exit(1);
+    }
+
+    float t = 0.0f;
+    float sigma = 1.0f;
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        t += (_v[i]-1.0f)*(_v[i]-1.0f) / (sigma*sigma);
+
+        // increase variance along this dimension
+        sigma *= 1.5f;
+    }
+
+    return expf(-t);
+}
+
+void autotest_gradsearch_maxutility()
+{
+    float tol = 1e-2f;                  // error tolerance
+    unsigned int num_parameters = 6;    // dimensionality of search (minimum 2)
+    unsigned int num_iterations = 4000; // number of iterations to run
+
+    // initialize vector for optimization
+    float v_opt[num_parameters];
+    unsigned int i;
+    for (i=0; i<num_parameters; i++)
+        v_opt[i] = 0.0f;
+
+    // create gradsearch object
+    gradsearch gs = gradsearch_create(NULL,
+                                      v_opt,
+                                      num_parameters,
+                                      utility_max_autotest,
+                                      LIQUID_OPTIM_MAXIMIZE);
+
+    // execute search one iteration at a time
+    unsigned int d=1;
+    for (i=0; i<num_iterations; i++) {
+        gradsearch_step(gs);
+
+        // periodically print updates
+        if (liquid_autotest_verbose) {
+            if (((i+1)%d)==0 || i==0 || i == num_iterations-1) {
+                printf("%5u: ", i+1);
+                gradsearch_print(gs);
+
+                if ((i+1)==10*d) d*=10;
+            }
+        }
+    }
+
+    // destroy gradient descent search object
+    gradsearch_destroy(gs);
+
+    // test results, optimum at [1, 1, 1, ... 1];
+    for (i=0; i<num_parameters; i++)
+        CONTEND_DELTA(v_opt[i], 1.0f, tol);
+
+    // test value of utility (should be nearly 1)
+    CONTEND_DELTA( utility_max_autotest(NULL, v_opt, num_parameters), 1.0f, tol );
+}
+
diff --git a/src/quantization/bench/compander_benchmark.c b/src/quantization/bench/compander_benchmark.c
new file mode 100644
index 0000000..0ac5735
--- /dev/null
+++ b/src/quantization/bench/compander_benchmark.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// 
+void benchmark_compress_mulaw(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+{
+    unsigned long int i;
+
+    float x  = -0.1f;
+    float mu = 255.0f;
+    float y  = 0.0f;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        y += compress_mulaw(x,mu);
+        y -= compress_mulaw(x,mu);
+        x += compress_mulaw(y,mu);
+        x -= compress_mulaw(y,mu);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+void benchmark_expand_mulaw(
+    struct rusage *_start,
+    struct rusage *_finish,
+    unsigned long int *_num_iterations)
+{
+    unsigned long int i;
+
+    float x  = 0.0f;
+    float mu = 255.0f;
+    float y  = 0.75f;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += expand_mulaw(y,mu);
+        x -= expand_mulaw(y,mu);
+        y += expand_mulaw(x,mu);
+        y -= expand_mulaw(x,mu);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
diff --git a/src/quantization/bench/quantizer_benchmark.c b/src/quantization/bench/quantizer_benchmark.c
new file mode 100644
index 0000000..7f6d992
--- /dev/null
+++ b/src/quantization/bench/quantizer_benchmark.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// 
+void benchmark_quantize_adc(struct rusage *     _start,
+                            struct rusage *     _finish,
+                            unsigned long int * _num_iterations)
+{
+    unsigned long int i;
+
+    unsigned int q = 0;
+    unsigned int num_bits=8;
+    float x=-0.1f;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        q ^= quantize_adc(x,num_bits);
+        q ^= quantize_adc(x,num_bits);
+        q ^= quantize_adc(x,num_bits);
+        q ^= quantize_adc(x,num_bits);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+void benchmark_quantize_dac(struct rusage *     _start,
+                            struct rusage *     _finish,
+                            unsigned long int * _num_iterations)
+{
+    unsigned long int i;
+
+    unsigned int q=0x0f;
+    unsigned int num_bits=8;
+    float x = 0;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += quantize_dac(q,num_bits);
+        x += quantize_dac(q,num_bits);
+        x += quantize_dac(q,num_bits);
+        x += quantize_dac(q,num_bits);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
diff --git a/src/quantization/src/compand.c b/src/quantization/src/compand.c
new file mode 100644
index 0000000..40431dd
--- /dev/null
+++ b/src/quantization/src/compand.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_VALIDATE_INPUT
+
+float compress_mulaw(float _x, float _mu)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if ( _mu <= 0.0f ) {
+        printf("error: compress_mulaw(), mu out of range\n");
+        exit(1);
+    }
+#endif
+    float y = logf(1 + _mu*fabsf(_x)) / logf(1 + _mu);
+    return copysignf(y, _x);
+}
+
+float expand_mulaw(float _y, float _mu)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if ( _mu <= 0.0f ) {
+        printf("error: expand_mulaw(), mu out of range\n");
+        exit(1);
+    }
+#endif
+    float x = (1/_mu)*( powf(1+_mu,fabsf(_y)) - 1);
+    return copysign(x, _y);
+}
+
+void compress_cf_mulaw(float complex _x, float _mu, float complex * _y)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if ( _mu <= 0.0f ) {
+        printf("error: compress_mulaw(), mu out of range\n");
+        exit(1);
+    }
+#endif
+    *_y = cexpf(_Complex_I*cargf(_x)) * logf(1 + _mu*cabsf(_x)) / logf(1 + _mu);
+}
+
+void expand_cf_mulaw(float complex _y, float _mu, float complex * _x)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if ( _mu <= 0.0f ) {
+        printf("error: expand_mulaw(), mu out of range\n");
+        exit(1);
+    }
+#endif
+    *_x = cexpf(_Complex_I*cargf(_y)) * (1/_mu)*( powf(1+_mu,cabsf(_y)) - 1);
+}
+
+/*
+float compress_alaw(float _x, float _a)
+{
+
+}
+
+float expand_alaw(float _x, float _a)
+{
+
+}
+*/
+
diff --git a/src/quantization/src/quantizer.c b/src/quantization/src/quantizer.c
new file mode 100644
index 0000000..f034d1e
--- /dev/null
+++ b/src/quantization/src/quantizer.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// structured quantizer
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+struct QUANTIZER(_s) {
+    int ctype;          // compander type
+    unsigned int n;     // number of bits
+                        // table?
+};
+
+// create quantizer object
+//  _ctype      :   compander type (e.g. LIQUID_COMPANDER_LINEAR)
+//  _range      :   maximum absolute input
+//  _num_bits   :   number of bits per sample
+QUANTIZER() QUANTIZER(_create)(liquid_compander_type _ctype,
+                               float _range,
+                               unsigned int _num_bits)
+{
+    // validate input
+    if (_num_bits == 0) {
+        fprintf(stderr,"error: quantizer_create(), must have at least one bit/sample\n");
+        exit(1);
+    }
+
+    // create quantizer object
+    QUANTIZER() q = (QUANTIZER()) malloc(sizeof(struct QUANTIZER(_s)));
+
+    // initialize values
+    q->ctype = _ctype;
+    q->n     = _num_bits;
+
+    // return object
+    return q;
+}
+
+void QUANTIZER(_destroy)(QUANTIZER() _q)
+{
+    // free main object memory
+    free(_q);
+}
+
+void QUANTIZER(_print)(QUANTIZER() _q)
+{
+    printf("quantizer:\n");
+    printf("  compander :   ");
+    switch(_q->ctype) {
+    case LIQUID_COMPANDER_NONE:     printf("none\n");   break;
+    case LIQUID_COMPANDER_LINEAR:   printf("linear\n"); break;
+    case LIQUID_COMPANDER_MULAW:    printf("mu-law\n"); break;
+    case LIQUID_COMPANDER_ALAW:     printf("A-law\n");  break;
+    default:
+        printf("unknown\n");
+    }
+    printf("  num bits  :   %u\n", _q->n);
+}
+
+void QUANTIZER(_execute_adc)(QUANTIZER() _q,
+                             T _x,
+                             unsigned int * _sample)
+{
+#if T_COMPLEX
+#else
+#endif
+    *_sample = 0;
+}
+
+void QUANTIZER(_execute_dac)(QUANTIZER() _q,
+                             unsigned int _sample,
+                             T * _x)
+{
+#if T_COMPLEX
+#else
+#endif
+    *_x = 0.0;
+}
+
+
diff --git a/src/quantization/src/quantizer.inline.c b/src/quantization/src/quantizer.inline.c
new file mode 100644
index 0000000..ec588ca
--- /dev/null
+++ b/src/quantization/src/quantizer.inline.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//
+//
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_VALIDATE_INPUT
+#define QUANTIZER_MAX_BITS      32
+
+// inline quantizer: 'analog' signal in [-1, 1]
+
+unsigned int quantize_adc(float _x, unsigned int _num_bits)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if (_num_bits > QUANTIZER_MAX_BITS) {
+        printf("error: quantize_adc(), maximum bits exceeded\n");
+        exit(1);
+    }
+#endif
+
+    if (_num_bits == 0)
+        return 0;
+
+    unsigned int n = _num_bits-1;   // 
+    unsigned int N = 1<<n;          // 2^n
+
+    // scale
+    int neg = (_x < 0);
+    unsigned int r = floorf(fabsf(_x)*N);
+
+    // clip
+    if (r >= N)
+        r = N-1;
+
+    // if negative set MSB to 1
+    if (neg)
+        r |= N;
+
+    return r;
+}
+
+float quantize_dac(unsigned int _s, unsigned int _num_bits)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    if (_num_bits > QUANTIZER_MAX_BITS) {
+        printf("error: quantize_dac(), maximum bits exceeded\n");
+        exit(1);
+    }
+#endif
+    if (_num_bits == 0)
+        return 0.0f;
+
+    unsigned int n = _num_bits-1;   //
+    unsigned int N = 1<<n;          // 2^n
+    float r = ((float)(_s & (N-1))+0.5f) / (float) (N);
+
+    // check MSB, return negative if 1
+    return (_s & N) ? -r : r;
+}
+
diff --git a/src/quantization/src/quantizercf.c b/src/quantization/src/quantizercf.c
new file mode 100644
index 0000000..16d043f
--- /dev/null
+++ b/src/quantization/src/quantizercf.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Quantizer API: complex floating point
+//
+
+#include "liquid.internal.h"
+
+// 
+#define QUANTIZER(name)     LIQUID_CONCAT(quantizercf,name)
+
+#define T                   float complex   // general
+
+#define T_COMPLEX           1
+
+// source files
+#include "quantizer.c"
diff --git a/src/quantization/src/quantizerf.c b/src/quantization/src/quantizerf.c
new file mode 100644
index 0000000..f54cb30
--- /dev/null
+++ b/src/quantization/src/quantizerf.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Quantizer API: floating-point
+//
+
+#include "liquid.internal.h"
+
+// 
+#define QUANTIZER(name)     LIQUID_CONCAT(quantizerf,name)
+
+#define T                   float   // general
+
+#define T_COMPLEX           0
+
+// source files
+#include "quantizer.c"
diff --git a/src/quantization/tests/compand_autotest.c b/src/quantization/tests/compand_autotest.c
new file mode 100644
index 0000000..7aa2616
--- /dev/null
+++ b/src/quantization/tests/compand_autotest.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+void autotest_compand_float() {
+    float x = -1.0f;
+    float mu=255.0f;
+    unsigned int n=30;
+
+    float dx = 2/(float)(n);
+    float y;
+    float x_hat;
+    float tol = 1e-6f;
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        y = compress_mulaw(x,mu);
+        x_hat = expand_mulaw(y,mu);
+
+        if (liquid_autotest_verbose)
+            printf("%8.4f -> %8.4f -> %8.4f\n", x, y, x_hat);
+
+        CONTEND_DELTA(x,x_hat,tol);
+
+        x += dx;
+        x = (x > 1.0f) ? 1.0f : x;
+    }
+}
+
+
+void autotest_compand_cfloat() {
+    float complex x = -0.707f - 0.707f*_Complex_I;
+    float mu=255.0f;
+    unsigned int n=30;
+
+    float complex dx = 2*(0.707f +0.707f* _Complex_I)/(float)(n);
+    float complex y;
+    float complex z;
+    float tol = 1e-6f;
+
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        compress_cf_mulaw(x,mu,&y);
+        expand_cf_mulaw(y,mu,&z);
+
+        if (liquid_autotest_verbose) {
+            printf("%8.4f +j%8.4f > ", crealf(x), cimagf(x));
+            printf("%8.4f +j%8.4f > ", crealf(y), cimagf(y));
+            printf("%8.4f +j%8.4f\n",  crealf(z), cimagf(z));
+        }
+
+        CONTEND_DELTA(crealf(x),crealf(z),tol);
+        CONTEND_DELTA(cimagf(x),cimagf(z),tol);
+
+        x += dx;
+        //x = (x > 1.0f) ? 1.0f : x;
+    }
+}
+
diff --git a/src/quantization/tests/quantize_autotest.c b/src/quantization/tests/quantize_autotest.c
new file mode 100644
index 0000000..172f5a6
--- /dev/null
+++ b/src/quantization/tests/quantize_autotest.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+void autotest_quantize_float_n8() {
+    float x = -1.0f;
+    unsigned int num_steps=30;
+    unsigned int num_bits=8;
+
+    float dx = 2/(float)(num_steps);
+    unsigned int q;
+    float x_hat;
+    float tol = 1.0f / (float)(1<<num_bits);
+
+    unsigned int i;
+    for (i=0; i<num_steps; i++) {
+        q = quantize_adc(x,num_bits);
+
+        // ensure only num_bits written to value q
+        CONTEND_EQUALITY(q>>num_bits, 0);
+
+        x_hat = quantize_dac(q,num_bits);
+
+        if (liquid_autotest_verbose)
+            printf("%8.4f > 0x%2.2x > %8.4f\n", x, q, x_hat);
+
+        // ensure original value is recovered withing tolerance
+        CONTEND_DELTA(x,x_hat,tol);
+
+        x += dx;
+        x = (x > 1.0f) ? 1.0f : x;
+    }
+}
+
diff --git a/src/random/bench/random_benchmark.c b/src/random/bench/random_benchmark.c
new file mode 100644
index 0000000..4808321
--- /dev/null
+++ b/src/random/bench/random_benchmark.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// 
+// BENCHMARK: uniform
+//
+void benchmark_random_uniform(struct rusage *_start,
+                              struct rusage *_finish,
+                              unsigned long int *_num_iterations)
+{
+    // normalize number of iterations
+    *_num_iterations *= 10;
+
+    float x = 0.0f;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += randf();
+        x += randf();
+        x += randf();
+        x += randf();
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+// BENCHMARK: normal
+//
+void benchmark_random_normal(struct rusage *_start,
+                             struct rusage *_finish,
+                             unsigned long int *_num_iterations)
+{
+    // normalize number of iterations
+    *_num_iterations *= 1;
+
+    float x = 0.0f;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += randnf();
+        x += randnf();
+        x += randnf();
+        x += randnf();
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+// BENCHMARK: complex normal
+//
+void benchmark_random_complex_normal(struct rusage *_start,
+                                     struct rusage *_finish,
+                                     unsigned long int *_num_iterations)
+{
+    // normalize number of iterations
+    *_num_iterations /= 2;
+
+    float complex x = 0.0f;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        crandnf(&x);
+        crandnf(&x);
+        crandnf(&x);
+        crandnf(&x);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+// BENCHMARK: Weibull
+//
+void benchmark_random_weibull(struct rusage *_start,
+                              struct rusage *_finish,
+                              unsigned long int *_num_iterations)
+{
+    // normalize number of iterations
+    *_num_iterations *= 2;
+
+    float x=0.0f;
+    float alpha=1.0f;
+    float beta=2.0f;
+    float gamma=6.0f;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += randweibf(alpha,beta,gamma);
+        x += randweibf(alpha,beta,gamma);
+        x += randweibf(alpha,beta,gamma);
+        x += randweibf(alpha,beta,gamma);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
+// 
+// BENCHMARK: Rice-K
+//
+void benchmark_random_ricek(struct rusage *_start,
+                            struct rusage *_finish,
+                            unsigned long int *_num_iterations)
+{
+    // normalize number of iterations
+    *_num_iterations /= 3;
+
+    float x = 0.0f;
+    float K=2.0f;
+    float omega=1.0f;
+    unsigned long int i;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        x += randricekf(K,omega);
+        x += randricekf(K,omega);
+        x += randricekf(K,omega);
+        x += randricekf(K,omega);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+}
+
diff --git a/src/random/src/rand.c b/src/random/src/rand.c
new file mode 100644
index 0000000..0b0dea4
--- /dev/null
+++ b/src/random/src/rand.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Uniform random number generator definitions
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// uniform random number generator
+float randf() {
+    return randf_inline();
+}
+
+// uniform random number probability distribution function
+float randf_pdf(float _x)
+{
+    return (_x < 0.0f || _x > 1.0f) ? 0.0f : 1.0f;
+}
+
+// uniform random number cumulative distribution function
+float randf_cdf(float _x)
+{
+    if (_x < 0.0f)
+        return 0.0f;
+    else if (_x > 1.0f)
+        return 1.0f;
+
+    return _x;
+}
+
diff --git a/src/random/src/randexp.c b/src/random/src/randexp.c
new file mode 100644
index 0000000..12d6ac2
--- /dev/null
+++ b/src/random/src/randexp.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Exponential distribution
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// Exponential
+float randexpf(float _lambda)
+{
+    // validate input
+    if (_lambda <= 0) {
+        fprintf(stderr,"error: randexpf(), lambda must be greater than zero\n");
+        return 0.0f;
+    }
+
+    // compute a non-zero uniform random variable in (0,1]
+    float u;
+    do {
+        u = randf();
+    } while (u==0.0f);
+
+    // perform variable transformation
+    return -logf( u ) / _lambda;
+}
+
+// Exponential random number probability distribution function
+float randexpf_pdf(float _x,
+                   float _lambda)
+{
+    // validate input
+    if (_lambda <= 0) {
+        fprintf(stderr,"error: randexpf(), lambda must be greater than zero\n");
+        return 0.0f;
+    }
+
+    if (_x < 0.0f)
+        return 0.0f;
+
+    return _lambda * expf(-_lambda*_x);
+}
+
+// Exponential random number cumulative distribution function
+float randexpf_cdf(float _x,
+                   float _lambda)
+{
+    // validate input
+    if (_lambda <= 0) {
+        fprintf(stderr,"error: randexpf(), lambda must be greater than zero\n");
+        return 0.0f;
+    }
+
+    if (_x < 0.0f)
+        return 0.0f;
+
+    return 1.0f - expf(-_lambda*_x);
+}
+
diff --git a/src/random/src/randgamma.c b/src/random/src/randgamma.c
new file mode 100644
index 0000000..27b3591
--- /dev/null
+++ b/src/random/src/randgamma.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Gamma distribution
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+float randgammaf(float _alpha,
+                 float _beta)
+{
+    // validate input
+    if (_alpha <= 0.0f) {
+        fprintf(stderr,"error: randgammaf(), alpha must be greater than zero\n");
+        exit(1);
+    } else if (_beta <= 0.0f) {
+        fprintf(stderr,"error: randgammaf(), beta must be greater than zero\n");
+        exit(1);
+    }
+
+    unsigned int n = (unsigned int) floorf(_alpha);
+
+    // residual
+    float delta = _alpha - (float)n;
+
+    // generate x' ~ Gamma(n,1)
+    float x_n = 0.0f;
+    unsigned int i;
+    for (i=0; i<n; i++) {
+        float u = randf();
+        x_n += - logf(u);
+    }
+
+    // generate x'' ~ Gamma(delta,1) using rejection method
+    float x_delta = randgammaf_delta(delta);
+
+    // 
+    return _beta * (x_delta + x_n);
+}
+
+// Gamma distribution cumulative distribution function
+//          x^(a-1) exp{-x/b)
+//  f(x) = -------------------
+//            Gamma(a) b^a
+//  where
+//      a = alpha, a > 0
+//      b = beta,  b > 0
+//      Gamma(z) = regular gamma function
+//      x >= 0
+float randgammaf_pdf(float _x,
+                     float _alpha,
+                     float _beta)
+{
+    // validate input
+    if (_alpha <= 0.0f) {
+        fprintf(stderr,"error: randgammaf_pdf(), alpha must be greater than zero\n");
+        exit(1);
+    } else if (_beta <= 0.0f) {
+        fprintf(stderr,"error: randgammaf_pdf(), beta must be greater than zero\n");
+        exit(1);
+    }
+
+    if (_x <= 0.0f)
+        return 0.0f;
+
+    float t0 = powf(_x, _alpha-1.0f);
+    float t1 = expf(-_x / _beta);
+    float t2 = liquid_gammaf(_alpha);
+    float t3 = powf(_beta, _alpha);
+
+    return (t0*t1)/(t2*t3);
+}
+
+// Gamma distribution cumulative distribution function
+//  F(x) = gamma(a,x/b) / Gamma(a)
+//  where
+//      a = alpha,  alpha > 0
+//      b = beta,   beta > 0
+//      gamma(a,z) = lower incomplete gamma function
+//      Gamma(z)   = regular gamma function
+//
+float randgammaf_cdf(float _x,
+                     float _alpha,
+                     float _beta)
+{
+    // validate input
+    if (_alpha <= 0.0f) {
+        fprintf(stderr,"error: randgammaf_cdf(), alpha must be greater than zero\n");
+        exit(1);
+    } else if (_beta <= 0.0f) {
+        fprintf(stderr,"error: randgammaf_cdf(), beta must be greater than zero\n");
+        exit(1);
+    }
+
+    if (_x <= 0.0f)
+        return 0.0f;
+
+    return liquid_lowergammaf(_alpha, _x/_beta) / liquid_gammaf(_alpha);
+}
+
+
+// 
+// internal methods
+//
+
+// generate x ~ Gamma(delta,1)
+float randgammaf_delta(float _delta)
+{
+    // validate input
+    if ( _delta < 0.0f || _delta >= 1.0f ) {
+        fprintf(stderr,"error: randgammaf_delta(), delta must be in [0,1)\n");
+        exit(1);
+    }
+
+    // initialization
+    float delta_inv = 1.0f / _delta;
+    float e = expf(1.0f);
+    float v0 = e / (e + _delta);
+
+    float V0 = 0.0f;
+    float V1 = 0.0f;
+    float V2 = 0.0f;
+
+    unsigned int m = 1;
+
+    float xi = 0.0f;
+    float eta = 0.0f;
+
+    while (1) {
+        // step 2
+        V0 = randf();
+        V1 = randf();
+        V2 = randf();
+
+        if (V2 <= v0) {
+            // step 4
+            xi = powf(V1, delta_inv);
+            eta = V0 * powf(xi, _delta - 1.0f);
+        } else {
+            // step 5
+            xi = 1.0f - logf(V1);
+            eta = V0 * expf(-xi);
+        }
+
+        // step 6
+        if ( eta > powf(xi,_delta-1.0f)*expf(-xi) ) {
+            m++;
+        } else {
+            break;
+        }
+    }
+
+    // xi ~ Gamma(delta,1)
+    return xi;
+}
+
diff --git a/src/random/src/randn.c b/src/random/src/randn.c
new file mode 100644
index 0000000..7cd2e24
--- /dev/null
+++ b/src/random/src/randn.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+//
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// Gauss
+float randnf()
+{
+    // generate two uniform random numbers
+    float u1, u2;
+
+    // ensure u1 does not equal zero
+    do {
+        u1 = randf();
+    } while (u1 == 0.0f);
+
+    u2 = randf();
+
+    return sqrtf(-2*logf(u1)) * sinf(2*M_PI*u2);
+    //return sqrtf(-2*logf(u1)) * cosf(2*M_PI*u2);
+}
+
+void awgn(float *_x, float _nstd)
+{
+    *_x += randnf()*_nstd;
+}
+
+// Complex Gauss
+void crandnf(float complex * _y)
+{
+    // generate two uniform random numbers
+    float u1, u2;
+
+    // ensure u1 does not equal zero
+    do {
+        u1 = randf();
+    } while (u1 == 0.0f);
+
+    u2 = randf();
+
+    *_y = sqrtf(-2*logf(u1)) * cexpf(_Complex_I*2*M_PI*u2);
+}
+
+// Internal complex Gauss (inline)
+float complex icrandnf()
+{
+    float complex y;
+    crandnf(&y);
+    return y;
+}
+
+void cawgn(float complex *_x, float _nstd)
+{
+    *_x += icrandnf()*_nstd*0.707106781186547f;
+}
+
+// Gauss random number probability distribution function
+float randnf_pdf(float _x,
+                 float _eta,
+                 float _sig)
+{
+    // validate input
+    if (_sig <= 0.0f) {
+        fprintf(stderr,"error: randnf_pdf(), standard deviation must be greater than zero\n");
+        exit(1);
+    }
+
+    float t  = _x - _eta;
+    float s2 = _sig * _sig;
+    return expf(-t*t/(2.0f*s2)) / sqrtf(2.0f*M_PI*s2);
+}
+
+// Gauss random number cumulative distribution function
+float randnf_cdf(float _x,
+                 float _eta,
+                 float _sig)
+{
+    return 0.5 + 0.5*erff( M_SQRT1_2*(_x-_eta)/_sig );
+}
+
diff --git a/src/random/src/randnakm.c b/src/random/src/randnakm.c
new file mode 100644
index 0000000..6a3a691
--- /dev/null
+++ b/src/random/src/randnakm.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Nakagami-m distribution
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+float randnakmf(float _m,
+                float _omega)
+{
+    // validate input
+    if (_m < 0.5f) {
+        fprintf(stderr,"error: randnakmf(), m cannot be less than 0.5\n");
+        exit(1);
+    } else if (_omega <= 0.0f) {
+        fprintf(stderr,"error: randnakmf(), omega must be greater than zero\n");
+        exit(1);
+    }
+
+    // generate Gamma random variable
+    float alpha = _m;
+    float beta  = _omega / _m;
+    float x = randgammaf(alpha,beta);
+
+    // sqrt(x) ~ Nakagami(m,omega)
+    return sqrtf(x);
+}
+
+// Nakagami-m distribution probability distribution function
+// Nakagami-m
+//  f(x) = (2/Gamma(m)) (m/omega)^m x^(2m-1) exp{-(m/omega)x^2}
+// where
+//      m       : shape parameter, m >= 0.5
+//      omega   : spread parameter, omega > 0
+//      Gamma(z): regular complete gamma function
+//      x >= 0
+float randnakmf_pdf(float _x,
+                    float _m,
+                    float _omega)
+{
+    // validate input
+    if (_m < 0.5f) {
+        fprintf(stderr,"error: randnakmf_pdf(), m cannot be less than 0.5\n");
+        exit(1);
+    } else if (_omega <= 0.0f) {
+        fprintf(stderr,"error: randnakmf_pdf(), omega must be greater than zero\n");
+        exit(1);
+    }
+
+    if (_x <= 0.0f)
+        return 0.0f;
+
+    float t0 = liquid_lngammaf(_m);
+    float t1 = _m * logf(_m/_omega);
+    float t2 = (2*_m - 1.0f) * logf(_x);
+    float t3 = -(_m/_omega)*_x*_x;
+
+    return 2.0f * expf( -t0 + t1 + t2 + t3 );
+}
+
+// Nakagami-m distribution cumulative distribution function
+//  F(x) = gamma(m, x^2 m / omega) / Gamma(m)
+//  where
+//      gamma(z,a) = lower incomplete gamma function
+//      Gamma(z)   = regular gamma function
+//
+float randnakmf_cdf(float _x,
+                    float _m,
+                    float _omega)
+{
+    // validate input
+    if (_m < 0.5f) {
+        fprintf(stderr,"error: randnakmf_cdf(), m cannot be less than 0.5\n");
+        exit(1);
+    } else if (_omega <= 0.0f) {
+        fprintf(stderr,"error: randnakmf_cdf(), omega must be greater than zero\n");
+        exit(1);
+    }
+
+    if (_x <= 0.0f)
+        return 0.0f;
+
+    float t0 = liquid_lnlowergammaf(_m, _x*_x*_m/_omega);
+    float t1 = liquid_lngammaf(_m);
+    return expf( t0 - t1 );
+}
+
+
diff --git a/src/random/src/randricek.c b/src/random/src/randricek.c
new file mode 100644
index 0000000..f08d566
--- /dev/null
+++ b/src/random/src/randricek.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Rice-K distribution
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// Rice-K
+float randricekf(float _K, float _omega)
+{
+    float complex x, y;
+    float s = sqrtf((_omega*_K)/(_K+1));
+    float sig = sqrtf(0.5f*_omega/(_K+1));
+    crandnf(&x);
+    y = _Complex_I*( crealf(x)*sig + s ) +
+                   ( cimagf(x)*sig     );
+    return cabsf(y);
+}
+
+
+// Rice-K random number probability distribution function
+//  f(x) = (x/sigma^2) exp{ -(x^2+s^2)/(2sigma^2) } I0( x s / sigma^2 )
+// where
+//  s     = sqrt( omega*K/(K+1) )
+//  sigma = sqrt(0.5 omega/(K+1))
+// and
+//  K     = shape parameter
+//  omega = spread parameter
+//  I0    = modified Bessel function of the first kind
+//  x >= 0
+float randricekf_pdf(float _x,
+                     float _K,
+                     float _omega)
+{
+    if (_x < 0.0f)
+        return 0.0f;
+
+    float s = sqrtf((_omega*_K)/(_K+1));
+    float sig = sqrtf(0.5f*_omega/(_K+1));
+
+    float t = _x*_x + s*s;
+
+    float sig2 = sig*sig;
+
+    // check high tail condition
+    if ( (_x*s/sig2) > 80.0f )
+        return 0.0f;
+
+    float t0 = logf(_x) - logf(sig2);
+    float t1 = -t / (2*sig2);
+    float t2 = liquid_lnbesselif(0.0f, _x*s/sig2);
+    return expf(t0 + t1 + t2);
+
+    //return (_x / sig2) * expf(-t / (2*sig2)) * liquid_besseli0f(_x*s/sig2);
+}
+
+// Rice-K random number cumulative distribution function
+float randricekf_cdf(float _x,
+                     float _K,
+                     float _omega)
+{
+    if (_x <= 0.0f)
+        return 0.0f;
+
+    float s = sqrtf((_omega*_K)/(_K+1));
+    float sig = sqrtf(0.5f*_omega/(_K+1));
+
+    // test arguments of Q1 function
+    float alpha = s/sig;
+    float beta  = _x/sig;
+    //float Q1 = liquid_MarcumQ1f(alpha, beta);
+    //printf("  Q1(%12.4e, %12.4e) = %12.4e\n", alpha, beta, Q1);
+
+    if ( (alpha / beta) > 3.0f )
+        return 0.0f;
+    if ( (beta / alpha) > 3.0f )
+        return 1.0f;
+
+    float F = 1.0f - liquid_MarcumQ1f(alpha, beta);
+
+    // check for precision error
+    if (F < 0.0f) return 0.0f;
+    if (F > 1.0f) return 1.0f;
+
+    return F;
+}
+
+
diff --git a/src/random/src/randweib.c b/src/random/src/randweib.c
new file mode 100644
index 0000000..a50505f
--- /dev/null
+++ b/src/random/src/randweib.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Weibull distribution
+//
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// Weibull
+float randweibf(float _alpha,
+                float _beta,
+                float _gamma)
+{
+    // validate input
+    if (_alpha <= 0) {
+        fprintf(stderr,"error: randweibf(), alpha must be greater than zero\n");
+        return 0.0f;
+    } else if (_beta <= 0) {
+        fprintf(stderr,"error: randweibf(), beta must be greater than zero\n");
+        return 0.0f;
+    }
+
+    float u;
+    do {
+        u = randf();
+    } while (u==0.0f);
+
+    return _gamma + _beta*powf( -logf(u), 1.0f/_alpha );
+}
+
+// Weibull random number probability distribution function
+float randweibf_pdf(float _x,
+                    float _alpha,
+                    float _beta,
+                    float _gamma)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    // validate input
+    if (_alpha <= 0) {
+        fprintf(stderr,"error: randweibf_pdf(), alpha must be greater than zero\n");
+        return 0.0f;
+    } else if (_beta <= 0) {
+        fprintf(stderr,"error: randweibf_pdf(), beta must be greater than zero\n");
+        return 0.0f;
+    }
+#endif
+
+    if (_x < _gamma)
+        return 0.0f;
+
+    float t = _x - _gamma;
+    return (_alpha/_beta) * powf(t/_beta, _alpha-1.0f) * expf( -powf(t/_beta, _alpha) );
+}
+
+// Weibull random number cumulative distribution function
+float randweibf_cdf(float _x,
+                    float _alpha,
+                    float _beta,
+                    float _gamma)
+{
+#ifdef LIQUID_VALIDATE_INPUT
+    // validate input
+    if (_alpha <= 0) {
+        fprintf(stderr,"error: randweibf_cdf(), alpha must be greater than zero\n");
+        return 0.0f;
+    } else if (_beta <= 0) {
+        fprintf(stderr,"error: randweibf_cdf(), beta must be greater than zero\n");
+        return 0.0f;
+    }
+#endif
+
+    if (_x <= _gamma)
+        return 0.0f;
+
+    return 1.0f - expf( -powf((_x-_gamma)/_beta, _alpha) );
+}
+
diff --git a/src/random/src/scramble.c b/src/random/src/scramble.c
new file mode 100644
index 0000000..a675dcb
--- /dev/null
+++ b/src/random/src/scramble.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Finite impulse response filter design
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+void scramble_data(unsigned char * _x,
+                   unsigned int _n)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t = (_n>>2)<<2;
+
+    // apply static masks
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _x[i  ] ^= LIQUID_SCRAMBLE_MASK0;
+        _x[i+1] ^= LIQUID_SCRAMBLE_MASK1;
+        _x[i+2] ^= LIQUID_SCRAMBLE_MASK2;
+        _x[i+3] ^= LIQUID_SCRAMBLE_MASK3;
+    }
+
+    // clean up remainder of elements
+    if ( (i+0) < _n ) _x[i+0] ^= LIQUID_SCRAMBLE_MASK0;
+    if ( (i+1) < _n ) _x[i+1] ^= LIQUID_SCRAMBLE_MASK1;
+    if ( (i+2) < _n ) _x[i+2] ^= LIQUID_SCRAMBLE_MASK2;
+    if ( (i+3) < _n ) _x[i+3] ^= LIQUID_SCRAMBLE_MASK3;
+}
+
+void unscramble_data(unsigned char * _x,
+                     unsigned int _n)
+{
+    // for now apply simple static mask (re-run scramble)
+    scramble_data(_x,_n);
+}
+
+// unscramble soft bits
+//  _x      :   input message (soft bits) [size: 8*_n x 1]
+//  _n      :   original message length (bytes)
+void unscramble_data_soft(unsigned char * _x,
+                          unsigned int _n)
+{
+    // bit mask
+    unsigned char mask = 0x00;
+
+    // apply static masks
+    unsigned int i;
+    for (i=0; i<_n; i++) {
+        switch ( i % 4 ) {
+        case 0: mask = LIQUID_SCRAMBLE_MASK0; break;
+        case 1: mask = LIQUID_SCRAMBLE_MASK1; break;
+        case 2: mask = LIQUID_SCRAMBLE_MASK2; break;
+        case 3: mask = LIQUID_SCRAMBLE_MASK3; break;
+        default:;
+        }
+
+#if 0
+        unsigned int j;
+        for (j=0; j<8; j++) {
+            if ( (mask >> (8-j-1)) & 0x01 )
+                _x[8*i+j] = 255 - _x[8*i+j];
+        }
+#else
+        if ( mask & 0x80 ) _x[8*i+0] = 255 - _x[8*i+0];
+        if ( mask & 0x40 ) _x[8*i+1] = 255 - _x[8*i+1];
+        if ( mask & 0x20 ) _x[8*i+2] = 255 - _x[8*i+2];
+        if ( mask & 0x10 ) _x[8*i+3] = 255 - _x[8*i+3];
+        if ( mask & 0x08 ) _x[8*i+4] = 255 - _x[8*i+4];
+        if ( mask & 0x04 ) _x[8*i+5] = 255 - _x[8*i+5];
+        if ( mask & 0x02 ) _x[8*i+6] = 255 - _x[8*i+6];
+        if ( mask & 0x01 ) _x[8*i+7] = 255 - _x[8*i+7];
+#endif
+    }
+}
+
diff --git a/src/random/tests/random_autotest.c b/src/random/tests/random_autotest.c
new file mode 100644
index 0000000..a9e389f
--- /dev/null
+++ b/src/random/tests/random_autotest.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+#define LIQUID_RANDOM_AUTOTEST_NUM_TRIALS (100000)
+#define LIQUID_RANDOM_AUTOTEST_ERROR_TOL  (0.1)
+
+// uniform
+void autotest_randf()
+{
+    unsigned long int N = LIQUID_RANDOM_AUTOTEST_NUM_TRIALS;
+    unsigned long int i;
+    float x, m1=0.0f, m2=0.0f;
+    float tol = LIQUID_RANDOM_AUTOTEST_ERROR_TOL;
+
+    // uniform
+    for (i=0; i<N; i++) {
+        x = randf();
+        m1 += x;
+        m2 += x*x;
+    }
+    m1 /= (float) N;
+    m2 = (m2 / (float)N) - m1*m1;
+
+    CONTEND_DELTA(m1, 0.5f, tol);
+    CONTEND_DELTA(m2, 1/12.0f, tol);
+}
+
+// Gauss
+void autotest_randnf()
+{
+    unsigned long int N = LIQUID_RANDOM_AUTOTEST_NUM_TRIALS; // number of trials
+    unsigned long int i;
+    float x, m1=0.0f, m2=0.0f;
+    float tol = LIQUID_RANDOM_AUTOTEST_ERROR_TOL;
+
+    // uniform
+    for (i=0; i<N; i++) {
+        x = randnf();
+        m1 += x;
+        m2 += x*x;
+    }
+    m1 /= (float) N;
+    m2 = (m2 / (float)N) - m1*m1;
+
+    CONTEND_DELTA(m1, 0.0f, tol);
+    CONTEND_DELTA(m2, 1.0f, tol);
+}
+
+// complex Gauss
+void autotest_crandnf()
+{
+    unsigned long int N = LIQUID_RANDOM_AUTOTEST_NUM_TRIALS;
+    unsigned long int i;
+    float complex x;
+    float m1=0.0f, m2=0.0f;
+    float tol = LIQUID_RANDOM_AUTOTEST_ERROR_TOL;
+
+    // uniform
+    for (i=0; i<N; i++) {
+        crandnf(&x);
+        m1 += crealf(x) + cimagf(x);
+        m2 += crealf(x)*crealf(x) + cimagf(x)*cimagf(x);
+    }
+    N *= 2; // double N for real and imag components
+    m1 /= (float) N;
+    m2 = (m2 / (float)N) - m1*m1;
+
+    CONTEND_DELTA(m1, 0.0f, tol);
+    CONTEND_DELTA(m2, 1.0f, tol);
+}
+
+// Weibull
+void autotest_randweibf()
+{
+    unsigned long int N = LIQUID_RANDOM_AUTOTEST_NUM_TRIALS;
+    unsigned long int i;
+    float x, m1=0.0f, m2=0.0f;
+    float tol = LIQUID_RANDOM_AUTOTEST_ERROR_TOL;
+    float alpha=1.0f, beta=2.0f, gamma=6.0f;
+
+    // uniform
+    for (i=0; i<N; i++) {
+        x = randweibf(alpha, beta, gamma);
+        m1 += x;
+        m2 += x*x;
+    }
+    m1 /= (float) N;
+    m2 = (m2 / (float)N) - m1*m1;
+
+    CONTEND_DELTA(m1, 1.2533f+gamma, tol);
+    CONTEND_DELTA(m2, 0.42920f, tol);
+}
+
+// Rice-K
+void autotest_randricekf()
+{
+    unsigned long int N = LIQUID_RANDOM_AUTOTEST_NUM_TRIALS;
+    unsigned long int i;
+    float x, m1=0.0f, m2=0.0f;
+    float tol = LIQUID_RANDOM_AUTOTEST_ERROR_TOL;
+    float K=2.0f, omega=1.0f;
+
+    // uniform
+    for (i=0; i<N; i++) {
+        x = randricekf(K, omega);
+        m1 += x;
+        m2 += x*x;
+    }
+    m1 /= (float) N;
+    m2 = (m2 / (float)N);
+
+    CONTEND_DELTA(m1, 0.92749f, tol);
+    CONTEND_DELTA(m2, omega, tol);
+}
+
diff --git a/src/random/tests/scramble_autotest.c b/src/random/tests/scramble_autotest.c
new file mode 100644
index 0000000..c2e0928
--- /dev/null
+++ b/src/random/tests/scramble_autotest.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// compute basic entropy metric
+float liquid_scramble_test_entropy(unsigned char * _x,
+                                   unsigned int _n)
+{
+    unsigned int i;
+
+    // count ones
+    unsigned int num_ones=0;
+    for (i=0; i<_n; i++)
+        num_ones += liquid_c_ones[_x[i]];
+
+    // compute probabilities (add small value to prevent
+    // possible log(0))
+    float p1 = (float)num_ones / (float)(8*_n) + 1e-12f;
+    float p0 = 1.0 - p1;
+    return -p0*log2f(p0) - p1*log2f(p1);
+}
+
+// helper function to keep code base small
+void liquid_scramble_test(unsigned int _n)
+{
+    unsigned char x[_n];    // input data
+    unsigned char y[_n];    // scrambled data
+    unsigned char z[_n];    // unscrambled data
+
+    unsigned int i;
+
+    // initialize data array
+    for (i=0; i<_n; i++)
+        x[i] = 0x00;
+
+    // scramble input
+    memmove(y,x,_n);
+    scramble_data(y,_n);
+
+    // unscramble result
+    memmove(z,y,_n);
+    unscramble_data(z,_n);
+
+    // ensure data are equivalent
+    CONTEND_SAME_DATA(x,z,_n*sizeof(unsigned char));
+
+    // compute entropy metric
+    float H = liquid_scramble_test_entropy(y,_n);
+    CONTEND_EXPRESSION( H > 0.8f );
+}
+
+
+// test unscrambling of soft bits (helper function to keep code base small)
+void liquid_scramble_soft_test(unsigned int _n)
+{
+    unsigned char msg_org[_n];      // input data
+    unsigned char msg_enc[_n];      // scrambled data 
+    unsigned char msg_soft[8*_n];   // scrambled data (soft bits)
+    unsigned char msg_dec[_n];      // unscrambled data
+
+    unsigned int i;
+
+    // initialize data array (random)
+    for (i=0; i<_n; i++)
+        msg_org[i] = rand() & 0xff;
+
+    // scramble input
+    memmove(msg_enc, msg_org, _n);
+    scramble_data(msg_enc,_n);
+
+    // convert to soft bits
+    for (i=0; i<_n; i++)
+        liquid_unpack_soft_bits(msg_enc[i], 8, &msg_soft[8*i]);
+
+    // unscramble result
+    unscramble_data_soft(msg_soft, _n);
+
+    // unpack soft bits
+    for (i=0; i<_n; i++) {
+        unsigned int sym_out;
+        liquid_pack_soft_bits(&msg_soft[8*i], 8, &sym_out);
+        msg_dec[i] = sym_out;
+    }
+
+    // ensure data are equivalent
+    CONTEND_SAME_DATA(msg_org, msg_dec, _n);
+}
+
+//
+// AUTOTESTS : simple data scrambling
+//
+void autotest_scramble_n16()     { liquid_scramble_test(16);  };
+void autotest_scramble_n64()     { liquid_scramble_test(64);  };
+void autotest_scramble_n256()    { liquid_scramble_test(256); };
+
+void autotest_scramble_n11()     { liquid_scramble_test(11);  };
+void autotest_scramble_n33()     { liquid_scramble_test(33);  };
+void autotest_scramble_n277()    { liquid_scramble_test(277); };
+
+//
+// AUTOTESTS : soft data scrambling
+//
+void autotest_scramble_soft_n16()     { liquid_scramble_soft_test(16);  };
+void autotest_scramble_soft_n64()     { liquid_scramble_soft_test(64);  };
+void autotest_scramble_soft_n256()    { liquid_scramble_soft_test(256); };
+
+void autotest_scramble_soft_n11()     { liquid_scramble_soft_test(11);  };
+void autotest_scramble_soft_n33()     { liquid_scramble_soft_test(33);  };
+void autotest_scramble_soft_n277()    { liquid_scramble_soft_test(277); };
+
+
diff --git a/src/sequence/bench/bsequence_benchmark.c b/src/sequence/bench/bsequence_benchmark.c
new file mode 100644
index 0000000..c0c3770
--- /dev/null
+++ b/src/sequence/bench/bsequence_benchmark.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <sys/resource.h>
+#include "liquid.h"
+
+// Helper function to keep code base small
+void bsequence_correlate_bench(struct rusage *_start,
+                               struct rusage *_finish,
+                               unsigned long int *_num_iterations,
+                               unsigned int _n)
+{
+    // normalize number of iterations
+    *_num_iterations *= 1000;
+    *_num_iterations /= _n;
+    if (*_num_iterations < 1) *_num_iterations = 1;
+
+    // create and initialize binary sequences
+    bsequence bs1 = bsequence_create(_n);
+    bsequence bs2 = bsequence_create(_n);
+
+    unsigned long int i;
+    int rxy = 0;
+
+    // start trials
+    getrusage(RUSAGE_SELF, _start);
+    for (i=0; i<(*_num_iterations); i++) {
+        rxy += bsequence_correlate(bs1, bs2);
+        rxy -= bsequence_correlate(bs1, bs2);
+        rxy += bsequence_correlate(bs1, bs2);
+        rxy -= bsequence_correlate(bs1, bs2);
+    }
+    getrusage(RUSAGE_SELF, _finish);
+    *_num_iterations *= 4;
+
+    // clean up memory
+    bsequence_destroy(bs1);
+    bsequence_destroy(bs2);
+}
+
+#define BSEQUENCE_BENCHMARK_API(N)          \
+(   struct rusage *_start,                  \
+    struct rusage *_finish,                 \
+    unsigned long int *_num_iterations)     \
+{ bsequence_correlate_bench(_start, _finish, _num_iterations, N); }
+
+// 
+void benchmark_bsequence_xcorr_n16      BSEQUENCE_BENCHMARK_API(16)
+void benchmark_bsequence_xcorr_n64      BSEQUENCE_BENCHMARK_API(64)
+void benchmark_bsequence_xcorr_n256     BSEQUENCE_BENCHMARK_API(256)
+void benchmark_bsequence_xcorr_n1024    BSEQUENCE_BENCHMARK_API(1024)
+
diff --git a/src/sequence/src/bsequence.c b/src/sequence/src/bsequence.c
new file mode 100644
index 0000000..c30d882
--- /dev/null
+++ b/src/sequence/src/bsequence.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// bsequence.c
+//
+// generic binary sequence
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+// 
+struct bsequence_s {
+    unsigned int * s;           // sequence array, memory pointer
+    unsigned int num_bits;      // number of bits in sequence
+    unsigned int num_bits_msb;  // number of bits in most-significant block
+    unsigned int bit_mask_msb;  // bit mask for most-significant block
+    unsigned int s_len;         // length of array, number of allocated blocks
+};
+
+// Create a binary sequence of a specific length
+bsequence bsequence_create(unsigned int _num_bits)
+{
+    bsequence bs;
+
+    // allocate memory for binary sequence
+    bs = (bsequence) malloc( sizeof(struct bsequence_s) );
+
+    // initialize variables
+    bs->s_len = 0;
+    bs->s = NULL;
+    bs->num_bits = _num_bits;
+    
+    // initialize array length
+    div_t d = div( bs->num_bits, sizeof(unsigned int)*8 );
+    bs->s_len = d.quot;
+    bs->s_len += (d.rem > 0) ? 1 : 0;
+
+    // number of bits in MSB block
+    bs->num_bits_msb = (d.rem == 0) ? 8*sizeof(unsigned int) : (unsigned int) d.rem;
+
+    // bit mask for MSB block
+    unsigned int i;
+    bs->bit_mask_msb = 0;
+    for (i=0; i<bs->num_bits_msb; i++) {
+        bs->bit_mask_msb <<= 1;
+        bs->bit_mask_msb |=  1;
+    }
+
+    // initialze array with zeros
+    bs->s = (unsigned int*) malloc( bs->s_len * sizeof(unsigned int) );
+    bsequence_clear(bs);
+
+    return bs;
+}
+
+// Free memory in a binary sequence
+void bsequence_destroy(bsequence _bs)
+{
+    free( _bs->s );
+    free( _bs );
+}
+
+void bsequence_clear(bsequence _bs)
+{
+    memset( _bs->s, 0x00, (_bs->s_len)*sizeof(unsigned int) );
+}
+
+// initialize sequence on external array
+void bsequence_init(bsequence _bs,
+                    unsigned char * _v)
+{
+    // push single bit at a time
+    unsigned int i;
+    unsigned int k=0;
+    unsigned char byte = 0x00;
+    unsigned char mask = 0x80;
+    for (i=0; i<_bs->num_bits; i++) {
+        if ( (i%8)==0 ) {
+            byte = _v[k++];
+            mask = 0x80;
+        }
+
+        bsequence_push(_bs, byte & mask ? 1 : 0);
+        mask >>= 1;
+    }
+}
+
+// Print sequence to the screen
+void bsequence_print(bsequence _bs)
+{
+    unsigned int i, j;
+    unsigned int chunk;
+    unsigned int p = 8*sizeof(unsigned int);
+
+    printf("bsequence[%6u]:     ", _bs->num_bits);
+    for (i=0; i<_bs->s_len; i++) {
+        // strip chunk from sequence, starting with most-significant bits
+        chunk = _bs->s[i];
+
+        for (j=0; j<p; j++) {
+            if (i==0 && j<p-_bs->num_bits_msb)
+                printf(".");    // print '.' for each bit in byte not included in first byte
+            else
+                printf("%c", (chunk >> (p-j-1)) & 0x01 ? '1' : '0');
+            
+            if ( ((j+1)%8)==0 )
+                printf(" ");
+        }
+    }
+    printf("\n");
+}
+
+// push bits in from the right
+void bsequence_push(bsequence _bs,
+                    unsigned int _bit)
+{
+    unsigned int overflow;
+    unsigned int i;
+    unsigned int p = 8*sizeof(unsigned int);
+
+    // shift first block
+    _bs->s[0] <<= 1;
+    _bs->s[0] &= _bs->bit_mask_msb;
+
+    for (i=1; i<_bs->s_len; i++) {
+        // overflow for i-th block is its MSB
+        overflow = (_bs->s[i] >> (p-1)) & 1;
+
+        // shift block 1 bit
+        _bs->s[i] <<= 1;
+
+        // apply overflow to (i-1)-th block's LSB
+        _bs->s[i-1] |= overflow;
+    }
+
+    // apply input bit to LSB of last block
+    _bs->s[_bs->s_len-1] |= ( _bit & 1 );
+}
+
+// circular shift (left)
+void bsequence_circshift(bsequence _bs)
+{
+    // extract most-significant (left-most) bit
+    unsigned int msb_mask = 1 << (_bs->num_bits_msb-1);
+    unsigned int b = (_bs->s[0] & msb_mask) >> (_bs->num_bits_msb-1);
+
+    // push bit into sequence
+    bsequence_push(_bs, b);
+}
+
+// Correlate two binary sequences together
+signed int bsequence_correlate(bsequence _bs1,
+                               bsequence _bs2)
+{
+    signed int rxy = 0;
+    unsigned int i;
+    
+    if ( _bs1->s_len != _bs2->s_len ) {
+        printf("error: bsequence_correlate(), binary sequences must be the same length!\n");
+        exit(-1);
+    }
+    
+    unsigned int chunk;
+
+    for (i=0; i<_bs1->s_len; i++) {
+        //
+        chunk = _bs1->s[i] ^ _bs2->s[i];
+        chunk = ~chunk;
+
+        rxy += liquid_count_ones(chunk);
+    }
+
+    // compensate for most-significant block and return
+    rxy -= 8*sizeof(unsigned int) - _bs1->num_bits_msb;
+    return rxy;
+}
+
+// compute the binary addition of two bit sequences
+void bsequence_add(bsequence _bs1,
+                   bsequence _bs2,
+                   bsequence _bs3)
+{
+    // test lengths of all sequences
+    if ( _bs1->s_len != _bs2->s_len ||
+         _bs1->s_len != _bs3->s_len ||
+         _bs2->s_len != _bs3->s_len )
+    {
+        fprintf(stderr,"error: bsequence_add(), binary sequences must be same length!\n");
+        exit(-1);
+    }
+
+    // b3 = b1 + b2
+    unsigned int i;
+    for (i=0; i<_bs1->s_len; i++)
+        _bs3->s[i] = _bs1->s[i] ^ _bs2->s[i];
+
+    // no need to mask most-significant byte
+}
+
+// compute the binary multiplication of two bit sequences
+void bsequence_mul(bsequence _bs1,
+                   bsequence _bs2,
+                   bsequence _bs3)
+{
+    // test lengths of all sequences
+    if ( _bs1->s_len != _bs2->s_len ||
+         _bs1->s_len != _bs3->s_len ||
+         _bs2->s_len != _bs3->s_len )
+    {
+        fprintf(stderr,"error: bsequence_mul(), binary sequences must be same length!\n");
+        exit(-1);
+    }
+
+    // b3 = b1 * b2
+    unsigned int i;
+    for (i=0; i<_bs1->s_len; i++)
+        _bs3->s[i] = _bs1->s[i] & _bs2->s[i];
+
+    // no need to mask most-significant byte
+}
+
+// accumulate the 1's in a binary sequence
+unsigned int bsequence_accumulate(bsequence _bs)
+{
+    unsigned int i;
+    unsigned int r=0;
+
+    for (i=0; i<_bs->s_len; i++)
+        r += liquid_count_ones(_bs->s[i]);
+
+    return r;
+}
+
+// return the number of ones in a sequence
+unsigned int bsequence_get_length(bsequence _bs)
+{
+    return _bs->num_bits;
+}
+
+// return the i-th bit of the sequence
+unsigned int bsequence_index(bsequence _bs,
+                             unsigned int _i)
+{
+    if (_i >= _bs->num_bits) {
+        fprintf(stderr,"error: bsequence_index(), invalid index %u\n", _i);
+        exit(-1);
+    }
+    div_t d = div( _i, 8*sizeof(unsigned int) );
+
+    // compute byte index
+    unsigned int k = _bs->s_len - d.quot - 1;
+
+    // return particular bit at byte index
+    return (_bs->s[k] >> d.rem ) & 1;
+}
+
+// intialize two sequences to complementary codes.  sequences must
+// be of length at least 8 and a power of 2 (e.g. 8, 16, 32, 64,...)
+void bsequence_create_ccodes(bsequence _qa, bsequence _qb)
+{
+    // make sure sequences are the same length
+    if (_qa->num_bits != _qb->num_bits) {
+        printf("error: bsequence_create_ccodes(), sequence lengths must match\n");
+        exit(1);
+    } else if (_qa->num_bits < 8) {
+        printf("error: bsequence_create_ccodes(), sequence too short\n");
+        exit(1);
+    } else if ( (_qa->num_bits)%8 != 0 ) {
+        printf("error: bsequence_create_ccodes(), sequence must be multiple of 8\n");
+        exit(1);
+    }
+
+    // generate two temporary arrays
+    unsigned int num_bytes = _qa->num_bits / 8;
+    unsigned char a[num_bytes];
+    unsigned char b[num_bytes];
+
+    memset(a, 0x00, num_bytes);
+    memset(b, 0x00, num_bytes);
+
+    // initialize
+    a[num_bytes-1] = 0xb8;  // 1011 1000
+    b[num_bytes-1] = 0xb7;  // 1011 0111
+
+    unsigned int i;
+    unsigned int n=1;
+    unsigned int i_n1;
+    unsigned int i_n0;
+    while (n < num_bytes) {
+
+        i_n1 = num_bytes - n;
+        i_n0 = num_bytes - 2*n;
+
+        // a -> [a  b]
+        // b -> [a ~b]
+        memmove(&a[i_n0], &a[i_n1], n*sizeof(unsigned char));
+        memmove(&b[i_n0], &a[i_n1], n*sizeof(unsigned char));
+
+        memmove(&a[i_n1], &b[i_n1], n*sizeof(unsigned char));
+        memmove(&b[i_n1], &b[i_n1], n*sizeof(unsigned char));
+
+        // complement lower half
+        for (i=0; i<n; i++)
+            b[num_bytes-i-1] ^= 0xff;
+
+        n += n;
+    }
+
+    // initialize on generated sequences
+    bsequence_init(_qa, a);
+    bsequence_init(_qb, b);
+}
+
diff --git a/src/sequence/src/msequence.c b/src/sequence/src/msequence.c
new file mode 100644
index 0000000..326e8e2
--- /dev/null
+++ b/src/sequence/src/msequence.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// m-sequence
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "liquid.internal.h"
+
+#define LIQUID_MIN_MSEQUENCE_M  2
+#define LIQUID_MAX_MSEQUENCE_M  15
+
+// msequence structure
+//  Note that 'g' is stored as the default polynomial shifted to the
+//  right by one bit; this bit is implied and not actually used in
+//  the shift register's feedback bit computation.
+struct msequence_s msequence_default[16] = {
+//   m,     g,      a,      n,      v,      b
+    {0,     0,      1,      0,      1,      0}, // dummy placeholder
+    {0,     0,      1,      0,      1,      0}, // dummy placeholder
+    {2,     0x0003, 0x0002, 3,      0x0002, 0},
+    {3,     0x0005, 0x0004, 7,      0x0004, 0},
+    {4,     0x0009, 0x0008, 15,     0x0008, 0},
+    {5,     0x0012, 0x0010, 31,     0x0010, 0},
+    {6,     0x0021, 0x0020, 63,     0x0020, 0},
+    {7,     0x0044, 0x0040, 127,    0x0040, 0},
+    {8,     0x008E, 0x0080, 255,    0x0080, 0},
+    {9,     0x0108, 0x0100, 511,    0x0100, 0},
+    {10,    0x0204, 0x0200, 1023,   0x0200, 0},
+    {11,    0x0402, 0x0400, 2047,   0x0400, 0},
+    {12,    0x0829, 0x0800, 4095,   0x0800, 0},
+    {13,    0x100d, 0x1000, 8191,   0x1000, 0},
+    {14,    0x2015, 0x2000, 16383,  0x2000, 0},
+    {15,    0x4001, 0x4000, 32767,  0x4000, 0}
+};
+
+// create a maximal-length sequence (m-sequence) object with
+// an internal shift register length of _m bits.
+//  _m      :   generator polynomial length, sequence length is (2^m)-1
+//  _g      :   generator polynomial, starting with most-significant bit
+//  _a      :   initial shift register state, default: 000...001
+msequence msequence_create(unsigned int _m,
+                           unsigned int _g,
+                           unsigned int _a)
+{
+    // validate input
+    if (_m > LIQUID_MAX_MSEQUENCE_M || _m < LIQUID_MIN_MSEQUENCE_M) {
+        fprintf(stderr,"error: msequence_create(), m not in range\n");
+        exit(1);
+    }
+    
+    // allocate memory for msequence object
+    msequence ms = (msequence) malloc(sizeof(struct msequence_s));
+
+    // set internal values
+    ms->m = _m;         // generator polynomial length
+    ms->g = _g >> 1;    // generator polynomial (clip off most significant bit)
+
+    // initialize state register, reversing order
+    // 0001 -> 1000
+    unsigned int i;
+    ms->a = 0;
+    for (i=0; i<ms->m; i++) {
+        ms->a <<= 1;
+        ms->a |= (_a & 0x01);
+        _a >>= 1;
+    }
+
+    ms->n = (1<<_m)-1;  // sequence length, (2^m)-1
+    ms->v = ms->a;      // shift register
+    ms->b = 0;          // return bit
+
+    return ms;
+}
+
+
+// create a maximal-length sequence (m-sequence) object from a generator polynomial
+msequence msequence_create_genpoly(unsigned int _g)
+{
+    unsigned int t = liquid_msb_index(_g);
+    
+    // validate input
+    if (t < 2) {
+        fprintf(stderr,"error: msequence_create_genpoly(), invalid generator polynomial: 0x%x\n", _g);
+        exit(1);
+    }
+
+    // compute derived values
+    unsigned int m = t - 1; // m-sequence shift register length
+    unsigned int a = 1;     // m-sequence initial state
+
+    // generate object and return
+    return msequence_create(m,_g,a);
+}
+
+// creates a default maximal-length sequence
+msequence msequence_create_default(unsigned int _m)
+{
+    // validate input
+    if (_m > LIQUID_MAX_MSEQUENCE_M || _m < LIQUID_MIN_MSEQUENCE_M) {
+        fprintf(stderr,"error: msequence_create(), m not in range\n");
+        exit(1);
+    }
+    
+    // allocate memory for msequence object
+    msequence ms = (msequence) malloc(sizeof(struct msequence_s));
+
+    // copy default sequence
+    memmove(ms, &msequence_default[_m], sizeof(struct msequence_s));
+
+    // return
+    return ms;
+}
+
+// destroy an msequence object, freeing all internal memory
+void msequence_destroy(msequence _ms)
+{
+    free(_ms);
+}
+
+// prints the sequence's internal state to the screen
+void msequence_print(msequence _m)
+{
+    unsigned int i;
+
+    printf("msequence: m=%u (n=%u):\n", _m->m, _m->n);
+
+    // print shift register
+    printf("    shift register: ");
+    for (i=0; i<_m->m; i++)
+        printf("%c", ((_m->v) >> (_m->m-i-1)) & 0x01 ? '1' : '0');
+    printf("\n");
+
+    // print generator polynomial
+    printf("    generator poly: ");
+    for (i=0; i<_m->m; i++)
+        printf("%c", ((_m->g) >> (_m->m-i-1)) & 0x01 ? '1' : '0');
+    printf("\n");
+}
+
+// advance msequence on shift register, returning output bit
+unsigned int msequence_advance(msequence _ms)
+{
+    // compute return bit as binary dot product between the
+    // internal shift register and the generator polynomial
+    _ms->b = liquid_bdotprod( _ms->v, _ms->g );
+
+    _ms->v <<= 1;       // shift internal register
+    _ms->v |= _ms->b;   // push bit onto register
+    _ms->v &= _ms->n;   // apply mask to register
+
+    return _ms->b;      // return result
+}
+
+
+// generate pseudo-random symbol from shift register
+//  _ms     :   m-sequence object
+//  _bps    :   bits per symbol of output
+unsigned int msequence_generate_symbol(msequence _ms,
+                                       unsigned int _bps)
+{
+    unsigned int i;
+    unsigned int s = 0;
+    for (i=0; i<_bps; i++) {
+        s <<= 1;
+        s |= msequence_advance(_ms);
+    }
+    return s;
+}
+
+// reset msequence shift register to original state, typically '1'
+void msequence_reset(msequence _ms)
+{
+    _ms->v = _ms->a;
+}
+
+// initialize a bsequence object on an msequence object
+//  _bs     :   bsequence object
+//  _ms     :   msequence object
+void bsequence_init_msequence(bsequence _bs,
+                              msequence _ms)
+{
+#if 0
+    if (_ms->n > LIQUID_MAX_MSEQUENCE_LENGTH) {
+        fprintf(stderr,"error: bsequence_init_msequence(), msequence length exceeds maximum\n");
+        exit(1);
+    }
+#endif
+
+    // clear binary sequence
+    bsequence_clear(_bs);
+
+    unsigned int i;
+    for (i=0; i<(_ms->n); i++)
+        bsequence_push(_bs, msequence_advance(_ms));
+}
+
+// get the length of the sequence
+unsigned int msequence_get_length(msequence _ms)
+{
+    return _ms->n;
+}
+
+// get the internal state of the sequence
+unsigned int msequence_get_state(msequence _ms)
+{
+    return _ms->v;
+}
+
+// set the internal state of the sequence
+void msequence_set_state(msequence    _ms,
+                         unsigned int _a)
+{
+    // set internal state
+    // NOTE: if state is set to zero, this will lock the sequence generator,
+    //       but let the user set this value if they wish
+    _ms->v = _a;
+}
+
diff --git a/src/sequence/tests/bsequence_autotest.c b/src/sequence/tests/bsequence_autotest.c
new file mode 100644
index 0000000..f0ec33e
--- /dev/null
+++ b/src/sequence/tests/bsequence_autotest.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// test initialization of binary sequence
+//
+void autotest_bsequence_init()
+{
+    // 1111 0000 1100 1010
+    unsigned char v[2] = {0xf0, 0xca};
+
+    // create and initialize sequence
+    bsequence q = bsequence_create(16);
+    bsequence_init(q,v);
+
+    // run tests
+    CONTEND_EQUALITY( bsequence_index(q,15), 1 );
+    CONTEND_EQUALITY( bsequence_index(q,14), 1 );
+    CONTEND_EQUALITY( bsequence_index(q,13), 1 );
+    CONTEND_EQUALITY( bsequence_index(q,12), 1 );
+    
+    CONTEND_EQUALITY( bsequence_index(q,11), 0 );
+    CONTEND_EQUALITY( bsequence_index(q,10), 0 );
+    CONTEND_EQUALITY( bsequence_index(q, 9), 0 );
+    CONTEND_EQUALITY( bsequence_index(q, 8), 0 );
+    
+    CONTEND_EQUALITY( bsequence_index(q, 7), 1 );
+    CONTEND_EQUALITY( bsequence_index(q, 6), 1 );
+    CONTEND_EQUALITY( bsequence_index(q, 5), 0 );
+    CONTEND_EQUALITY( bsequence_index(q, 4), 0 );
+    
+    CONTEND_EQUALITY( bsequence_index(q, 3), 1 );
+    CONTEND_EQUALITY( bsequence_index(q, 2), 0 );
+    CONTEND_EQUALITY( bsequence_index(q, 1), 1 );
+    CONTEND_EQUALITY( bsequence_index(q, 0), 0 );
+    
+    // clean up memory
+    bsequence_destroy(q);
+}
+
+// 
+// test correlation between to sequences
+//
+void autotest_bsequence_correlate()
+{
+    // v0   :   1111 0000 1100 1010
+    // v1   :   1100 1011 0001 1110
+    // sim  :   1100 0100 0010 1011 (7 similar bits)
+    unsigned char v0[2] = {0xf0, 0xca};
+    unsigned char v1[2] = {0xcb, 0x1e};
+
+    // create and initialize sequences
+    bsequence q0 = bsequence_create(16);
+    bsequence q1 = bsequence_create(16);
+    bsequence_init(q0,v0);
+    bsequence_init(q1,v1);
+
+    // run tests
+    CONTEND_EQUALITY( bsequence_correlate(q0,q1), 7 );
+    
+    // clean up memory
+    bsequence_destroy(q0);
+    bsequence_destroy(q1);
+}
+
+
+// 
+// test add operations on two sequences
+//
+void autotest_bsequence_add()
+{
+    // v0   :   1111 0000 1100 1010
+    // v1   :   1100 1011 0001 1110
+    // sum  :   0011 1011 1101 0100
+    unsigned char v0[2] = {0xf0, 0xca};
+    unsigned char v1[2] = {0xcb, 0x1e};
+
+    // create and initialize sequences
+    bsequence q0 = bsequence_create(16);
+    bsequence q1 = bsequence_create(16);
+    bsequence_init(q0,v0);
+    bsequence_init(q1,v1);
+
+    // create result sequence
+    bsequence r = bsequence_create(16);
+    bsequence_add(q0, q1, r);
+
+    // run tests
+    CONTEND_EQUALITY( bsequence_index(r,15), 0 );
+    CONTEND_EQUALITY( bsequence_index(r,14), 0 );
+    CONTEND_EQUALITY( bsequence_index(r,13), 1 );
+    CONTEND_EQUALITY( bsequence_index(r,12), 1 );
+    
+    CONTEND_EQUALITY( bsequence_index(r,11), 1 );
+    CONTEND_EQUALITY( bsequence_index(r,10), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 9), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 8), 1 );
+    
+    CONTEND_EQUALITY( bsequence_index(r, 7), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 6), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 5), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 4), 1 );
+    
+    CONTEND_EQUALITY( bsequence_index(r, 3), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 2), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 1), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 0), 0 );
+    
+    // clean up memory
+    bsequence_destroy(q0);
+    bsequence_destroy(q1);
+    bsequence_destroy(r);
+}
+
+// 
+// test multiply operations on two sequences
+//
+void autotest_bsequence_mul()
+{
+    // v0   :   1111 0000 1100 1010
+    // v1   :   1100 1011 0001 1110
+    // prod :   1100 0000 0000 1010
+    unsigned char v0[2] = {0xf0, 0xca};
+    unsigned char v1[2] = {0xcb, 0x1e};
+
+    // create and initialize sequences
+    bsequence q0 = bsequence_create(16);
+    bsequence q1 = bsequence_create(16);
+    bsequence_init(q0,v0);
+    bsequence_init(q1,v1);
+
+    // create result sequence
+    bsequence r = bsequence_create(16);
+    bsequence_mul(q0, q1, r);
+
+    // run tests
+    CONTEND_EQUALITY( bsequence_index(r,15), 1 );
+    CONTEND_EQUALITY( bsequence_index(r,14), 1 );
+    CONTEND_EQUALITY( bsequence_index(r,13), 0 );
+    CONTEND_EQUALITY( bsequence_index(r,12), 0 );
+    
+    CONTEND_EQUALITY( bsequence_index(r,11), 0 );
+    CONTEND_EQUALITY( bsequence_index(r,10), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 9), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 8), 0 );
+    
+    CONTEND_EQUALITY( bsequence_index(r, 7), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 6), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 5), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 4), 0 );
+    
+    CONTEND_EQUALITY( bsequence_index(r, 3), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 2), 0 );
+    CONTEND_EQUALITY( bsequence_index(r, 1), 1 );
+    CONTEND_EQUALITY( bsequence_index(r, 0), 0 );
+    
+    // clean up memory
+    bsequence_destroy(q0);
+    bsequence_destroy(q1);
+    bsequence_destroy(r);
+}
+
+// 
+// test accumulation of binary sequence
+//
+void autotest_bsequence_accumulate()
+{
+    // 1111 0000 1100 1010 (8 total bits)
+    unsigned char v[2] = {0xf0, 0xca};
+
+    // create and initialize sequence
+    bsequence q = bsequence_create(16);
+    bsequence_init(q,v);
+
+    // run tests
+    CONTEND_EQUALITY( bsequence_accumulate(q), 8 );
+    
+    // clean up memory
+    bsequence_destroy(q);
+}
+
+
diff --git a/src/sequence/tests/complementary_codes_autotest.c b/src/sequence/tests/complementary_codes_autotest.c
new file mode 100644
index 0000000..1344f75
--- /dev/null
+++ b/src/sequence/tests/complementary_codes_autotest.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// 
+// AUTOTEST: validate autocorrelation properties of
+//           complementary codes
+//
+void complementary_codes_test(unsigned int _n)
+{
+    // create and initialize codes
+    bsequence a = bsequence_create(_n);
+    bsequence b = bsequence_create(_n);
+    bsequence_create_ccodes(a, b);
+
+    // print
+    if (liquid_autotest_verbose) {
+        bsequence_print(a);
+        bsequence_print(b);
+    }
+
+    // generate test sequences
+    bsequence ax = bsequence_create(_n);
+    bsequence bx = bsequence_create(_n);
+    bsequence_create_ccodes(ax, bx);
+
+    unsigned int i;
+    signed int raa, rbb;
+    for (i=0; i<_n; i++) {
+        // correlate like sequences
+        raa = 2*bsequence_correlate(a,ax) - _n;
+        rbb = 2*bsequence_correlate(b,bx) - _n;
+
+        if (i==0) { CONTEND_EQUALITY(raa+rbb,2*_n); }
+        else      { CONTEND_EQUALITY(raa+rbb,0);    }
+
+        bsequence_circshift(ax);
+        bsequence_circshift(bx);
+    }
+    
+    // clean up memory
+    bsequence_destroy(a);
+    bsequence_destroy(b);
+    bsequence_destroy(ax);
+    bsequence_destroy(bx);
+}
+
+void autotest_complementary_code_n8()       {   complementary_codes_test(8);    }
+void autotest_complementary_code_n16()      {   complementary_codes_test(16);   }
+void autotest_complementary_code_n32()      {   complementary_codes_test(32);   }
+void autotest_complementary_code_n64()      {   complementary_codes_test(64);   }
+void autotest_complementary_code_n128()     {   complementary_codes_test(128);  }
+void autotest_complementary_code_n256()     {   complementary_codes_test(256);  }
+void autotest_complementary_code_n512()     {   complementary_codes_test(512);  }
+
diff --git a/src/sequence/tests/msequence_autotest.c b/src/sequence/tests/msequence_autotest.c
new file mode 100644
index 0000000..21b62c9
--- /dev/null
+++ b/src/sequence/tests/msequence_autotest.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.h"
+
+// test initialization of binary sequence on m-sequence
+void autotest_bsequence_init_msequence() {
+    // create and initialize m-sequence
+    msequence ms = msequence_create_default(4);
+    
+    // create and initialize binary sequence on m-sequence
+    bsequence bs;
+    bs = bsequence_create( msequence_get_length(ms) );
+    bsequence_init_msequence(bs, ms);
+    if (liquid_autotest_verbose)
+        bsequence_print(bs);
+
+    CONTEND_EQUALITY( bsequence_get_length(bs), msequence_get_length(ms) )
+
+    // clean up memory
+    bsequence_destroy(bs);
+    msequence_destroy(ms);
+}
+
+// helper function to test auto-correlation properties of m-sequence
+void msequence_test_autocorrelation(unsigned int _m)
+{
+    // create and initialize m-sequence
+    msequence ms = msequence_create_default(_m);
+    unsigned int n = msequence_get_length(ms);
+    
+    // create and initialize first binary sequence on m-sequence
+    bsequence bs1 = bsequence_create(n);
+    bsequence_init_msequence(bs1, ms);
+
+    // create and initialize second binary sequence on same m-sequence
+    bsequence bs2 = bsequence_create(n);
+    bsequence_init_msequence(bs2, ms);
+
+    // ensure sequences are the same length
+    CONTEND_EQUALITY( bsequence_get_length(bs1), n )
+    CONTEND_EQUALITY( bsequence_get_length(bs2), n )
+
+    // when sequences are aligned, autocorrelation is equal to length
+    signed int rxy;
+    rxy = bsequence_correlate(bs1, bs2);
+    CONTEND_EQUALITY( rxy, n )
+
+    // when sequences are misaligned, autocorrelation is equal to -1
+    unsigned int i;
+    for (i=0; i<n-1; i++) {
+        bsequence_push(bs2, msequence_advance(ms));
+        rxy = 2*bsequence_correlate(bs1, bs2) - n;
+        CONTEND_EQUALITY( rxy, -1 );
+    }
+
+    // clean up memory
+    bsequence_destroy(bs1);
+    bsequence_destroy(bs2);
+    msequence_destroy(ms);
+}
+
+void autotest_msequence_m2()    {   msequence_test_autocorrelation(2);  }   // n = 3
+void autotest_msequence_m3()    {   msequence_test_autocorrelation(3);  }   // n = 7
+void autotest_msequence_m4()    {   msequence_test_autocorrelation(4);  }   // n = 15
+void autotest_msequence_m5()    {   msequence_test_autocorrelation(5);  }   // n = 31
+void autotest_msequence_m6()    {   msequence_test_autocorrelation(6);  }   // n = 63
+void autotest_msequence_m7()    {   msequence_test_autocorrelation(7);  }   // n = 127
+void autotest_msequence_m8()    {   msequence_test_autocorrelation(8);  }   // n = 255
+void autotest_msequence_m9()    {   msequence_test_autocorrelation(9);  }   // n = 511
+void autotest_msequence_m10()   {   msequence_test_autocorrelation(10); }   // n = 1023
+void autotest_msequence_m11()   {   msequence_test_autocorrelation(11); }   // n = 2047
+void autotest_msequence_m12()   {   msequence_test_autocorrelation(12); }   // n = 4095
+
diff --git a/src/utility/src/bshift_array.c b/src/utility/src/bshift_array.c
new file mode 100644
index 0000000..6f42795
--- /dev/null
+++ b/src/utility/src/bshift_array.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// bshift_array.c
+//
+// bit-wise array shifting
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+ 
+// shift array to the left _b bits, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_lbshift(unsigned char * _src,
+                    unsigned int _n,
+                    unsigned int _b)
+{
+    // validate input
+    if (_b >= 8) {
+        fprintf(stderr,"error: liquid_lbshift(), shift amount must be in [0,7]\n");
+        exit(1);
+    }
+
+    // invoke circular shift left and mask last byte
+    liquid_lbcircshift(_src, _n, _b);
+    _src[_n-1] &= 0xff << _b;
+}
+
+ 
+// shift array to the right _b bits filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_rbshift(unsigned char * _src,
+                    unsigned int _n,
+                    unsigned int _b)
+{
+    // validate input
+    if (_b >= 8) {
+        fprintf(stderr,"error: liquid_rbshift(), shift amount must be in [0,7]\n");
+        exit(1);
+    }
+
+    // invoke circular shift right and mask first byte
+    liquid_rbcircshift(_src, _n, _b);
+    _src[0] &= 0xff >> _b;
+}
+
+ 
+// circularly shift array to the left _b bits
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_lbcircshift(unsigned char * _src,
+                        unsigned int _n,
+                        unsigned int _b)
+{
+    // validate input
+    if (_b >= 8) {
+        fprintf(stderr,"error: liquid_lbcircshift(), shift amount must be in [0,7]\n");
+        exit(1);
+    }
+
+    // 
+    unsigned int shift_0 = _b;              // shift amount: first byte
+    unsigned int shift_1 = 8 - _b;          // shift amount: second byte
+    unsigned char mask_0 = 0xff << shift_0; // bit mask: first byte
+    unsigned char mask_1 = 0xff >> shift_1; // bit mask: second byte
+#if 0
+    printf("num_bits = %1u, shift_0 = %1u, shift_1 = %1u mask_0=%.2x, mask_1=%.2x\n",
+            _b, shift_0, shift_1, mask_0, mask_1);
+#endif
+    // shift then mask
+    unsigned int i;
+    unsigned char byte;
+    unsigned char byte_0;
+    unsigned char byte_1;
+    unsigned char src_0 = _src[0];  // retain first byte
+    for (i=0; i<_n; i++) {
+        // strip bytes
+        byte_0 = _src[i];
+        byte_1 = (i==_n-1) ? src_0 : _src[i+1];
+
+        // shift then mask
+        byte = ((byte_0 << shift_0) & mask_0) |
+               ((byte_1 >> shift_1) & mask_1);
+
+        // store result
+        _src[i] = byte;
+    }
+}
+
+ 
+// circularly shift array to the right _b bits
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bits to shift
+void liquid_rbcircshift(unsigned char * _src,
+                        unsigned int _n,
+                        unsigned int _b)
+{
+    // validate input
+    if (_b >= 8) {
+        fprintf(stderr,"error: liquid_rbshift(), shift amount must be in [0,7]\n");
+        exit(1);
+    }
+
+    // 
+    unsigned int shift_0 = 8 - _b;              // shift amount: first byte
+    unsigned int shift_1 = _b;          // shift amount: second byte
+    unsigned char mask_0 = 0xff << shift_0; // bit mask: first byte
+    unsigned char mask_1 = 0xff >> shift_1; // bit mask: second byte
+#if 0
+    printf("num_bits = %1u, shift_0 = %1u, shift_1 = %1u mask_0=%.2x, mask_1=%.2x\n",
+            _b, shift_0, shift_1, mask_0, mask_1);
+#endif
+    // shift then mask
+    int i;
+    unsigned char byte;
+    unsigned char byte_0;
+    unsigned char byte_1;
+    unsigned char src_n = _src[_n-1];   // retain last byte
+    for (i=_n-1; i>=0; i--) {
+        // strip bytes
+        byte_0 = (i==0) ? src_n : _src[i-1];
+        byte_1 = _src[i];
+
+        // shift then mask
+        byte = ((byte_0 << shift_0) & mask_0) |
+               ((byte_1 >> shift_1) & mask_1);
+
+        // store result
+        _src[i] = byte;
+    }
+}
+
diff --git a/src/utility/src/byte_utilities.c b/src/utility/src/byte_utilities.c
new file mode 100644
index 0000000..561beca
--- /dev/null
+++ b/src/utility/src/byte_utilities.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// general utilities for manipulating bits and bytes, including
+//  * counting ones in a byte, word, long word, etc.
+//  * counting bit differences (e.g. errors) in arrays
+//  * arrays for fast counting
+//  * array for reversing bytes
+//
+
+#include <stdio.h>
+#include "liquid.internal.h"
+
+// count the number of ones in an integer
+unsigned int liquid_count_ones(unsigned int _x) {
+#if SIZEOF_INT == 2
+    return liquid_count_ones_uint16(_x);
+#elif SIZEOF_INT == 4
+    return liquid_count_ones_uint32(_x);
+#else
+    unsigned int i;
+    unsigned int c=0;
+    for (i=0; i<SIZEOF_INT; i++) {
+        c += liquid_c_ones[ _x & 0xff ];
+        _x >>= 8;
+    }   
+    return c;
+#endif
+}
+
+// count the number of ones in an integer, modulo 2
+unsigned int liquid_count_ones_mod2(unsigned int _x)
+{
+#if SIZEOF_INT == 2
+    return liquid_count_ones_mod2_uint16(_x);
+#elif SIZEOF_INT == 4
+    return liquid_count_ones_mod2_uint32(_x);
+#else
+    unsigned int i;
+    unsigned int c=0;
+    for (i=0; i<SIZEOF_INT; i++) {
+        c += liquid_c_ones_mod2[ _x & 0xff ];
+        _x >>= 8;
+    }   
+    return c & 1;
+#endif
+}
+
+// count the binary dot-product between two integers
+unsigned int liquid_bdotprod(unsigned int _x,
+                             unsigned int _y)
+{
+    unsigned int t = _x & _y;
+#if SIZEOF_INT == 2
+    return liquid_count_ones_mod2_uint16(t);
+#elif SIZEOF_INT == 4
+    return liquid_count_ones_mod2_uint32(t);
+#else
+    unsigned int i;
+    unsigned int c=0;
+    for (i=0; i<SIZEOF_INT; i++) {
+        c += liquid_c_ones_mod2[ t & 0xff ];
+        t >>= 8;
+    }   
+    return c & 1;
+#endif
+}
+
+
+// counts the number of different bits between two symbols
+unsigned int count_bit_errors(unsigned int _s1,
+                              unsigned int _s2)
+{
+    return liquid_count_ones(_s1^_s2);
+}
+
+// counts the number of different bits between two arrays of symbols
+//  _msg0   :   original message [size: _n x 1]
+//  _msg1   :   copy of original message [size: _n x 1]
+//  _n      :   message size
+unsigned int count_bit_errors_array(unsigned char * _msg0,
+                                    unsigned char * _msg1,
+                                    unsigned int _n)
+{
+    unsigned int num_bit_errors = 0;
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        num_bit_errors += liquid_c_ones[_msg0[i] ^ _msg1[i]];
+
+    return num_bit_errors;
+}
+
+// print string of bits to standard output
+void liquid_print_bitstring(unsigned int _x,
+                            unsigned int _n)
+{
+    unsigned int i;
+    for (i=0; i<_n; i++)
+        printf("%1u", (_x >> (_n-i-1)) & 1);
+}
+
+// number of ones in a byte
+unsigned const char liquid_c_ones[256] = {
+    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
+    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
+    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
+    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
+
+// number of ones in a byte modulo 2
+unsigned const char liquid_c_ones_mod2[256] = {
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
+    0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
+
+// reverse byte table
+unsigned const char liquid_reverse_byte_gentab[256] = {
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+    0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+    0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+    0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+    0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+    0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+    0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+    0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+    0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+    0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+    0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+    0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+    0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+    0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+    0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+    0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+    0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+    0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+    0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+    0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+    0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+    0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff};
+
+// 
+// byte reversal
+//
+
+// slow implementation of byte reversal
+unsigned char liquid_reverse_byte(unsigned char _x)
+{
+    unsigned char y = 0x00;
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        y <<= 1;
+        y |= _x & 1;
+        _x >>= 1;
+    }
+    return y;
+}
+
+// reverse integer with 16 bits of data
+unsigned int liquid_reverse_uint16(unsigned int _x)
+{
+    return (liquid_reverse_byte_gentab[(_x     ) & 0xff] << 8) |
+           (liquid_reverse_byte_gentab[(_x >> 8) & 0xff]     );
+}
+
+// reverse integer with 24 bits of data
+unsigned int liquid_reverse_uint24(unsigned int _x)
+{
+    return (liquid_reverse_byte_gentab[(_x      ) & 0xff] << 16) |
+           (liquid_reverse_byte_gentab[(_x >>  8) & 0xff] <<  8) |
+           (liquid_reverse_byte_gentab[(_x >> 16) & 0xff]      );
+}
+
+// reverse integer with 32 bits of data
+unsigned int liquid_reverse_uint32(unsigned int _x)
+{
+    return (liquid_reverse_byte_gentab[(_x      ) & 0xff] << 24) |
+           (liquid_reverse_byte_gentab[(_x >>  8) & 0xff] << 16) |
+           (liquid_reverse_byte_gentab[(_x >> 16) & 0xff] <<  8) |
+           (liquid_reverse_byte_gentab[(_x >> 24) & 0xff]      );
+}
+
diff --git a/src/utility/src/msb_index.c b/src/utility/src/msb_index.c
new file mode 100644
index 0000000..f24cdac
--- /dev/null
+++ b/src/utility/src/msb_index.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#include "liquid.internal.h"
+
+// number of leading zeros in a byte
+//  0   0000 0000   :   8
+//  1   0000 0001   :   7
+//  2   0000 0010   :   6
+//  3   0000 0011   :   6
+//  4   0000 0100   :   5
+//  ...
+//  126 0111 1110   :   1
+//  127 0111 1111   :   1
+//  128 1000 0000   :   0
+//  129 1000 0001   :   0
+//  ...
+//  253 1111 1101   :   0
+//  254 1111 1110   :   0
+//  255 1111 1111   :   0
+unsigned int liquid_c_leading_zeros[256] = {
+    8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// count leading zeros in an integer (like leading_zeros[], but
+// extending to integer)
+unsigned int liquid_count_leading_zeros(unsigned int _x) 
+{
+#if 0
+    // look for first non-zero byte in _x
+
+    int i, B, clz=0;
+
+    for (i=(SIZEOF_UNSIGNED_INT-1)*8; i>=0; i-=8) {
+        B = (_x >> i) & 0xff;
+        if ( B ) // B != 0
+            return clz + leading_zeros[B];
+        else
+            clz += 8;
+    }   
+    return (SIZEOF_UNSIGNED_INT*8);
+#else
+    return SIZEOF_UNSIGNED_INT*8 - liquid_msb_index(_x);
+#endif
+}
+
+// computes the index of the most-significant bit
+//
+// Examples:
+//  0x00000000  :   0
+//  0x00000001  :   1
+//  0x00000002  :   2
+//  ...
+//  0x00010000  :   17
+//  0x00020000  :   17
+//  0x00040000  :   17
+//  ...
+//  0x80000000  :   32
+//
+//  0   0000 0000   :   0
+//  1   0000 0001   :   1
+//  2   0000 0010   :   2
+//  3   0000 0011   :   2
+//  4   0000 0100   :   3
+//  ...
+//  126 0111 1110   :   7
+//  127 0111 1111   :   7
+//  128 1000 0000   :   8
+//  129 1000 0001   :   8
+//  ...
+//  253 1111 1101   :   8
+//  254 1111 1110   :   8
+//  255 1111 1111   :   8
+
+unsigned int liquid_msb_index(unsigned int _x)
+{
+    unsigned int bits;
+
+#if defined __i386__ || defined __amd64__ || defined __x86_64__
+    if (!_x) return 0;
+    __asm volatile("bsrl %1,%0\n"
+        : "=r" (bits)
+        : "c" (_x)
+    );
+    return bits + 1;
+#elif 0
+    // slow method; look one bit at a time
+    for (bits = 0; _x != 0 && bits < 32; _x >>= 1, ++bits)
+        ;
+    return bits;
+#else
+    // look for first non-zero byte
+    unsigned int i, b;
+    bits = 8*SIZEOF_UNSIGNED_INT;
+    for (i=SIZEOF_UNSIGNED_INT*8; i>0; i-=8) {
+        b = (_x >> (i-8)) & 0xff;
+        if ( b )
+            return bits - liquid_c_leading_zeros[b];
+        else
+            bits -= 8;
+    }
+    return 0;
+
+#endif
+}
+
+
diff --git a/src/utility/src/msb_index.x86.s b/src/utility/src/msb_index.x86.s
new file mode 100644
index 0000000..b20da41
--- /dev/null
+++ b/src/utility/src/msb_index.x86.s
@@ -0,0 +1,13 @@
+; Index of most-significant bit
+
+.text
+.globl msb_index
+msb_index:
+    pushl %ebx
+    pushl %ecx
+    pushl %edx
+    bsrl %eax,%eax
+    popl %edx
+    popl %ecx
+    popl %ebx
+    ret
diff --git a/src/utility/src/pack_bytes.c b/src/utility/src/pack_bytes.c
new file mode 100644
index 0000000..5e51731
--- /dev/null
+++ b/src/utility/src/pack_bytes.c
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// pack_bytes.c
+//
+// Useful byte-packing utilities
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "liquid.internal.h"
+
+// pack binary array with symbol(s)
+//  _src        :   source array [size: _n x 1]
+//  _n          :   input source array length
+//  _k          :   bit index to write in _src
+//  _b          :   number of bits in input symbol
+//  _sym_in     :   input symbol
+void liquid_pack_array(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _k,
+                       unsigned int _b,
+                       unsigned char _sym_in)
+{
+    // validate input
+    if (_k >= 8*_n) {
+        fprintf(stderr,"error: liquid_pack_array(), bit index exceeds array length\n");
+        exit(1);
+    } else if (_b > 8) {
+        fprintf(stderr,"error: liquid_pack_array(), symbol size cannot exceed 8 bits\n");
+        exit(1);
+    }
+
+    // find base index
+    unsigned int i0 = _k / 8;       // byte index
+    unsigned int b0 = _k - 8*i0;    // bit index
+    //printf("base index : %2u, %2u\n", i0, b0);
+
+    // determine if index spans multiple bytes
+    if (b0 + _b > 8) {
+        // compute number of bits in each symbol
+        unsigned int n0 = 8 - b0;
+        unsigned int n1 = _b - n0;
+
+        // generate mask for each symbol
+        unsigned char mask_0 =  0xff >> (8-n0);
+        unsigned char mask_1 = (0xff >> (8-n1)) << (8-n1);
+
+        // shift then mask
+        unsigned char sym_0 = (_sym_in >>    n1 ) & mask_0;
+        unsigned char sym_1 = (_sym_in << (8-n1)) & mask_1;
+
+        // mask and pack first byte
+        _src[i0] &= ~mask_0;        // clear relevant bits
+        _src[i0] |= sym_0;          // set relevant bits
+
+        // mask and pack second byte (if not exceeding array size)
+        if (i0 < _n-1) {
+            _src[i0+1] &= ~mask_1;  // clear relevant bits
+            _src[i0+1] |= sym_1;    // set relevant bits
+        }
+
+#if 0
+        printf("  output symbol spans multiple bytes\n");
+        printf("  n0    : %u\n", n0);
+        printf("  n1    : %u\n", n1);
+        printf("  mask0 : 0x%.2x\n", mask_0);
+        printf("  mask1 : 0x%.2x\n", mask_1);
+        printf("  sym 0 : 0x%.2x\n", sym_0);
+        printf("  sym 1 : 0x%.2x\n", sym_1);
+#endif
+    } else {
+        // compute mask
+        unsigned char mask_0 = (0xff >> (8-_b)) << (8-_b-b0);
+        unsigned char sym_0  = (_sym_in << (8-_b-b0)) & mask_0;
+
+        // shift then mask
+        _src[i0] &= ~mask_0;    // clear relevant bits
+        _src[i0] |= sym_0;      // set relevant bits
+
+#if 0
+        printf("  _b   : %u\n", _b);
+        printf("  b0   : %u\n", b0);
+        printf("  mask : 0x%.2x\n", mask_0);
+#endif
+    }
+}
+
+// unpack symbols from binary array
+//  _src        :   source array [size: _n x 1]
+//  _n          :   input source array length
+//  _k          :   bit index to write in _src
+//  _b          :   number of bits in output symbol
+//  _sym_out    :   output symbol
+void liquid_unpack_array(unsigned char * _src,
+                         unsigned int _n,
+                         unsigned int _k,
+                         unsigned int _b,
+                         unsigned char * _sym_out)
+{
+    // validate input
+    if (_k >= 8*_n) {
+        fprintf(stderr,"error: liquid_unpack_array(), bit index exceeds array length\n");
+        exit(1);
+    } else if (_b > 8) {
+        fprintf(stderr,"error: liquid_unpack_array(), symbol size cannot exceed 8 bits\n");
+        exit(1);
+    }
+
+    // find base index
+    unsigned int i0 = _k / 8;       // byte index
+    unsigned int b0 = _k - 8*i0;    // bit index
+    //printf("base index : %2u, %2u\n", i0, b0);
+
+    // determine if index spans multiple bytes
+    if (b0 + _b > 8) {
+        // compute number of bits in each symbol
+        unsigned int n0 = 8 - b0;
+        unsigned int n1 = _b - n0;
+
+        // generate mask for each symbol
+        unsigned char mask_0 = 0xff >> (8-n0);
+        unsigned char mask_1 = 0xff >> (8-n1);
+
+        // shift then mask
+        unsigned char sym_0 = _src[i0] & mask_0;
+        unsigned char sym_1 = (i0==_n-1) ? 0x00 : (_src[i0+1] >> (8-n1)) & mask_1;
+
+        // concatenate output symbols
+        *_sym_out = (sym_0 << n1) | sym_1;
+
+#if 0
+        printf("  output symbol spans multiple bytes\n");
+        printf("  n0    : %u\n", n0);
+        printf("  n1    : %u\n", n1);
+        printf("  mask0 : 0x%.2x\n", mask_0);
+        printf("  mask1 : 0x%.2x\n", mask_1);
+        printf("  sym 0 : 0x%.2x\n", sym_0);
+        printf("  sym 1 : 0x%.2x\n", sym_1);
+#endif
+    } else {
+        // compute mask
+        unsigned char mask_0 = ((1 << _b) - 1);
+
+        // shift then mask
+        *_sym_out = (_src[i0] >> (8-_b-b0)) & mask_0;
+
+#if 0
+        printf("  _b   : %u\n", _b);
+        printf("  b0   : %u\n", b0);
+        printf("  mask : 0x%.2x\n", mask_0);
+#endif
+    }
+}
+
+
+
+
+// pack one-bit symbols into bytes (8-bit symbols)
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_pack_bytes(unsigned char * _sym_in,
+                       unsigned int _sym_in_len,
+                       unsigned char * _sym_out,
+                       unsigned int _sym_out_len,
+                       unsigned int * _num_written)
+{
+    div_t d = div(_sym_in_len,8);
+    unsigned int req__sym_out_len = d.quot;
+    req__sym_out_len += ( d.rem > 0 ) ? 1 : 0;
+    if ( _sym_out_len < req__sym_out_len ) {
+        fprintf(stderr,"error: pack_bytes(), output too short\n");
+        exit(-1);
+    }
+    
+    unsigned int i;
+    unsigned int N = 0;         // number of bytes written to output
+    unsigned char byte = 0;
+    
+    for (i=0; i<_sym_in_len; i++) {
+        byte |= _sym_in[i] & 0x01;
+        
+        if ( (i+1)%8 == 0 ) {
+            _sym_out[N++] = byte;
+            byte = 0;
+        } else {
+            byte <<= 1;
+        }
+    }
+
+    if ( i%8 != 0 )
+        _sym_out[N++] = byte >> 1;
+    
+    *_num_written = N;
+}
+
+
+
+// unpack 8-bit symbols (full bytes) into one-bit symbols
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols array
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_unpack_bytes(unsigned char * _sym_in,
+                         unsigned int _sym_in_len,
+                         unsigned char * _sym_out,
+                         unsigned int _sym_out_len,
+                         unsigned int * _num_written)
+{
+    unsigned int i;
+    unsigned int n = 0;
+    unsigned char byte;
+
+    if ( _sym_out_len < 8*_sym_in_len ) {
+        fprintf(stderr,"error: unpack_bytes(), output too short\n");
+        exit(-1);
+    }
+    
+    for (i=0; i<_sym_in_len; i++) {
+        // read input byte
+        byte = _sym_in[i];
+
+        // unpack byte into 8 one-bit symbols
+        _sym_out[n++] = (byte >> 7) & 0x01;
+        _sym_out[n++] = (byte >> 6) & 0x01;
+        _sym_out[n++] = (byte >> 5) & 0x01;
+        _sym_out[n++] = (byte >> 4) & 0x01;
+        _sym_out[n++] = (byte >> 3) & 0x01;
+        _sym_out[n++] = (byte >> 2) & 0x01;
+        _sym_out[n++] = (byte >> 1) & 0x01;
+        _sym_out[n++] =  byte       & 0x01;
+    }
+
+    *_num_written = n;
+}
+
+// repack bytes with arbitrary symbol sizes
+//  _sym_in             :   input symbols array [size: _sym_in_len x 1]
+//  _sym_in_bps         :   number of bits per input symbol
+//  _sym_in_len         :   number of input symbols
+//  _sym_out            :   output symbols array
+//  _sym_out_bps        :   number of bits per output symbol
+//  _sym_out_len        :   number of bytes allocated to output symbols array
+//  _num_written        :   number of output symbols actually written
+void liquid_repack_bytes(unsigned char * _sym_in,
+                         unsigned int _sym_in_bps,
+                         unsigned int _sym_in_len,
+                         unsigned char * _sym_out,
+                         unsigned int _sym_out_bps,
+                         unsigned int _sym_out_len,
+                         unsigned int * _num_written)
+{
+    // compute number of output symbols and determine if output array
+    // is sufficiently sized
+    div_t d = div(_sym_in_len*_sym_in_bps,_sym_out_bps);
+    unsigned int req__sym_out_len = d.quot;
+    req__sym_out_len += ( d.rem > 0 ) ? 1 : 0;
+    if ( _sym_out_len < req__sym_out_len ) {
+        fprintf(stderr,"error: repack_bytes(), output too short\n");
+        fprintf(stderr,"  %u %u-bit symbols cannot be packed into %u %u-bit elements\n",
+                _sym_in_len, _sym_in_bps,
+                _sym_out_len, _sym_out_bps);
+        exit(-1);
+    }
+    
+    unsigned int i;
+    unsigned char s_in = 0;     // input symbol
+    unsigned char s_out = 0;    // output symbol
+
+    // there is probably a more efficient way to do this, but...
+    unsigned int total_bits = _sym_in_len*_sym_in_bps;
+    unsigned int i_in = 0;  // input index counter
+    unsigned int i_out = 0; // output index counter
+    unsigned int k=0;       // input symbol enable
+    unsigned int n=0;       // output symbol enable
+    unsigned int v;         // bit mask
+
+    for (i=0; i<total_bits; i++) {
+        // shift output symbol by one bit
+        s_out <<= 1;
+
+        // pop input if necessary
+        if ( k == 0 ) {
+            //printf("\n\n_sym_in[%d] = %d", i_in, _sym_in[i_in]);
+            s_in = _sym_in[i_in++];
+        }
+
+        // compute shift amount and append input bit at index
+        // to output symbol
+        v = _sym_in_bps - k - 1;
+        s_out |= (s_in >> v) & 0x01;
+        //printf("\n    b = %d, v = %d, s_in = %d, sym_out = %d", (sym_in >> v) & 0x01, v, s_in, sym_out );
+
+        // push output if available    
+        if ( n == _sym_out_bps-1 ) {
+            //printf("\n  _sym_out[%d] = %d", i_out, sym_out);
+            _sym_out[i_out++] = s_out;
+            s_out = 0;
+        }
+
+        // update input/output symbol pop/push flags
+        k = (k+1) % _sym_in_bps;
+        n = (n+1) % _sym_out_bps;
+    }
+
+    // if uneven, push zeros into remaining output symbol
+    if (i_out != req__sym_out_len) {
+        for (i=n; i<_sym_out_bps; i++)
+            s_out <<= 1;
+        _sym_out[i_out++] = s_out;
+    }
+    
+    *_num_written = i_out;
+}
+
diff --git a/src/utility/src/shift_array.c b/src/utility/src/shift_array.c
new file mode 100644
index 0000000..2b4147c
--- /dev/null
+++ b/src/utility/src/shift_array.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// shift_array.c
+//
+// byte-wise array shifting
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "liquid.internal.h"
+
+
+// shift array to the left _b bytes, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_lshift(unsigned char * _src,
+                   unsigned int _n,
+                   unsigned int _b)
+{
+    // shift amount exceeds buffer size; fill with zeros
+    if (_b >= _n) {
+        memset(_src, 0x00, _n*sizeof(unsigned char));
+        return;
+    }
+
+    // move memory
+    memmove(_src, &_src[_b], (_n-_b)*sizeof(unsigned char));
+
+    // fill remaining buffer with zeros
+    memset(&_src[_n-_b], 0x00, _b*sizeof(unsigned char));
+}
+ 
+// shift array to the right _b bytes, filling in zeros
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_rshift(unsigned char * _src,
+                   unsigned int _n,
+                   unsigned int _b)
+{
+    // shift amount exceeds buffer size; fill with zeros
+    if (_b >= _n) {
+        memset(_src, 0x00, _n*sizeof(unsigned char));
+        return;
+    }
+
+    // move memory
+    memmove(&_src[_b], _src, (_n-_b)*sizeof(unsigned char));
+
+    // fill remaining buffer with zeros
+    memset(_src, 0x00, _b*sizeof(unsigned char));
+}
+ 
+ 
+// circular shift array to the left _n bytes
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_lcircshift(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _b)
+{
+    // validate input
+    if (_n == 0)
+        return;
+
+    // ensure 0 <= _b < _n
+    _b = _b % _n;
+
+    // check if less memory is used with rcircshift
+    if (_b > (_n>>1)) {
+        liquid_rcircshift(_src, _n, _n-_b);
+        return;
+    }
+
+    // allocate memory for temporary array
+    unsigned char * tmp = (unsigned char*) malloc(_b*sizeof(unsigned char));
+
+    // copy to temporary array
+    memmove(tmp, _src, _b*sizeof(unsigned char));
+
+    // shift left
+    memmove(_src, &_src[_b], (_n-_b)*sizeof(unsigned char));
+
+    // copy from temporary array
+    memmove(&_src[_n-_b], tmp, _b*sizeof(unsigned char));
+
+    // free temporary array
+    free(tmp);
+}
+
+
+// circular shift array to the right _n bytes
+//  _src        :   source address [size: _n x 1]
+//  _n          :   input data array size
+//  _b          :   number of bytes to shift
+void liquid_rcircshift(unsigned char * _src,
+                       unsigned int _n,
+                       unsigned int _b)
+{
+    // validate input
+    if (_n == 0)
+        return;
+
+    // ensure 0 <= _b < _n
+    _b = _b % _n;
+
+    // check if less memory is used with lcircshift
+    if (_b > (_n>>1)) {
+        liquid_lcircshift(_src, _n, _n-_b);
+        return;
+    }
+
+    // allocate memory for temporary array
+    unsigned char * tmp = (unsigned char*) malloc(_b*sizeof(unsigned char));
+
+    // copy to temporary array
+    memmove(tmp, &_src[_n-_b], _b*sizeof(unsigned char));
+
+    // shift right
+    memmove(&_src[_b], _src, (_n-_b)*sizeof(unsigned char));
+
+    // copy from temporary array
+    memmove(_src, tmp, _b*sizeof(unsigned char));
+
+    // free temporary array
+    free(tmp);
+}
+ 
diff --git a/src/utility/tests/bdotprod_gentest.m b/src/utility/tests/bdotprod_gentest.m
new file mode 100644
index 0000000..d0f9061
--- /dev/null
+++ b/src/utility/tests/bdotprod_gentest.m
@@ -0,0 +1,20 @@
+
+clear all;
+close all;
+
+a = floor(rand(1) * 2^16);
+b = floor(rand(1) * 2^16);
+
+as = dec2bin(a,16);
+bs = dec2bin(b,16);
+ap = [];
+bp = [];
+for i=1:16,
+    ap = [ap str2num(as(i))];
+    bp = [bp str2num(bs(i))];
+end;
+
+bit = mod(sum(ap.*bp),2);
+
+printf('CONTEND_EQUALITY( liquid_bdotprod(0x%.4x, 0x%.4x), %1u);\n', a,b,bit);
+
diff --git a/src/utility/tests/bshift_array_autotest.c b/src/utility/tests/bshift_array_autotest.c
new file mode 100644
index 0000000..57abae6
--- /dev/null
+++ b/src/utility/tests/bshift_array_autotest.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+
+//
+// AUTOTEST : lbshift
+//
+void autotest_lbshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 0000 0011 1101 1110 1011 1111 0101 0100
+    // output [2]   : 0000 0111 1011 1101 0111 1110 1010 1000
+    // output [3]   : 0000 1111 0111 1010 1111 1101 0101 0000
+    // output [4]   : 0001 1110 1111 0101 1111 1010 1010 0000
+    // output [5]   : 0011 1101 1110 1011 1111 0101 0100 0000
+    // output [6]   : 0111 1011 1101 0111 1110 1010 1000 0000
+    // output [7]   : 1111 0111 1010 1111 1101 0101 0000 0000
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0x03, 0xDE, 0xBF, 0x54};
+    unsigned char output_test_2[4] = {0x07, 0xBD, 0x7E, 0xA8};
+    unsigned char output_test_3[4] = {0x0F, 0x7A, 0xFD, 0x50};
+    unsigned char output_test_4[4] = {0x1E, 0xF5, 0xFA, 0xA0};
+    unsigned char output_test_5[4] = {0x3D, 0xEB, 0xF5, 0x40};
+    unsigned char output_test_6[4] = {0x7B, 0xD7, 0xEA, 0x80};
+    unsigned char output_test_7[4] = {0xF7, 0xAF, 0xD5, 0x00};
+
+    unsigned char output[4];
+    
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        memmove(output, input, 4);
+        liquid_lbshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        case 5: CONTEND_SAME_DATA( output, output_test_5, 4 ); break;
+        case 6: CONTEND_SAME_DATA( output, output_test_6, 4 ); break;
+        case 7: CONTEND_SAME_DATA( output, output_test_7, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : rbshift
+//
+void autotest_rbshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 0100 0000 1111 0111 1010 1111 1101 0101
+    // output [2]   : 0010 0000 0111 1011 1101 0111 1110 1010
+    // output [3]   : 0001 0000 0011 1101 1110 1011 1111 0101
+    // output [4]   : 0000 1000 0001 1110 1111 0101 1111 1010
+    // output [5]   : 0000 0100 0000 1111 0111 1010 1111 1101
+    // output [6]   : 0000 0010 0000 0111 1011 1101 0111 1110
+    // output [7]   : 0000 0001 0000 0011 1101 1110 1011 1111
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0x40, 0xF7, 0xAF, 0xD5};
+    unsigned char output_test_2[4] = {0x20, 0x7B, 0xD7, 0xEA};
+    unsigned char output_test_3[4] = {0x10, 0x3D, 0xEB, 0xF5};
+    unsigned char output_test_4[4] = {0x08, 0x1E, 0xF5, 0xFA};
+    unsigned char output_test_5[4] = {0x04, 0x0F, 0x7A, 0xFD};
+    unsigned char output_test_6[4] = {0x02, 0x07, 0xBD, 0x7E};
+    unsigned char output_test_7[4] = {0x01, 0x03, 0xDE, 0xBF};
+
+    unsigned char output[4];
+    
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        memmove(output, input, 4);
+        liquid_rbshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        case 5: CONTEND_SAME_DATA( output, output_test_5, 4 ); break;
+        case 6: CONTEND_SAME_DATA( output, output_test_6, 4 ); break;
+        case 7: CONTEND_SAME_DATA( output, output_test_7, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : lbcircshift
+//
+void autotest_lbcircshift() {
+    // input        : 1001 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1001 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 0010 0011 1101 1110 1011 1111 0101 0101
+    // output [2]   : 0100 0111 1011 1101 0111 1110 1010 1010
+    // output [3]   : 1000 1111 0111 1010 1111 1101 0101 0100
+    // output [4]   : 0001 1110 1111 0101 1111 1010 1010 1001
+    // output [5]   : 0011 1101 1110 1011 1111 0101 0101 0010
+    // output [6]   : 0111 1011 1101 0111 1110 1010 1010 0100
+    // output [7]   : 1111 0111 1010 1111 1101 0101 0100 1000
+    unsigned char input[4] = {0x91, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x91, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0x23, 0xDE, 0xBF, 0x55};
+    unsigned char output_test_2[4] = {0x47, 0xBD, 0x7E, 0xAA};
+    unsigned char output_test_3[4] = {0x8F, 0x7A, 0xFD, 0x54};
+    unsigned char output_test_4[4] = {0x1E, 0xF5, 0xFA, 0xA9};
+    unsigned char output_test_5[4] = {0x3D, 0xEB, 0xF5, 0x52};
+    unsigned char output_test_6[4] = {0x7B, 0xD7, 0xEA, 0xA4};
+    unsigned char output_test_7[4] = {0xF7, 0xAF, 0xD5, 0x48};
+
+    unsigned char output[4];
+    
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        memmove(output, input, 4);
+        liquid_lbcircshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        case 5: CONTEND_SAME_DATA( output, output_test_5, 4 ); break;
+        case 6: CONTEND_SAME_DATA( output, output_test_6, 4 ); break;
+        case 7: CONTEND_SAME_DATA( output, output_test_7, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : rbcircshift
+//
+void autotest_rbcircshift() {
+    // input        : 1001 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1001 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 0100 1000 1111 0111 1010 1111 1101 0101
+    // output [2]   : 1010 0100 0111 1011 1101 0111 1110 1010
+    // output [3]   : 0101 0010 0011 1101 1110 1011 1111 0101
+    // output [4]   : 1010 1001 0001 1110 1111 0101 1111 1010
+    // output [5]   : 0101 0100 1000 1111 0111 1010 1111 1101
+    // output [6]   : 1010 1010 0100 0111 1011 1101 0111 1110
+    // output [7]   : 0101 0101 0010 0011 1101 1110 1011 1111
+    unsigned char input[4] = {0x91, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x91, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0x48, 0xF7, 0xAF, 0xD5};
+    unsigned char output_test_2[4] = {0xA4, 0x7B, 0xD7, 0xEA};
+    unsigned char output_test_3[4] = {0x52, 0x3D, 0xEB, 0xF5};
+    unsigned char output_test_4[4] = {0xA9, 0x1E, 0xF5, 0xFA};
+    unsigned char output_test_5[4] = {0x54, 0x8F, 0x7A, 0xFD};
+    unsigned char output_test_6[4] = {0xAA, 0x47, 0xBD, 0x7E};
+    unsigned char output_test_7[4] = {0x55, 0x23, 0xDE, 0xBF};
+
+    unsigned char output[4];
+    
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<8; i++) {
+        memmove(output, input, 4);
+        liquid_rbcircshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        case 5: CONTEND_SAME_DATA( output, output_test_5, 4 ); break;
+        case 6: CONTEND_SAME_DATA( output, output_test_6, 4 ); break;
+        case 7: CONTEND_SAME_DATA( output, output_test_7, 4 ); break;
+        default:;
+        }
+    }
+}
+
diff --git a/src/utility/tests/count_bits_autotest.c b/src/utility/tests/count_bits_autotest.c
new file mode 100644
index 0000000..dfa9c78
--- /dev/null
+++ b/src/utility/tests/count_bits_autotest.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+// 
+// AUTOTEST: count number of ones in an integer
+//
+void autotest_count_ones() {
+    CONTEND_EQUALITY( liquid_count_ones(0x0000), 0 );
+    CONTEND_EQUALITY( liquid_count_ones(0x0001), 1 );
+    CONTEND_EQUALITY( liquid_count_ones(0x0003), 2 );
+    CONTEND_EQUALITY( liquid_count_ones(0xFFFF), 16 );
+    CONTEND_EQUALITY( liquid_count_ones(0x00FF), 8 );
+    CONTEND_EQUALITY( liquid_count_ones(0x5555), 8 );
+    CONTEND_EQUALITY( liquid_count_ones(0x0007), 3 );
+    CONTEND_EQUALITY( liquid_count_ones(0x0037), 5 );
+    CONTEND_EQUALITY( liquid_count_ones(0x0137), 6 );
+    CONTEND_EQUALITY( liquid_count_ones(0xf137), 10 );
+}
+
+// 
+// AUTOTEST: count number of ones in an integer (modulo 2)
+//
+void autotest_count_ones_mod2() {
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0000), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0001), 1 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0003), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0xFFFF), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x00FF), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x5555), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0007), 1 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0037), 1 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0x0137), 0 );
+    CONTEND_EQUALITY( liquid_count_ones_mod2(0xf137), 0 );
+}
+
+// 
+// AUTOTEST: binary dot product
+//
+void autotest_bdotprod()
+{
+    // simple checks
+    CONTEND_EQUALITY( liquid_bdotprod(0x1111,0x1111), 0 );
+    CONTEND_EQUALITY( liquid_bdotprod(0xffff,0xffff), 0 );
+    CONTEND_EQUALITY( liquid_bdotprod(0xffff,0x0000), 0 );
+    CONTEND_EQUALITY( liquid_bdotprod(0x0001,0x0001), 1 );
+
+    // random data
+    CONTEND_EQUALITY( liquid_bdotprod(0x4379, 0xf2dc), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xc9a1, 0xc99d), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0xa8ba, 0x26d9), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0x5235, 0x8e1b), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0x0f85, 0xa3d1), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0x23e0, 0x5869), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0xc8a4, 0x32a4), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xe1c3, 0x000c), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0x4039, 0x192d), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0x2e1c, 0x55a3), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0x5a1b, 0x0241), 0);
+    CONTEND_EQUALITY( liquid_bdotprod(0x440c, 0x7ddb), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xd2e2, 0x5c98), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xe36c, 0x5bc9), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xaa96, 0xf233), 1);
+    CONTEND_EQUALITY( liquid_bdotprod(0xab0f, 0x3912), 0);
+}
+
+// 
+// AUTOTEST: count number of leading zeros in an integer
+//
+void autotest_count_leading_zeros() {
+    // NOTE: this tests assumes a 4-byte integer
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000000), 32 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000001), 31 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000002), 30 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000004), 29 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000008), 28 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000010), 27 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000020), 26 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000040), 25 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000080), 24 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000100), 23 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000200), 22 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000400), 21 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00000800), 20 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00001000), 19 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00002000), 18 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00004000), 17 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00008000), 16 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00010000), 15 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00020000), 14 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00040000), 13 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00080000), 12 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00100000), 11 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00200000), 10 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00400000),  9 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x00800000),  8 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x01000000),  7 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x02000000),  6 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x04000000),  5 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x08000000),  4 );
+
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x10000000),  3 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x20000000),  2 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x40000000),  1 );
+    CONTEND_EQUALITY( liquid_count_leading_zeros(0x80000000),  0 );
+}
+
+// 
+// AUTOTEST: find location of most-significant bit
+//
+void autotest_msb_index() {
+    // NOTE: this tests assumes a 4-byte integer
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00000000),  0 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00000001),  1 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000002),  2 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000004),  3 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000008),  4 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00000010),  5 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000020),  6 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000040),  7 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000080),  8 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00000100),  9 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000200), 10 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000400), 11 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00000800), 12 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00001000), 13 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00002000), 14 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00004000), 15 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00008000), 16 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00010000), 17 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00020000), 18 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00040000), 19 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00080000), 20 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x00100000), 21 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00200000), 22 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00400000), 23 );
+    CONTEND_EQUALITY( liquid_msb_index(0x00800000), 24 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x01000000), 25 );
+    CONTEND_EQUALITY( liquid_msb_index(0x02000000), 26 );
+    CONTEND_EQUALITY( liquid_msb_index(0x04000000), 27 );
+    CONTEND_EQUALITY( liquid_msb_index(0x08000000), 28 );
+
+    CONTEND_EQUALITY( liquid_msb_index(0x10000000), 29 );
+    CONTEND_EQUALITY( liquid_msb_index(0x20000000), 30 );
+    CONTEND_EQUALITY( liquid_msb_index(0x40000000), 31 );
+    CONTEND_EQUALITY( liquid_msb_index(0x80000000), 32 );
+}
+
diff --git a/src/utility/tests/pack_bytes_autotest.c b/src/utility/tests/pack_bytes_autotest.c
new file mode 100644
index 0000000..02f55ad
--- /dev/null
+++ b/src/utility/tests/pack_bytes_autotest.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST : pack_array
+//
+void autotest_pack_array() {
+    // input symbols
+    unsigned int sym_size[9] = {8, 2, 3, 6, 1, 3, 3, 4, 3};
+    unsigned char input[9] = {
+        0x81,   // 1000 0001
+        0x03,   //        11
+        0x05,   //       101
+        0x3a,   //   11 1010
+        0x01,   //         1
+        0x07,   //       111
+        0x06,   //       110
+        0x0a,   //      1010
+        0x04    //     10[0] <- last bit is stripped
+    };
+
+    // output       : 1000 0001 1110 1111 0101 1111 1010 1010
+    // symbol       : 0000 0000 1122 2333 3334 5556 6677 7788
+    unsigned char output_test[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output[4]      = {0xff, 0xff, 0xff, 0xff};
+
+    unsigned int k=0;
+    unsigned int i;
+    for (i=0; i<9; i++) {
+        liquid_pack_array(output, 4, k, sym_size[i], input[i]);
+        k += sym_size[i];
+    }
+    
+    CONTEND_SAME_DATA( output, output_test, 4 );
+}
+
+
+//
+// AUTOTEST : unpack_array
+//
+void autotest_unpack_array() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // symbol       : 0000 0000 1122 2333 3334 5556 6677 7788
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned int sym_size[9] = {8, 2, 3, 6, 1, 3, 3, 4, 3};
+
+    // output syms
+    unsigned char output_test[9] = {
+        0x81,   // 1000 0001
+        0x03,   //        11
+        0x05,   //       101
+        0x3a,   //   11 1010
+        0x01,   //         1
+        0x07,   //       111
+        0x06,   //       110
+        0x0a,   //      1010
+        0x04    //     10[0] <- last bit is implied
+    };
+
+    unsigned char output[9];
+
+    unsigned int k=0;
+    unsigned int i;
+    for (i=0; i<9; i++) {
+        liquid_unpack_array(input, 4, k, sym_size[i], &output[i]);
+        k += sym_size[i];
+    }
+    
+    CONTEND_SAME_DATA( output, output_test, 9 );
+}
+
+//
+// AUTOTEST : unpack/pack_array
+//
+void autotest_repack_array() {
+    unsigned int n=512;     // input/output array size
+    unsigned char src[n];   // original data array
+    unsigned char dst[n];   // repacked data array
+
+    unsigned int i;
+    // initialize input/output arrays
+    for (i=0; i<n; i++) {
+        src[i] = rand() & 0xff;
+        dst[i] = 0x00;
+    }
+
+    unsigned int k=0;
+    unsigned char sym=0;
+    unsigned int sym_size=0;
+    while (k < 8*n) {
+        // random symbol size
+        sym_size = (rand()%8) + 1;
+
+        // unpack symbol from input array
+        liquid_unpack_array(src, n, k, sym_size, &sym);
+
+        // pack symbol into output array
+        liquid_pack_array(dst, n, k, sym_size, sym);
+
+        // update bit index counter
+        k += sym_size;
+    }
+    
+    CONTEND_SAME_DATA( src, dst, n );
+}
+
+void autotest_pack_bytes_01() {
+    unsigned char output[8];
+    unsigned int N;
+    
+    unsigned char input[36] = {
+        0, 0, 0, 0, 0, 0, 0, 0, // 0:   0000 0000
+        1, 1, 1, 1, 1, 1, 1, 1, // 255: 1111 1111
+        0, 0, 0, 0, 1, 1, 1, 1, // 15:  0000 1111
+        1, 0, 1, 0, 1, 0, 1, 0  // 170: 1010 1010
+    };
+
+    // Test packing entire array
+    unsigned char output_test_01[4] = {0x00, 0xFF, 0x0F, 0xAA};
+    liquid_pack_bytes( input, 32, output, 8, &N );
+    CONTEND_EQUALITY( N, 4 );
+    CONTEND_SAME_DATA( output, output_test_01, 4 );
+
+    // Test packing only 28 elements
+    unsigned char output_test_02[4] = {0x00, 0xFF, 0x0F, 0x0A};
+    liquid_pack_bytes( input, 28, output, 8, &N );
+    CONTEND_EQUALITY( N, 4 );
+    CONTEND_SAME_DATA( output, output_test_02, 4 );
+    
+    // Test packing only 25 elements
+    unsigned char output_test_03[4] = {0x00, 0xFF, 0x0F, 0x01};
+    liquid_pack_bytes( input, 25, output, 8, &N );
+    CONTEND_EQUALITY( N, 4 );
+    CONTEND_SAME_DATA( output, output_test_03, 4 );
+
+    // Test packing only 24 elements (3 bytes)
+    unsigned char output_test_04[3] = {0x00, 0xFF, 0x0F};
+    liquid_pack_bytes( input, 24, output, 8, &N );
+    CONTEND_EQUALITY( N, 3 );
+    CONTEND_SAME_DATA( output, output_test_04, 3 );
+}
+
+
+//
+// unpack_bytes
+//
+
+void autotest_unpack_bytes_01() {
+    unsigned char input[5] = {0x00, 0x01, 0xFF, 0x0F, 0xAA};
+    
+    unsigned char output[64];
+    unsigned int N;
+
+    unsigned char output_test[40] = {
+        0, 0, 0, 0, 0, 0, 0, 0, // 0:   0000 0000
+        0, 0, 0, 0, 0, 0, 0, 1, // 1:   0000 0001
+        1, 1, 1, 1, 1, 1, 1, 1, // 255: 1111 1111
+        0, 0, 0, 0, 1, 1, 1, 1, // 15:  0000 1111
+        1, 0, 1, 0, 1, 0, 1, 0  // 170: 1010 1010
+    };
+    
+    // Test packing entire array
+    liquid_unpack_bytes( input, 4, output, 40, &N );
+    CONTEND_EQUALITY( N, 32 );
+    CONTEND_SAME_DATA( output, output_test, 32 );
+}
+
+
+//
+// repack_bytes
+//
+
+void autotest_repack_bytes_01() {
+    unsigned char input[] = {
+        0x07,   // 111
+        0x00,   // 000
+        0x06,   // 110
+        0x07    // 111
+    };
+
+    unsigned char output_test[] = {
+        0x03,   // 11
+        0x02,   // 10
+        0x00,   // 00
+        0x03,   // 11
+        0x01,   // 01
+        0x03    // 11
+    };
+
+    unsigned char output[6];
+    unsigned int N;
+
+    liquid_repack_bytes( input, 3, 4, output, 2, 6, &N );
+
+    CONTEND_EQUALITY( N, 6 );
+    CONTEND_SAME_DATA( output, output_test, 6 );
+}
+
+void autotest_repack_bytes_02() {
+    unsigned char input[] = {
+        0x01,   // 00001
+        0x02,   // 00010
+        0x04    // 00100
+    };
+
+    unsigned char output_test[] = {
+        0x00,   // 000
+        0x02,   // 010
+        0x01,   // 001
+        0x00,   // 000
+        0x04    // 100
+    };
+
+    unsigned char output[5];
+    unsigned int N;
+
+    liquid_repack_bytes( input, 5, 3, output, 3, 5, &N );
+
+    CONTEND_EQUALITY( N, 5 );
+    CONTEND_SAME_DATA( output, output_test, 5 );
+}
+
+void autotest_repack_bytes_03() {
+    unsigned char input[] = {
+        0x00,   // 000
+        0x02,   // 010
+        0x01,   // 001
+        0x00,   // 000
+        0x04    // 100
+    };
+
+    unsigned char output_test[] = {
+        0x01,   // 00001
+        0x02,   // 00010
+        0x04    // 00100
+    };
+
+    unsigned char output[3];
+    unsigned int N;
+
+    liquid_repack_bytes( input, 3, 5, output, 5, 3, &N );
+
+    CONTEND_EQUALITY( N, 3 );
+    CONTEND_SAME_DATA( output, output_test, 3 );
+}
+
+void autotest_repack_bytes_04_uneven() {
+    unsigned char input[3] = {
+        0x07,   // 111
+        0x07,   // 111
+        0x07    // 111(0)
+    };
+
+    unsigned char output_test[] = {
+        0x03,   // 11
+        0x03,   // 11
+        0x03,   // 11
+        0x03,   // 11
+        0x02    // 10
+    };
+
+    unsigned char output[5];
+    unsigned int N;
+
+    liquid_repack_bytes( input, 3, 3, output, 2, 5, &N );
+
+    CONTEND_EQUALITY( N, 5 );
+    CONTEND_SAME_DATA( output, output_test, 5 );
+}
+
+
+
diff --git a/src/utility/tests/shift_array_autotest.c b/src/utility/tests/shift_array_autotest.c
new file mode 100644
index 0000000..1714894
--- /dev/null
+++ b/src/utility/tests/shift_array_autotest.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+#include "autotest/autotest.h"
+#include "liquid.internal.h"
+
+//
+// AUTOTEST : lshift
+//
+void autotest_lshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 1110 1111 0101 1111 1010 1010 0000 0000
+    // output [2]   : 0101 1111 1010 1010 0000 0000 0000 0000
+    // output [3]   : 1010 1010 0000 0000 0000 0000 0000 0000
+    // output [4]   : 0000 0000 0000 0000 0000 0000 0000 0000
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0xEF, 0x5F, 0xAA, 0x00};
+    unsigned char output_test_2[4] = {0x5F, 0xAA, 0x00, 0x00};
+    unsigned char output_test_3[4] = {0xAA, 0x00, 0x00, 0x00};
+    unsigned char output_test_4[4] = {0x00, 0x00, 0x00, 0x00};
+
+    unsigned char output[4];
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        memmove(output, input, 4);
+        liquid_lshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : rshift
+//
+void autotest_rshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 0000 0000 1000 0001 1110 1111 0101 1111
+    // output [2]   : 0000 0000 0000 0000 1000 0001 1110 1111
+    // output [3]   : 0000 0000 0000 0000 0000 0000 1000 0001
+    // output [4]   : 0000 0000 0000 0000 0000 0000 0000 0000
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0x00, 0x81, 0xEF, 0x5F};
+    unsigned char output_test_2[4] = {0x00, 0x00, 0x81, 0xEF};
+    unsigned char output_test_3[4] = {0x00, 0x00, 0x00, 0x81};
+    unsigned char output_test_4[4] = {0x00, 0x00, 0x00, 0x00};
+
+    unsigned char output[4];
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        memmove(output, input, 4);
+        liquid_rshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : lcircshift
+//
+void autotest_lcircshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 1110 1111 0101 1111 1010 1010 1000 0001
+    // output [2]   : 0101 1111 1010 1010 1000 0001 1110 1111
+    // output [3]   : 1010 1010 1000 0001 1110 1111 0101 1111
+    // output [4]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0xEF, 0x5F, 0xAA, 0x81};
+    unsigned char output_test_2[4] = {0x5F, 0xAA, 0x81, 0xEF};
+    unsigned char output_test_3[4] = {0xAA, 0x81, 0xEF, 0x5F};
+    unsigned char output_test_4[4] = {0x81, 0xEF, 0x5F, 0xAA};
+
+    unsigned char output[4];
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        memmove(output, input, 4);
+        liquid_lcircshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        default:;
+        }
+    }
+}
+
+
+//
+// AUTOTEST : rcircshift
+//
+void autotest_rcircshift() {
+    // input        : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [0]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    // output [1]   : 1010 1010 1000 0001 1110 1111 0101 1111
+    // output [2]   : 0101 1111 1010 1010 1000 0001 1110 1111
+    // output [3]   : 1110 1111 0101 1111 1010 1010 1000 0001
+    // output [4]   : 1000 0001 1110 1111 0101 1111 1010 1010
+    unsigned char input[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    
+    unsigned char output_test_0[4] = {0x81, 0xEF, 0x5F, 0xAA};
+    unsigned char output_test_1[4] = {0xAA, 0x81, 0xEF, 0x5F};
+    unsigned char output_test_2[4] = {0x5F, 0xAA, 0x81, 0xEF};
+    unsigned char output_test_3[4] = {0xEF, 0x5F, 0xAA, 0x81};
+    unsigned char output_test_4[4] = {0x81, 0xEF, 0x5F, 0xAA};
+
+    unsigned char output[4];
+
+    // 
+    // run tests
+    //
+    unsigned int i;
+    for (i=0; i<5; i++) {
+        memmove(output, input, 4);
+        liquid_rcircshift( output, 4, i);
+        switch (i) {
+        case 0: CONTEND_SAME_DATA( output, output_test_0, 4 ); break;
+        case 1: CONTEND_SAME_DATA( output, output_test_1, 4 ); break;
+        case 2: CONTEND_SAME_DATA( output, output_test_2, 4 ); break;
+        case 3: CONTEND_SAME_DATA( output, output_test_3, 4 ); break;
+        case 4: CONTEND_SAME_DATA( output, output_test_4, 4 ); break;
+        default:;
+        }
+    }
+}
+
diff --git a/src/vector/src/vector_add.c b/src/vector/src/vector_add.c
new file mode 100644
index 0000000..0de1dfd
--- /dev/null
+++ b/src/vector/src/vector_add.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic vector addition
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+// basic vector addition, unrolling loop
+//  _x      :   first array  [size: _n x 1]
+//  _y      :   second array [size: _n x 1]
+//  _n      :   array lengths
+//  _z      :   output array pointer [size: _n x 1]
+void VECTOR(_add)(T *          _x,
+                  T *          _y,
+                  unsigned int _n,
+                  T *          _z)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _z[i  ] = _x[i  ] + _y[i  ];
+        _z[i+1] = _x[i+1] + _y[i+1];
+        _z[i+2] = _x[i+2] + _y[i+2];
+        _z[i+3] = _x[i+3] + _y[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        _z[i] = _x[i] + _y[i];
+}
+
+// basic vector scalar addition, unrolling loop
+//  _x      :   input array  [size: _n x 1]
+//  _n      :   array length
+//  _v      :   scalar
+//  _y      :   output array pointer [size: _n x 1]
+void VECTOR(_addscalar)(T *          _x,
+                        unsigned int _n,
+                        T            _v,
+                        T *          _y)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _y[i  ] = _x[i  ] + _v;
+        _y[i+1] = _x[i+1] + _v;
+        _y[i+2] = _x[i+2] + _v;
+        _y[i+3] = _x[i+3] + _v;
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        _y[i] = _x[i] + _v;
+}
+
diff --git a/src/vector/src/vector_mul.c b/src/vector/src/vector_mul.c
new file mode 100644
index 0000000..d387424
--- /dev/null
+++ b/src/vector/src/vector_mul.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic vector multiplication
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+// basic vector multiplication, unrolling loop
+//  _x      :   first array  [size: _n x 1]
+//  _y      :   second array [size: _n x 1]
+//  _n      :   array lengths
+//  _z      :   output array pointer [size: _n x 1]
+void VECTOR(_mul)(T *          _x,
+                  T *          _y,
+                  unsigned int _n,
+                  T *          _z)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _z[i  ] = _x[i  ] * _y[i  ];
+        _z[i+1] = _x[i+1] * _y[i+1];
+        _z[i+2] = _x[i+2] * _y[i+2];
+        _z[i+3] = _x[i+3] * _y[i+3];
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        _z[i] = _x[i] * _y[i];
+}
+
+// basic vector scalar multiplication, unrolling loop
+//  _x      :   input array  [size: _n x 1]
+//  _n      :   array length
+//  _v      :   scalar
+//  _y      :   output array pointer [size: _n x 1]
+void VECTOR(_mulscalar)(T *          _x,
+                        unsigned int _n,
+                        T            _v,
+                        T *          _y)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _y[i  ] = _x[i  ] * _v;
+        _y[i+1] = _x[i+1] * _v;
+        _y[i+2] = _x[i+2] * _v;
+        _y[i+3] = _x[i+3] * _v;
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        _y[i] = _x[i] * _v;
+}
+
diff --git a/src/vector/src/vector_norm.c b/src/vector/src/vector_norm.c
new file mode 100644
index 0000000..2bb052b
--- /dev/null
+++ b/src/vector/src/vector_norm.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic vector norm computation
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+// compute l2-norm on vector
+//  _x      :   input array [size: _n x 1]
+//  _n      :   array length
+TP VECTOR(_norm)(T *          _x,
+                 unsigned int _n)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // initialize accumulator
+    TP norm = 0;
+
+    // compute in groups of 4
+    // TODO: use generic 'real' and 'conj' functions
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        norm += crealf( _x[i  ]*conjf(_x[i  ]) );
+        norm += crealf( _x[i+1]*conjf(_x[i+1]) );
+        norm += crealf( _x[i+2]*conjf(_x[i+2]) );
+        norm += crealf( _x[i+3]*conjf(_x[i+3]) );
+    }
+
+    // clean up remaining
+    // TODO: use generic 'real' and 'conj' functions
+    for ( ; i<_n; i++)
+        norm += crealf( _x[i]*conjf(_x[i]) );
+
+    // return square root of accumulation
+    // TODO: use generic 'sqrt' function
+    return sqrtf(norm);
+}
+
+// scale vector to its l2-norm
+//  _x      :   input array [size: _n x 1]
+//  _n      :   array length
+//  _y      :   output array [size: _n x 1]
+void VECTOR(_normalize)(T *          _x,
+                        unsigned int _n,
+                        T *          _y)
+{
+    // compute l2-norm on vector
+    TP norm = VECTOR(_norm)(_x, _n);
+
+    // compute inverse
+    TP norm_inv = 1.0 / norm;
+
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // scale by inverse; compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+        _y[i  ] = _x[i  ] * norm_inv;
+        _y[i+1] = _x[i+1] * norm_inv;
+        _y[i+2] = _x[i+2] * norm_inv;
+        _y[i+3] = _x[i+3] * norm_inv;
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++)
+        _y[i] = _x[i] * norm_inv;
+}
+
diff --git a/src/vector/src/vector_trig.c b/src/vector/src/vector_trig.c
new file mode 100644
index 0000000..bb668f0
--- /dev/null
+++ b/src/vector/src/vector_trig.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+//
+// Generic vector addition
+//
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <complex.h>
+
+// compute complex phase rotation: x[i] = exp{ j theta[i] }
+//  _theta  :   input primitive array [size: _n x 1]
+//  _n      :   array length
+//  _x      :   output array pointer [size: _n x 1]
+void VECTOR(_cexpj)(TP *         _theta,
+                    unsigned int _n,
+                    T *          _x)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+#if T_COMPLEX
+        _x[i  ] = cexpf(_Complex_I*_theta[i  ]);
+        _x[i+1] = cexpf(_Complex_I*_theta[i+1]);
+        _x[i+2] = cexpf(_Complex_I*_theta[i+2]);
+        _x[i+3] = cexpf(_Complex_I*_theta[i+3]);
+#else
+        _x[i  ] = _theta[i  ] > 0 ? 1.0 : -1.0;
+        _x[i+1] = _theta[i+1] > 0 ? 1.0 : -1.0;
+        _x[i+2] = _theta[i+2] > 0 ? 1.0 : -1.0;
+        _x[i+3] = _theta[i+3] > 0 ? 1.0 : -1.0;
+#endif
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++) {
+#if T_COMPLEX
+        _x[i] = cexpf(_Complex_I*_theta[i]);
+#else
+        _x[i] = _theta[i] > 0 ? 1.0 : -1.0;
+#endif
+    }
+}
+
+// compute complex phase rotation: x[i] = exp{ j theta[i] }
+//  _x      :   input array [size: _n x 1]
+//  _n      :   array length
+//  _theta  :   output primitive array [size: _n x 1]
+void VECTOR(_carg)(T *          _x,
+                   unsigned int _n,
+                   TP *         _theta)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+#if T_COMPLEX
+        _theta[i  ] = cargf(_x[i  ]);
+        _theta[i+1] = cargf(_x[i+1]);
+        _theta[i+2] = cargf(_x[i+2]);
+        _theta[i+3] = cargf(_x[i+3]);
+#else
+        _theta[i  ] = _x[i  ] > 0 ? 0 : M_PI;
+        _theta[i+1] = _x[i+1] > 0 ? 0 : M_PI;
+        _theta[i+2] = _x[i+2] > 0 ? 0 : M_PI;
+        _theta[i+3] = _x[i+3] > 0 ? 0 : M_PI;
+#endif
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++) {
+#if T_COMPLEX
+        _theta[i] = cargf(_x[i]);
+#else
+        _theta[i] = _x[i] > 0 ? 0 : M_PI;
+#endif
+    }
+}
+
+// compute absolute value of each element: y[i] = |x[i]|
+//  _x      :   input array [size: _n x 1]
+//  _n      :   array length
+//  _y      :   output primitive array pointer [size: _n x 1]
+void VECTOR(_abs)(T *          _x,
+                  unsigned int _n,
+                  TP *         _y)
+{
+    // t = 4*(floor(_n/4))
+    unsigned int t=(_n>>2)<<2; 
+
+    // compute in groups of 4
+    unsigned int i;
+    for (i=0; i<t; i+=4) {
+#if T_COMPLEX
+        _y[i  ] = cabsf(_x[i  ]);
+        _y[i+1] = cabsf(_x[i+1]);
+        _y[i+2] = cabsf(_x[i+2]);
+        _y[i+3] = cabsf(_x[i+3]);
+#else
+        _x[i  ] = fabsf(_x[i  ]);
+        _x[i+1] = fabsf(_x[i+1]);
+        _x[i+2] = fabsf(_x[i+2]);
+        _x[i+3] = fabsf(_x[i+3]);
+#endif
+    }
+
+    // clean up remaining
+    for ( ; i<_n; i++) {
+#if T_COMPLEX
+        _y[i] = cabsf(_x[i]);
+#else
+        _x[i] = fabsf(_x[i]);
+#endif
+    }
+}
+
diff --git a/src/vector/src/vectorcf_add.port.c b/src/vector/src/vectorcf_add.port.c
new file mode 100644
index 0000000..dd1905d
--- /dev/null
+++ b/src/vector/src/vectorcf_add.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorcf,name)
+#define T               float complex
+#define TP              float
+
+#include "vector_add.c"
+
diff --git a/src/vector/src/vectorcf_mul.port.c b/src/vector/src/vectorcf_mul.port.c
new file mode 100644
index 0000000..bf0f587
--- /dev/null
+++ b/src/vector/src/vectorcf_mul.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorcf,name)
+#define T               float complex
+#define TP              float
+
+#include "vector_mul.c"
+
diff --git a/src/vector/src/vectorcf_norm.port.c b/src/vector/src/vectorcf_norm.port.c
new file mode 100644
index 0000000..189d090
--- /dev/null
+++ b/src/vector/src/vectorcf_norm.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorcf,name)
+#define T               float complex
+#define TP              float
+
+#include "vector_norm.c"
+
diff --git a/src/vector/src/vectorcf_trig.port.c b/src/vector/src/vectorcf_trig.port.c
new file mode 100644
index 0000000..9773245
--- /dev/null
+++ b/src/vector/src/vectorcf_trig.port.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorcf,name)
+#define T               float complex
+#define TP              float
+
+#define T_COMPLEX       1
+
+#include "vector_trig.c"
+
diff --git a/src/vector/src/vectorf_add.port.c b/src/vector/src/vectorf_add.port.c
new file mode 100644
index 0000000..fe9a015
--- /dev/null
+++ b/src/vector/src/vectorf_add.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorf,name)
+#define T               float
+#define TP              float
+
+#include "vector_add.c"
+
diff --git a/src/vector/src/vectorf_mul.port.c b/src/vector/src/vectorf_mul.port.c
new file mode 100644
index 0000000..b4936d2
--- /dev/null
+++ b/src/vector/src/vectorf_mul.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorf,name)
+#define T               float
+#define TP              float
+
+#include "vector_mul.c"
+
diff --git a/src/vector/src/vectorf_norm.port.c b/src/vector/src/vectorf_norm.port.c
new file mode 100644
index 0000000..50d7def
--- /dev/null
+++ b/src/vector/src/vectorf_norm.port.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorf,name)
+#define T               float
+#define TP              float
+
+#include "vector_norm.c"
+
diff --git a/src/vector/src/vectorf_trig.port.c b/src/vector/src/vectorf_trig.port.c
new file mode 100644
index 0000000..b2fb0e7
--- /dev/null
+++ b/src/vector/src/vectorf_trig.port.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2007 - 2015 Joseph Gaeddert
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+// 
+// Vector
+//
+
+#include "liquid.internal.h"
+
+#define VECTOR(name)    LIQUID_CONCAT(liquid_vectorf,name)
+#define T               float
+#define TP              float
+
+#define T_COMPLEX       0
+
+#include "vector_trig.c"
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/liquid-dsp.git



More information about the pkg-hamradio-commits mailing list