
# If step is 0.01 as one might expect, then HRest says that all
# examples are 2-states long, and this plays havoc with a
# single-state phoneme definition that does not include looping.
# This may be a HTK bug.
STEP = 0.006
FINALDUR = 0.500

def prep_phones(pa, pmap, phonology, is_manual_transcription=False,
				insert_optional_phones=False, datasource='?'):
	"""This maps from the string of phonemes that the
	human or dictionary produces into a string of
	C, V, S and transition phonemes that the ASR systems wants.
	It inserts opportunities for liason where possible.
	@param is_manual_transcription: are pauses transcribed?  Otherwise, the
		system assumes that they may appear at word boundaries.
	@type is_manual_transcription: bool
	@param pa: The transcribed phoneme sequence
	@type pa: C{list(str)}
	@param pmap: A mapping from input phonemes to output phonemes.
		Basically, every input phone gets mapped into 'C', 'V',
		'_sil_', or ''.   (Blanks indicate tones or other things that
		aren't markers for segments.)
	@type pmap: C{dict(str:str)}
	"""
	#: the output list of phonemes gets accumulated here.
	phones = [GP.phone_c('_sil_', s=0.0)]
	#: are we in the middle of a garbled section?
	garble = False
	#: counters are just used for trouble detection.
	counters = dictops.dict_of_accums()
	#: Have we just seen an end-of-word marker?
	iwp = False
	last = GP.phone_c('#')
	for p in pa:
		assert not phones[-1].startswith('IPP_')
		assert not phones[-1].startswith('IWP_')
		try:
			pm = pmap[p.lbl]
		except KeyError:
			pm = None
		if p.lbl in ['#', '##', '_sil_']:
			counters.add('S', 1)
			if phones[-1].lbl in Normal:
				phones.append(GP.phone_c('IPP_%s%s' % (phones[-1].lbl, 'S'), dur = STEP))
				counters.add(phones[-1].lbl, 1)
				phones.append(GP.phone_c('S', s=p.s, e=p.e))
				last = GP.phone_c('S')
			iwp = False
		elif p.startswith('#w') or p.startswith('#p'):
			# Word or punctuation boundaries.
			garble = False
			if phones[-1].lbl in Normal:
				iwp = True
		elif p.startswith('#['):
			# Beginning of garble/correction
			garble = True
		elif p.startswith('#]') or p.startswith(']#'):
			# end of garble/correction
			iwp = True
			garble = False
		elif p.startswith('#'):
			# Other non-phoneme markers.
			garble = False
		elif pm is not None and pm:
			# All normal phonemes.
			assert phones[-1].lbl in Normal or phones[-1].lbl=='S'
			assert not (phones[-1].lbl=='S' and pm=='S')
			assert pm != 'S'
			if iwp:
				counters.add('#w', 1)
			if garble:
				pm = 'X'
			if iwp:
				if is_manual_transcription:
					if isC(phonology, last.lbl) and isC(phonology, p.lbl):
						# Liason here.
						phones.append(GP.phone_c('IPP_%s%sL' % (phones[-1].lbl, pm), dur=STEP))
					else:
						phones.append(GP.phone_c('IPP_%s%s' % (phones[-1].lbl, pm), dur=STEP))
				else:
					# Automatically generated transcription
					# Note: Liason is hidden in the definition of IWP_VV.
					phones.append(GP.phone_c('IWP_%s%s' % (phones[-1].lbl, pm), dur=STEP))
			else:
				# Not a word boundary.
				phones.append(GP.phone_c('IPP_%s%s' % (phones[-1].lbl, pm), dur=STEP))
			counters.add(phones[-1].lbl, 1)
			iwp = False
			phones.append(GP.phone_c(pm, s=p.s, e=p.e))
			last = p
			counters.add(pm, 1)
			counters.add('*', 1)
		elif pm is None:
			# Legal non-phonemes like tone markers.
			pass
		else:
			raise BadPhonesError("Unrecognized phone: %s" % p, datasource)
	if len(phones) < 3:
		raise BadPhonesError("Too few phones: %d"
					% counters.get('*',0),
					pa, datasource)
	if not is_manual_transcription and counters.get('#w', 0) < (counters.get('*',0)-7)//15:
		raise BadPhonesError("Too few word boundaries: %d in a string of %d phones"
					% (counters.get('#w',0), counters.get('*',0)),
					pa, datasource)
	if counters.get('C',0) < (counters.get('*',0)-5)//4:
		raise BadPhonesError("Too few consonants: %d in a string of %d phones"
					% (counters.get('C',0), counters.get('*',0)),
					pa, datasource)
	if counters.get('V',0) < (counters.get('*',0)-5)//4:
		raise BadPhonesError("Too few vowels: %d in a string of %d phones"
					% (counters.get('V',0), counters.get('*',0)),
					pa, datasource)
	if counters.get('X',0) > (counters.get('*',0)+5)//10:
		raise BadPhonesError("Too many garbles: %d in a string of %d phones"
					% (counters.get('X',0), counters.get('*',0)),
					pa, datasource)
	if phones[-1].lbl != 'S':
		if phones[-1].lbl in Normal:
			phones.append(GP.phone_c('IPP_%s%s' % (phones[-1].lbl, 'S'), dur=STEP))
		phones.append(GP.phone_c('S', dur=FINALDUR))

	replace_X_with_XX(phones)
	if is_manual_transcription:
		fill_NaNs_in_phone_sequence(phones)
	return phones

