Skip to content
Snippets Groups Projects
Forked from lscsoft / GstLAL
4099 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
gstlal_resample.c 53.70 KiB
/*
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


/*
 * =============================================================================
 *
 *				 Preamble
 *
 * =============================================================================
 */


/*
 * stuff from C
 */


#include <string.h>
#include <math.h>
#include <complex.h>


/*
 * stuff from gobject/gstreamer
 */


#include <glib.h>
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/base/gstbasetransform.h>


/*
 * our own stuff
 */


#include <gstlal/gstlal.h>
#include <gstlal_resample.h>


/* Ideally, these should be odd */
#define SHORT_SINC_LENGTH 33
#define LONG_SINC_LENGTH 193


/*
 * ============================================================================
 *
 *				 Utilities
 *
 * ============================================================================
 */


/*
 * First, the constant upsample functions, which just copy inputs to n outputs 
 */
#define DEFINE_CONST_UPSAMPLE(size) \
static void const_upsample_ ## size(const gint ## size *src, gint ## size *dst, guint64 src_size, gint32 cadence) \
{ \
	const gint ## size *src_end; \
	gint32 i; \
 \
	for(src_end = src + src_size; src < src_end; src++) { \
		for(i = 0; i < cadence; i++, dst++) \
			*dst = *src; \
	} \
}

DEFINE_CONST_UPSAMPLE(8)
DEFINE_CONST_UPSAMPLE(16)
DEFINE_CONST_UPSAMPLE(32)
DEFINE_CONST_UPSAMPLE(64)


static void const_upsample_other(const gint8 *src, gint8 *dst, guint64 src_size, gint unit_size, gint32 cadence)
{
	const gint8 *src_end;
	gint32 i;

	for(src_end = src + src_size * unit_size; src < src_end; src += unit_size) {
		for(i = 0; i < cadence; i++, dst += unit_size)
			memcpy(dst, src, unit_size);
	}
}


/*
 * Linear upsampling functions, in which upsampled output samples 
 * lie on lines connecting input samples 
 */
#define DEFINE_LINEAR_UPSAMPLE(DTYPE, COMPLEX) \
static void linear_upsample_ ## DTYPE ## COMPLEX(const DTYPE COMPLEX *src, DTYPE COMPLEX *dst, guint64 src_size, gint32 cadence, DTYPE COMPLEX *end_samples, gint32 *num_end_samples) \
{ \
	/* First, fill in previous data using the last sample of the previous input buffer */ \
	DTYPE COMPLEX slope; /* first derivative between points we are connecting */ \
	gint32 i; \
	if(*num_end_samples > 0) { \
		slope = *src - *end_samples; \
		*dst = *end_samples; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = *end_samples + slope * i / cadence; \
	} \
 \
	/* Now, process the current input buffer */ \
	const DTYPE COMPLEX *src_end; \
	for(src_end = src + src_size - 1; src < src_end; src++) { \
		slope = *(src + 1) - *src; \
		*dst = *src; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = *src + slope * i / cadence; \
	} \
 \
	/* Save the last input sample for the next buffer, so that we can find the slope */ \
	*end_samples = *src; \
}

DEFINE_LINEAR_UPSAMPLE(float, )
DEFINE_LINEAR_UPSAMPLE(double, )
DEFINE_LINEAR_UPSAMPLE(float, complex)
DEFINE_LINEAR_UPSAMPLE(double, complex)


/*
 * Qaudratic spline interpolating functions. The curve connecting 
 * two points depends on those two points and the previous point. 
 */
#define DEFINE_QUADRATIC_UPSAMPLE(DTYPE, COMPLEX) \
static void quadratic_upsample_ ## DTYPE ## COMPLEX(const DTYPE COMPLEX *src, DTYPE COMPLEX *dst, guint64 src_size, gint32 cadence, DTYPE COMPLEX *end_samples, gint32 *num_end_samples) \
{ \
	/* First, fill in previous data using the last samples of the previous input buffer */ \
	DTYPE COMPLEX dxdt0 = 0.0, half_d2xdt2 = 0.0; /* first derivative and half of second derivative at initial point */ \
	gint32 i; \
	if(*num_end_samples > 1) { \
		g_assert(end_samples); \
		dxdt0 = (*src - end_samples[1]) / 2.0; \
		half_d2xdt2 = *src - end_samples[0] - dxdt0; \
		*dst = end_samples[0]; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = end_samples[0] + dxdt0 * i / cadence + (i * i * half_d2xdt2) / (cadence * cadence); \
	} \
 \
	/* This needs to happen even if the first section was skipped */ \
	if(*num_end_samples >= 1) { \
		if(*num_end_samples < 2) { \
			/* In this case, we also must fill in data from end_samples to the start of src, assuming an initial slope of zero */ \
			half_d2xdt2 = *src - end_samples[0] - dxdt0; \
			*dst = end_samples[0]; \
			dst++; \
			for(i = 1; i < cadence; i++, dst++) \
				*dst = end_samples[0] + (i * i * half_d2xdt2) / (cadence * cadence); \
		} \
		if(src_size > 1) { \
			dxdt0 = (*(src + 1) - end_samples[0]) / 2.0; \
			half_d2xdt2 = *(src + 1) - *src - dxdt0; \
			*dst = *src; \
			dst++; \
			for(i = 1; i < cadence; i++, dst++) \
				*dst = *src + dxdt0 * i / cadence + (i * i * half_d2xdt2) / (cadence * cadence); \
			src++; \
		} \
 \
	} else { \
		/* This function should not be called if there is not enough data to make an output buffer */ \
		g_assert(src_size > 1); \
		/* If this is the first input data or follows a discontinuity, assume the initial slope is zero */ \
		half_d2xdt2 = *(src + 1) - *src; \
		*dst = *src; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = *src + (i * i * half_d2xdt2) / (cadence * cadence); \
		src++; \
	} \
 \
	/* Now, process the current input buffer */ \
	const DTYPE COMPLEX *src_end; \
	for(src_end = src + src_size - 2; src < src_end; src++) { \
		dxdt0 = (*(src + 1) - *(src - 1)) / 2.0; \
		half_d2xdt2 = *(src + 1) - *src - dxdt0; \
		*dst = *src; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = *src + dxdt0 * i / cadence + (i * i * half_d2xdt2) / (cadence * cadence); \
	} \
 \
	/* Save the last two samples for the next buffer */ \
	if(src_size == 1 && *num_end_samples >= 1) { \
		*num_end_samples = 2; \
		end_samples[1] = end_samples[0]; \
	} else if(src_size > 1) { \
		*num_end_samples = 2; \
		end_samples[1] = *(src - 1); \
	} else \
		*num_end_samples = 1; \
	end_samples[0] = *src; \
}

DEFINE_QUADRATIC_UPSAMPLE(float, )
DEFINE_QUADRATIC_UPSAMPLE(double, )
DEFINE_QUADRATIC_UPSAMPLE(float, complex)
DEFINE_QUADRATIC_UPSAMPLE(double, complex)


/*
 * Cubic spline interpolating functions. The curve connecting two points 
 * depends on those two points and the previous and following point. 
 */
