diff --git a/gstlal-calibration/gst/lal/Makefile.am b/gstlal-calibration/gst/lal/Makefile.am
index aeda966d361451322c4b45b113a40dff9d603db7..69e8bcedf59d9458b2a1c4bd67a09dc3a9a9185e 100644
--- a/gstlal-calibration/gst/lal/Makefile.am
+++ b/gstlal-calibration/gst/lal/Makefile.am
@@ -16,7 +16,8 @@ lib@GSTPLUGINPREFIX@gstlalcalibration_la_SOURCES = \
 	gstlal_fccupdate.c gstlal_fccupdate.h \
 	gstlal_transferfunction.c gstlal_transferfunction.h \
 	gstlal_trackfrequency.c gstlal_trackfrequency.h \
-	gstlal_adaptivefirfilt.c gstlal_adaptivefirfilt.h
+	gstlal_adaptivefirfilt.c gstlal_adaptivefirfilt.h \
+	gstlal_dqtukey.c gstlal_dqtukey.h
 lib@GSTPLUGINPREFIX@gstlalcalibration_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_CPPFLAGS)
 lib@GSTPLUGINPREFIX@gstlalcalibration_la_CFLAGS = $(AM_CFLAGS) $(LAL_CFLAGS) $(GSTLAL_CFLAGS) $(gstreamer_CFLAGS) $(gstreamer_audio_CFLAGS)
 lib@GSTPLUGINPREFIX@gstlalcalibration_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_dqtukey.c b/gstlal-calibration/gst/lal/gstlal_dqtukey.c
