diff --git a/gstlal-burst/gst/lal/gstlal_string_triggergen.c b/gstlal-burst/gst/lal/gstlal_string_triggergen.c
index 526872bc3a7e28916a687a080dc414c45887ca2a..670ff08a7d62371fa99929252900e175a888ef6a 100644
--- a/gstlal-burst/gst/lal/gstlal_string_triggergen.c
+++ b/gstlal-burst/gst/lal/gstlal_string_triggergen.c
@@ -153,6 +153,36 @@ static int setup_bankfile_input(GSTLALStringTriggergen *element, char *bank_file
 }
 
 
+static int exists_unrealized_triggers(GSTLALStringTriggergen *element)
+{
+	gint channel;
+
+	for(channel = 0; channel < element->num_templates; channel++)
+		if(element->bank[channel].snr != 0.)
+			return TRUE;
+
+	return FALSE;
+}
+
+
+/*
+ * return min(time of offset, min(times of realized triggers))
+ */
+
+
+static GstClockTime buffer_pts(GSTLALStringTriggergen *element, guint64 offset)
+{
+	gint channel;
+	/* time of first SNR sample that can be a trigger */
+	GstClockTime t = element->t0 + gst_util_uint64_scale_int_round(offset + (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info));
+	/* scan for unrealized triggers with an earlier time */
+	for(channel = 0; channel < element->num_templates; channel++)
+		if(element->bank[channel].snr > 0.0 && (GstClockTime) XLALGPSToINT8NS(&element->bank[channel].peak_time) < t)
+			t = XLALGPSToINT8NS(&element->bank[channel].peak_time);
+	return t;
+}
+
+
 /*
  * compute autocorrelation norms --- the expectation value in noise.
  */