#define DEFINE_CUBIC_UPSAMPLE(DTYPE, COMPLEX) \
static void cubic_upsample_ ## DTYPE ## COMPLEX(const DTYPE COMPLEX *src, DTYPE COMPLEX *dst, guint64 src_size, gint32 cadence, DTYPE COMPLEX *dxdt0, DTYPE COMPLEX *end_samples, gint32 *num_end_samples) \
{ \
	/* First, fill in previous data using the last samples of the previous input buffer */ \
	DTYPE COMPLEX dxdt1, half_d2xdt2_0, sixth_d3xdt3; /* first derivative at end point, half of second derivative and one sixth of third derivative at initial point */ \
	gint32 i; \
	if(*num_end_samples > 1) { \
		g_assert(end_samples); \
		dxdt1 = (*src - end_samples[1]) / 2.0; \
		half_d2xdt2_0 =  3 * (end_samples[0] - end_samples[1]) - dxdt1 - 2 * *dxdt0; \
		sixth_d3xdt3 = 2 * (end_samples[1] - end_samples[0]) + dxdt1 + *dxdt0; \
		*dst = end_samples[1]; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = end_samples[1] + *dxdt0 * i / cadence + (i * i * half_d2xdt2_0) / (cadence * cadence) + (i * i * i * sixth_d3xdt3) / (cadence * cadence* cadence); \
		/* Save the slope at the end point as the slope at the next initial point */ \
		*dxdt0 = dxdt1; \
	} \
 \
	if(*num_end_samples > 0 && src_size > 1) { \
		dxdt1 = (*(src + 1) - end_samples[0]) / 2.0; \
		half_d2xdt2_0 =  3 * (*src - end_samples[0]) - dxdt1 - 2 * *dxdt0; \
		sixth_d3xdt3 = 2 * (end_samples[0] - *src) + dxdt1 + *dxdt0; \
		*dst = end_samples[0]; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = end_samples[0] + *dxdt0 * i / cadence + (i * i * half_d2xdt2_0) / (cadence * cadence) + (i * i * i * sixth_d3xdt3) / (cadence * cadence* cadence); \
		/* Save the slope at the end point as the slope at the next initial point */ \
		*dxdt0 = dxdt1; \
	} \
 \
	/* Now, process the current input buffer */ \
	const DTYPE COMPLEX *src_end; \
	for(src_end = src + src_size - 2; src < src_end; src++) { \
		dxdt1 = (*(src + 2) - *src) / 2.0; \
		half_d2xdt2_0 =  3 * (*(src + 1) - *src) - dxdt1 - 2 * *dxdt0; \
		sixth_d3xdt3 = 2 * (*src - *(src + 1)) + dxdt1 + *dxdt0; \
		*dst = *src; \
		dst++; \
		for(i = 1; i < cadence; i++, dst++) \
			*dst = *src + *dxdt0 * i / cadence + (i * i * half_d2xdt2_0) / (cadence * cadence) + (i * i * i * sixth_d3xdt3) / (cadence * cadence* cadence); \
		/* Save the slope at the end point as the slope at the next initial point */ \
		*dxdt0 = dxdt1; \
	} \
 \
	/* Save the last two samples for the next buffer */ \
	if(src_size == 1 && *num_end_samples > 0) { \
		end_samples[1] = end_samples[0]; \
		end_samples[0] = *src; \
		*num_end_samples = 2; \
	} else if(src_size == 1) { \
		end_samples[0] = *src; \
		*num_end_samples = 1; \
	} else { \
		end_samples[1] = *src; \
		end_samples[0] = *(src + 1); \
		*num_end_samples = 2; \
	} \
}

DEFINE_CUBIC_UPSAMPLE(float, )
DEFINE_CUBIC_UPSAMPLE(double, )
DEFINE_CUBIC_UPSAMPLE(float, complex)
DEFINE_CUBIC_UPSAMPLE(double, complex)


/*
 * Simple downsampling functions that just pick every nth value 
 */
#define DEFINE_DOWNSAMPLE(size) \
static void downsample_ ## size(const gint ## size *src, gint ## size *dst, guint64 dst_size, gint32 inv_cadence, gint16 leading_samples) \
{ \
	/* increnent the pointer to the input buffer data to point to the first outgoing sample */ \
	src += leading_samples; \
	const gint ## size *dst_end; \
	for(dst_end = dst + dst_size; dst < dst_end; dst++, src += inv_cadence) \
		*dst = *src; \
 \
}

DEFINE_DOWNSAMPLE(8)
DEFINE_DOWNSAMPLE(16)
DEFINE_DOWNSAMPLE(32)
DEFINE_DOWNSAMPLE(64)


static void downsample_other(const gint8 *src, gint8 *dst, guint64 dst_size, gint unit_size, guint16 inv_cadence, gint16 leading_samples)
{
	/* increnent the pointer to the input buffer data to point to the first outgoing sample */	
	src += unit_size * leading_samples; \
	const gint8 *dst_end;

	for(dst_end = dst + dst_size * unit_size; dst < dst_end; dst += unit_size, src += unit_size * inv_cadence)
		memcpy(dst, src, unit_size);
}


/*
 * Downsampling functions that average n samples, where the 
 * middle sample has the timestamp of the outgoing sample 
 */