new file mode 100644
index 0000000000000000000000000000000000000000..6e559913c3a3331e0cda719f7bd78fdae036b52d
--- /dev/null
+++ b/gstlal-calibration/gst/lal/gstlal_dqtukey.c
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2018, 2016  Aaron Viets
+ *
+ * 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.
+ */
+
+
+/*
+ * =============================================================================
+ *
+ *			       Preamble
+ *
+ * =============================================================================
+ */
+
+
+/*
+ * stuff from C
+ */
+
+
+#include <string.h>
+#include <math.h>
+
+
+/*
+ * stuff from gobject/gstreamer
+ */
+
+
+#include <glib.h>
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <gst/base/gstbasetransform.h>
+#include <gstlal/gstlal.h>
+#include <gstlal/gstlal_audio_info.h>
+#include <gstlal_dqtukey.h>
+
+
+/*
+ * ============================================================================
+ *
+ *			       Utilities
+ *
+ * ============================================================================
+ */
+
+
+#define DEFINE_DQ_TO_TUKEY(INTYPE, WIDTH, OUTTYPE) \
+static void dq_g ## INTYPE ## WIDTH ## _to_tukey_ ## OUTTYPE(const g ## INTYPE ## WIDTH *src, gint64 src_size, OUTTYPE *dst, enum gstlal_dqtukey_state *state, guint32 required_on, guint32 required_on_xor_off, OUTTYPE *ramp, gint64 transition_samples, gint64 *ramp_up_index, gint64 *ramp_down_index, int num_cycle_in, int num_cycle_out, gint64 *num_leftover, gint64 *num_since_bad, gboolean invert_window, gboolean invert_control) { \
+ \
+	gint64 i, i_stop; \
+	i = 0; \
+	int j, j_stop; \
+ \
+	switch(*state) { \
+	case START: \
+		goto start; \
+	case ONES: \
+		goto ones; \
+	case ZEROS: \
+		goto zeros; \
+	case RAMP_UP: \
+		goto ramp_up; \
+	case RAMP_DOWN: \
+		goto ramp_down; \
+	case DOUBLE_RAMP: \
+		goto double_ramp; \
+	default: \
+		g_assert_not_reached(); \
+	} \
+ \
+start: \
+	i_stop = src_size * num_cycle_out / num_cycle_in + *num_leftover < transition_samples ? src_size : ((transition_samples - *num_leftover) * num_cycle_in + num_cycle_out - 1) / num_cycle_out; \
+	if(invert_control) { \
+		for(i = 0; i < i_stop; i++) { \
+			if((src[i] ^ required_on) & required_on_xor_off) { \
+				/* Since invert_control is TRUE, this means the conditions were met */ \
+				if(!(i % num_cycle_in)) \
+					*num_since_bad += num_cycle_out; \
+			} else \
+				*num_since_bad = 0; \
+		} \
+	} else { \
+		for(i = 0; i < i_stop; i++) { \
+			if((src[i] ^ required_on) & required_on_xor_off) { \
+				/* The conditions were not all met */ \
+				*num_since_bad = 0; \
+			} \
+			else if(!(i % num_cycle_in)) \
+				*num_since_bad += num_cycle_out; \
+		} \
+	} \
+	/* Track the number of output samples we could produce if there were no latency */ \
+	*num_leftover += i_stop * num_cycle_out / num_cycle_in; \
+	/* Check if any output samples should be produced */ \
+	if(*num_leftover > transition_samples) { \
+		j_stop = *num_leftover - transition_samples; \
+		for(j = 0; j < j_stop; j++, dst++) { \
+			if(*num_since_bad == *num_leftover) \
+				*dst = invert_window ? 0.0 : 1.0; \
+			else if(j_stop - j <= *num_since_bad - transition_samples) { \
+				*dst = invert_window ? 1.0 - ramp[j - j_stop + *num_since_bad - transition_samples] : ramp[j - j_stop + *num_since_bad - transition_samples]; \
+				(*ramp_up_index)++; \
+			} else \
+				*dst = invert_window ? 1.0 : 0.0; \
+		} \
+	} \
+	/* Check if we are at the end of the input buffer and still need more input to make output */ \
+	if(*num_leftover < transition_samples) { \
+		return; \
+		/* Check if we have enough leftover samples to start producing output, but no more */ \
+	} else if(i_stop == src_size) { \
+		/* Decide what we should do with the next data */ \
+		if(*num_since_bad == *num_leftover) \
+			*state = ONES; \
+		else if(*num_since_bad <= transition_samples) \
+			*state = ZEROS; \
+		else \
+			*state = RAMP_UP; \
+		*num_leftover = 0; \
+		return; \
+	} else { \
+		/* Decide what we should do with the next data on this buffer */ \
+		if(*num_since_bad == *num_leftover) { \
+			*num_leftover = 0; \
+			goto ones; \
+		} \
+		*num_leftover = 0; \
+		if(*num_since_bad <= transition_samples) \
+			goto zeros; \
+		else \
+			goto ramp_up; \
+	} \
+ \
+ones: \
+	/* Deal with any output samples that still need to be produced from the last input */ \
+	if(invert_window) { \
+		for(j = 0; j < *num_leftover; j++, dst++) \
+			*dst = 0.0; \
+	} else { \
+		for(j = 0; j < *num_leftover; j++, dst++) \
+			*dst = 1.0; \
+	} \
+	*num_leftover = 0; \
+	while(i < src_size) { \
+		if((gboolean) ((src[i] ^ required_on) & required_on_xor_off) == invert_control) { \
+			/* Conditions were met */ \
+			if(!((i + 1) % num_cycle_in)) { \
+				/* In case rate in > rate out */ \
+				if(invert_window) { \
+					for(j = 0; j < num_cycle_out; j++, dst++) \
+						*dst = 0.0; \
+				} else { \
+					for(j = 0; j < num_cycle_out; j++, dst++) \
+						*dst = 1.0; \
+				} \
+			} \
+		} else { \
+			/* Failed to meet conditions */ \
+			*num_since_bad = 0; \
+			*state = RAMP_DOWN; \
+			goto ramp_down; \
+		} \
+		i++; \
+	} \
+	return; \
+ \
+zeros: \
+	/* Deal with any output samples that still need to be produced from the last input */ \
+	if(invert_window) { \
+		for(j = 0; j < *num_leftover; j++, dst++) \
+			*dst = 1.0; \
+	} else { \
+		for(j = 0; j < *num_leftover; j++, dst++) \
+			*dst = 0.0; \
+	} \
+	*num_leftover = 0; \
+	while(i < src_size) { \
+		if((gboolean) ((src[i] ^ required_on) & required_on_xor_off) == invert_control) { \
+			/* Conditions were met */ \
+			if(!(i % num_cycle_in)) { \
+				/* In case rate in > rate out */ \
+				*num_since_bad += num_cycle_out; \
+			} \
+		} else { \
+			/* Failed to meet conditions */ \
+			*num_since_bad = 0; \
+		} \
+		i++; \
+		if(!(i % num_cycle_in)) { \
+			if(*num_since_bad <= transition_samples) { \
+				if(invert_window) { \
+					for(j = 0; j < num_cycle_out; j++, dst++) \
+						*dst = 1.0; \
+				} else { \
+					for(j = 0; j < num_cycle_out; j++, dst++) \
+						*dst = 0.0; \
+				} \
+			} else { \
+				if(invert_window) { \
+					for(j = 0; j < *num_since_bad - transition_samples; j++, dst++) \
+						*dst = 1.0; \
+				} else { \
+					for(j = 0; j < *num_since_bad - transition_samples; j++, dst++) \
+						*dst = 0.0; \
+				} \
+				*num_leftover = num_cycle_out - *num_since_bad + transition_samples; \
+				*state = RAMP_UP; \
+				goto ramp_up; \
+			} \
+		} \
+	} \
+	return; \
+ \
+ramp_up: \
+	/* Deal with any output samples that still need to be produced from the last input */ \
+	if(invert_window) { \
+		for(j = 0; j < *num_leftover; j++, dst++, (*ramp_up_index)++) \
+			*dst = 1.0 - ramp[*ramp_up_index]; \
+	} else { \
+		for(j = 0; j < *num_leftover; j++, dst++, (*ramp_up_index)++) \
+			*dst = ramp[*ramp_up_index]; \
+	} \
+	*num_leftover = 0; \
+	while(i < src_size) { \
+		if((gboolean) ((src[i] ^ required_on) & required_on_xor_off) == invert_control) { \
+			/* Conditions were met */ \
+			if(!((i + 1) % num_cycle_in)) { \
+				/* In case rate in > rate out */ \
+				for(j = 0; j < num_cycle_out; j++, dst++, (*ramp_up_index)++) { \
+					if(*ramp_up_index == transition_samples) { \
+						/* The transition is over */ \
+						*ramp_up_index = 0; \
+						*state = ONES; \
+						*num_leftover = (num_cycle_out - j) % num_cycle_out; \
+						i++; \
+						goto ones; \
+					} \
+					if(invert_window) \
+						*dst = 1.0 - ramp[*ramp_up_index]; \
+					else \
+						*dst = ramp[*ramp_up_index]; \
+				} \
+			} \
+		} else { \
+			/* Failed to meet conditions */ \
+			*num_since_bad = 0; \
+			*state = DOUBLE_RAMP; \
+			goto double_ramp; \
+		} \
+		i++; \
+	} \
+	return; \
+ \
+ramp_down: \
+	while(i < src_size) { \
+		if((gboolean) ((src[i] ^ required_on) & required_on_xor_off) == invert_control) { \
+			/* Conditions were met */ \
+			if(!(i % num_cycle_in)) { \
+				/* In case rate in > rate out */ \
+				*num_since_bad += num_cycle_out; \
+			} \
+		} else { \
+			/* Failed to meet conditions */ \
+			*num_since_bad = 0; \
+		} \
+		i++; \
+		if(!(i % num_cycle_in)) { \
+			for(j = 0; j < num_cycle_out; j++, dst++, (*ramp_down_index)++) { \
+				if(*ramp_down_index == transition_samples) { \
+					/* The transition is over */ \
+					*ramp_down_index = 0; \
+					*state = ZEROS; \
+					*num_leftover = (num_cycle_out - j) % num_cycle_out; \
+					goto zeros; \
+				} \
+				if(invert_window) \
+					*dst = ramp[*ramp_down_index]; \
+				else \
+					*dst = 1.0 - ramp[*ramp_down_index]; \
+			} \
+		} \
+	} \
+	return; \
+ \
+double_ramp: \
+	while(i < src_size) { \
+		if((gboolean) ((src[i] ^ required_on) & required_on_xor_off) == invert_control) { \
+			/* Conditions were met */ \
+			if(!(i % num_cycle_in)) { \
+				/* In case rate in > rate out */ \
+				*num_since_bad += num_cycle_out; \
+			} \
+		} else { \
+			/* Failed to meet conditions */ \
+			*num_since_bad = 0; \
+		} \
+		i++; \
+		if(!(i % num_cycle_in)) { \
+			for(j = 0; j < num_cycle_out; j++, dst++, (*ramp_down_index)++) { \
+				if(*ramp_down_index == transition_samples) { \
+					/* The transition is over */ \
+					*ramp_up_index = 0; \
+					*ramp_down_index = 0; \
+					*state = ZEROS; \
+					*num_leftover = (num_cycle_out - j) % num_cycle_out; \
+					goto zeros; \
+				} \
+				if(invert_window) \
+					*dst = ramp[*ramp_down_index] * (1.0 - (*ramp_up_index < transition_samples ? ramp[*ramp_up_index] : 1.0)); \
+				else \
+					*dst = (1.0 - ramp[*ramp_down_index]) * (*ramp_up_index < transition_samples ? ramp[*ramp_up_index] : 1.0); \
+			} \
+		} \
+	} \
+	return; \
+}
+
+
+DEFINE_DQ_TO_TUKEY(int, 8, float)
+DEFINE_DQ_TO_TUKEY(int, 8, double)
+DEFINE_DQ_TO_TUKEY(uint, 8, float)
+DEFINE_DQ_TO_TUKEY(uint, 8, double)
+DEFINE_DQ_TO_TUKEY(int, 16, float)
+DEFINE_DQ_TO_TUKEY(int, 16, double)
+DEFINE_DQ_TO_TUKEY(uint, 16, float)
+DEFINE_DQ_TO_TUKEY(uint, 16, double)
+DEFINE_DQ_TO_TUKEY(int, 32, float)
+DEFINE_DQ_TO_TUKEY(int, 32, double)
+DEFINE_DQ_TO_TUKEY(uint, 32, float)
+DEFINE_DQ_TO_TUKEY(uint, 32, double)
+
+
+/*
+ * set the metadata on an output buffer
+ */
+
+
+static void set_metadata(GSTLALDQTukey *element, GstBuffer *buf, guint64 outsamples, gboolean gap)
+{
+	GST_BUFFER_OFFSET(buf) = element->next_out_offset;
+	element->next_out_offset += outsamples;
+	GST_BUFFER_OFFSET_END(buf) = element->next_out_offset;
+	GST_BUFFER_PTS(buf) = element->t0 + gst_util_uint64_scale_int_round(GST_BUFFER_OFFSET(buf) - element->offset0, GST_SECOND, element->rate_out);
+	GST_BUFFER_DURATION(buf) = element->t0 + gst_util_uint64_scale_int_round(GST_BUFFER_OFFSET_END(buf) - element->offset0, GST_SECOND, element->rate_out) - GST_BUFFER_PTS(buf);
+	if(G_UNLIKELY(element->need_discont)) {
+		GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_DISCONT);
+		element->need_discont = FALSE;
+	}
+	if(gap)
+		GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_GAP);
+	else
+		GST_BUFFER_FLAG_UNSET(buf, GST_BUFFER_FLAG_GAP);
+}
+
+
+/*
+ * ============================================================================
+ *
+ *			   GStreamer Boiler Plate
+ *
+ * ============================================================================
+ */
+
+
+static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
+	GST_BASE_TRANSFORM_SINK_NAME,
+	GST_PAD_SINK,
+	GST_PAD_ALWAYS,
+	GST_STATIC_CAPS(
+		"audio/x-raw, " \
+		"rate = " GST_AUDIO_RATE_RANGE ", " \
+		"channels = (int) 1, " \
+		"format = (string) {" GST_AUDIO_NE(S8) ", " GST_AUDIO_NE(S16) ", " GST_AUDIO_NE(S32) ", " GST_AUDIO_NE(U8) ", " GST_AUDIO_NE(U16) ", " GST_AUDIO_NE(U32) "}, " \
+		"layout = (string) interleaved, " \
+		"channel-mask = (bitmask) 0"
+	)
+);
+
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(
+	GST_BASE_TRANSFORM_SRC_NAME,
+	GST_PAD_SRC,
+	GST_PAD_ALWAYS,
+	GST_STATIC_CAPS(
+		"audio/x-raw, " \
+		"rate = " GST_AUDIO_RATE_RANGE ", " \
+		"channels = (int) 1, " \
+		"format = (string) {" GST_AUDIO_NE(F32) ", " GST_AUDIO_NE(F64) "}, " \
+		"layout = (string) interleaved, " \
+		"channel-mask = (bitmask) 0"
+	)
+);
+
+
+G_DEFINE_TYPE(
+	GSTLALDQTukey,
+	gstlal_dqtukey,
+	GST_TYPE_BASE_TRANSFORM
+);
+
+
+/*
+ * ============================================================================
+ *
+ *		     GstBaseTransform Method Overrides
+ *
+ * ============================================================================
+ */
+
+
+/*
+ * get_unit_size()
+ */
+
+
+static gboolean get_unit_size(GstBaseTransform *trans, GstCaps *caps, gsize *size)
+{
+	GstAudioInfo info;
+	gboolean success = TRUE;
+
+	success &= gstlal_audio_info_from_caps(&info, caps);
+
+	if(success) {
+		*size = GST_AUDIO_INFO_BPF(&info);
+	} else
+		GST_WARNING_OBJECT(trans, "unable to parse caps %" GST_PTR_FORMAT, caps);
+
+	return success;
+}
+
+
+/*
+ * set_caps()
+ */
+
+
+static gboolean set_caps(GstBaseTransform *trans, GstCaps *incaps, GstCaps *outcaps)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(trans);
+	gint rate_in, rate_out;
+	gsize unit_size_in, unit_size_out;
+	const gchar *format;
+	static char *formats[] = {GST_AUDIO_NE(S8), GST_AUDIO_NE(S16), GST_AUDIO_NE(S32), GST_AUDIO_NE(U8), GST_AUDIO_NE(U16), GST_AUDIO_NE(U32)};
+	gboolean sign[] = {TRUE, TRUE, TRUE, FALSE, FALSE, FALSE};
+
+	/*
+	 * parse the caps
+	 */
+
+	if(!get_unit_size(trans, incaps, &unit_size_in)) {
+		GST_DEBUG_OBJECT(element, "failed to get unit size from input caps %" GST_PTR_FORMAT, incaps);
+		return FALSE;
+	}
+	if(!get_unit_size(trans, outcaps, &unit_size_out)) {
+		GST_DEBUG_OBJECT(element, "failed to get unit size from output caps %" GST_PTR_FORMAT, outcaps);
+		return FALSE;
+	}
+	if(!gst_structure_get_int(gst_caps_get_structure(incaps, 0), "rate", &rate_in)) {
+		GST_DEBUG_OBJECT(element, "unable to parse rate from input  caps %" GST_PTR_FORMAT, incaps);
+		return FALSE;
+	}
+	if(!gst_structure_get_int(gst_caps_get_structure(outcaps, 0), "rate", &rate_out)) {
+		GST_DEBUG_OBJECT(element, "unable to parse rate from output caps %" GST_PTR_FORMAT, outcaps);
+		return FALSE;
+	}
+
+	/*
+	 * require the input rate to be an integer multiple or divisor 
+	 * of the output rate
+	 */
+
+	if(rate_in % rate_out && rate_out % rate_in) {
+		GST_ERROR_OBJECT(element, "input rate is not an integer multiple or divisor of output rate.  input caps = %" GST_PTR_FORMAT " output caps = %" GST_PTR_FORMAT, incaps, outcaps);
+		return FALSE;
+	}
+
+	/*
+	 * record stream parameters
+	 */
+
+	element->rate_in = rate_in;
+	element->rate_out = rate_out;
+	element->num_cycle_in = rate_in > rate_out ? rate_in / rate_out : 1;
+	element->num_cycle_out = rate_out > rate_in ? rate_out / rate_in : 1;
+	element->unit_size_in = unit_size_in;
+	element->unit_size_out = unit_size_out;
+
+	/* Check the incaps to see if it contains S32, etc. */
+	GstStructure *str = gst_caps_get_structure(incaps, 0);
+	g_assert(str);
+
+	if(gst_structure_has_field(str, "format")) {
+		format = gst_structure_get_string(str, "format");
+	} else {
+		GST_ERROR_OBJECT(element, "No format! Cannot set element caps.\n");
+		return FALSE;
+	}
+	int test = 0;
+	for(unsigned int i = 0; i < sizeof(formats) / sizeof(*formats); i++) {
+		if(!strcmp(format, formats[i])) {
+			element->sign = sign[i];
+			test++;
+		}			
+	}
+	if(test != 1) {
+		GST_ERROR_OBJECT(element, "element->sign not properly set");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+/*
+ * transform_size()
+ */
+
+
+static gboolean transform_size(GstBaseTransform *trans, GstPadDirection direction, GstCaps *caps, gsize size, GstCaps *othercaps, gsize *othersize)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(trans);
+	gint64 temp_othersize;
+
+	switch(direction) {
+	case GST_PAD_SRC:
+
+		/*
+		 * convert byte count to samples
+		 */
+
+		if(G_UNLIKELY(size % element->unit_size_out)) {
+			GST_DEBUG_OBJECT(element, "buffer size %" G_GSIZE_FORMAT " is not a multiple of unit_size %" G_GSIZE_FORMAT, size, (gsize) element->unit_size_out);
+			return FALSE;
+		}
+		size /= element->unit_size_out;
+
+		/*
+		 * compute othersize = # of samples required on sink
+		 * pad to produce requested sample count on source pad
+		 *
+		 * size = # of samples requested on source pad
+		 * transition_samples = # of latency samples of this element
+		 * num_leftover = # of samples saved from previous buffer
+		 */
+
+		temp_othersize = ((gint64) size + element->transition_samples - element->num_leftover) * element->rate_in / element->rate_out;
+
+		/*
+		 * convert sample count to byte count
+		 */
+
+		*othersize = (gsize) (temp_othersize * element->unit_size_in);
+
+		break;
+
+	case GST_PAD_SINK:
+		/*
+		 * compute othersize = # of samples to be produced on
+		 * source pad from sample count available on sink pad
+		 *
+		 * size = # of samples available on sink pad
+		 * transition_samples = # of latency samples of this element
+		 * num_leftover = # of samples saved from previous buffer
+		 */
+
+		temp_othersize = (gint64) size * element->rate_out / element->rate_in - element->transition_samples + element->num_leftover;
+
+		/*
+		 * convert sample count to byte count, and don't allow for negative sizes
+		 */
+
+		*othersize = temp_othersize > 0 ? (gsize) (temp_othersize * element->unit_size_out) : 0;
+
+		break;
+
+	case GST_PAD_UNKNOWN:
+		GST_ELEMENT_ERROR(trans, CORE, NEGOTIATION, (NULL), ("invalid direction GST_PAD_UNKNOWN"));
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+/*
+ * start()
+ */
+
+
+static gboolean start(GstBaseTransform *trans)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(trans);
+
+	element->t0 = GST_CLOCK_TIME_NONE;
+	element->offset0 = GST_BUFFER_OFFSET_NONE;
+	element->next_in_offset = GST_BUFFER_OFFSET_NONE;
+	element->next_out_offset = GST_BUFFER_OFFSET_NONE;
+	element->need_discont = TRUE;
+
+	element->state = START;
+	element->ramp_up_index = 0;
+	element->ramp_down_index = 0;
+	element->num_leftover = 0;
+	element->num_since_bad = 0;
+
+	if(element->required_on & element->required_off)
+		GST_WARNING_OBJECT(element, "One or more bits are requested to be required both on and off. These bits will be ignored.");
+	element->required_on_xor_off = element->required_on ^ element->required_off;
+
+	return TRUE;
+}
+
+
+/*
+ * transform()
+ */
+
+
+static GstFlowReturn transform(GstBaseTransform *trans, GstBuffer *inbuf, GstBuffer *outbuf)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(trans);
+	GstMapInfo inmap, outmap;
+	GstFlowReturn result = GST_FLOW_OK;
+
+	/*
+	 * check for discontinuity
+	 */
+
+	if(G_UNLIKELY(GST_BUFFER_IS_DISCONT(inbuf) || GST_BUFFER_OFFSET(inbuf) != element->next_in_offset || !GST_CLOCK_TIME_IS_VALID(element->t0))) {
+		GST_DEBUG_OBJECT(element, "pushing discontinuous buffer at input timestamp %lu", (long unsigned) GST_TIME_AS_SECONDS(GST_BUFFER_PTS(inbuf)));
+		element->t0 = GST_BUFFER_PTS(inbuf);
+		element->offset0 = element->next_out_offset = gst_util_uint64_scale_ceil(GST_BUFFER_OFFSET(inbuf), element->rate_out, element->rate_in);
+		element->need_discont = TRUE;
+		element->state = START;
+		element->ramp_up_index = 0;
+		element->ramp_down_index = 0;
+		element->num_leftover = 0;
+		element->num_since_bad = 0;
+
+		/* If we haven't made a half-Hann window for transitions between zeros and ones yet, do it now */
+		if(!element->ramp) {
+			if(element->unit_size_out == 4) {
+				element->ramp = g_malloc(element->transition_samples * sizeof(float));
+				float *ramp = (float *) element->ramp;
+				gint64 i;
+				for(i = 0; i < element->transition_samples; i++)
+					ramp[i] = (float) sin((i + 1) * M_PI / (2 * (element->transition_samples + 1))) * sin((i + 1) * M_PI / (2 * (element->transition_samples + 1)));
+			} else {
+				element->ramp = g_malloc(element->transition_samples * sizeof(double));
+				double *ramp = (double *) element->ramp;
+				gint64 i;
+				for(i = 0; i < element->transition_samples; i++)
+					ramp[i] = sin((i + 1) * M_PI / (2 * (element->transition_samples + 1))) * sin((i + 1) * M_PI / (2 * (element->transition_samples + 1)));
+			}
+		}
+	}
+
+	element->next_in_offset = GST_BUFFER_OFFSET_END(inbuf);
+
+	/*
+	 * process buffer
+	 */
+
+	gst_buffer_map(inbuf, &inmap, GST_MAP_READ);
+	gst_buffer_map(outbuf, &outmap, GST_MAP_WRITE);
+
+	g_assert_cmpuint(inmap.size % element->unit_size_in, ==, 0);
+	g_assert_cmpuint(outmap.size % element->unit_size_out, ==, 0);
+
+	gint64 src_size = (gint64) (inmap.size / element->unit_size_in);
+
+	switch(element->unit_size_in) {
+	case 1:
+		if(element->sign) {
+			if(element->unit_size_out == 4)
+				dq_gint8_to_tukey_float((const gint8 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_gint8_to_tukey_double((const gint8 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		} else {
+			if(element->unit_size_out == 4)
+				dq_guint8_to_tukey_float((const guint8 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_guint8_to_tukey_double((const guint8 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		}
+		break;
+	case 2:
+		if(element->sign) {
+			if(element->unit_size_out == 4)
+				dq_gint16_to_tukey_float((const gint16 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_gint16_to_tukey_double((const gint16 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		} else {
+			if(element->unit_size_out == 4)
+				dq_guint16_to_tukey_float((const guint16 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_guint16_to_tukey_double((const guint16 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		}
+		break;
+	case 4:
+		if(element->sign) {
+			if(element->unit_size_out == 4)
+				dq_gint32_to_tukey_float((const gint32 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_gint32_to_tukey_double((const gint32 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		} else {
+			if(element->unit_size_out == 4)
+				dq_guint32_to_tukey_float((const guint32 *) inmap.data, src_size, (float *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+			else
+				dq_guint32_to_tukey_double((const guint32 *) inmap.data, src_size, (double *) outmap.data, &element->state, element->required_on, element->required_on_xor_off, element->ramp, element->transition_samples, &element->ramp_up_index, &element->ramp_down_index, element->num_cycle_in, element->num_cycle_out, &element->num_leftover, &element->num_since_bad, element->invert_window, element->invert_control);
+		}
+		break;
+	default:
+		g_assert_not_reached();
+	}
+
+	set_metadata(element, outbuf, outmap.size / element->unit_size_out, GST_BUFFER_FLAG_IS_SET(inbuf, GST_BUFFER_FLAG_GAP));
+	gst_buffer_unmap(outbuf, &outmap);
+	gst_buffer_unmap(inbuf, &inmap);
+
+	/*
+	 * done
+	 */
+
+	return result;
+}
+
+
+/*
+ * ============================================================================
+ *
+ *			  GObject Method Overrides
+ *
+ * ============================================================================
+ */
+
+
+/*
+ * properties
+ */
+
+
+enum property {
+	ARG_REQUIRED_ON = 1,
+	ARG_REQUIRED_OFF,
+	ARG_TRANSITION_SAMPLES,
+	ARG_INVERT_WINDOW,
+	ARG_INVERT_CONTROL
+};
+
+
+static void set_property(GObject *object, enum property prop_id, const GValue *value, GParamSpec *pspec)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(object);
+
+	GST_OBJECT_LOCK(element);
+
+	switch (prop_id) {
+	case ARG_REQUIRED_ON:
+		element->required_on = (guint32) g_value_get_uint64(value);
+		break;
+
+	case ARG_REQUIRED_OFF:
+		element->required_off = (guint32) g_value_get_uint64(value);
+		break;
+
+	case ARG_TRANSITION_SAMPLES:
+		element->transition_samples = g_value_get_int64(value);
+		break;
+
+	case ARG_INVERT_WINDOW:
+		element->invert_window = g_value_get_boolean(value);
+		break;
+
+	case ARG_INVERT_CONTROL:
+		element->invert_control = g_value_get_boolean(value);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+
+	GST_OBJECT_UNLOCK(element);
+}
+
+
+static void get_property(GObject *object, enum property prop_id, GValue *value, GParamSpec *pspec)
+{
+	GSTLALDQTukey *element = GSTLAL_DQTUKEY(object);
+
+	GST_OBJECT_LOCK(element);
+
+	switch (prop_id) {
+	case ARG_REQUIRED_ON:
+		g_value_set_uint64(value, (guint64) element->required_on);
+		break;
+
+	case ARG_REQUIRED_OFF:
+		g_value_set_uint64(value, (guint64) element->required_off);
+		break;
+
+	case ARG_TRANSITION_SAMPLES:
+		g_value_set_int64(value, element->transition_samples);
+		break;
+
+	case ARG_INVERT_WINDOW:
+		g_value_set_boolean(value, element->invert_window);
+		break;
+
+	case ARG_INVERT_CONTROL:
+		g_value_set_boolean(value, element->invert_control);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+
+	GST_OBJECT_UNLOCK(element);
+}
+
+
+/*
+ * class_init()
+ */
+
+
+static void gstlal_dqtukey_class_init(GSTLALDQTukeyClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+	GstBaseTransformClass *transform_class = GST_BASE_TRANSFORM_CLASS(klass);
+
+	gobject_class->set_property = GST_DEBUG_FUNCPTR(set_property);
+	gobject_class->get_property = GST_DEBUG_FUNCPTR(get_property);
+
+	transform_class->transform_size = GST_DEBUG_FUNCPTR(transform_size);
+	transform_class->get_unit_size = GST_DEBUG_FUNCPTR(get_unit_size);
+	transform_class->set_caps = GST_DEBUG_FUNCPTR(set_caps);
+	transform_class->start = GST_DEBUG_FUNCPTR(start);
+	transform_class->transform = GST_DEBUG_FUNCPTR(transform);
+
+	gst_element_class_set_details_simple(element_class,
+		"DQTukey",
+		"Filter/Audio",
+		"Reads in a DQ bit vector and writes out a Tukey window. If the required bits in\n\t\t\t   "
+		"the DQ vector are on, the output will be ones. If the required bits are off, the\n\t\t\t   "
+		"output will be zeros. The transition between zeros and ones is made smooth with\n\t\t\t   "
+		"half of a Hann window, which occupies the time during which the required bits\n\t\t\t   "
+		"are on, just before or after a transition.",
+		"Aaron Viets <aaron.viets@ligo.org>"
+	);
+
+	gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&src_factory));
+	gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sink_factory));
+
+	g_object_class_install_property(
+		gobject_class,
+		ARG_REQUIRED_ON,
+		g_param_spec_uint64(
+			"required-on",
+			"On bits",
+			"Bit mask setting the bits that must be on in the incoming stream for the output\n\t\t\t"
+			"stream to be 1.0. Note: if the mask is wider than the input stream, the\n\t\t\t"
+			"high-order bits should be 0 or the on condition will never be met.",
+			0, G_MAXUINT32, 0x1,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_REQUIRED_OFF,
+		g_param_spec_uint64(
+			"required-off",
+			"Off bits",
+			"Bit mask setting the bits that must be off in the incoming stream for the\n\t\t\t"
+			"output stream to be 1.0.",
+			0, G_MAXUINT32, 0x0,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_TRANSITION_SAMPLES,
+		g_param_spec_int64(
+			"transition-samples",
+			"Transition Samples",
+			"Number of output samples used for smooth transitions between 0.0 and 1.0. Half\n\t\t\t"
+			"of a Hann window is used to make transitions.",
+			0, G_MAXINT64, 4096,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_INVERT_WINDOW,
+		g_param_spec_boolean(
+			"invert-window",
+			"Invert Window",
+			"If set to True, output is replaced by 1 - output. This inverts the Tukey window.",
+			FALSE,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+	g_object_class_install_property(
+		gobject_class,
+		ARG_INVERT_CONTROL,
+		g_param_spec_boolean(
+			"invert-control",
+			"Invert Control",
+			"If set to True, the conditions required by the bitmasks must not all be met in\n\t\t\t"
+			"order for the output stream to be 1.0",
+			FALSE,
+			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
+		)
+	);
+}
+
+
+/*
+ * init()
+ */
+
+
+static void gstlal_dqtukey_init(GSTLALDQTukey *element)
+{
+	element->rate_in = 0;
+	element->rate_out = 0;
+	element->unit_size_in = 0;
+	element->unit_size_out = 0;
+	element->ramp = NULL;
+	gst_base_transform_set_gap_aware(GST_BASE_TRANSFORM(element), TRUE);
+}
+
diff --git a/gstlal-calibration/gst/lal/gstlal_dqtukey.h b/gstlal-calibration/gst/lal/gstlal_dqtukey.h
new file mode 100644
index 0000000000000000000000000000000000000000..7ef5ea5ea2eb984f7c301f31a405d0711fa2614c
--- /dev/null
+++ b/gstlal-calibration/gst/lal/gstlal_dqtukey.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2018 Aaron Viets <aaron.viets@ligo.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * 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_DQTUKEY_H__
+#define __GSTLAL_DQTUKEY_H__
+
+
+#include <glib.h>
+#include <gst/gst.h>
+#include <gst/base/gstbasetransform.h>
+
+
+G_BEGIN_DECLS
+#define GSTLAL_DQTUKEY_TYPE \
+	(gstlal_dqtukey_get_type())
+#define GSTLAL_DQTUKEY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj), GSTLAL_DQTUKEY_TYPE, GSTLALDQTukey))
+#define GSTLAL_DQTUKEY_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass), GSTLAL_DQTUKEY_TYPE, GSTLALDQTukeyClass))
+#define GST_IS_GSTLAL_DQTUKEY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj), GSTLAL_DQTUKEY_TYPE))
+#define GST_IS_GSTLAL_DQTUKEY_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass), GSTLAL_DQTUKEY_TYPE))
+
+
+typedef struct _GSTLALDQTukey GSTLALDQTukey;
+typedef struct _GSTLALDQTukeyClass GSTLALDQTukeyClass;
+
+
+/*
+ * GSTLALDQTukey:
+ */
+
+
+struct _GSTLALDQTukey {
+	GstBaseTransform element;
+
+	/* stream info */
+
+	gint rate_in;
+	gint rate_out;
+	gint unit_size_in;
+	gint unit_size_out;
+	gboolean sign;
+	gint num_cycle_in;
+	gint num_cycle_out;
+
+	/* timestamp book-keeping */
+
+	GstClockTime t0;
+	guint64 offset0;
+	guint64 next_in_offset;
+	guint64 next_out_offset;
+	gboolean need_discont;
+
+	/* internal state */
+
+	enum gstlal_dqtukey_state {
+		START = 0,
+		ONES,
+		ZEROS,
+		RAMP_UP,
+		RAMP_DOWN,
+		DOUBLE_RAMP
+	} state;
+	gint64 ramp_up_index;
+	gint64 ramp_down_index;
+	gint64 num_leftover;
+	gint64 num_since_bad;
+	void *ramp;
+
+	/* properties  */
+
+	guint32 required_on;
+	guint32 required_off;
+	guint32 required_on_xor_off;
+	gint64 transition_samples;
+	gboolean invert_window;
+	gboolean invert_control;
+};
+
+
+/*
+ * GSTLALDQTukeyClass:
+ * @parent_class:  the parent class
+ */
+
+
+struct _GSTLALDQTukeyClass {
+	GstBaseTransformClass parent_class;
+};
+
+
+GType gstlal_dqtukey_get_type(void);
+
+
+G_END_DECLS
+
+
+#endif  /* __GSTLAL_DQTUKEY_H__ */
diff --git a/gstlal-calibration/gst/lal/gstlalcalibration.c b/gstlal-calibration/gst/lal/gstlalcalibration.c
index 417fc79bff46c6d66ee1215f795da387c7c3144d..d7101bfac2958a56a290a91901af4714ea90a6db 100644
--- a/gstlal-calibration/gst/lal/gstlalcalibration.c
+++ b/gstlal-calibration/gst/lal/gstlalcalibration.c
@@ -65,6 +65,7 @@
 #include <gstlal_transferfunction.h>
 #include <gstlal_trackfrequency.h>
 #include <gstlal_adaptivefirfilt.h>
+#include <gstlal_dqtukey.h>
 
 
 /*
@@ -97,6 +98,7 @@ static gboolean plugin_init(GstPlugin *plugin)
 		{"lal_transferfunction", GSTLAL_TRANSFERFUNCTION_TYPE},
 		{"lal_trackfrequency", GSTLAL_TRACKFREQUENCY_TYPE},
 		{"lal_adaptivefirfilt", GSTLAL_ADAPTIVEFIRFILT_TYPE},
+		{"lal_dqtukey", GSTLAL_DQTUKEY_TYPE},
 		{NULL, 0},
 	};