#!/usr/bin/env python

"""Duration estimator for speech.

It takes a local spectrum, bins it onto the Bark scale,
converts to perceptual loudness (via **E).
Then, it computes a measure of how far you can go
from each point before the spectrum changes too much.
"""

import math as M
import numpy
from gmisclib import die
from gmisclib import cache as CC
from gmisclib import gpkmisc
from gpk_voicing import fv_pdur as FVP
import gpk_voicing.fv_misc as FVM

TYP_DUR = 0.1	# Seconds


def pdur_guts(s, t, dir, Dt, C):
	"""t is an integer; an index into the data.
	S is the normalized perceptual spectrum."""
	n = len(s)
	assert s.shape[1] < 200, "Implausibly long feature vector.  Is s transposed?"
	sumdiff = 0.0
	len_sum = 0.5	# The integral for the i==t case is trivial.
	ctr_sum = 0.125*dir	# The integral for i==t is trivial.
	i = t + dir
	while i>=0 and i<n and sumdiff<8:
		delta_diff = Dt * C * numpy.square( numpy.absolute(s[i]-s[t])).sum()

		# We assume that the spectrum is s[i] in the
		# region from i-0.5 to i+0.5, and compute the
		# integral( M.exp(-integral((s(t0)-s(t''))**2 from 0 to t', dt''))
		# 	from 0 to i+0.5, dt')
		# The constancy of s over the interval (i-0.5,i+0.5)
		# makes the inner integral piecewise linear.
		# Sumdiff holds integral((s(t0)-s(t''))**2 from 0 to i-0.5)
		if delta_diff <= 0:
			# assume delta_diff = 0
			lsd = M.exp(-sumdiff)
			len_sum += lsd
			# integral( (t''-t0)*M.exp(-integral((s(t0)-s(t''))**2 from 0 to t', dt''))
			# 	from 0 to i+0.5, dt')
			slopeint = M.exp(-sumdiff) * 0.5
			ctr_sum += (i-t-0.5*dir)*lsd + dir * slopeint
			i += dir
		else:
			lsd = M.exp(-sumdiff) * (1.0-M.exp(-delta_diff))/delta_diff
			len_sum += lsd
			# integral( (t''-t0)*M.exp(-integral((s(t0)-s(t''))**2 from 0 to t', dt''))
			# 	from 0 to i+0.5, dt')
			slopeint = M.exp(-sumdiff)	\
					* (1.0-M.exp(-delta_diff)*(1+delta_diff))/delta_diff**2
			ctr_sum += (i-t-0.5*dir)*lsd + dir * slopeint
			i += dir
			sumdiff += delta_diff
		# Now, sumdiff holds integral((s(t0)-s(t''))**2 from 0 to i+0.5)
	# print 't=', t, sum
	return (len_sum*Dt, ctr_sum*Dt)


def pdur(data, dt, Dt, out, c=None, lfac=None, Nsv=None, cache_info=None):
	"""Note that if C{cache_info} is non-Null, it *must* uniquely identify the data.
	"""
	# print 's.n=', data.shape
	if Nsv is None:
		Nsv = FVM.NSV
	o = None
	ci = None
	if cache_info is not None:
		assert isinstance(cache_info, CC.cache_info)
		ci = cache_info.addinfo(dt, Dt, c, Nsv, 'pseudoduration', out)
		try:
			o = ci.load()
		except ci.Errors:
			pass

	if o is None:
		sp, descr, Dt, t0 = FVP.feature_vec(data, dt, Dt, Nsv=Nsv, cache_info=cache_info)
		nfvc = len(sp)
		sp = numpy.transpose(sp)
		assert sp.shape[1] == nfvc
	
		ns = sp.shape[0]
		assert abs(ns*Dt - data.shape[0]*dt) < 0.1*ns*Dt
		o = numpy.zeros((ns,), numpy.float)
		if out=='pseudoduration' or out=='log(pseudoduration)':
			for i in range(ns):
				plp, pcp = pdur_guts(sp, i, 1, Dt, c)
				plm, pcm = pdur_guts(sp, i, -1, Dt, c)
				o[i] = plp + plm
		elif out=='center_time':
			for i in range(ns):
				plp, pcp = pdur_guts(sp, i, 1, Dt, c)
				plm, pcm = pdur_guts(sp, i, -1, Dt, c)
				o[i] = Dt*(pcp + pcm)/(plp+plm)
		else:
			die.die('Whoops: out=%s' % out)
	
		if ci is not None:
			ci.dump(o)

	if out == 'pseudoduration':
		numpy.multiply(o, lfac, o)
	elif out == 'log(pseudoduration)':
		o = numpy.log((lfac/TYP_DUR)*o)

	return o