#define DEFINE_AVG_DOWNSAMPLE(DTYPE, COMPLEX) \
static void avg_downsample_ ## DTYPE ## COMPLEX(const DTYPE COMPLEX *src, DTYPE COMPLEX *dst, guint64 src_size, guint64 dst_size, gint32 inv_cadence, gint16 leading_samples, DTYPE COMPLEX *end_samples, gint32 *num_end_samples) \
{ \
	/*
	 * If inverse cadence (rate in / rate out) is even, we take inv_cadence/2 samples
	 * from before and after the middle sample (which is timestamped with the outgoing timestamp).
	 * We then sum 1/2 first sample + 1/2 last sample + all other samples,
	 * and divide by inv_cadence. Technically, this is a Tukey window,
	 * but for large inv_cadence, it is almost an average.
	 */ \
	if(!(inv_cadence % 2) && dst_size != 0) { \
		/* First, see if we need to fill in a sample corresponding to the end of the last input buffer */ \
		if(*num_end_samples + leading_samples >= inv_cadence) { \
			if(*num_end_samples > 0) \
				*dst = *end_samples; \
			else \
				*dst = 0.0; \
			const DTYPE COMPLEX *src_end; \
			for(src_end = src + leading_samples - inv_cadence / 2; src < src_end; src++) \
				*dst += *src; \
			*dst += *src / 2; \
			*dst /= (*num_end_samples + leading_samples - inv_cadence / 2); \
			dst++; \
		/* Otherwise, we need to use up the leftover samples from the previous input buffer to make the first output sample of this buffer */ \
		} else { \
			if(*num_end_samples > 0) \
				*dst = *end_samples; \
			else \
				*dst = 0.0; \
			const DTYPE COMPLEX *src_end; \
			for(src_end = src + leading_samples + inv_cadence / 2; src < src_end; src++) \
				*dst += *src; \
			*dst += *src / 2; \
			*dst /= (*num_end_samples + leading_samples + inv_cadence / 2); \
			dst++; \
		} \
 \
		/* Process current buffer */ \
		const DTYPE COMPLEX *dst_end; \
		gint16 i; \
		for(dst_end = dst + dst_size - 1; dst < dst_end; dst++) { \
			*dst = *src / 2; \
			src++; \
			for(i = 0; i < inv_cadence - 1; i++, src++) \
				*dst += *src; \
			*dst += *src / 2; \
			*dst /= inv_cadence; \
		} \
 \
		/* Save the sum of the unused samples in end_samples and the number of unused samples in num_end_samples */ \
		*num_end_samples = (src_size + inv_cadence / 2 - leading_samples) % inv_cadence; \
		*end_samples = *src / 2; \
		src++; \
		for(i = 1; i < *num_end_samples; i++, src++) \
			*end_samples += *src; \
 \
	/*
	 * If inverse cadence (rate in / rate out) is odd, we take the average of samples starting
	 * at inv_cadence/2 - 1 samples before the middle sample (which is timestamped with the
	 * outgoing timestamp) and ending at inv_cadence/2 - 1 samples after the middle sample.
	 */ \
	} else if(inv_cadence % 2 && dst_size != 0) { \
		/* First, see if we need to fill in a sample corresponding to the end of the last input buffer */ \
		if(*num_end_samples + leading_samples >= inv_cadence) { \
			if(*num_end_samples > 0) \
				*dst = *end_samples; \
			else \
				*dst = 0.0; \
			const DTYPE COMPLEX *src_end; \
			for(src_end = src + leading_samples - inv_cadence / 2; src < src_end; src++) \
				*dst += *src; \
			*dst /= (*num_end_samples + leading_samples - inv_cadence / 2); \
			dst++; \
		/* Otherwise, we need to use up the leftover samples from the previous input buffer to make the first output sample of this buffer */ \
		} else { \
			if(*num_end_samples > 0) \
				*dst = *end_samples; \
			else \
				*dst = 0.0; \
			const DTYPE COMPLEX *src_end; \
			for(src_end = src + leading_samples + 1 + inv_cadence / 2; src < src_end; src++) \
				*dst += *src; \
			*dst /= (*num_end_samples + leading_samples + 1 + inv_cadence / 2); \
			dst++; \
		} \
 \
		/* Process current buffer */ \
		const DTYPE COMPLEX *dst_end; \
		gint32 i; \
		for(dst_end = dst + dst_size - 1; dst < dst_end; dst++) { \
			for(i = 0; i < inv_cadence; i++, src++) \
				*dst += *src; \
			*dst /= inv_cadence; \
		} \
 \
		/* Save the sum of the unused samples in end_samples and the number of unused samples in num_end_samples */ \
		*num_end_samples = (src_size + inv_cadence / 2 - leading_samples) % inv_cadence; \
		*end_samples = *src; \
		src++; \
		for(i = 1; i < *num_end_samples; i++, src++) \
			*end_samples += *src; \
 \
	/*
	 * If the size of the outgoing buffer has been computed to be zero, all we want to
	 * do is store the additional data from the input buffer in end_samples and num_end_samples.
	 */ \
	} else { \
		guint16 i; \
		if(*num_end_samples == 0 && !(inv_cadence % 2)) { \
			/* Then the first input sample should be divided by two, since it is the first to affect the next output sample. */ \
			*end_samples = *src / 2; \
			src++; \
			for(i = 1; i < src_size; i++, src++) \
				*end_samples += *src; \
		} else { \
			/* Then each sample contributes its full value to end_samples. */ \
			for(i = 0; i < src_size; i++, src++) \
				*end_samples += *src; \
		} \
		*num_end_samples += src_size; \
	} \
}

DEFINE_AVG_DOWNSAMPLE(float, )
DEFINE_AVG_DOWNSAMPLE(double, )
DEFINE_AVG_DOWNSAMPLE(float, complex)
DEFINE_AVG_DOWNSAMPLE(double, complex)


/*
 * Downsampling functions that reduce aliasing by filtering the inputs
 * with a sinc table [ sin(pi * x * cadence) / (pi * x * cadence) ]
 */
