"""Dectalk voice definitions using ACSS.

This module encapsulates Dectalk-specific voice definitions.  It
maps device-independent ACSS voice definitions into appropriate
Dectalk voice parameter settings.

"""

__id__ = "$Id: dectalk.py 3535 2005-11-17 14:32:59Z raman $"
__author__ = "$Author: raman $"
__version__ = "$Revision: 3535 $"
__date__ = "$Date: 2005-11-17 06:32:59 -0800 (Thu, 17 Nov 2005) $"
__copyright__ = "Copyright (c) 2005 T. V. Raman"
__license__ = "LGPL"

_defined_voices = {}

# Map from ACSS dimensions to Dectalk settings:

_table ={}
#family codes:

_table['family'] = {
    'male' : ' :np ',
    'paul' :  ':np',
    'man' :  ':nh',
    'man' : ' :nh ',
    'dennis' :  ':nd',
    'frank' :  ':nf',
    'betty' :  ':nb',
    'female' : ' :nb ',
    'ursula' :  ':nu',
    'wendy' :  ':nw',
    'rita' :  ':nr',
    'kid' :  ':nk',
    'child' : ' :nk '
    }

# average-pitch :
# Average pitch for standard male voice is 122hz --this is mapped to
# a setting of 5.
# Average pitch varies inversely with speaker head size --a child
# has a small head and a higher pitched voice.
# We change parameter head-size in conjunction with average pitch to
# produce a more natural change on the Dectalk.

#male average pitch

def _update_map(table, key, format,  settings):
    """Internal function to update acss->synth mapping."""
    table[key] ={}
    for setting  in  settings:
        _table[key][setting[0]] = format % setting[1:]

_male_ap = [
    (0, 96, 115),
    (1, 101, 112),
    (2, 108, 109),
    (3, 112, 106),
    (4, 118, 103, ),
    (5, 122, 100),
    (6, 128, 98),
    (7, 134, 96),
    (8, 140, 94),
    (9, 147, 91)]

_update_map(_table, ('male', 'average-pitch'),
            " ap %s hs %s ",  _male_ap)

#Man  has a big head --and a lower pitch for the middle setting
_man_ap = [
    (0, 50, 125),
    (1, 59, 123),
    (2, 68, 121),
    (3, 77, 120),
    (4, 83, 118, ),
    (5, 89, 115),
    (6, 95, 112),
    (7, 110, 105),
    (8, 125, 100),
    (9, 140, 95)
    ]

_update_map(_table,('man', 'average-pitch'),
            " ap %s hs %s ",_man_ap)

_female_ap = [
    (0, 160, 115),
    (1, 170, 112),
    (2, 181, 109),
    (3, 192, 106),
    (4, 200, 103, ),
    (5, 208, 100),
    (6, 219, 98),
    (7, 225, 96),
    (8, 240, 94),
    (9, 260, 91)
    ]

_update_map(_table, ('female', 'average-pitch'),
            " ap %s hs %s ",_female_ap)

# pitch-range for male:

#  Standard pitch range is 100 and is  mapped to
# a setting of 5.
# A value of 0 produces a flat monotone voice --maximum value of 250
# produces a highly animated voice.
# Additionally, we also set the assertiveness of the voice so the
# voice is less assertive at lower pitch ranges.

_male_pr = [
    (0, 0, 0),
    (1, 20, 10),
    (2, 40, 20),
    (3, 60, 30),
    (4, 80, 40, ),
    (5, 100, 50, ),
    (6, 137, 60),
    (7, 174, 70),
    (8, 211, 80),
    (9, 250, 100),
    ]

_update_map(_table, ('male', 'pitch-range'),
            " pr %s as %s ", _male_pr)

_man_pr = [
    (0, 0, 0),
    (1, 16, 20),
    (2, 32, 40),
    (3, 48, 60),
    (4, 64, 80, ),
    (5, 80, 100, ),
    (6, 137, 100),
    (7, 174, 100),
    (8, 211, 100),
    (9, 250, 100)
    ]

_update_map(_table, ('man', 'pitch-range'),
            " pr %s as %s ", _man_pr)

_female_pr = [
    (0, 0, 0),
    (1, 50, 10),
    (2, 80, 20),
    (3, 100, 25),
    (4, 110, 30, ),
    (5, 140, 35),
    (6, 165, 57),
    (7, 190, 75),
    (8, 220, 87),
    (9, 250, 100)
    ]

_update_map(_table, ('female', 'pitch-range'),
            " pr %s as %s ", _female_pr)

# Stress:

