From 148d5eadbdf0f3c90c37267afa4d6a6beadfd386 Mon Sep 17 00:00:00 2001
From: Aaron Viets <aaron.viets@ligo.org>
Date: Wed, 24 Jan 2018 14:45:51 -0800
Subject: [PATCH] gstlal-calibration:  new element lal_transferfunction to
 compute transfer functions between 2 or more channels in real time.

---
 gstlal-calibration/gst/lal/Makefile.am        |    3 +-
 .../gst/lal/gstlal_transferfunction.c         | 1352 +++++++++++++++++
 .../gst/lal/gstlal_transferfunction.h         |  158 ++
 .../gst/lal/gstlalcalibration.c               |    2 +
 4 files changed, 1514 insertions(+), 1 deletion(-)
 create mode 100644 gstlal-calibration/gst/lal/gstlal_transferfunction.c
 create mode 100644 gstlal-calibration/gst/lal/gstlal_transferfunction.h

diff --git a/gstlal-calibration/gst/lal/Makefile.am b/gstlal-calibration/gst/lal/Makefile.am
index c54707c73f..f477046450 100644
--- a/gstlal-calibration/gst/lal/Makefile.am
+++ b/gstlal-calibration/gst/lal/Makefile.am
@@ -13,7 +13,8 @@ libgstlalcalibration_la_SOURCES = \
 	gstlal_logicalundersample.c gstlal_logicalundersample.h \
 	gstlal_demodulate.c gstlal_demodulate.h \
 	gstlal_insertgap.c gstlal_insertgap.h \
-	gstlal_fccupdate.c gstlal_fccupdate.h
+	gstlal_fccupdate.c gstlal_fccupdate.h \
+	gstlal_transferfunction.c gstlal_transferfunction.h
 libgstlalcalibration_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_CPPFLAGS)
 libgstlalcalibration_la_CFLAGS = $(AM_CFLAGS) $(LAL_CFLAGS) $(GSTLAL_CFLAGS) $(gstreamer_CFLAGS) $(gstreamer_audio_CFLAGS)
 libgstlalcalibration_la_LDFLAGS = $(AM_LDFLAGS) $(LAL_LIBS) $(GSTLAL_LIBS) $(PYTHON_LIBS) $(gstreamer_LIBS) $(gstreamer_audio_LIBS) $(GSTLAL_PLUGIN_LDFLAGS)
diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.c b/gstlal-calibration/gst/lal/gstlal_transferfunction.c
new file mode 100644
index 0000000000..6b78117270
--- /dev/null
+++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.c
@@ -0,0 +1,1352 @@
+/*
+ * Copyright (C) 2018  Aaron Viets <aaron.viets@ligo.org>
+ *
+ * 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 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.
+ */
+
+
+/*
+ * SECTION:gstlal_transferfunction
+ * @short_description:  Compute transfer function between two or more
+ * time series (up to 11 total).
+ */
+
+
+/*
+ * ========================================================================
+ *
+ *				  Preamble
+ *
+ * ========================================================================
+ */
+
+
+/*
+ * stuff from the C library
+ */
+
+
+#include <complex.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*
+ * stuff from glib/gstreamer
+ */
+
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+#include <gst/audio/audio.h>
+#include <gst/audio/audio-format.h>
+
+
+/*
+ * stuff from FFTW and GSL
+ */
+
+
+#include <fftw3.h>
+#include <gsl/gsl_complex.h>
+#include <gsl/gsl_complex_math.h>
+#include <gsl/gsl_vector.h>
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_blas.h>
+#include <gsl/gsl_linalg.h>
+#include <gsl/gsl_permutation.h>
+
+
+/*
+ * our own stuff
+ */
+
+
+#include <gstlal/gstlal.h>
+#include <gstlal/gstlal_audio_info.h>
+#include <gstlal/gstlal_debug.h>
+#include <gstlal_transferfunction.h>
+
+
+/*
+ * ============================================================================
+ *
+ *			   GStreamer Boilerplate
+ *
+ * ============================================================================
+ */
+
+
+#define GST_CAT_DEFAULT gstlal_transferfunction_debug
+GST_DEBUG_CATEGORY_STATIC(GST_CAT_DEFAULT);
+
+
+G_DEFINE_TYPE_WITH_CODE(
+	GSTLALTransferFunction,
+	gstlal_transferfunction,
+	GST_TYPE_BASE_SINK,
+	GST_DEBUG_CATEGORY_INIT(GST_CAT_DEFAULT, "lal_transferfunction", 0, "lal_transferfunction element")
+);
+
+
+/*
+ * ============================================================================
+ *
+ *				 Parameters
+ *
+ * ============================================================================
+ */
+
+
+/*
+ * ============================================================================
+ *
+ *				  Utilities
+ *
+ * ============================================================================
+ */
+
+
+static void rebuild_workspace_and_reset(GObject *object)
+{
+	return;
+}
+
+
+#define DEFINE_MINIMUM(size) \
+gint ## size minimum ## size(gint ## size value1, gint ## size value2) { \
+	return value1 < value2 ? value1 : value2; \
+}
+
+
+DEFINE_MINIMUM(8);
+DEFINE_MINIMUM(16);
+DEFINE_MINIMUM(32);
+DEFINE_MINIMUM(64);
+
+
+#define DEFINE_MAXIMUM(size) \
+gint ## size maximum ## size(gint ## size value1, gint ## size value2) { \
+	return value1 > value2 ? value1 : value2; \
+}
+
+
+DEFINE_MAXIMUM(8);
+DEFINE_MAXIMUM(16);
+DEFINE_MAXIMUM(32);
+DEFINE_MAXIMUM(64);
+
+
+static void write_transfer_functions(complex double *tfs, char *element_name, gint64 rows, int columns, gboolean write_to_screen, char *filename) {
+	gint64 i;
+	int j, j_stop;
+	if(write_to_screen) {
+		g_print("\n\n========= Transfer functions computed by %s =========\n", element_name);
+		for(j = 1; j < columns; j++)
+			g_print("ch%d -> ch0\t", j);
+		g_print("ch%d -> ch0\n\n", columns);
+
+		j_stop = columns - 1;
+		for(i = 0; i < rows; i++) {
+			for(j = 0; j < j_stop; j++)
+				g_print("%10e + %10e i\t", creal(tfs[i + j * rows]), cimag(tfs[i + j * rows]));
+			g_print("%10e + %10e i\n", creal(tfs[i + j_stop * rows]), cimag(tfs[i + j_stop * rows]));
+		}
+		g_print("\n\n");
+	}
+
+	if(filename) {
+		FILE *fp;
+		fp = fopen(filename, "w");
+		g_fprintf(fp, "========= Transfer functions computed by %s =========\n", element_name);
+		for(j = 1; j < columns; j++)
+			g_fprintf(fp, "ch%d -> ch0\t", j);
+		g_fprintf(fp, "ch%d -> ch0\n\n", columns);
+
+		j_stop = columns - 1;
+		for(i = 0; i < rows; i++) {
+			for(j = 0; j < j_stop; j++)
+				g_fprintf(fp, "%10e + %10e i\t", creal(tfs[i + j * rows]), cimag(tfs[i + j * rows]));
+			g_fprintf(fp, "%10e + %10e i\n", creal(tfs[i + j_stop * rows]), cimag(tfs[i + j_stop * rows]));
+		}
+		fclose(fp);
+	}
+	g_free(element_name);
+}
+
+
+static void write_fir_filters(double *filters, char *element_name, gint64 rows, int columns, gboolean write_to_screen, char *filename) {
+	gint64 i;
+	int j, j_stop;
+	if(write_to_screen) {
+		g_print("============ FIR filters computed by %s ============\n", element_name);
+		for(j = 1; j < columns; j++)
+			g_print("ch%d -> ch0\t", j);
+		g_print("ch%d -> ch0\n\n", columns);
+
+		j_stop = columns - 1;
+		for(i = 0; i < rows; i++) {
+			for(j = 0; j < j_stop; j++)
+				g_print("%10e\t", filters[i + j * rows]);
+			g_print("%10e\n", filters[i + j_stop * rows]);
+		}
+		g_print("\n\n");
+	}
+
+	if(filename) {
+		FILE *fp;
+		fp = fopen(filename, "w");
+		g_fprintf(fp, "\n\n============ FIR filters computed by %s ============\n", element_name);
+		for(j = 1; j < columns; j++)
+			g_fprintf(fp, "ch%d -> ch0\t", j);
+		g_fprintf(fp, "ch%d -> ch0\n\n", columns);
+
+		j_stop = columns - 1;
+		for(i = 0; i < rows; i++) {
+			for(j = 0; j < j_stop; j++)
+				g_fprintf(fp, "%10e\t", filters[i + j * rows]);
+			g_fprintf(fp, "%10e\n", filters[i + j_stop * rows]);
+		}
+		fclose(fp);
+	}
+	g_free(element_name);
+}
+
+
+#define DEFINE_UPDATE_TRANSFER_FUNCTIONS(DTYPE) \
+static void update_transfer_functions_ ## DTYPE(complex DTYPE *autocorrelation_matrix, int num_tfs, gint64 length_tfs, gint64 num_avg, gsl_vector_complex *transfer_functions_at_f, gsl_vector_complex *transfer_functions_solved_at_f, gsl_matrix_complex *autocorrelation_matrix_at_f, gsl_permutation *permutation, complex double *transfer_functions) { \
+ \
+	gint64 i, first_index; \
+	int j, j_stop, signum; \
+	complex double z; \
+	gsl_complex gslz; \
+	for(i = 0; i < length_tfs; i++) { \
+		/* First, copy samples at a specific frequency from the big autocorrelation matrix to the gsl vector transfer_functions_at_f */ \
+		first_index = i * num_tfs; \
+		for(j = 0; j < num_tfs; j++) { \
+			z = (complex double) autocorrelation_matrix[first_index + j] / num_avg; \
+			gsl_vector_complex_set(transfer_functions_at_f, j, gsl_complex_rect(creal(z), cimag(z))); \
+		} \
+ \
+		/* Next, copy samples at a specific frequency from the big autocorrelation matrix to the gsl matrix autocorrelation_matrix_at_f */ \
+		j_stop = num_tfs * num_tfs; \
+		first_index += num_tfs; \
+		for(j = 0; j < j_stop; j++) { \
+			z = (complex double) autocorrelation_matrix[first_index + j] / num_avg; \
+			gsl_matrix_complex_set(autocorrelation_matrix_at_f, j / num_tfs, j % num_tfs, gsl_complex_rect(creal(z), cimag(z))); \
+			/* autocorrelation_matrix_at_f->data[j] = gsl_complex_rect(creal(z), cimag(z)); */ \
+		} \
+ \
+		/* Now solve [autocorrelation_matrix_at_f] [transfer_functions(f)] = [transfer_functions_at_f] for [transfer_functions(f)] using gsl */ \
+		gsl_linalg_complex_LU_decomp(autocorrelation_matrix_at_f, permutation, &signum); \
+		gsl_linalg_complex_LU_solve (autocorrelation_matrix_at_f, permutation, transfer_functions_at_f, transfer_functions_solved_at_f); \
+ \
+		/* Now copy the result into transfer_functions */ \
+		for(j = 0; j < num_tfs; j++) { \
+			gslz = gsl_vector_complex_get(transfer_functions_solved_at_f, j); \
+			transfer_functions[j * length_tfs + i] = GSL_REAL(gslz) + I * GSL_IMAG(gslz); \
+		} \
+	} \
+}
+
+
+DEFINE_UPDATE_TRANSFER_FUNCTIONS(float);
+DEFINE_UPDATE_TRANSFER_FUNCTIONS(double);
+
+
+#define DEFINE_UPDATE_FIR_FILTERS(DTYPE, F_OR_BLANK) \
+static void update_fir_filters_ ## DTYPE(complex double *transfer_functions, int num_tfs, gint64 length_tfs, int sample_rate, complex DTYPE *fir_filter, fftw ## F_OR_BLANK ## _plan fir_plan, DTYPE *fd_window, double *tukey, double *fir_filters) { \
+ \
+	int i; \
+	gint64 j, zero_index, fir_length; \
+	DTYPE df, delay, exp_arg; \
+	fir_length = 2 * (length_tfs - 1); \
+	df = sample_rate / 2.0 / (length_tfs - 1); /* frequency spacing is Nyquist frequency / number of frequency increments */ \
+	delay = (length_tfs - 1.0) / sample_rate; /* number of samples of delay is length of transfer functions - 1 */ \
+	exp_arg = -2.0 * M_PI * I * df * delay; \
+	for(i = 0; i < num_tfs; i++) { \
+		/*
+		 * First, copy samples from transfer_functions to fir_filter for fftw(f) to take an inverse fft.
+		 * The frequency domain window is applied here to roll off low and high freqneucies.
+		 * A delay is also added in order to center the filter in time.
+		 */ \
+		for(j = 0; j < length_tfs; j++) \
+			fir_filter[j] = fd_window[j] * cexp ## F_OR_BLANK(exp_arg * j) * transfer_functions[i * length_tfs + j]; \
+ \
+		/* Now make fir_filter conjugate symmetric by filling the remaining memory with complex conjugates */ \
+		zero_index = length_tfs - 1; \
+		for(j = 1; j <= length_tfs - 2; j++) \
+			fir_filter[zero_index + j] = conj ## F_OR_BLANK(fir_filter[zero_index - j]); \
+ \
+		/* Take the inverse Fourier transform */ \
+		fftw ## F_OR_BLANK ## _execute(fir_plan); \
+ \
+		/* Apply the Tukey window and copy to fir_filters */ \
+		for(j = 0; j < fir_length; j++) \
+			fir_filters[i * fir_length + j] = tukey[j] * fir_filter[j]; \
+	} \
+}
+
+
+DEFINE_UPDATE_FIR_FILTERS(float, f);
+DEFINE_UPDATE_FIR_FILTERS(double, );
+
+
+#define DEFINE_FIND_TRANSFER_FUNCTION(DTYPE, S_OR_D, F_OR_BLANK) \
+static void find_transfer_functions_ ## DTYPE(GSTLALTransferFunction *element, DTYPE *src, guint64 src_size) { \
+ \
+	/* Convert src_size from bytes to samples */ \
+	g_assert(!(src_size % element->unit_size)); \
+	src_size /= element->unit_size; \
+ \
+	gint64 i, j, k, m, num_ffts, k_start, k_stop, first_index, first_index2, stride, num_tfs; \
+	stride = element->fft_length - element->fft_overlap; \
+	num_tfs = element->channels - 1; \
+ \
+	/* Determine how many fft's we will calculate from combined leftover and new input data */ \
+	num_ffts = minimum64((element->workspace.w ## S_OR_D ## pf.num_leftover + stride - 1) / stride, element->num_ffts); \
+	num_ffts = minimum64(num_ffts, (element->workspace.w ## S_OR_D ## pf.num_leftover + (gint64) src_size - element->fft_overlap) / stride); \
+	if(num_ffts < 0) \
+		num_ffts = 0; \
+ \
+	/* Loop through the input data and compute transfer functions */ \
+	for(i = 0; i < num_ffts; i++) { \
+		for(j = 0; j < element->channels; j++) { \
+			/* First, copy the inputs from leftover data */ \
+			k_stop = element->workspace.w ## S_OR_D ## pf.num_leftover - i * stride; \
+			first_index = i * stride * element->channels + j; \
+			for(k = 0; k < k_stop; k++) \
+				element->workspace.w ## S_OR_D ## pf.fft[k] = element->workspace.w ## S_OR_D ## pf.fft_window[k] * element->workspace.w ## S_OR_D ## pf.leftover_data[first_index + k * element->channels]; \
+ \
+			/* Now copy the inputs from new input data */ \
+			k_start = k_stop; \
+			k_stop = element->fft_length; \
+			for(k = k_start; k < k_stop; k++) \
+				element->workspace.w ## S_OR_D ## pf.fft[k] = element->workspace.w ## S_OR_D ## pf.fft_window[k] * src[j + element->channels * (k - k_start)]; \
+ \
+			/* Take an FFT */ \
+			fftw ## F_OR_BLANK ## _execute(element->workspace.w ## S_OR_D ## pf.plan); \
+ \
+			/* Copy FFT to the proper location */ \
+			first_index = j * element->fft_length; \
+			for(k = 0; k < element->fft_length; k++) \
+				element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \
+		} \
+ \
+		/* 
+		 * Add into the autocorrelation matrix to be averaged. The autocorrelation
+		 * matrix includes all transfer functions. Note that the data is stored in
+		 * "frequency-major" order: transfer functions at a particular frequency are
+		 * stored contiguously in memory before incrementing to the next frequency.
+		 */ \
+		for(j = 0; j < element->fft_length; j++) { \
+			first_index = j * element->channels * num_tfs - 1; \
+			for(k = 1; k <= num_tfs; k++) { \
+				/* First, divide FFT's of first channel by others to get those transfer functions */ \
+				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * element->fft_length]; \
+ \
+				/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
+				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k * element->channels] += 1.0; \
+ \
+				/* Now find all other elements of the autocorrelation matrix */ \
+				first_index2 = first_index + k * element->channels; \
+				for(m = 1; m <= num_tfs - k; m++) { \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * element->fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * element->fft_length]; \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += 1.0 / element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m]; \
+				} \
+			} \
+		} \
+	} \
+ \
+	element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg += num_ffts; \
+ \
+	/* Determine how many fft's we will calculate from only new input samples */ \
+	num_ffts = (element->sample_count - element->update_samples - element->fft_overlap) / stride - element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg; /* how many more we could compute */ \
+	num_ffts = minimum64(num_ffts, element->num_ffts - element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg); /* how many more we need to update the transfer functions */ \
+	if(num_ffts < 0) \
+		num_ffts = 0; \
+ \
+	/* Find the location of the first sample in src that will be used */ \
+	DTYPE *ptr; \
+	if(element->update_samples >= element->sample_count - (gint64) src_size) \
+		ptr = src + (element->update_samples - element->sample_count + (gint64) src_size) * element->channels; \
+	else \
+		ptr = src + (stride - (element->sample_count - (gint64) src_size - element->update_samples) % stride) % stride; \
+ \
+	/* Loop through the input data and compute transfer functions */ \
+	for(i = 0; i < num_ffts; i++) { \
+		for(j = 0; j < element->channels; j++) { \
+			/* Copy inputs to take an FFT */ \
+			k_stop = element->fft_length; \
+			first_index = i * stride * element->channels + j; \
+			for(k = 0; k < k_stop; k++) \
+				element->workspace.w ## S_OR_D ## pf.fft[k] = element->workspace.w ## S_OR_D ## pf.fft_window[k] * ptr[first_index + k * element->channels]; \
+ \
+			/* Take an FFT */ \
+			fftw ## F_OR_BLANK ## _execute(element->workspace.w ## S_OR_D ## pf.plan); \
+ \
+			/* Copy FFT to the proper location */ \
+			first_index = j * element->fft_length; \
+			for(k = 0; k < element->fft_length; k++) \
+				element->workspace.w ## S_OR_D ## pf.ffts[first_index + k] = element->workspace.w ## S_OR_D ## pf.fft[k]; \
+		} \
+ \
+		/* 
+		 * Add into the autocorrelation matrix to be averaged. The autocorrelation
+		 * matrix includes all transfer functions. Note that the data is stored in
+		 * "frequency-major" order: transfer functions at a particular frequency are
+		 * stored contiguously in memory before incrementing to the next frequency.
+		 */ \
+		for(j = 0; j < element->fft_length; j++) { \
+			first_index = j * element->channels * num_tfs - 1; \
+			for(k = 1; k <= num_tfs; k++) { \
+				/* First, divide FFT's of first channel by others to get those transfer functions */ \
+				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k] += element->workspace.w ## S_OR_D ## pf.ffts[j] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * element->fft_length]; \
+ \
+				/* Now set elements of the autocorrelation matrix along the diagonal equal to one */ \
+				element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index + k * element->channels] += 1.0; \
+ \
+				/* Now find all other elements of the autocorrelation matrix */ \
+				first_index2 = first_index + k * element->channels; \
+				for(m = 1; m <= num_tfs - k; m++) { \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m] += element->workspace.w ## S_OR_D ## pf.ffts[j + (k + m) * element->fft_length] / element->workspace.w ## S_OR_D ## pf.ffts[j + k * element->fft_length]; \
+					element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m * num_tfs] += 1.0 / element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix[first_index2 + m]; \
+				} \
+			} \
+		} \
+	} \
+ \
+	element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg += num_ffts; \
+	g_assert_cmpint(element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg, <=, element->num_ffts); \
+ \
+	/* Now store samples for the next buffer. First, find the sample count of the start of the next fft */ \
+	gint64 sample_count_next_fft; \
+	if(element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg == element->num_ffts) \
+		sample_count_next_fft = 2 * element->update_samples + element->num_ffts * stride + element->fft_overlap + 1; /* If we finished updating the transfer functions */ \
+	else \
+		sample_count_next_fft = element->update_samples + 1 + element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg * stride; \
+ \
+	/* Deal with any leftover samples that will remain leftover */ \
+	first_index = sample_count_next_fft - (element->sample_count - (gint64) src_size - element->workspace.w ## S_OR_D ## pf.num_leftover); \
+	k_stop = (element->sample_count - (gint64) src_size + 1 - sample_count_next_fft) * element->channels; \
+	for(k = 0; k < k_stop; k++) \
+		element->workspace.w ## S_OR_D ## pf.leftover_data[k] = element->workspace.w ## S_OR_D ## pf.leftover_data[first_index + k]; \
+ \
+	/* Deal with new samples that will be leftover */ \
+	first_index = sample_count_next_fft - (element->sample_count - (gint64) src_size + 1); \
+	k_start = maximum64(k_stop, 0); \
+	k_stop = (element->sample_count + 1 - sample_count_next_fft) * element->channels; \
+	for(k = k_start; k < k_stop; k++) \
+		element->workspace.w ## S_OR_D ## pf.leftover_data[k] = src[first_index + k]; \
+ \
+	/* k_stop is the total number of leftover samples */ \
+	element->workspace.w ## S_OR_D ## pf.num_leftover = maximum64(0, k_stop); \
+ \
+	/* Finally, update transfer functions if ready */ \
+	if(element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg == element->num_ffts) { \
+		update_transfer_functions_ ## DTYPE(element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix, num_tfs, element->fft_length, element->num_ffts, element->workspace.w ## S_OR_D ## pf.transfer_functions_at_f, element->workspace.w ## S_OR_D ## pf.transfer_functions_solved_at_f, element->workspace.w ## S_OR_D ## pf.autocorrelation_matrix_at_f, element->workspace.w ## S_OR_D ## pf.permutation, element->transfer_functions); \
+		GST_INFO_OBJECT(element, "Just computed new transfer functions"); \
+		element->sample_count -= element->update_samples + element->num_ffts * stride + element->fft_overlap; \
+		element->workspace.w ## S_OR_D ## pf.num_ffts_in_avg = 0; \
+ \
+		/* Update FIR filters if we want */ \
+		if(element->make_fir_filters) { \
+			update_fir_filters_ ## DTYPE(element->transfer_functions, num_tfs, element->fft_length, element->rate, element->workspace.w ## S_OR_D ## pf.fir_filter, element->workspace.w ## S_OR_D ## pf.fir_plan, element->workspace.w ## S_OR_D ## pf.fir_window, element->workspace.w ## S_OR_D ## pf.tukey, element->fir_filters); \
+			GST_INFO_OBJECT(element, "Just computed new FIR filters"); \
+		} \
+ \
+		/* Let the pipeline know about the updates */ \
+		/* FIXME: nothing here yet */ \
+ \
+		/* Write output to the screen or a file if we want */ \
+		if(element->write_to_screen || element->filename) { \
+			write_transfer_functions(element->transfer_functions, gst_element_get_name(element), element->fft_length, num_tfs, element->write_to_screen, element->filename); \
+			if(element->make_fir_filters) \
+				write_fir_filters(element->fir_filters, gst_element_get_name(element), 2 * (element->fft_length - 1), num_tfs, element->write_to_screen, element->filename); \
+		} \
+	} \
+}
+
+
+DEFINE_FIND_TRANSFER_FUNCTION(float, s, f);
+DEFINE_FIND_TRANSFER_FUNCTION(double, d, );
+
+
+/*
+ * ============================================================================
+ *
+ *			    GstBaseSink Overrides
+ *
+ * ============================================================================
+ */
+
+
+/*
+ * set_caps()
+ */
+
+
+static gboolean set_caps(GstBaseSink *sink, GstCaps *caps) {
+
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(sink);
+
+	gboolean success = TRUE;
+
+	/* Parse the caps to find the format, sample rate, and number of channels */
+	GstStructure *str = gst_caps_get_structure(caps, 0);
+	const gchar *name = gst_structure_get_string(str, "format");
+	success &= (name != NULL);
+	success &= gst_structure_get_int(str, "rate", &element->rate);
+	success &= gst_structure_get_int(str, "channels", &element->channels);
+	g_assert_cmpint(element->channels, >, 1);
+	
+	/* Record the data type and unit size */
+	if(success) {
+		if(!strcmp(name, GST_AUDIO_NE(F32))) {
+			element->data_type = GSTLAL_TRANSFERFUNCTION_F32;
+			element->unit_size = 4 * element->channels;
+		} else if(!strcmp(name, GST_AUDIO_NE(F64))) {
+			element->data_type = GSTLAL_TRANSFERFUNCTION_F64;
+			element->unit_size = 8 * element->channels;
+		} else
+			g_assert_not_reached();
+	}
+	return success;
+}
+
+
+/*
+ * start()
+ */
+
+
+static gboolean start(GstBaseSink *sink) {
+
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(sink);
+
+	/* Sanity checks */
+	if(element->num_ffts > 1 && element->fft_overlap >= element->fft_length) {
+		GST_ERROR_OBJECT(element, "fft_overlap must not be greater than fft_length! Reset fft_length and/or fft_overlap properties.");
+		g_assert_not_reached();
+	}
+	if((!element->make_fir_filters) && (element->high_pass != 9 || element->low_pass != 0))
+		GST_WARNING_OBJECT(element, "A FIR filter cutoff frequency is set, but no FIR filter is being produced. Set the property make_fir_filters = True to make FIR filters.");
+	if(element->high_pass != 0 && element->low_pass != 0 && element->high_pass > element->low_pass)
+		GST_WARNING_OBJECT(element, "The high-pass cutoff frequency of the FIR filters is above the low-pass cutoff frequency. Reset high_pass and/or low_pass to change this.");
+	if(element->update_samples + element->fft_length < element->rate)
+		GST_WARNING_OBJECT(element, "The chosen fft_length and update_samples are very short. Errors may result.");
+
+	/* Timestamp bookkeeping */
+	element->t0 = GST_CLOCK_TIME_NONE;
+	element->offset0 = GST_BUFFER_OFFSET_NONE;
+	element->next_in_offset = GST_BUFFER_OFFSET_NONE;
+
+	/* Memory allocation */
+	if(!element->transfer_functions)
+		element->transfer_functions = g_malloc((element->channels - 1) * element->fft_length * sizeof(*element->transfer_functions));
+	if(element->make_fir_filters && !element->fir_filters)
+		element->fir_filters = g_malloc((element->channels - 1) * 2 * (element->fft_length - 1) * sizeof(*element->fir_filters));
+
+	element->sample_count = element->update_samples;
+
+	/* Prepare workspace for finding transfer functions and FIR filters */
+	if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) {
+		/* window functions */
+		element->workspace.wspf.fft_window = g_malloc(element->fft_length * sizeof(*element->workspace.wspf.fft_window));
+		gint64 i, i_stop, i_start;
+		for(i = 0; i < element->fft_length; i++)
+			element->workspace.wspf.fft_window[i] = (float) pow(sin(M_PI * i / (element->fft_length - 1)), 2.0);
+		if(element->make_fir_filters) {
+
+			/*
+			 * Make a frequency-donain window to roll off low and high frequencies
+			 */
+
+			element->workspace.wspf.fir_window = g_malloc(element->fft_length * sizeof(*element->workspace.wspf.fir_window));
+
+			/* Initialize to ones */
+			for(i = 0; i < element->fft_length; i++)
+				element->workspace.wspf.fir_window[i] = 1.0;
+
+			int f_nyquist = element->rate / 2;
+			float df_per_hz = (element->fft_length - 1.0) / f_nyquist;
+
+			/* high-pass filter */
+			/* Remove low frequencies */
+			i_stop = (gint64) (element->high_pass * df_per_hz / 2.0 + 0.49);
+			for(i = 0; i < i_stop; i++)
+				element->workspace.wspf.fir_window[i] = 0.0;
+
+			/* Apply half of a Hann window raised to the fourth power */
+			i_start = i_stop;
+			i_stop = (gint64) (element->high_pass * df_per_hz + 0.49);
+			for(i = i_start; i < i_stop; i++)
+				element->workspace.wspf.fir_window[i] *= (float) pow(sin((M_PI / 2.0) * (i - i_start) / (i_stop - i_start)), 8.0);
+
+			/* low-pass filter */
+			if(element->low_pass > 0) {
+				/* Apply half of a Hann window */
+				i_start = (gint64) (element->low_pass * df_per_hz + 0.49);
+				i_stop = minimum64(element->fft_length, 1.4 * i_start);
+				for(i = i_start; i < i_stop; i++)
+					element->workspace.wspf.fir_window[i] *= (float) pow(cos((M_PI / 2.0) * (i - i_start) / (i_stop - i_start)), 2.0);
+
+				/* Remove high frequencies */
+				i_start = i_stop;
+				i_stop = element->fft_length;
+				for(i = i_start; i < i_stop; i++)
+					element->workspace.wspf.fir_window[i] = 0.0;
+			}
+
+			/*
+			 * Make a time-domain Tukey window so that the filter falls off smoothly at the edges
+			 */
+
+			gint64 fir_length, edge_to_corner;
+			fir_length = 2 * (element->fft_length - 1);
+			edge_to_corner = (gint64) (0.45 * fir_length);
+
+			/* first curve of window */
+			for(i = 0; i < edge_to_corner; i++)
+				element->workspace.wspf.tukey[i] = pow(sin((M_PI / 2.0) * i / edge_to_corner), 2.0);
+
+			/* flat top of window */
+			i_stop = fir_length - edge_to_corner;
+			for(i = edge_to_corner; i < i_stop; i++)
+				element->workspace.wspf.tukey[i] = 1.0;
+
+			/* last curve of window */
+			i_start = i_stop;
+			for(i = i_start; i < fir_length; i++)
+				element->workspace.wspf.tukey[i] = pow(cos((M_PI / 2.0) * (i + 1 - i_start) / (fir_length - i_start)), 2.0);
+		}
+
+		/* intermediate data storage */
+		element->workspace.wspf.leftover_data = g_malloc(element->channels * (element->fft_length - 1) * sizeof(*element->workspace.wspf.leftover_data));
+		element->workspace.wspf.num_leftover = 0;
+		element->workspace.wspf.ffts = g_malloc(element->channels * element->fft_length * sizeof(*element->workspace.wspf.ffts));
+		element->workspace.wspf.num_ffts_in_avg = 0;
+		element->workspace.wspf.autocorrelation_matrix = g_malloc(element->channels * (element->channels - 1) * element->fft_length * sizeof(*element->workspace.wspf.autocorrelation_matrix));
+
+		/* Allocate memory for gsl matrix manipulations. The same memory locations will be used repeatedly */
+		element->workspace.wspf.transfer_functions_at_f = gsl_vector_complex_alloc(element->channels - 1);
+		element->workspace.wspf.transfer_functions_solved_at_f = gsl_vector_complex_alloc(element->channels - 1);
+		element->workspace.wspf.autocorrelation_matrix_at_f = gsl_matrix_complex_alloc(element->channels - 1, element->channels - 1);
+		element->workspace.wspf.permutation = gsl_permutation_alloc(element->channels - 1);
+
+		/* Allocate memory for fftwf to do Fourier transforms of data. The same memory locations will be used repeatedly */
+		gstlal_fftw_lock();
+
+		GST_LOG_OBJECT(element, "starting FFTWF planning");
+
+		/* data that will be Fourier transformed into frequency domain */
+		element->workspace.wspf.fft = (complex float *) fftwf_malloc(element->fft_length * sizeof(*element->workspace.wspf.fft));
+		element->workspace.wspf.plan = fftwf_plan_dft_r2c_1d(element->fft_length, (float *) element->workspace.wspf.fft, element->workspace.wspf.fft, FFTW_MEASURE);
+
+		if(element->make_fir_filters && !element->fir_filters) {
+
+			/* data that will be inverse Fourier transformed back into the time domain */
+			element->workspace.wspf.fir_filter = (complex float *) fftwf_malloc(2 * (element->fft_length - 1) * sizeof(*element->workspace.wspf.fir_filter));
+			element->workspace.wspf.fir_plan = fftwf_plan_dft_c2r_1d(2 * (element->fft_length - 1), element->workspace.wspf.fir_filter, (float *) element->workspace.wspf.fir_filter, FFTW_MEASURE);
+		}
+		GST_LOG_OBJECT(element, "FFTWF planning complete");
+
+		gstlal_fftw_unlock();
+
+	} else if(element->data_type == GSTLAL_TRANSFERFUNCTION_F64) {
+		/* window functions */
+		element->workspace.wdpf.fft_window = g_malloc(element->fft_length * sizeof(*element->workspace.wdpf.fft_window));
+		gint64 i, i_stop, i_start;
+		for(i = 0; i < element->fft_length; i++)
+			element->workspace.wdpf.fft_window[i] = pow(sin(M_PI * i / (element->fft_length - 1)), 2.0);
+		if(element->make_fir_filters) {
+
+			/*
+			 * Make a frequency-donain window to roll off low and high frequencies
+			 */
+
+			element->workspace.wdpf.fir_window = g_malloc(element->fft_length * sizeof(*element->workspace.wdpf.fir_window));
+
+			/* Initialize to ones */
+			for(i = 0; i < element->fft_length; i++)
+				element->workspace.wdpf.fir_window[i] = 1.0;
+
+			int f_nyquist = element->rate / 2;
+			double df_per_hz = (element->fft_length - 1.0) / f_nyquist;
+
+			/* high-pass filter */
+			/* Remove low frequencies */
+			i_stop = (gint64) (element->high_pass * df_per_hz / 2.0 + 0.49);
+			for(i = 0; i < i_stop; i++)
+				element->workspace.wdpf.fir_window[i] = 0.0;
+
+			/* Apply half of a Hann window raised to the fourth power */
+			i_start = i_stop;
+			i_stop = (gint64) (element->high_pass * df_per_hz + 0.49);
+			for(i = i_start; i < i_stop; i++)
+				element->workspace.wdpf.fir_window[i] *= pow(sin((M_PI / 2.0) * (i - i_start) / (i_stop - i_start)), 8.0);
+
+			/* low-pass filter */
+			if(element->low_pass > 0) {
+				/* Apply half of a Hann window */
+				i_start = (gint64) (element->low_pass * df_per_hz + 0.49);
+				i_stop = minimum64(element->fft_length, 1.4 * i_start);
+				for(i = i_start; i < i_stop; i++)
+					element->workspace.wdpf.fir_window[i] *= pow(cos((M_PI / 2.0) * (i - i_start) / (i_stop - i_start)), 2.0);
+
+				/* Remove high frequencies */
+				i_start = i_stop;
+				i_stop = element->fft_length;
+				for(i = i_start; i < i_stop; i++)
+					element->workspace.wdpf.fir_window[i] = 0.0;
+			}
+
+			/*
+			 * Make a time-domain Tukey window so that the filter falls off smoothly at the edges
+			 */
+
+			gint64 fir_length, edge_to_corner;
+			fir_length = 2 * (element->fft_length - 1);
+			edge_to_corner = (gint64) (0.45 * fir_length);
+
+			/* first curve of window */
+			for(i = 0; i < edge_to_corner; i++)
+				element->workspace.wdpf.tukey[i] = pow(sin((M_PI / 2.0) * i / edge_to_corner), 2.0);
+
+			/* flat top of window */
+			i_stop = fir_length - edge_to_corner;
+			for(i = edge_to_corner; i < i_stop; i++)
+				element->workspace.wdpf.tukey[i] = 1.0;
+
+			/* last curve of window */
+			i_start = i_stop;
+			for(i = i_start; i < fir_length; i++)
+				element->workspace.wdpf.tukey[i] = pow(cos((M_PI / 2.0) * (i + 1 - i_start) / (fir_length - i_start)), 2.0);
+		}
+
+		/* intermediate data storage */
+		element->workspace.wdpf.leftover_data = g_malloc(element->channels * (element->fft_length - 1) * sizeof(*element->workspace.wdpf.leftover_data));
+		element->workspace.wdpf.num_leftover = 0;
+		element->workspace.wdpf.ffts = g_malloc(element->channels * element->fft_length * sizeof(*element->workspace.wdpf.ffts));
+		element->workspace.wdpf.num_ffts_in_avg = 0;
+		element->workspace.wdpf.autocorrelation_matrix = g_malloc(element->channels * (element->channels - 1) * element->fft_length * sizeof(*element->workspace.wdpf.autocorrelation_matrix));
+
+		/* Allocate memory for gsl matrix manipulations. The same memory locations will be used repeatedly */
+		element->workspace.wdpf.transfer_functions_at_f = gsl_vector_complex_alloc(element->channels - 1);
+		element->workspace.wdpf.transfer_functions_solved_at_f = gsl_vector_complex_alloc(element->channels - 1);
+		element->workspace.wdpf.autocorrelation_matrix_at_f = gsl_matrix_complex_alloc(element->channels - 1, element->channels - 1);
+		element->workspace.wdpf.permutation = gsl_permutation_alloc(element->channels - 1);
+
+		/* Allocate memory for fftw to do Fourier transforms of data. The same memory locations will be used repeatedly */
+		gstlal_fftw_lock();
+
+		GST_LOG_OBJECT(element, "starting FFTW planning");
+
+		/* data that will be Fourier transformed into frequency domain */
+		element->workspace.wdpf.fft = (complex double *) fftw_malloc(element->fft_length * sizeof(*element->workspace.wdpf.fft));
+		element->workspace.wdpf.plan = fftw_plan_dft_r2c_1d(element->fft_length, (double *) element->workspace.wdpf.fft, element->workspace.wdpf.fft, FFTW_MEASURE);
+
+		if(element->make_fir_filters && !element->fir_filters) {
+
+			/* data that will be inverse Fourier transformed back into the time domain */
+			element->workspace.wdpf.fir_filter = (complex double *) fftw_malloc(2 * (element->fft_length  - 1) * sizeof(*element->workspace.wdpf.fir_filter));
+			element->workspace.wdpf.fir_plan = fftw_plan_dft_c2r_1d(2 * (element->fft_length - 1), element->workspace.wdpf.fir_filter, (double *) element->workspace.wdpf.fir_filter, FFTW_MEASURE);
+		}
+		GST_LOG_OBJECT(element, "FFTW planning complete");
+
+		gstlal_fftw_unlock();
+
+	} else
+		g_assert_not_reached();
+
+	return TRUE;
+}
+
+
+/*
+ * stop()
+ */
+
+
+static gboolean stop(GstBaseSink *sink) {
+
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(sink);
+
+	g_free(element->transfer_functions);
+	element->transfer_functions = NULL;
+	if(element->make_fir_filters) {
+		g_free(element->fir_filters);
+		element->fir_filters = NULL;
+	}
+	if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) {
+
+		/* free allocated memory in workspace */
+		g_free(element->workspace.wspf.leftover_data);
+		element->workspace.wspf.leftover_data = NULL;
+		g_free(element->workspace.wspf.ffts);
+		element->workspace.wspf.ffts = NULL;
+		g_free(element->workspace.wspf.autocorrelation_matrix);
+		element->workspace.wspf.autocorrelation_matrix = NULL;
+
+		/* free gsl stuff in workspace */
+		gsl_vector_complex_free(element->workspace.wspf.transfer_functions_at_f);
+		element->workspace.wspf.transfer_functions_at_f = NULL;
+		gsl_vector_complex_free(element->workspace.wspf.transfer_functions_solved_at_f);
+		element->workspace.wspf.transfer_functions_solved_at_f = NULL;
+		gsl_matrix_complex_free(element->workspace.wspf.autocorrelation_matrix_at_f);
+		element->workspace.wspf.autocorrelation_matrix_at_f = NULL;
+		gsl_permutation_free(element->workspace.wspf.permutation);
+		element->workspace.wspf.permutation = NULL;
+
+		/* free fftwf stuff in workspace */
+		gstlal_fftw_lock();
+		fftwf_free(element->workspace.wspf.fft);
+		element->workspace.wspf.fft = NULL;
+		fftwf_destroy_plan(element->workspace.wspf.plan);
+		if(element->make_fir_filters) {
+			fftwf_free(element->workspace.wspf.fir_filter);
+			element->workspace.wspf.fir_filter = NULL;
+			fftwf_destroy_plan(element->workspace.wspf.fir_plan);
+		}
+		gstlal_fftw_unlock();
+
+	} else {
+
+		/* free allocated memory in workspace */
+		g_free(element->workspace.wdpf.leftover_data);
+		element->workspace.wdpf.leftover_data = NULL;
+		g_free(element->workspace.wdpf.ffts);
+		element->workspace.wdpf.ffts = NULL;
+		g_free(element->workspace.wdpf.autocorrelation_matrix);
+		element->workspace.wdpf.autocorrelation_matrix = NULL;
+
+		/* free gsl stuff in workspace */
+		gsl_vector_complex_free(element->workspace.wdpf.transfer_functions_at_f);
+		element->workspace.wdpf.transfer_functions_at_f = NULL;
+		gsl_vector_complex_free(element->workspace.wdpf.transfer_functions_solved_at_f);
+		element->workspace.wdpf.transfer_functions_solved_at_f = NULL;
+		gsl_matrix_complex_free(element->workspace.wdpf.autocorrelation_matrix_at_f);
+		element->workspace.wdpf.autocorrelation_matrix_at_f = NULL;
+		gsl_permutation_free(element->workspace.wdpf.permutation);
+		element->workspace.wdpf.permutation = NULL;
+
+		/* free fftw stuff in workspace*/
+		gstlal_fftw_lock();
+		fftw_free(element->workspace.wdpf.fft);
+		element->workspace.wdpf.fft = NULL;
+		fftw_destroy_plan(element->workspace.wdpf.plan);
+		if(element->make_fir_filters) {
+			fftw_free(element->workspace.wdpf.fir_filter);
+			element->workspace.wdpf.fir_filter = NULL;
+			fftw_destroy_plan(element->workspace.wdpf.fir_plan);
+		}
+		gstlal_fftw_unlock();
+	}
+	return TRUE;
+}
+
+
+/*
+ * render()
+ */
+
+
+static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer) {
+
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(sink);
+	GstMapInfo mapinfo;
+	GstFlowReturn result = GST_FLOW_OK;
+
+	/*
+	 * check for discontinuity
+	 */
+
+	if(G_UNLIKELY(GST_BUFFER_IS_DISCONT(buffer) || GST_BUFFER_OFFSET(buffer) != element->next_in_offset || !GST_CLOCK_TIME_IS_VALID(element->t0))) {
+		element->t0 = GST_BUFFER_PTS(buffer);
+		element->offset0 = GST_BUFFER_OFFSET(buffer);
+		if(element->sample_count > element->update_samples) 
+			element->sample_count = element->update_samples;
+		if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) {
+			element->workspace.wspf.num_ffts_in_avg = 0;
+			element->workspace.wspf.num_leftover = 0;
+		} else {
+			element->workspace.wdpf.num_ffts_in_avg = 0;
+			element->workspace.wdpf.num_leftover = 0;
+		}
+	}
+	element->next_in_offset = GST_BUFFER_OFFSET_END(buffer);
+	GST_DEBUG_OBJECT(element, "have buffer spanning %" GST_BUFFER_BOUNDARIES_FORMAT, GST_BUFFER_BOUNDARIES_ARGS(buffer));
+
+	/* Get the data from the buffer */
+	gst_buffer_map(buffer, &mapinfo, GST_MAP_READ);
+
+	/* Increment the sample count */
+	element->sample_count += (mapinfo.size / element->unit_size);
+
+	/* Deal with gaps */
+	if(GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_GAP) && mapinfo.size != 0) {
+		if(element->update_after_gap || element->sample_count > element->update_samples)
+			element->sample_count = element->update_samples;
+		if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) {
+			element->workspace.wspf.num_ffts_in_avg = 0;
+			element->workspace.wspf.num_leftover = 0;
+		} else {
+			element->workspace.wdpf.num_ffts_in_avg = 0;
+			element->workspace.wdpf.num_leftover = 0;
+		}
+	}
+
+	/* Check whether we need to do anything with this data */
+	if(element->sample_count > element->update_samples) {
+		if(element->data_type == GSTLAL_TRANSFERFUNCTION_F32) {
+			/* If we are just beginning to compute new transfer functions with this data, initialize memory that we will fill to zero */
+			if(!element->workspace.wspf.num_ffts_in_avg)
+				memset(element->workspace.wspf.autocorrelation_matrix, 0, element->channels * (element->channels - 1) * element->fft_length * sizeof(*element->workspace.wspf.autocorrelation_matrix));
+
+			/* Send the data to a function to compute fft's and transfer functions */
+			find_transfer_functions_float(element, (float *) mapinfo.data, mapinfo.size);
+		} else {
+			/* If we are just beginning to compute new transfer functions with this data, initialize memory that we will fill to zero */
+			if(!element->workspace.wdpf.num_ffts_in_avg)
+				memset(element->workspace.wdpf.autocorrelation_matrix, 0, element->channels * (element->channels - 1) * element->fft_length * sizeof(*element->workspace.wdpf.autocorrelation_matrix));
+
+			/* Send the data to a function to compute fft's and transfer functions */
+			find_transfer_functions_double(element, (double *) mapinfo.data, mapinfo.size);
+		}
+	}
+
+	return result;
+}
+
+
+/*
+ * ============================================================================
+ *
+ *			      GObject Methods
+ *
+ * ============================================================================
+ */
+
+
+/*
+ * properties
+ */
+
+
+enum property {
+	ARG_FFT_LENGTH = 1,
+	ARG_FFT_OVERLAP,
+	ARG_NUM_FFTS,
+	ARG_UPDATE_SAMPLES,
+	ARG_UPDATE_AFTER_GAP,
+	ARG_WRITE_TO_SCREEN,
+	ARG_FILENAME,
+	ARG_MAKE_FIR_FILTERS,
+	ARG_HIGH_PASS,
+	ARG_LOW_PASS,
+	ARG_TRANSFER_FUNCTIONS,
+	ARG_FIR_FILTERS,
+};
+
+
+static void set_property(GObject *object, enum property id, const GValue *value, GParamSpec *pspec)
+{
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(object);
+
+	GST_OBJECT_LOCK(element);
+
+	switch(id) {
+	case ARG_FFT_LENGTH:
+		element->fft_length = g_value_get_int64(value);
+		break;
+
+	case ARG_FFT_OVERLAP:
+		element->fft_overlap = g_value_get_int64(value);
+		break;
+
+	case ARG_NUM_FFTS:
+		element->num_ffts = g_value_get_int64(value);
+		break;
+
+	case ARG_UPDATE_SAMPLES:
+		element->update_samples = g_value_get_int64(value);
+		break;
+
+	case ARG_UPDATE_AFTER_GAP:
+		element->update_after_gap = g_value_get_boolean(value);
+		break;
+
+	case ARG_WRITE_TO_SCREEN:
+		element->write_to_screen = g_value_get_boolean(value);
+		break;
+
+	case ARG_FILENAME:
+		element->filename = g_value_dup_string(value);
+		break;
+
+	case ARG_MAKE_FIR_FILTERS:
+		element->make_fir_filters = g_value_get_boolean(value);
+		break;
+
+	case ARG_HIGH_PASS:
+		element->high_pass = g_value_get_int(value);
+		break;
+
+	case ARG_LOW_PASS:
+		element->low_pass = g_value_get_int(value);
+		break;
+
+	case ARG_TRANSFER_FUNCTIONS:
+		if(element->transfer_functions)
+			g_free(element->transfer_functions);
+		int n;
+		gstlal_doubles_from_g_value_array(g_value_get_boxed(value), (double *) element->transfer_functions, &n);
+		break;
+
+	case ARG_FIR_FILTERS:
+		if(element->fir_filters)
+			g_free(element->fir_filters);
+		int m;
+		gstlal_doubles_from_g_value_array(g_value_get_boxed(value), element->fir_filters, &m);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec);
+		break;
+	}
+
+	GST_OBJECT_UNLOCK(element);
+}
+
+
+static void get_property(GObject *object, enum property id, GValue *value, GParamSpec *pspec)
+{
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(object);
+
+	GST_OBJECT_LOCK(element);
+
+	switch(id) {
+	case ARG_FFT_LENGTH:
+		g_value_set_int64(value, element->fft_length);
+		break;
+
+	case ARG_FFT_OVERLAP:
+		g_value_set_int64(value, element->fft_overlap);
+		break;
+
+	case ARG_NUM_FFTS:
+		g_value_set_int64(value, element->num_ffts);
+		break;
+
+	case ARG_UPDATE_SAMPLES:
+		g_value_set_int64(value, element->update_samples);
+		break;
+
+	case ARG_UPDATE_AFTER_GAP:
+		g_value_set_boolean(value, element->update_after_gap);
+		break;
+
+	case ARG_WRITE_TO_SCREEN:
+		g_value_set_boolean(value, element->write_to_screen);
+		break;
+
+	case ARG_FILENAME:
+		g_value_set_string(value, element->filename);
+		break;
+
+	case ARG_MAKE_FIR_FILTERS:
+		g_value_set_boolean(value, element->make_fir_filters);
+		break;
+
+	case ARG_HIGH_PASS:
+		g_value_set_int(value, element->high_pass);
+		break;
+
+	case ARG_LOW_PASS:
+		g_value_set_int(value, element->low_pass);
+		break;
+
+	case ARG_TRANSFER_FUNCTIONS:
+		if(element->transfer_functions)
+			g_value_take_boxed(value, gstlal_g_value_array_from_doubles((double *) element->transfer_functions, 2 * (element->channels - 1) * element->fft_length));
+		break;
+
+	case ARG_FIR_FILTERS:
+		if(element->fir_filters)
+			g_value_take_boxed(value, gstlal_g_value_array_from_doubles(element->fir_filters, (element->channels - 1) * 2 * (element->fft_length - 1)));
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, id, pspec);
+		break;
+	}
+
+	GST_OBJECT_UNLOCK(element);
+}
+
+
+/*
+ * finalize()
+ */
+
+
+static void finalize(GObject *object) {
+
+	GSTLALTransferFunction *element = GSTLAL_TRANSFERFUNCTION(object);
+
+	if(element->transfer_functions) {
+		g_free(element->transfer_functions);
+		element->transfer_functions = NULL;
+	}
+	if(element->fir_filters) {
+		g_free(element->fir_filters);
+		element->fir_filters = NULL;
+	}
+	G_OBJECT_CLASS(gstlal_transferfunction_parent_class)->finalize(object);
+}
+
+
+/*
+ * class_init()
+ */
+
+
+#define CAPS \
+	"audio/x-raw, " \
+	"rate = (int) [1, MAX], " \
+	"channels = (int) [2, MAX], " \
+	"format = (string) {"GST_AUDIO_NE(F32)", "GST_AUDIO_NE(F64)"}, " \
+	"layout = (string) interleaved, " \
+	"channel-mask = (bitmask) 0"
+
+
+static void gstlal_transferfunction_class_init(GSTLALTransferFunctionClass *klass) {
+
+	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+	GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS(klass);
+
+	gstbasesink_class->set_caps = GST_DEBUG_FUNCPTR(set_caps);
+	gstbasesink_class->start = GST_DEBUG_FUNCPTR(start);
+	gstbasesink_class->render = GST_DEBUG_FUNCPTR(render);
+	gstbasesink_class->stop = GST_DEBUG_FUNCPTR(stop);
+
+	gobject_class->set_property = GST_DEBUG_FUNCPTR(set_property);
+	gobject_class->get_property = GST_DEBUG_FUNCPTR(get_property);
+	gobject_class->finalize = GST_DEBUG_FUNCPTR(finalize);
+
+	gst_element_class_set_details_simple(
+		element_class,
+		"TransferFunction",
+		"Sink",
+		"Compute the transfer function(s) between an output signal and one or more input signals.\n\t\t\t   "
+		"This sink element only has one sink pad, so it requires interleaving all input data. The\n\t\t\t   "
+		"first channel is treated as the output of the transfer function, and the rest are\n\t\t\t   "
+		"treated as inputs. If there are multiple inputs, the transfer functions are optimized\n\t\t\t   "
+		"to minimize the RMS difference between the output and the approximated output. A maximum\n\t\t\t   "
+		"of 10 inputs are supported.",
+		"Aaron Viets <aaron.viets@ligo.org>"
+	);
+
+	gst_element_class_add_pad_template(
+		element_class,
+		gst_pad_template_new(
+			"sink",
+			GST_PAD_SINK,
+			GST_PAD_ALWAYS,
+			gst_caps_from_string(CAPS)
+		)
+	);
+
+	g_object_class_install_property(
+		gobject_class,
+		ARG_FFT_LENGTH,
+		g_param_spec_int64(
+			"fft-length",
+			"FFT Length",
+			"Length in samples of the FFTs used to compute the transfer function(s)",
+			0, G_MAXINT64, 16384,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_FFT_OVERLAP,
+		g_param_spec_int64(
+			"fft-overlap",
+			"FFT Overlap",
+			"The overlap in samples of the FFTs used to compute the transfer function(s)",
+			-G_MAXINT64, G_MAXINT64, 8192,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_NUM_FFTS,
+		g_param_spec_int64(
+			"num-ffts",
+			"Number of FFTs",
+			"Number of FFTs that will be averaged to compute the transfer function(s)",
+			1, G_MAXINT64, 16,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_UPDATE_SAMPLES,
+		g_param_spec_int64(
+			"update-samples",
+			"Update Samples",
+			"Number of input samples after which to update the transfer function(s)",
+			0, G_MAXINT64, 58982400,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_UPDATE_AFTER_GAP,
+		g_param_spec_boolean(
+			"update-after-gap",
+			"Update After Gap",
+			"Set to True in order to update the transfer function(s) after a gap in the\n\t\t\t"
+			"input data.",
+			FALSE,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_WRITE_TO_SCREEN,
+		g_param_spec_boolean(
+			"write-to-screen",
+			"Write to Screen",
+			"Set to True in order to write transfer functions and/or FIR filters to the screen.",
+			FALSE,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_FILENAME,
+		g_param_spec_string(
+			"filename",
+			"Filename",
+			"Name of file to write transfer functions and/or FIR filters to. If not given,\n\t\t\t"
+			"no file is produced.",
+			NULL,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_MAKE_FIR_FILTERS,
+		g_param_spec_boolean(
+			"make-fir-filters",
+			"Make FIR Filters",
+			"If True, FIR filters will be produced each time the transfer functions are computed.",
+			FALSE,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_HIGH_PASS,
+		g_param_spec_int(
+			"high-pass",
+			"High Pass",
+			"The high-pass cutoff frequency (in Hz) of the FIR filters.\n\t\t\t"
+			"If zero, no high-pass cutoff is added.",
+			0, G_MAXINT, 9,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_LOW_PASS,
+		g_param_spec_int(
+			"low-pass",
+			"Low Pass",
+			"The low-pass cutoff frequency (in Hz) of the FIR filters.\n\t\t\t"
+			"If zero, no low-pass cutoff is added.",
+			0, G_MAXINT, 0,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_TRANSFER_FUNCTIONS,
+		g_param_spec_value_array(
+			"transfer-functions",
+			"Transfer Functions",
+			"Array of the computed transfer functions",
+			g_param_spec_value_array(
+				"transfer-function",
+				"Transfer Function",
+				"A single transfer function",
+				g_param_spec_double(
+					"value",
+					"Value",
+					"Value of the transfer function at a particular frequency",
+					-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+				),
+				G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+			),
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_FIR_FILTERS,
+		g_param_spec_value_array(
+			"fir-filters",
+			"FIR Filters",
+			"Array of the computed FIR filters",
+			g_param_spec_value_array(
+				"fir-filter",
+				"FIR Filter",
+				"A single FIR filter",
+				g_param_spec_double(
+					"sample",
+					"Sample",
+					"A sample from the FIR filter",
+					-G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+				),
+				G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
+			),
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE
+		)
+	);
+}
+
+
+/*
+ * init()
+ */
+
+
+static void gstlal_transferfunction_init(GSTLALTransferFunction *element) {
+
+	g_signal_connect(G_OBJECT(element), "notify::transfer-function", G_CALLBACK(rebuild_workspace_and_reset), NULL);
+	g_signal_connect(G_OBJECT(element), "notify::fir-filter", G_CALLBACK(rebuild_workspace_and_reset), NULL);
+	element->rate = 0;
+	element->unit_size = 0;
+	element->channels = 0;
+}
+
diff --git a/gstlal-calibration/gst/lal/gstlal_transferfunction.h b/gstlal-calibration/gst/lal/gstlal_transferfunction.h
new file mode 100644
index 0000000000..51876c189f
--- /dev/null
+++ b/gstlal-calibration/gst/lal/gstlal_transferfunction.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2017  Aaron Viets <aaron.viets@ligo.org>
+ *
+ *  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 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.,
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef __GSTLAL_TRANSFERFUNCTION_H__
+#define __GSTLAL_TRANSFERFUNCTION_H__
+
+
+#include <complex.h>
+
+#include <glib.h>
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+#include <fftw3.h>
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_permutation.h>
+
+
+G_BEGIN_DECLS
+
+
+#define GSTLAL_TRANSFERFUNCTION_TYPE \
+	(gstlal_transferfunction_get_type())
+#define GSTLAL_TRANSFERFUNCTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj), GSTLAL_TRANSFERFUNCTION_TYPE, GSTLALTransferFunction))
+#define GSTLAL_TRANSFERFUNCTION_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass), GSTLAL_TRANSFERFUNCTION_TYPE, GSTLALTransferFunctionClass))
+#define GST_IS_GSTLAL_TRANSFERFUNCTION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj), GSTLAL_TRANSFERFUNCTION_TYPE))
+#define GST_IS_GSTLAL_TRANSFERFUNCTION_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass), GSTLAL_TRANSFERFUNCTION_TYPE))
+
+
+typedef struct _GSTLALTransferFunction GSTLALTransferFunction;
+typedef struct _GSTLALTransferFunctionClass GSTLALTransferFunctionClass;
+
+
+/**
+ * GSTLALTransferFunction:
+ */
+
+
+struct _GSTLALTransferFunction {
+	GstBaseSink basesink;
+
+	/* stream info */
+	gint rate;
+	gint unit_size;
+	gint channels;
+	gint64 sample_count;
+	enum gstlal_transferfunction_data_type {
+		GSTLAL_TRANSFERFUNCTION_F32 = 0,
+		GSTLAL_TRANSFERFUNCTION_F64,
+	} data_type;
+
+	/* timestamp bookkeeping */
+	GstClockTime t0;
+	guint64 offset0;
+	guint64 next_in_offset;
+
+	/* transfer function work space */
+	union {
+		struct {
+			float *fft_window;
+			float *fir_window;
+			double *tukey;
+			float *leftover_data;
+			gint64 num_leftover;
+			complex float *ffts;
+			gint64 num_ffts_in_avg;
+			complex float *autocorrelation_matrix;
+
+			/* gsl stuff */
+			gsl_vector_complex *transfer_functions_at_f;
+			gsl_vector_complex *transfer_functions_solved_at_f;
+			gsl_matrix_complex *autocorrelation_matrix_at_f;
+			gsl_permutation *permutation;
+
+			/* fftwf stuff */
+			complex float *fft;
+			fftwf_plan plan;
+			complex float *fir_filter;
+			fftwf_plan fir_plan;
+		} wspf;  /* workspace single-precision float */
+		struct {
+			double *fft_window;
+			double *fir_window;
+			double *tukey;
+			double *leftover_data;
+			gint64 num_leftover;
+			complex double *ffts;
+			gint64 num_ffts_in_avg;
+			complex double *autocorrelation_matrix;
+
+			/* gsl stuff */
+			gsl_vector_complex *transfer_functions_at_f;
+			gsl_vector_complex *transfer_functions_solved_at_f;
+			gsl_matrix_complex *autocorrelation_matrix_at_f;
+			gsl_permutation *permutation;
+
+			/* fftw stuff */
+			complex double *fft;
+			fftw_plan plan;
+			complex double *fir_filter;
+			fftw_plan fir_plan;
+		} wdpf;  /* workspace double-precision float */
+	} workspace;
+
+	/* properties */
+	gint64 fft_length;
+	gint64 fft_overlap;
+	gint64 num_ffts;
+	gint64 update_samples;
+	gboolean update_after_gap;
+	gboolean write_to_screen;
+	char *filename;
+	gboolean make_fir_filters;
+	int high_pass;
+	int low_pass;
+	complex double *transfer_functions;
+	double *fir_filters;
+};
+
+
+/**
+ * GSTLALTransferFunctionClass:
+ * @parent_class:  the parent class
+ */
+
+
+struct _GSTLALTransferFunctionClass {
+	GstBaseSinkClass parent_class;
+};
+
+
+GType gstlal_transferfunction_get_type(void);
+
+
+G_END_DECLS
+
+
+#endif	/* __GSTLAL_TRANSFERFUNCTION_H__ */
diff --git a/gstlal-calibration/gst/lal/gstlalcalibration.c b/gstlal-calibration/gst/lal/gstlalcalibration.c
index ef299ab4ff..2fb51e570e 100644
--- a/gstlal-calibration/gst/lal/gstlalcalibration.c
+++ b/gstlal-calibration/gst/lal/gstlalcalibration.c
@@ -62,6 +62,7 @@
 #include <gstlal_demodulate.h>
 #include <gstlal_insertgap.h>
 #include <gstlal_fccupdate.h>
+#include <gstlal_transferfunction.h>
 
 
 /*
@@ -91,6 +92,7 @@ static gboolean plugin_init(GstPlugin *plugin)
 		{"lal_demodulate", GSTLAL_DEMODULATE_TYPE},
 		{"lal_insertgap", GSTLAL_INSERTGAP_TYPE},
 		{"lal_fcc_update", GSTLAL_FCC_UPDATE_TYPE},
+		{"lal_transferfunction", GSTLAL_TRANSFERFUNCTION_TYPE},
 		{NULL, 0},
 	};
 
-- 
GitLab