#define DEFINE_SINC_DOWNSAMPLE(DTYPE, COMPLEX) \
static void sinc_downsample_ ## DTYPE ## COMPLEX(const DTYPE COMPLEX *src, DTYPE COMPLEX *dst, guint64 src_size, guint64 dst_size, gint32 inv_cadence, gint16 leading_samples, DTYPE COMPLEX *end_samples, gint32 *num_end_samples, gint32 *index_end_samples, gint32 max_end_samples, double *sinc_table) \
{ \
	/*
	 * If this is the start of stream or right after a discont, record the location
	 * corresponding to the first output sample produced relative to the start of end_samples
	 * and set the location of the latest end sample to the end of end_samples.
	 */ \
	if(*index_end_samples == -1) { \
		*index_end_samples = leading_samples; \
		index_end_samples[1] = max_end_samples - 1; \
	} \
 \
	DTYPE COMPLEX *end_samples_end; \
	end_samples_end = end_samples + index_end_samples[1]; \
 \
	/* move the pointer to the element of end_samples corresponding to the next output sample */ \
	end_samples += *index_end_samples; \
	guint16 i; \
	gint32 j, k; \
	/* If we have enough input to produce output and this is the first output buffer (or first after a discontinuity)... */ \
	if(dst_size && *num_end_samples < max_end_samples) { \
		DTYPE COMPLEX *ptr_before, *ptr_after, *ptr_end; \
		for(i = 0; i < dst_size; i++, dst++) { \
			/*
			 * In this case, the inputs in end_samples are in chronological order, so it is simple
			 * to determine whether the sinc table is centered in end_samples or src
			 */ \
			*dst = (DTYPE) *sinc_table * (*index_end_samples < *num_end_samples ? *end_samples : *(src + *index_end_samples - *num_end_samples)); \
			sinc_table++; \
 \
			/*
			 * First, deal with dependence of output sample on elements of end_samples before and
			 * after center of sinc table. j is the number of steps to reach a boundary of *end_samples
			 */ \
			j = *index_end_samples < *num_end_samples - *index_end_samples - 1 ? *index_end_samples : *num_end_samples - *index_end_samples - 1; \
			ptr_end = end_samples + j; \
			for(ptr_before = end_samples - 1, ptr_after = end_samples + 1; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++) \
				*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
 \
			/* Next, deal with potential "one-sided" dependence of output sample on elements of end_samples after center of sinc table. */ \
			j = *num_end_samples - *index_end_samples - 1 < max_end_samples / 2 ? *num_end_samples - *index_end_samples - 1 : max_end_samples / 2; \
			ptr_end = end_samples + j; \
			for(ptr_after = end_samples + *index_end_samples + 1; ptr_after <= ptr_end; ptr_after++, sinc_table++) \
				*dst += (DTYPE) *sinc_table * *ptr_after; \
 \
			/* Next, deal with dependence of output sample on current input samples before and after center of sinc table */ \
			j = *index_end_samples - *num_end_samples < max_end_samples / 2 ? *index_end_samples - *num_end_samples : max_end_samples / 2; \
			ptr_end = (DTYPE COMPLEX *) src + *index_end_samples - *num_end_samples + j; \
			for(ptr_before = (DTYPE COMPLEX *) src + *index_end_samples - *num_end_samples - 1, ptr_after = ptr_end - j + 1; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++) \
				*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
 \
			/* Next, deal with dependence of output sample on current input samples after and end_samples before center of sinc table, which is in end_samples */ \
			if(*index_end_samples < *num_end_samples) { \
				j = *index_end_samples < max_end_samples / 2 ? 2 * *index_end_samples - *num_end_samples + 1 : max_end_samples / 2 - *num_end_samples + *index_end_samples + 1; \
				ptr_end = (DTYPE COMPLEX *) src + j - 1; \
				for(ptr_before = end_samples - (*num_end_samples - *index_end_samples), ptr_after = (DTYPE COMPLEX *) src; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++) \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
			} else { \
				/* Next, deal with dependence of output sample on current input samples after and end_samples before center of sinc table, which is in src */ \
				j = *num_end_samples < max_end_samples / 2 - (*index_end_samples - *num_end_samples) ? *num_end_samples : max_end_samples / 2 - (*index_end_samples - *num_end_samples); \
				ptr_end = (DTYPE COMPLEX *) src + 2 * (*index_end_samples - *num_end_samples) + j; \
				for(ptr_before = end_samples_end, ptr_after = ptr_end - j + 1; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++) \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
			} \
 \
			/* Next, deal with potential "one-sided" dependence of output sample on current input samples after center of sinc table. */ \
			j = max_end_samples / 2 - (2 * *index_end_samples > *num_end_samples - 1 ? *index_end_samples : (*num_end_samples - *index_end_samples - 1)); \
			ptr_end = (DTYPE COMPLEX *) src + *index_end_samples - *num_end_samples + max_end_samples / 2; \
			for(ptr_after = ptr_end - j + 1; ptr_after <= ptr_end; ptr_after++, sinc_table++) \
				*dst += (DTYPE) *sinc_table * *ptr_after; \
 \
			/* We've now reached the end of the sinc table. Move the pointer back. */ \
			sinc_table -= (1 + max_end_samples / 2); \
 \
			/* Also need to increment end_samples. *index_end_samples is used to find our place in *src, so it is reduced later */ \
			end_samples += (inv_cadence - ((*index_end_samples % max_end_samples) + inv_cadence < max_end_samples ? 0 : max_end_samples)); \
			*index_end_samples += inv_cadence; \
		} \
		/* *index_end_samples += inv_cadence; */ \
 \
	} else if(dst_size) { \
		/* We have enough input to produce output and this is not the first output buffer */ \
		g_assert_cmpint(*num_end_samples, ==, max_end_samples); \
		/* artificially increase index_end_samples[1] so that comparison to *index_end_samples tells us whether the sinc table is centered in end_samples or src. */ \
		index_end_samples[1] += *index_end_samples > index_end_samples[1] ? max_end_samples : 0; \
		DTYPE COMPLEX *ptr_before, *ptr_after, *ptr_end; \
		gint32 j1, j2, j3; \
		for(i = 0; i < dst_size; i++, dst++) { \
			int h = 1; \
			if(*index_end_samples <= index_end_samples[1]) { \
				/*
			 	 * sinc table is centered in end_samples. First, deal with dependence of output sample on
			 	 * elements of end_samples before and after center of sinc table. There are 2 possible end
			 	 * points for a for loop: we hit the boundary of end_samples in either chronology or memory.
				 */ \
				*dst = (DTYPE) *sinc_table * *end_samples; \
				sinc_table++; \
				j1 = index_end_samples[1] - *index_end_samples; \
				j2 = *index_end_samples % max_end_samples; \
				j3 = max_end_samples - *index_end_samples % max_end_samples - 1; \
				/* Number of steps in for loop is minimum of above */ \
				j = j1 < (j2 < j3 ? j2 : j3) ? j1 : (j2 < j3 ? j2 : j3); \
				ptr_end = end_samples + j; \
				for(ptr_before = end_samples - 1, ptr_after = end_samples + 1; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++, h++) \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
				if(j2 <= j1 && j2 < j3) { \
					ptr_before += max_end_samples; \
					j = j1 - j2; \
				} else if(j3 <= j1 && j3 < j2) { \
					ptr_after -= max_end_samples; \
					j = j1 - j3; \
				} else \
					j = 0; \
				ptr_end = ptr_after + j; \
				while(ptr_after < ptr_end) { \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
					ptr_after++; \
					ptr_before--; \
					sinc_table++; \
					h++; \
				} \
				/* Now deal with dependence of output sample on current input samples after and end_samples before center of sinc table */ \
				j2 = 1 + (j2 - j1 - 1 + max_end_samples) % max_end_samples; \
				j1 = max_end_samples / 2 - j1; \
				j = j1 < j2 ? j1 : j2; \
				ptr_after = (DTYPE COMPLEX *) src; \
				ptr_end = ptr_after + j; \
				while(ptr_after < ptr_end) { \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
					ptr_after++; \
					ptr_before--; \
					sinc_table++; \
					h++; \
				} \
 \
				j = j1 - j2; \
				ptr_before += max_end_samples; \
				ptr_end += j; \
				while(ptr_after < ptr_end) { \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
					ptr_after++; \
					ptr_before--; \
					sinc_table++; \
					h++; \
				} \
			} else { \
				/*
				 * sinc table is centered in src. First, deal with dependence of output samples on current
				 * input samples before and after center of sinc table.
				 */ \
				*dst = (DTYPE) *sinc_table * *(src + *index_end_samples - index_end_samples[1] - 1); \
				sinc_table++; \
				j1 = max_end_samples / 2; \
				j2 = *index_end_samples - index_end_samples[1] - 1; \
				j = j1 < j2 ? j1 : j2; \
				ptr_end = (DTYPE COMPLEX *) src + *index_end_samples - index_end_samples[1] - 1 + j; \
				for(ptr_before = ptr_end - j - 1, ptr_after = ptr_end - j + 1; ptr_after <= ptr_end; ptr_after++, ptr_before--, sinc_table++, h++) \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
 \
				/* Now deal with dependence of output sample on current input samples after and end_samples before center of sinc table */ \
				j1 -= j2; \
				j2 = 1 + index_end_samples[1] % max_end_samples; \
				j = j1 < j2 ? j1 : j2; \
				ptr_before = end_samples_end; \
				ptr_end = ptr_after + j; \
				while(ptr_after < ptr_end) { \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
					ptr_after++; \
					ptr_before--; \
					sinc_table++; \
					h++; \
				} \
 \
				j = j1 - j2; \
				ptr_before += max_end_samples; \
				ptr_end += j; \
				while(ptr_after < ptr_end) { \
					*dst += (DTYPE) *sinc_table * (*ptr_before + *ptr_after); \
					ptr_after++; \
					ptr_before--; \
					sinc_table++; \
					h++; \
				} \
			} \
			/* We've now reached the end of the sinc table. Move the pointer back. */ \
			sinc_table -= (1 + max_end_samples / 2); \
 \
			/* Also need to increment end_samples. *index_end_samples is used to find our place in *src, so it is reduced later */ \
			end_samples += (inv_cadence - ((*index_end_samples % max_end_samples) + inv_cadence < max_end_samples ? 0 : max_end_samples)); \
			*index_end_samples += inv_cadence; \
		} \
		/* *index_end_samples += inv_cadence; */ \
	} \
	/* Move *index_end_samples back to the appropriate location within end_samples */ \
	*index_end_samples %= max_end_samples; \
 \
	/* Store current input samples we will need later in end_samples */ \
	end_samples_end += ((index_end_samples[1] + src_size) % max_end_samples - index_end_samples[1] % max_end_samples); \
	index_end_samples[1] = (index_end_samples[1] + src_size) % max_end_samples; \
	src += (src_size - 1); \
	j = index_end_samples[1] + 1 < (gint32) src_size ? index_end_samples[1] + 1 : (gint32) src_size; \
	for(k = 0; k < j; k++, src--, end_samples_end--) \
		*end_samples_end = *src; \
 \
	/* A second for loop is necessary in case we hit the boundary of end_samples before we've stored the end samples */ \
	end_samples_end += max_end_samples; \
	j = index_end_samples[1] + 1 < (gint32) src_size ? ((gint32) src_size < max_end_samples ? (gint32) src_size : max_end_samples)  - index_end_samples[1] - 1 : 0; \
	for(k = 0; k < j; k++, src--, end_samples_end--) \
		*end_samples_end = *src; \
 \
	/* record how many samples are stored in end_samples */ \
	*num_end_samples += src_size; \
	if(*num_end_samples > max_end_samples) \
		*num_end_samples = max_end_samples; \
}