@@ -198,50 +228,24 @@ static gsl_vector_float *gstlal_autocorrelation_chi2_compute_norms_string(const
  */
 
 
-static GstClockTime buffer_pts(GSTLALStringTriggergen *element, guint64 offset)
-{
-	gint channel;
-	/* start time of SNR used to construct buffer */
-	GstClockTime t = element->t0 + gst_util_uint64_scale_int_round(offset + (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info));
-	/* pin buffer start time at earliest unrealized trigger */
-	for(channel = 0; channel < element->num_templates; channel++)
-		if(element->bank[channel].snr > 0.0 && (GstClockTime) XLALGPSToINT8NS(&element->bank[channel].peak_time) < t)
-			t = XLALGPSToINT8NS(&element->bank[channel].peak_time);
-	return t;
-}
-
-
 static GstFlowReturn trigger_generator(GSTLALStringTriggergen *element, GstBuffer *outbuf)
 {
 	float *snrdata;
 	float *snrsample;
 	SnglBurst *triggers = NULL;
 	guint ntriggers = 0;
-	guint64 offset;
-	guint64 length;
+	guint64 offset = gst_audioadapter_offset(element->adapter);
+	guint64 length = get_available_samples(element);
 	guint sample;
 	gint channel;
 
-	g_mutex_lock(&element->bank_lock);
-
-	/* check that autocorrelation vector has odd number of samples.
-	 * NOTE:  autocorrelation_length() returns 0 if the
-	 * autocorrelation_matrix is not set, so this g_assert also tests
-	 * for a missing autocorrelation_matrix.  in set_property(), if the
-	 * conversion from GValueArray fails the matrix will be left set to
-	 * NULL, so this is also catching those failures. */
-	g_assert(autocorrelation_length(element->autocorrelation_matrix) & 1);
+	/*
+	 * obtain PTS and DURATION of output buffer.  must be done before
+	 * generating triggers
+	 */
 
-	/* do we have enough SNR to search for triggers? */
-	offset = gst_audioadapter_offset(element->adapter);
-	length = get_available_samples(element);
-	if(length < autocorrelation_length(element->autocorrelation_matrix)) {
-		GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
-		GST_BUFFER_DURATION(outbuf) = 0;
-		GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf) + ntriggers;
-		g_mutex_unlock(&element->bank_lock);
-		return GST_FLOW_OK;
-	}
+	GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
+	GST_BUFFER_DURATION(outbuf) = element->t0 + gst_util_uint64_scale_int_round(offset + length - (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info)) - GST_BUFFER_PTS(outbuf);
 
 	/* copy samples */
 	snrsample = snrdata = g_malloc(length * element->num_templates * sizeof(*snrdata));
@@ -267,32 +271,31 @@ static GstFlowReturn trigger_generator(GSTLALStringTriggergen *element, GstBuffe
 			float snr = fabsf(*snrsample);
 			if(snr >= element->threshold) {
 				/*
-				 * If there was a discontinuity (e.g. gap) that made this sample and the last sample above threshold larger than
-				 * the clustering time window, pass the previous trigger and reset the SNR so as to start with a new trigger.
+				 * If this is the first sample above
+				 * threshold (i.e. snr of trigger is
+				 * (re)set to 0), record the start time.
 				 */
-				if(element->bank[channel].snr > element->threshold && XLALGPSDiff(&t, &element->last_time[channel]) > element->cluster) {
-					triggers = g_renew(SnglBurst, triggers, ntriggers + 1);
-					triggers[ntriggers++] = element->bank[channel];
-					element->bank[channel].snr = 0.0;
-					element->bank[channel].chisq = 0.0;
-					element->bank[channel].chisq_dof = 0.0;
-				}
 
-				/*
-				 * If this is the first sample above threshold (i.e. snr of trigger is (re)set to 0), record the start time.
-				 */
 				if(element->bank[channel].snr < element->threshold)
 					element->bank[channel].start_time = t;
+
 				/*
-				 * Keep track of last time above threshold and the duration.
-				 * For duration add a sample of fuzz on both sides (like in lalapps_StringSearch).
+				 * Keep track of last time above threshold
+				 * and the duration.  For duration add a
+				 * sample of fuzz on both sides (like in
+				 * lalapps_StringSearch).  FIXME:  do we
+				 * need the fuzz?
 				 */
+
 				element->last_time[channel] = t;
-				element->bank[channel].duration = XLALGPSDiff(&element->last_time[channel], &element->bank[channel].start_time) + (float) 2.0 / GST_AUDIO_INFO_RATE(&element->audio_info);
+				element->bank[channel].duration = XLALGPSDiff(&t, &element->bank[channel].start_time) + (float) 2.0 / GST_AUDIO_INFO_RATE(&element->audio_info);
+
+				/*
+				 * if this sample is the highest SNR,
+				 * update the trigger
+				 */
+
 				if(snr > element->bank[channel].snr) {
-					/*
-					 * Higher SNR than the "current winner". Update.
-					 */
 					const float *autocorrelation = (const float *) gsl_matrix_float_const_ptr(element->autocorrelation_matrix, channel, 0);
 					const float *autocorrelation_end = autocorrelation + autocorrelation_length(element->autocorrelation_matrix);
 					float *snrseries = snrsample - (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 * element->num_templates;
@@ -309,9 +312,12 @@ static GstFlowReturn trigger_generator(GSTLALStringTriggergen *element, GstBuffe
 				}
 			} else if(element->bank[channel].snr != 0. && XLALGPSDiff(&t, &element->last_time[channel]) > element->cluster) {
 				/*
-				 * Trigger is ready to be passed.
-				 * Push trigger to buffer, and reset it.
+				 * there is a trigger ready to be pushed,
+				 * and we have been below threshold for
+				 * longer than the clustering time, so make
+				 * a new trigger and reset this template
 				 */
+
 				triggers = g_renew(SnglBurst, triggers, ntriggers + 1);
 				triggers[ntriggers++] = element->bank[channel];
 				element->bank[channel].snr = 0.0;
@@ -320,7 +326,6 @@ static GstFlowReturn trigger_generator(GSTLALStringTriggergen *element, GstBuffe
 			}
 		}
 	}
-	g_mutex_unlock(&element->bank_lock);
 
 	g_free(snrdata);
 	gst_audioadapter_flush_samples(element->adapter, length - (autocorrelation_length(element->autocorrelation_matrix) - 1));
@@ -330,13 +335,6 @@ static GstFlowReturn trigger_generator(GSTLALStringTriggergen *element, GstBuffe
 	else
 		gst_buffer_remove_all_memory(outbuf);
 
-	/*
-	 * obtain PTS and DURATION of output buffer.
-	 */
-
-	GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
-	GST_BUFFER_DURATION(outbuf) = element->t0 + gst_util_uint64_scale_int_round(offset + length - (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info)) - GST_BUFFER_PTS(outbuf);
-
 	GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf) + ntriggers;
 
 	return GST_FLOW_OK;
@@ -481,7 +479,6 @@ static gboolean start(GstBaseTransform *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;
 	}
 
@@ -511,43 +508,102 @@ static GstFlowReturn transform(GstBaseTransform *trans, GstBuffer *inbuf, GstBuf
 {
 	GSTLALStringTriggergen *element = GSTLAL_STRING_TRIGGERGEN(trans);
 	guint64 length;
+	guint64 offset;
 	GstFlowReturn result;
 
+	/* FIXME:  this code isn't setting the output offset to anything meaningful */
+
 	g_assert(GST_BUFFER_PTS_IS_VALID(inbuf));
 	g_assert(GST_BUFFER_DURATION_IS_VALID(inbuf));
 	g_assert(GST_BUFFER_OFFSET_IS_VALID(inbuf));
 	g_assert(GST_BUFFER_OFFSET_END_IS_VALID(inbuf));
-	
+
 	if(GST_BUFFER_IS_DISCONT(inbuf) || GST_BUFFER_OFFSET(inbuf) != element->next_in_offset || !GST_CLOCK_TIME_IS_VALID(element->t0)) {
 		gst_audioadapter_clear(element->adapter);
 		element->t0 = GST_BUFFER_PTS(inbuf);
 		element->offset0 = GST_BUFFER_OFFSET(inbuf);
-		element->next_out_offset = GST_BUFFER_OFFSET(inbuf);
 	} else if(!gst_audioadapter_is_empty(element->adapter))
 		g_assert_cmpuint(GST_BUFFER_PTS(inbuf), ==, gst_audioadapter_expected_timestamp(element->adapter));
 	element->next_in_offset = GST_BUFFER_OFFSET_END(inbuf);
 
+	/* check that autocorrelation vector has odd number of samples.
+	 * NOTE:  autocorrelation_length() returns 0 if the
+	 * autocorrelation_matrix is not set, so this g_assert also tests
+	 * for a missing autocorrelation_matrix.  in set_property(), if the
+	 * conversion from GValueArray fails the matrix will be left set to
+	 * NULL, so this is also catching those failures. */
+	g_assert(autocorrelation_length(element->autocorrelation_matrix) & 1);
+
 	/*
 	 * gap logic
 	 */
 
 	gst_buffer_ref(inbuf);
 	gst_audioadapter_push(element->adapter, inbuf);
-	if (!GST_BUFFER_FLAG_IS_SET(inbuf, GST_BUFFER_FLAG_GAP)) {
-		/* not gaps */
+	length = get_available_samples(element);
+	offset = gst_audioadapter_offset(element->adapter);
+	g_mutex_lock(&element->bank_lock);
+	if(length < autocorrelation_length(element->autocorrelation_matrix)) {
+		/* not enough SNR to search for triggers */
+		GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
+		GST_BUFFER_DURATION(outbuf) = 0;
+		GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf) + 0 /* ntriggers */;
+		result = GST_FLOW_OK;
+	} else if(!GST_BUFFER_FLAG_IS_SET(inbuf, GST_BUFFER_FLAG_GAP)) {
+		/* have enough SNR to make triggers, and this buffer is not a gap */
 		result = trigger_generator(element, outbuf);
-	} else {
-		/* gaps */
-		length = get_available_samples(element);
-		element->next_out_offset += length;
-		gst_audioadapter_flush_samples(element->adapter, length);
-		GST_BUFFER_PTS(outbuf) = element->t0 + gst_util_uint64_scale_int_round(element->next_out_offset - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info));
-		GST_BUFFER_DURATION(outbuf) = gst_util_uint64_scale_int_round(length, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info));
-		/* we get no triggers, so outbuf offset is unchanged */
-		GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf);
+	} else if(!exists_unrealized_triggers(element)) {
+		/* this is a gap, there are no unrealized triggers waiting */
+		gst_audioadapter_flush_samples(element->adapter, length - (autocorrelation_length(element->autocorrelation_matrix) - 1));
+		GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
+		GST_BUFFER_DURATION(outbuf) = element->t0 + gst_util_uint64_scale_int_round(offset + length - (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info)) - GST_BUFFER_PTS(outbuf);
+		GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf) + 0 /* ntriggers */;
 		GST_BUFFER_FLAG_SET(outbuf, GST_BUFFER_FLAG_GAP);
 		result = GST_FLOW_OK;
+	} else {
+		/* this is a gap, there are still unrealized triggers waiting */
+		/* FIXME:  there are really two cases:  we have unrealized
+		 * triggers waiting and the input gap buffer will push them
+		 * all beyond the clustering time (forcing them all to be
+		 * cleared), or it isn't (some might remain for next time).
+		 * in the former case, we can produce two buffers:  a
+		 * non-gap buffer containing triggers, followed by a gap
+		 * buffer representing the period of input gap data which
+		 * is guaranteed not to contain triggers.  for now we are
+		 * lazy and just produce one non-gap buffer, which might or
+		 * might not contain triggers */
+		SnglBurst *triggers = NULL;
+		guint ntriggers = 0;
+		LIGOTimeGPS t;
+
+		/* set output buffer properties.  must be done before clearing triggers */
+		GST_BUFFER_PTS(outbuf) = buffer_pts(element, offset);
+		GST_BUFFER_DURATION(outbuf) = element->t0 + gst_util_uint64_scale_int_round(offset + length - (autocorrelation_length(element->autocorrelation_matrix) - 1) / 2 - element->offset0, GST_SECOND, GST_AUDIO_INFO_RATE(&element->audio_info)) - GST_BUFFER_PTS(outbuf);
+
+		/* has the clustering time passed for any of the unrealized triggers? */
+		XLALINT8NSToGPS(&t, GST_BUFFER_PTS(outbuf) + GST_BUFFER_DURATION(outbuf));
+		for(gint channel = 0; channel < element->num_templates; channel++) {
+			if(element->bank[channel].snr != 0. && XLALGPSDiff(&t, &element->last_time[channel]) > element->cluster){
+				/* this channel has made a trigger */
+				triggers = g_renew(SnglBurst, triggers, ntriggers + 1);
+				triggers[ntriggers++] = element->bank[channel];
+				element->bank[channel].snr = 0.0;
+				element->bank[channel].chisq = 0.0;
+				element->bank[channel].chisq_dof = 0.0;
+			}
+		}
+
+		gst_audioadapter_flush_samples(element->adapter, length - (autocorrelation_length(element->autocorrelation_matrix) - 1));
+
+		if(ntriggers)
+			gst_buffer_replace_all_memory(outbuf, gst_memory_new_wrapped(GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS, triggers, ntriggers * sizeof(*triggers), 0, ntriggers * sizeof(*triggers), triggers, g_free));
+		else
+			gst_buffer_remove_all_memory(outbuf);
+
+		GST_BUFFER_OFFSET_END(outbuf) = GST_BUFFER_OFFSET(outbuf) + ntriggers;
+		result = GST_FLOW_OK;
 	}
+	g_mutex_unlock(&element->bank_lock);
 
 	/*
 	 * done
@@ -739,6 +795,7 @@ static void gstlal_string_triggergen_class_init(GSTLALStringTriggergenClass *kla
 	transform_class->transform = GST_DEBUG_FUNCPTR(transform);
 	transform_class->transform_caps = GST_DEBUG_FUNCPTR(transform_caps);
 	transform_class->start = GST_DEBUG_FUNCPTR(start);
+	/* FIXME: add a stop method to push any final unrealized triggers */
 	transform_class->prepare_output_buffer = GST_DEBUG_FUNCPTR(prepare_output_buffer);
 
 	g_object_class_install_property(
diff --git a/gstlal-burst/gst/lal/gstlal_string_triggergen.h b/gstlal-burst/gst/lal/gstlal_string_triggergen.h
index 3f13ceec70e42d8c9650ab589754ff89bef3f378..245f9fbc150352bde39ca4153d58f3449bbb2645 100644
--- a/gstlal-burst/gst/lal/gstlal_string_triggergen.h
+++ b/gstlal-burst/gst/lal/gstlal_string_triggergen.h
@@ -54,7 +54,6 @@ typedef struct {
 	GstClockTime t0;
 	guint64 offset0;
 	guint64 next_in_offset;
-	guint64 next_out_offset;
 	gboolean need_discont;
 
 	/*