#!/usr/bin/env python


import Numeric as Num
import random
import math

import gpkimgclass

import sys
import os
sys.path.append(os.environ['OXIVOICE'])
import power

EXTRASMOOTH = 0.040
W = 3
DEF_THRESH = 0.07
DEF_PADDING = 0.050
HARD_THRESHOLD = 0
MINW = 0.050	# THE SHORTEST POSSIBLE UTTERANCE, DEFAULT.
MAXSPIKE = 0.015	# The longest possible non-speech spike

def eval_err(above_thresh, istart, iend, been_there):
	"""This compares the observed above_thresh to a model
	which is one between istart and iend, and zero outside.
	That corresponds to a single region of voicing.
	"""
	if( (istart, iend) in been_there):
		return been_there[(istart,iend)]

	if istart >= iend or istart<0 or iend>above_thresh.shape[0]:
		rv = 100000
	else:
		early = Num.sum(above_thresh[:istart])
		late = Num.sum(above_thresh[iend:])
		mid = Num.sum((1-above_thresh)[istart:iend])
		rv = early + mid + late
	been_there[ (istart, iend) ] = rv
	return rv


def find_best_fit(above_thresh, iminw):
	"""This finds the ends of the single best-fit region
	of speech.   The model assumes speech in the middle,
	and silence outside.
	"""
	been_there = {}		# This is a cache for the results of eval_err.
	ipass = 0
	n = above_thresh.shape[0]
	istart = n/3
	iend = n - n/3
	e = eval_err(above_thresh, istart, iend, been_there)
	endgame = 0
	passes = 0
	while e>0 and (endgame<4*W*W or passes<n):
		passes += 1
		if endgame < W*W  or passes<n:
			if iend > iminw:
				tmp1 = random.randrange(0, iend-iminw)
				et = eval_err(above_thresh, tmp1, iend, been_there)
				if et == e:
					istart = tmp1
				elif et < e:
					e = et
					istart = tmp1
					endgame = 0
			if istart+iminw < n:
				tmp2 = random.randrange(istart+iminw, n)
				et = eval_err(above_thresh, istart, tmp2, been_there)
				if et == e:
					iend = tmp2
				elif et < e:
					e = et
					iend = tmp2
					endgame = 0

		tmp1 = random.randrange(istart-W, istart+W+1)
		tmp2 = random.randrange(iend-W, iend+W+1)
		if tmp2 - tmp1 >= iminw:
			et = eval_err(above_thresh, tmp1, tmp2, been_there)
			if et == e:
				istart = tmp1
				iend = tmp2
			elif et < e:
				endgame += 1
				e = et
				istart = tmp1
				iend = tmp2
			else:
				endgame += 1
	return (istart, iend)


if HARD_THRESHOLD:
	def find_trim_times_rms(x, colname, padding, threshold, minw=MINW):
		xx = power.smooth(x.column(colname), x.dt(), x.dt(), EXTRASMOOTH)
	
		xpeak = xx[Num.argmax(xx)]
		xcut = threshold * xpeak
		iminw = int(round(minw/x.dt()))
	
		istart, iend = find_best_fit( Num.greater(xx, xcutL), iminw )
		return (max(x.time(istart)-padding, x.start()),
			min(x.time(iend)+padding, x.end()))
else:
	def find_trim_times_rms(x, colname, padding, threshold, minw=MINW):
		xx = power.smooth(x.column(colname), x.dt(), x.dt(), EXTRASMOOTH)
	
		xpeak = xx[Num.argmax(xx)]
		xexp = math.log(0.5)/math.log(threshold)
		iminw = int(round(minw/x.dt()))
	
		istart, iend = find_best_fit( (xx/xpeak)**xexp, iminw )
		return (max(x.time(istart)-padding, x.start()),
			min(x.time(iend)+padding, x.end()))



def find_ref_ampl_guts(x, istart, iend, ispike):
	rms = x[istart:iend]
	return Num.sort(rms)[iend - istart - ispike]


def find_ref_ampl(x, colname, tstart, tend):
	istart = int(round(x.t_index(tstart)))
	iend = int(round(x.t_index(tend)))
	rms = x.column(colname)[istart:iend]
	maxspike = int(round(MAXSPIKE/x.dt()))
	return Num.sort(rms)[iend - istart - maxspike]


def find_trim_times(fbase, padding=DEF_PADDING, threshold=DEF_THRESH, suffix='rms.dat', minw=MINW):
	x = gpkimgclass.read('%s.%s' % (fbase, suffix))
	tstart, tend =  find_trim_times_rms(x, 'rms', padding, threshold, minw=minw)
	ref_ampl = find_ref_ampl(x, 'rms', tstart, tend)
	return (tstart, tend, ref_ampl)