DEFINE_SINC_DOWNSAMPLE(float, )
DEFINE_SINC_DOWNSAMPLE(double, )
DEFINE_SINC_DOWNSAMPLE(float, complex)
DEFINE_SINC_DOWNSAMPLE(double, complex)


/* Based on given parameters, this function calls the proper resampling function */
static void resample(const void *src, guint64 src_size, void *dst, guint64 dst_size, gint unit_size, enum gstlal_resample_data_type data_type, gint32 cadence, gint32 inv_cadence, guint quality, void *dxdt0, void *end_samples, gint16 leading_samples, gint32 *num_end_samples, gint32 *index_end_samples, gint32 max_end_samples, double *sinc_table)
{
	/* Sanity checks */
	g_assert_cmpuint(src_size % unit_size, ==, 0);
	g_assert_cmpuint(dst_size % unit_size, ==, 0);
	g_assert(cadence > 1 || inv_cadence > 1);

	/* convert buffer sizes to number of samples */
	src_size /= unit_size;
	dst_size /= unit_size;

	/* 
	 * cadence is # of output samples per input sample, so if cadence > 1, we are upsampling.
	 * quality is the degree of the polynomial used to interpolate between points,
	 * so quality = 0 means we are just copying input samples n times.
	 */
	if(cadence > 1 && quality == 0) {
		switch(unit_size) {
		case 1:
			const_upsample_8(src, dst, src_size, cadence);
			break;
		case 2:
			const_upsample_16(src, dst, src_size, cadence);
			break;
		case 4:
			const_upsample_32(src, dst, src_size, cadence);
			break;
		case 8:
			const_upsample_64(src, dst, src_size, cadence);
			break;
		default:
			const_upsample_other(src, dst, src_size, unit_size, cadence);
			break;
		}

	} else if(cadence > 1 && quality == 1) {
		switch(data_type) {
		case GSTLAL_RESAMPLE_F32:
			linear_upsample_float(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_F64:
			linear_upsample_double(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z64:
			linear_upsample_floatcomplex(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z128:
			linear_upsample_doublecomplex(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		default:
			g_assert_not_reached();
			break;
		}

	} else if(cadence > 1 && quality == 2) {
		switch(data_type) {
		case GSTLAL_RESAMPLE_F32:
			quadratic_upsample_float(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_F64:
			quadratic_upsample_double(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z64:
			quadratic_upsample_floatcomplex(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z128:
			quadratic_upsample_doublecomplex(src, dst, src_size, cadence, end_samples, num_end_samples);
			break;
		default:
			g_assert_not_reached();
			break;
		}

	} else if(cadence > 1 && quality == 3) {
		switch(data_type) {
		case GSTLAL_RESAMPLE_F32:
			cubic_upsample_float(src, dst, src_size, cadence, dxdt0, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_F64:
			cubic_upsample_double(src, dst, src_size, cadence, dxdt0, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z64:
			cubic_upsample_floatcomplex(src, dst, src_size, cadence, dxdt0, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z128:
			cubic_upsample_doublecomplex(src, dst, src_size, cadence, dxdt0, end_samples, num_end_samples);
			break;
		default:
			g_assert_not_reached();
			break;
		}

	/* 
	 * inv_cadence is # of input samples per output sample, so if inv_cadence > 1, we are downsampling.
	 * The meaning of "quality" when downsampling is simpler: if quality = 0, we
	 * just pick every nth input sample, and if quality > 0, we take an average.
	 */

	} else if(inv_cadence > 1 && quality == 0) {
		switch(unit_size) {
		case 1:
			downsample_8(src, dst, dst_size, inv_cadence, leading_samples);
			break;
		case 2:
			downsample_16(src, dst, dst_size, inv_cadence, leading_samples);
			break;
		case 4:
			downsample_32(src, dst, dst_size, inv_cadence, leading_samples);
			break;
		case 8:
			downsample_64(src, dst, dst_size, inv_cadence, leading_samples);
			break;
		default:
			downsample_other(src, dst, dst_size, unit_size, inv_cadence, leading_samples);
			break;
		}

	} else if(inv_cadence > 1 && quality == 1) {
		switch(data_type) {
		case GSTLAL_RESAMPLE_F32:
			avg_downsample_float(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_F64:
			avg_downsample_double(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z64:
			avg_downsample_floatcomplex(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples);
			break;
		case GSTLAL_RESAMPLE_Z128:
			avg_downsample_doublecomplex(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples);
			break;
		default:
			g_assert_not_reached();
			break;
		}
	} else if(inv_cadence > 1 && quality > 1) {
		switch(data_type) {
		case GSTLAL_RESAMPLE_F32:
			sinc_downsample_float(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples, index_end_samples, max_end_samples, sinc_table);
			break;
		case GSTLAL_RESAMPLE_F64:
			sinc_downsample_double(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples, index_end_samples, max_end_samples, sinc_table);
			break;
		case GSTLAL_RESAMPLE_Z64:
			sinc_downsample_floatcomplex(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples, index_end_samples, max_end_samples, sinc_table);
			break;
		case GSTLAL_RESAMPLE_Z128:
			sinc_downsample_doublecomplex(src, dst, src_size, dst_size, inv_cadence, leading_samples, end_samples, num_end_samples, index_end_samples, max_end_samples, sinc_table);
			break;
		default:
			g_assert_not_reached();
			break;
		}
	} else
		g_assert_not_reached();
}


/*
 * set the metadata on an output buffer
 */


static void set_metadata(GSTLALResample *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;
	if(element->zero_latency) {
		GST_BUFFER_PTS(buf) = element->t0 + gst_util_uint64_scale_int_round(GST_BUFFER_OFFSET(buf) - element->offset0 + (element->rate_out * element->max_end_samples / 2) / element->rate_in, GST_SECOND, element->rate_out);
		GST_BUFFER_DURATION(buf) = gst_util_uint64_scale_int_round(GST_BUFFER_OFFSET_END(buf) - element->offset0, GST_SECOND, element->rate_out) - gst_util_uint64_scale_int_round(GST_BUFFER_OFFSET(buf) - element->offset0, GST_SECOND, element->rate_out);
	} else {
		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 || element->need_gap) {
		GST_BUFFER_FLAG_SET(buf, GST_BUFFER_FLAG_GAP);
		if(outsamples > 0)
			element->need_gap = FALSE;
	}
	else
		GST_BUFFER_FLAG_UNSET(buf, GST_BUFFER_FLAG_GAP);
}


/*
 * ============================================================================
 *
 *			   GStreamer Boiler Plate
 *
 * ============================================================================
 */


#define CAPS \
	"audio/x-raw, " \
	"rate = (int) [1, MAX], " \
	"channels = (int) 1, " \
	"format = (string) {"GST_AUDIO_NE(F32)", "GST_AUDIO_NE(F64)", "GST_AUDIO_NE(Z64)", "GST_AUDIO_NE(Z128)"}, " \
	"layout = (string) interleaved, " \
	"channel-mask = (bitmask) 0"


static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
	GST_BASE_TRANSFORM_SINK_NAME,
	GST_PAD_SINK,
	GST_PAD_ALWAYS,
	GST_STATIC_CAPS(CAPS)
);


static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(
	GST_BASE_TRANSFORM_SRC_NAME,
	GST_PAD_SRC,
	GST_PAD_ALWAYS,
	GST_STATIC_CAPS(CAPS)
);

G_DEFINE_TYPE(
	GSTLALResample,
	gstlal_resample,
	GST_TYPE_BASE_TRANSFORM
);


/*
 * ============================================================================
 *
 *		     GstBaseTransform Method Overrides
 *
 * ============================================================================
 */


/*
 * get_unit_size()
 */


static gboolean get_unit_size(GstBaseTransform *trans, GstCaps *caps, gsize *size)
{
	/*
	 * It seems that the function gst_audio_info_from_caps() does not work for gstlal's complex formats.
	 * Therefore, a different method is used below to parse the caps.
	 */
	const gchar *format;
	static char *formats[] = {"F32LE", "F32BE", "F64LE", "F64BE", "Z64LE", "Z64BE", "Z128LE", "Z128BE"};
	gint sizes[] = {4, 4, 8, 8, 8, 8, 16, 16};

	GstStructure *str = gst_caps_get_structure(caps, 0);
	g_assert(str);

	if(gst_structure_has_field(str, "format")) {
		format = gst_structure_get_string(str, "format");
	} else {
		GST_ERROR_OBJECT(trans, "No format! Cannot infer unit size.\n");
		return FALSE;
	}
	int test = 0;
	for(unsigned int i = 0; i < sizeof(formats) / sizeof(*formats); i++) {
		if(!strcmp(format, formats[i])) {
			*size = sizes[i];
			test++;
		}
	}
	if(test != 1)
		GST_WARNING_OBJECT(trans, "unit size not properly set");

	return TRUE;
}


/*
 * transform_caps()
 */


static GstCaps *transform_caps(GstBaseTransform *trans, GstPadDirection direction, GstCaps *caps, GstCaps *filter)
{
	guint n;

	caps = gst_caps_copy(caps);

	switch(direction) {

	case GST_PAD_SRC:
	case GST_PAD_SINK:
		/*
		 * Source and sink pad formats are the same except that
		 * the rate can change to any integer value going in either 
		 * direction. (Really needs to be either an integer multiple
		 * or an integer divisor of the rate on the other pad, but 
		 * that requirement is not enforced here).
		 */

		for(n = 0; n < gst_caps_get_size(caps); n++) {
			GstStructure *s = gst_caps_get_structure(caps, n);
			const GValue *v = gst_structure_get_value(s, "rate");

			if(!(GST_VALUE_HOLDS_INT_RANGE(v) || G_VALUE_HOLDS_INT(v)))
				GST_ELEMENT_ERROR(trans, CORE, NEGOTIATION, (NULL), ("invalid type for rate in caps"));
			gst_structure_set(s, "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
		}
		break;

	case GST_PAD_UNKNOWN:
		GST_ELEMENT_ERROR(trans, CORE, NEGOTIATION, (NULL), ("invalid direction GST_PAD_UNKNOWN"));
		gst_caps_unref(caps);
		return GST_CAPS_NONE;

	default:
		g_assert_not_reached();
	}
	return caps;
}


/*
 * set_caps()
 */


static gboolean set_caps(GstBaseTransform *trans, GstCaps *incaps, GstCaps *outcaps)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(trans);
	gboolean success = TRUE;
	gint32 rate_in, rate_out;
	gsize unit_size;

	/*
	 * parse the caps
	 */

	success &= get_unit_size(trans, incaps, &unit_size);
	GstStructure *str = gst_caps_get_structure(incaps, 0);
	const gchar *name = gst_structure_get_string(str, "format");
	success &= (name != NULL);
	success &= gst_structure_get_int(str, "rate", &rate_in);
	success &= gst_structure_get_int(gst_caps_get_structure(outcaps, 0), "rate", &rate_out);
	if(!success)
		GST_ERROR_OBJECT(element, "unable to parse caps.  input caps = %" GST_PTR_FORMAT " output caps = %" GST_PTR_FORMAT, incaps, outcaps);

	/* require the output rate to be an integer multiple or divisor of the input rate */
	success &= !(rate_out % rate_in) || !(rate_in % rate_out);
	if((rate_out % rate_in) && (rate_in % rate_out))
		GST_ERROR_OBJECT(element, "output rate is not an integer multiple or divisor of input rate.  input caps = %" GST_PTR_FORMAT " output caps = %" GST_PTR_FORMAT, incaps, outcaps);

	/*
	 * record stream parameters
	 */

	if(success) {
		if(!strcmp(name, GST_AUDIO_NE(F32))) {
			element->data_type = GSTLAL_RESAMPLE_F32;
			g_assert_cmpuint(unit_size, ==, 4);
		} else if(!strcmp(name, GST_AUDIO_NE(F64))) {
			element->data_type = GSTLAL_RESAMPLE_F64;
			g_assert_cmpuint(unit_size, ==, 8);
		} else if(!strcmp(name, GST_AUDIO_NE(Z64))) {
			element->data_type = GSTLAL_RESAMPLE_Z64;
			g_assert_cmpuint(unit_size, ==, 8);
		} else if(!strcmp(name, GST_AUDIO_NE(Z128))) {
			element->data_type = GSTLAL_RESAMPLE_Z128;
			g_assert_cmpuint(unit_size, ==, 16);
		} else
			g_assert_not_reached();

		element->rate_in = rate_in;
		element->rate_out = rate_out;
		element->unit_size = unit_size;
	}
	return success;
}


/*
 * transform_size()
 */


static gboolean transform_size(GstBaseTransform *trans, GstPadDirection direction, GstCaps *caps, gsize size, GstCaps *othercaps, gsize *othersize)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(trans);
	guint16 cadence = element->rate_out / element->rate_in;
	guint16 inv_cadence = element->rate_in / element->rate_out;
	g_assert(inv_cadence > 1 || cadence > 1);
	/* input and output unit sizes are the same */
	gsize unit_size;

	if(!get_unit_size(trans, caps, &unit_size))
		return FALSE;

	/*
	 * convert byte count to samples
	 */

	if(G_UNLIKELY(size % unit_size)) {
		GST_DEBUG_OBJECT(element, "buffer size %" G_GSIZE_FORMAT " is not a multiple of %" G_GSIZE_FORMAT, size, unit_size);
		return FALSE;
	}
	size /= unit_size;

	switch(direction) {
	case GST_PAD_SRC:
		/*
		 * compute samples needed on sink pad from sample count on source pad.
		 * size = # of samples needed on source pad
		 * cadence = # of output samples per input sample
		 * inv_cadence = # of input samples per output sample
		 */

		if(inv_cadence > 1)
			*othersize = size * inv_cadence;
		else
			*othersize = size / cadence;
		break;

	case GST_PAD_SINK:
		/*
		 * compute samples to be produced on source pad from sample
		 * count available on sink pad.
		 * size = # of samples available on sink pad
		 * cadence = # of output samples per input sample
		 * inv_cadence = # of input samples per output sample
		 */

		if(cadence > 1)
			*othersize = size * cadence;
		else {
			*othersize = size / inv_cadence;
			guint16 *weight = (void *) &element->dxdt0;
			if(size % inv_cadence || (element->quality == 1 && *weight < (inv_cadence + 1) / 2) || (element->quality > 1 && element->num_end_samples < element->max_end_samples)) {
				element->need_buffer_resize = TRUE;
				*othersize += (element->num_end_samples / inv_cadence + 2); /* max possible size */
			}
		}
		break;

	case GST_PAD_UNKNOWN:
		GST_ELEMENT_ERROR(trans, CORE, NEGOTIATION, (NULL), ("invalid direction GST_PAD_UNKNOWN"));
		return FALSE;

	default:
		g_assert_not_reached();
	}

	/*
	 * convert sample count to byte count
	 */

	*othersize *= unit_size;

	return TRUE;
}


/*
 * start()
 */


static gboolean start(GstBaseTransform *trans)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(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->need_gap = FALSE;
	element->dxdt0 = 0.0;
	element->end_samples = NULL;
	element->sinc_table = NULL;
	element->index_end_samples = NULL;
	element->max_end_samples = 0;
	element->num_end_samples = 0;
	element->leading_samples = 0;

	return TRUE;
}


/*
 * transform()
 */


static GstFlowReturn transform(GstBaseTransform *trans, GstBuffer *inbuf, GstBuffer *outbuf)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(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))) {
		element->t0 = GST_BUFFER_PTS(inbuf);
		if(element->rate_in > element->rate_out && abs(GST_BUFFER_PTS(inbuf) - gst_util_uint64_scale_round(gst_util_uint64_scale_round(GST_BUFFER_PTS(inbuf), element->rate_out, 1000000000), 1000000000, element->rate_out)) >= 500000000 / element->rate_in)
			element->t0 = gst_util_uint64_scale_round(gst_util_uint64_scale_ceil(GST_BUFFER_PTS(inbuf), element->rate_out, 1000000000), 1000000000, element->rate_out);
		else
			element->t0 = gst_util_uint64_scale_round(gst_util_uint64_scale_round(GST_BUFFER_PTS(inbuf), element->rate_out, 1000000000), 1000000000, element->rate_out);
		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->dxdt0 = 0.0;
		element->num_end_samples = 0;
		if(element->rate_in > element->rate_out && element->quality > 1) {
			/* 
			 * In this case, we are filtering inputs with a sinc table. max_end_samples is the
			 * maximum number of samples that could need to be stored between buffers. It is
			 * one less than the length of the sinc table in samples. To make the gain as close
			 * to one as possible below the output Nyquist rate, we cut off the sinc table at either
			 * relative maxima or minima.
			 */
			if(!element->sinc_table) {
				element->max_end_samples = ((1 + (SHORT_SINC_LENGTH + (element->quality - 2) * (LONG_SINC_LENGTH - SHORT_SINC_LENGTH)) * element->rate_in / element->rate_out) / 2) * 2;

				/* end_samples stores input samples needed to produce output with the next buffer(s) */
				element->end_samples = g_malloc(element->max_end_samples * element->unit_size);

				/* index_end_samples records locations in end_samples of the next output sample and the newest sample in end_samples. */
				element->index_end_samples = g_malloc(2 * sizeof(gint32));

				/* To save memory, we use symmetry and only record half of the sinc table */
				element->sinc_table = g_malloc((element->max_end_samples / 2) * sizeof(double));
				*(element->sinc_table) = 1.0;
				gint32 i;
				for(i = 1; i <= element->max_end_samples / 2; i++)
					element->sinc_table[i] = pow(cos(M_PI * i / (element->max_end_samples * 1.15)), 6) * sin(M_PI * i * element->rate_out / element->rate_in) / (M_PI * i * element->rate_out / element->rate_in);

				/* normalize sinc_table to make the DC gain exactly 1 */
				double normalization = 1.0;
				for(i = 1; i <= element->max_end_samples / 2; i++)
					normalization += 2 * element->sinc_table[i];

				for(i = 0; i <= element->max_end_samples / 2; i++)
					element->sinc_table[i] /= normalization;
			}
			/* tell the downsampling function about the discont */
			*element->index_end_samples = -1;

		} else if(element->rate_out > element->rate_in && element->quality > 1 && !element->end_samples)
			element->end_samples = g_malloc(2 * element->unit_size);
		else if(element->quality > 0 && !element->end_samples)
			element->end_samples = g_malloc(element->unit_size);
		element->leading_samples = 0;
		element->num_end_samples = 0;
		if(element->quality > 0)
			element->need_buffer_resize = TRUE;
	}

	element->next_in_offset = GST_BUFFER_OFFSET_END(inbuf);

	if(element->rate_out > element->rate_in && ((element->quality > 0 && element->num_end_samples == 0) || (element->quality > 2 && element->num_end_samples < 2)))
		element->need_buffer_resize = TRUE;

	guint16 inv_cadence = element->rate_in / element->rate_out;
	if(element->rate_in > element->rate_out) {
		/* leading_samples is the number of input samples that come before the first timestamp that is a multiple of the output sampling period */
		element->leading_samples = gst_util_uint64_scale_int_round(GST_BUFFER_PTS(inbuf), element->rate_in, 1000000000) % inv_cadence;
		if(element->leading_samples != 0)
			element->leading_samples = inv_cadence - element->leading_samples;
	}

	/*
	 * adjust output buffer size if necessary
	 */ 

	if(element->need_buffer_resize) {
		gint64 outbuf_size = 0;
		if(element->rate_out > element->rate_in) {
			/* We are upsampling */
			outbuf_size = (gint64) gst_buffer_get_size(outbuf);

			/*
			 * If using any interpolation, each input buffer leaves one or two samples at the end to add 
			 * to the next buffer. If these are absent, we need to reduce the output buffer size.
			 */
			if(element->quality > 2 && element->num_end_samples == 0)
				outbuf_size -= 2 * element->unit_size * element->rate_out / element->rate_in;
			else
				outbuf_size -= element->unit_size * element->rate_out / element->rate_in;
		} else if(element->rate_in > element->rate_out) {
			guint64 inbuf_samples = gst_buffer_get_size(inbuf) / element->unit_size;

			/*
			 * Assuming we are simply picking every nth sample, the number of samples in the 
			 * output buffer is the number of samples in the input buffer that carry 
			 * timestamps that are multiples of the output sampling period 
			 */
			outbuf_size = ((inbuf_samples - element->leading_samples + inv_cadence - 1) / inv_cadence) * element->unit_size;

			/* We now adjust the size if we are applying a tukey window when downsampling */
			if(element->quality == 1) {
				/* trailing_samples is the number of input samples that come after the last timestamp that is a multiple of the output sampling period */
				guint trailing_samples = (inbuf_samples - element->leading_samples - 1) % inv_cadence;
				outbuf_size -= element->unit_size * (1 - trailing_samples / (inv_cadence / 2));
				/* Check if there will be an outgoing sample on this buffer before the presentation timestamp of the input buffer */
				guint *weight = (void *) &element->dxdt0;
				if(element->leading_samples + *weight >= inv_cadence && *weight + inbuf_samples >= inv_cadence)
					outbuf_size += element->unit_size;
			} else if(element->quality > 1 && element->num_end_samples == element->max_end_samples)
				outbuf_size = element->unit_size * ((inbuf_samples + (-element->max_end_samples / 2 - element->leading_samples - 1) % inv_cadence) / inv_cadence);
			else if(element->quality > 1) {
				if((gint32) inbuf_samples + element->num_end_samples >= element->max_end_samples)
					outbuf_size = element->unit_size * ((inbuf_samples + element->num_end_samples - element->leading_samples - element->max_end_samples / 2 + inv_cadence - 1) / inv_cadence);
				else
					outbuf_size = 0;
			}
		}
		if(outbuf_size < 0)
			outbuf_size = 0;
		gst_buffer_set_size(outbuf, (gssize) outbuf_size);
		element->need_buffer_resize = FALSE;
	}

	/*
	 * process buffer
	 */

	gst_buffer_map(inbuf, &inmap, GST_MAP_READ);

	if(!GST_BUFFER_FLAG_IS_SET(inbuf, GST_BUFFER_FLAG_GAP) && inmap.size != 0) {

		/*
		 * input is not 0s.
		 */

		gst_buffer_map(outbuf, &outmap, GST_MAP_WRITE);
		resample(inmap.data, inmap.size, outmap.data, outmap.size, element->unit_size, element->data_type, element->rate_out / element->rate_in, element->rate_in / element->rate_out, element->quality, (void *) &element->dxdt0, (void *) element->end_samples, element->leading_samples, &element->num_end_samples, element->index_end_samples, element->max_end_samples, element->sinc_table);
		set_metadata(element, outbuf, outmap.size / element->unit_size, FALSE);
		gst_buffer_unmap(outbuf, &outmap);
	} else {
		/*
		 * input is 0s.
		 */

		GST_BUFFER_FLAG_SET(outbuf, GST_BUFFER_FLAG_GAP);
		gst_buffer_map(outbuf, &outmap, GST_MAP_WRITE);
		memset(outmap.data, 0, outmap.size);
		set_metadata(element, outbuf, outmap.size / element->unit_size, TRUE);
		if(outmap.size / element->unit_size == 0)
			element->need_gap = TRUE;
		gst_buffer_unmap(outbuf, &outmap);
	}

	gst_buffer_unmap(inbuf, &inmap);

	/*
	 * done
	 */

	return result;
}


/*
 * ============================================================================
 *
 *			  GObject Method Overrides
 *
 * ============================================================================
 */


/*
 * properties
 */


enum property {
	ARG_QUALITY = 1,
	ARG_ZERO_LATENCY
};


static void set_property(GObject *object, enum property prop_id, const GValue *value, GParamSpec *pspec)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(object);

	GST_OBJECT_LOCK(element);

	switch (prop_id) {
	case ARG_QUALITY:
		element->quality = g_value_get_uint(value);
		break;
	case ARG_ZERO_LATENCY:
		element->zero_latency = 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)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(object);

	GST_OBJECT_LOCK(element);

	switch (prop_id) {
	case ARG_QUALITY:
		g_value_set_uint(value, element->quality);
		break;
	case ARG_ZERO_LATENCY:
		g_value_set_boolean(value, element->zero_latency);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
		break;
	}

	GST_OBJECT_UNLOCK(element);
}


/*
 * finalize()
 */


static void finalize(GObject *object)
{
	GSTLALResample *element = GSTLAL_RESAMPLE(object);

	/*
	 * free resources
	 */

	if(element->sinc_table) {
		g_free(element->sinc_table);
		element->sinc_table = NULL;
	}
	if(element->end_samples) {
		g_free(element->end_samples);
		element->end_samples = NULL;
	}

	/*
	 * chain to parent class' finalize() method
	 */

	G_OBJECT_CLASS(gstlal_resample_parent_class)->finalize(object);
}


/*
 * class_init()
 */


static void gstlal_resample_class_init(GSTLALResampleClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
	GstBaseTransformClass *transform_class = GST_BASE_TRANSFORM_CLASS(klass);

	transform_class->transform_caps = GST_DEBUG_FUNCPTR(transform_caps);
	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);
	transform_class->passthrough_on_same_caps = TRUE;

	gst_element_class_set_details_simple(element_class,
		"Resamples a data stream",
		"Filter/Audio",
		"Resamples a stream with adjustable quality.",
		"Aaron Viets <aaron.viets@ligo.org>"
	);
	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_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_QUALITY,
		g_param_spec_uint(
			"quality",
			"Quality of Resampling",
			"When upsampling, this is the order of the polynomial used to interpolate between\n\t\t\t"
			"input samples. 0 yields a constant upsampling, 1 is linear interpolation, 3 is a\n\t\t\t"
			"cubic spline. When downsampling, this determines whether we just pick every nth\n\t\t\t"
			"sample (0), apply a Tukey window to (rate-in / rate-out) input samples surrounding\n\t\t\t"
			"output sample timestamps (1), or filter with a sinc table to reduce aliasing\n\t\t\t"
			"effects (2 - 3).",
			0, 3, 0,
			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
		)
	);
	g_object_class_install_property(
		gobject_class,
		ARG_ZERO_LATENCY,
		g_param_spec_boolean(
			"zero-latency",
			"Zero Latency",
			"If set to true, applies a timestamp shift in order to make the latency zero.\n\t\t\t"
			"Default is false",
			FALSE,
			G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT
		)
	);
}


/*
 * init()
 */


static void gstlal_resample_init(GSTLALResample *element)
{
	element->rate_in = 0;
	element->rate_out = 0;
	element->unit_size = 0;
	gst_base_transform_set_gap_aware(GST_BASE_TRANSFORM(element), TRUE);
}