# On the Dectalk we vary four parameters
# The hat rise which controls the overall shape of the F0 contour
# for sentence level intonation and stress,
# The stress rise that controls the level of stress on stressed
# syllables,
# the baseline fall for paragraph level intonation
# and the quickness --a parameter that controls whether the final
# frequency targets are completely achieved in the phonetic transitions.


_male_stress =[
    (0, 0, 0, 0, 0),
    (1, 3, 6, 20, 3),
    (2, 6, 12, 40, 6),
    (3, 9, 18, 60, 9, ),
    (4, 12, 24, 80, 14),
    (5, 18, 32, 100, 18),
    (6, 34, 50, 100, 20),
    (7, 48, 65, 100, 35),
    (8, 63, 82, 100, 60),
    (9, 80, 90, 100, 40)
]

_update_map(_table, ('male', 'stress'),
            " hr %s sr %s qu %s bf %s ", _male_stress)

_man_stress = [
    (0, 0, 0, 0, 0),
    (1, 4, 6, 2, 2, ),
    (2, 8, 12, 4, 4, ),
    (3, 12, 18, 6, 6, ),
    (4, 16, 24, 8, 8, ),
    (5, 20, 30, 10, 9),
    (6, 40, 48, 32, 16),
    (7, 60, 66, 54, 22),
    (8, 80, 78, 77, 34),
    (9, 100, 100, 100, 40)
    ]


_update_map(_table, ('man', 'stress'),
            " hr %s sr %s qu %s bf %s ", _man_stress)

_female_stress = [
    (0, 1, 1, 0, 0),
    (1, 3, 4, 11, 0),
    (2, 5, 8, 22, 0),
    (3, 8, 12, 33, 0, ),
    (4, 11, 16, 44, 0),
    (5, 14, 20, 55, 0),
    (6, 35, 40, 65, 10),
    (7, 56, 80, 75, 20),
    (8, 77, 90, 85, 30),
    (9, 100, 100, 100, 40)

    ]

_update_map(_table, ('female', 'stress'),
            " hr %s sr %s qu %s bf %s ", _female_stress)

#richness

# Smoothness and richness vary inversely.
# a  maximally smooth voice produces a quieter effect
# a rich voice is "bright" in contrast.


_male_richness = [
    (0, 0, 100),
    (1, 14, 80),
    (2, 28, 60),
    (3, 42, 40),
    (4, 56, 30),
    (5, 70, 28),
    (6, 60, 24 ),
    (7, 70, 16),
    (8, 80, 8),
    (9, 100, 0)
    ]

_update_map(_table, ('male', 'richness'),
            " ri %s sm %s " ,_male_richness)

_man_richness = [
    (0, 100, 0),
    (1, 96, 3),
    (2, 93, 6),
    (3, 90, 9),
    (4, 88, 11),
    (5, 86, 12),
    (6, 60, 24, ),
    (7, 40, 44),
    (8, 20, 65),
    (9, 0, 70)
    ]

_update_map(_table, ('man', 'richness'),
            " ri %s sm %s " , _man_richness)

_female_richness = [
    (0, 0, 100),
    (1, 8, 76),
    (2, 16, 52),
    (3, 24,28),
    (4, 32, 10),
    (5, 40, 4),
    (6, 50, 3),
    (7, 65, 3),
    (8, 80,  2),
    (9, 100, 0)
    ]

_update_map(_table, ('female', 'richness'),
            " ri %s sm %s ", _female_richness)
def getrate(r):    return 180 + 4*r

def getvoicelist(): return _table['family'].keys()

def getvoice(acss):
    """Memoized function that returns  synthesizer code for
    specified  ACSS setting.
    Synthesizer code is a tupple of the form (open,close)
    where open sets the voice, and close resets it."""
    
    name=acss.name()
    if name in _defined_voices: return _defined_voices[name]
    _defined_voices[name] =acss2voice(acss)
    return _defined_voices[name]

def acss2voice(acss):
    """Return synthesizer code."""
    code = ""
    family ='male'
    if 'family'in acss:
        family = acss['family']
        code += _table['family'][family]
    if 'rate' in acss: code += " :ra %s" % getrate(acss['rate'])
    if 'punctuations' in acss: code += " :punc %s" %acss['punctuations']
    voice = ""
    dv = ""
    for d in ['average-pitch', 'pitch-range',
              'richness', 'stress']:
        if d in acss:voice += _table[(family, d)][acss[d]]
    if voice: dv = " :dv %s" % voice
    if code or voice: code = "[%s  %s]" % (code, dv)
    return (code, " [:np] ")
