package VQVAD;

import java.util.Iterator;

import org.apache.commons.collections.buffer.CircularFifoBuffer;

import edu.cmu.sphinx.frontend.BaseDataProcessor;
import edu.cmu.sphinx.frontend.Data;
import edu.cmu.sphinx.frontend.DataEndSignal;
import edu.cmu.sphinx.frontend.DataProcessingException;
import edu.cmu.sphinx.frontend.endpoint.SpeechClassifiedData;

/**
 * Since VQVAD is quite good at discerning noise and speech there
 * are bound to be gaps between utterances due to speech pauses.
 *
 * This filter attempts to smooth gaps of a configurable width.
 */
public class GapSmoothing extends BaseDataProcessor {

	protected int gapWidthInFrames = 10;
	protected CircularFifoBuffer frameBuffer;

	@SuppressWarnings("serial")
	protected Data skip = new Data() {};

	protected boolean dataEndSeen = false;
	protected DataEndSignal des = null;

	public GapSmoothing() {
		frameBuffer = new CircularFifoBuffer(gapWidthInFrames+1);
	}

	@Override
	public Data getData() throws DataProcessingException {
		Data d;
		do {
			d = process(getPredecessor().getData());
		} while (d == skip);
		return d;
	}

	protected Data process(Data d) {
		if (d instanceof DataEndSignal) {
			dataEndSeen = true;
			des = (DataEndSignal) d;
		}

		// Drain buffer and emit DataEndSignal.
		if (dataEndSeen) {
			if (frameBuffer.size() > 0) {
				SpeechClassifiedData v = (SpeechClassifiedData) frameBuffer.get();
				frameBuffer.remove();
				return v;
			} else {
				return des;
			}
		}

		if (!(d instanceof SpeechClassifiedData)) {
			return d;
		}

		SpeechClassifiedData sd = (SpeechClassifiedData) d;
		frameBuffer.add(sd);

		// Return early as we cannot do smoothing without a full buffer.
		if (!frameBuffer.isFull()) {
			return skip; //return (SpeechClassifiedData) frameBuffer.get();
		}

		@SuppressWarnings("unchecked")
		Iterator<SpeechClassifiedData> iterator = frameBuffer.iterator();

		// First and second frame forming a potential gap of size gapWidthInFrames.
		SpeechClassifiedData first, second;

		first = iterator.next();

		for (int i=gapWidthInFrames-1; i > 0; i--) {
			iterator.next();
		}

		second = iterator.next();

		// Both are speech. That means there is a small enough gap so mark all frames in between as speech.
		if (first.isSpeech() && second.isSpeech()) {
			fillGap();
		}

		return (SpeechClassifiedData) frameBuffer.get();
	}

	protected void fillGap() {
		@SuppressWarnings("unchecked")
		Iterator<SpeechClassifiedData> iterator = frameBuffer.iterator();

		for (int i=gapWidthInFrames; i > 0; i--) {
			SpeechClassifiedData sd = iterator.next();
			sd.setSpeech(true);
		}
	}

}

@@ -616,16 +616,76 @@
 ssor %7B%0A%0A
+%09public final static int DEFAULT_GAP_WIDTH_IN_FRAMES = 10;%0A%0A
 %09protect
@@ -710,18 +710,43 @@
 rames = 
-10
+DEFAULT_GAP_WIDTH_IN_FRAMES
 ;%0A%09prote
@@ -954,69 +954,199 @@
 ing(
-) %7B%0A%09%09frameBuffer = new CircularFifoBuffer(gapWidthInFrames+1
+int gapWidthInFrames) %7B%0A%09%09this.gapWidthInFrames = gapWidthInFrames;%0A%09%09frameBuffer = new CircularFifoBuffer(gapWidthInFrames+1);%0A%09%7D%0A%0A%09public GapSmoothing() %7B%0A%09%09this(DEFAULT_GAP_WIDTH_IN_FRAMES
 );%0A%09

Make gaps in GapSmoothing configurable