praatio.klattgrid API documentation

# praatio.klattgrid

Functions for reading/writing/manipulating klattgrid files

A klattgrid can be used for speech synthesis/resynthesis. For more information on the praat klattgrid: http://www.fon.hum.uva.nl/praat/manual/KlattGrid.html

There are three kinds of data types in a klattgrid: null tiers (contain no data points -- seem to function as headers for a set of regular tiers) regular tiers embedded tiers

In this code: null tiers and regular tiers are both represented by KlattPointTier

embedded tiers contain tiers of tiers (3 layers) A KlattContainerTier contains a list of KlattIntermediateTiers which contains a list of KlattSubPointTiers. Only the KlattSubPointTiers contain any points

see examples/klatt_resynthesis.py

View Source

      1"""Functions for reading/writing/manipulating klattgrid files
      2
      3A klattgrid can be used for speech synthesis/resynthesis.
      4For more information on the praat klattgrid:
      5http://www.fon.hum.uva.nl/praat/manual/KlattGrid.html
      6
      7There are three kinds of data types in a klattgrid:
      8null tiers (contain no data points -- seem to function as headers for a
      9            set of regular tiers)
     10regular tiers
     11embedded tiers
     12
     13In this code:
     14null tiers and regular tiers are both represented by KlattPointTier
     15
     16embedded tiers contain tiers of tiers (3 layers)
     17A KlattContainerTier contains a list of KlattIntermediateTiers which
     18contains a list of KlattSubPointTiers.  Only the KlattSubPointTiers contain
     19any points
     20
     21see **examples/klatt_resynthesis.py**
     22"""
     23
     24import io
     25from os.path import join
     26from typing import List, Tuple, Optional
     27
     28from praatio.data_classes.klattgrid import (
     29    Klattgrid,
     30    KlattPointTier,
     31    KlattContainerTier,
     32    KlattSubPointTier,
     33    KlattIntermediateTier,
     34)
     35from praatio.utilities import utils
     36
     37
     38def openKlattgrid(fnFullPath: str) -> Klattgrid:
     39    try:
     40        with io.open(fnFullPath, "r", encoding="utf-16") as fd:
     41            data = fd.read()
     42    except UnicodeError:
     43        with io.open(fnFullPath, "r", encoding="utf-8") as fd:
     44            data = fd.read()
     45    data = data.replace("\r\n", "\n")
     46
     47    # Right now, can only open normal klatt grid and not short ones
     48    kg = _openNormalKlattgrid(data)
     49
     50    return kg
     51
     52
     53def wavToKlattgrid(
     54    praatEXE: str,
     55    inputFullPath: str,
     56    outputFullPath: str,
     57    timeStep: float = 0.005,
     58    numFormants: int = 5,
     59    maxFormantFreq: float = 5500.0,
     60    windowLength: float = 0.025,
     61    preEmphasis: float = 50.0,
     62    pitchFloor: float = 60.0,
     63    pitchCeiling: float = 600.0,
     64    minPitch: float = 50.0,
     65    subtractMean: bool = True,
     66    scriptFN: Optional[str] = None,
     67) -> None:
     68    """Extracts the klattgrid from a wav file
     69
     70    The default values are the same as the ones used in praat
     71    """
     72
     73    if subtractMean is True:
     74        subtractMeanStr = "yes"
     75    else:
     76        subtractMeanStr = "no"
     77
     78    if scriptFN is None:
     79        scriptFN = join(utils.scriptsPath, "sound_to_klattgrid.praat")
     80
     81    utils.runPraatScript(
     82        praatEXE,
     83        scriptFN,
     84        [
     85            inputFullPath,
     86            outputFullPath,
     87            timeStep,
     88            numFormants,
     89            maxFormantFreq,
     90            windowLength,
     91            preEmphasis,
     92            pitchFloor,
     93            pitchCeiling,
     94            minPitch,
     95            subtractMeanStr,
     96        ],
     97    )
     98
     99
    100def resynthesize(
    101    praatEXE: str,
    102    wavFN: str,
    103    klattFN: str,
    104    outputWavFN: str,
    105    doCascade: bool = True,
    106    scriptFN: Optional[str] = None,
    107) -> None:
    108    if doCascade:
    109        method = "Cascade"
    110    else:
    111        method = "Parallel"
    112
    113    if scriptFN is None:
    114        scriptFN = join(utils.scriptsPath, "resynthesize_from_klattgrid.praat")
    115
    116    #  Praat crashes on exit after resynthesis with a klattgrid
    117    utils.runPraatScript(praatEXE, scriptFN, [wavFN, klattFN, outputWavFN, method])
    118
    119
    120def _openNormalKlattgrid(data: str) -> Klattgrid:
    121    kg = Klattgrid()
    122
    123    # Toss header
    124    data = data.split("\n\n", 1)[1]
    125
    126    # Not sure if this is needed
    127    startI = data.index("points")
    128    startI = data.index("\n", startI)
    129
    130    # Find sections
    131    sectionIndexList = _findIndicies(data, "<exists>")
    132
    133    sectionIndexList.append(-1)
    134
    135    for i in range(len(sectionIndexList) - 1):
    136        dataTuple = _getSectionHeader(data, sectionIndexList, i)
    137        name, minT, maxT, sectionData, sectionTuple = dataTuple
    138
    139        # Container Tier
    140        if name in [
    141            "oral_formants",
    142            "nasal_formants",
    143            "nasal_antiformants",
    144            "tracheal_formants",
    145            "tracheal_antiformants",
    146            "delta_formants",
    147            "frication_formants",
    148        ]:
    149            kct = _proccessContainerTierInput(sectionData, name)
    150            kg.addTier(kct)
    151
    152        else:
    153            # Process entries if this tier has any
    154            entries = _buildEntries(sectionTuple)
    155            tier = KlattPointTier(name, entries, minT, maxT)
    156            kg.addTier(tier)
    157
    158    return kg
    159
    160
    161def _proccessContainerTierInput(sectionData: str, name: str):
    162    sectionData = sectionData.split("\n", 3)[-1]
    163
    164    formantIndexList = _findIndicies(sectionData, "formants")
    165
    166    subFilterList = [
    167        "bandwidths",
    168        "oral_formants_amplitudes",
    169        "nasal_formants_amplitudes",
    170        "tracheal_formants_amplitudes",
    171        "frication_formants_amplitudes",
    172    ]
    173
    174    # Find the index of all the different data sections
    175    subFilterIndexList = [
    176        _findIndicies(sectionData, subName) for subName in subFilterList
    177    ]
    178
    179    # 'Formant' search query finds duplicates -- remove them
    180    newFormantList = []
    181    for value in formantIndexList:
    182        if all([value not in subList for subList in subFilterIndexList]):
    183            newFormantList.append(value)
    184    formantIndexList = newFormantList
    185
    186    # Combine regular query with formant query
    187    indexListOfLists = [
    188        formantIndexList,
    189    ] + subFilterIndexList
    190
    191    # Flatten index list
    192    masterIndexList = [value for sublist in indexListOfLists for value in sublist]
    193    masterIndexList.sort()
    194
    195    # If an index list is last, it it needs to include '-1' to capture the
    196    # rest of the data
    197    for subList in indexListOfLists:
    198        try:
    199            val = subList[-1]
    200        except IndexError:
    201            continue
    202        ii = masterIndexList.index(val)  # Index of the index
    203        try:
    204            subList.append(masterIndexList[ii + 1] - 1)
    205        except IndexError:
    206            subList.append(-1)
    207
    208    # Build the tier structure
    209    kct = KlattContainerTier(name)
    210    for indexList in indexListOfLists:
    211        if indexList == []:
    212            continue
    213        tierList = []
    214        for j in range(len(indexList) - 1):
    215            try:
    216                tmpTuple = _getSectionHeader(sectionData, indexList, j)
    217            except ValueError:
    218                continue
    219            subName, subMin, subMax, _, subTuple = tmpTuple
    220            subName = subName[:-1]
    221
    222            entries = _buildEntries(subTuple)
    223            tier = KlattSubPointTier(subName, entries, subMin, subMax)
    224            tierList.append(tier)
    225        kit = KlattIntermediateTier(subName.split()[0])
    226        for tier in tierList:
    227            kit.addTier(tier)
    228        kct.addTier(kit)
    229
    230    return kct
    231
    232
    233def _findIndicies(data, keyword):
    234    indexList = utils.findAll(data, keyword)
    235    indexList = [data.rfind("\n", 0, i) for i in indexList]
    236
    237    return indexList
    238
    239
    240def _getSectionHeader(data, indexList, i):
    241    sectionStart = indexList[i]
    242    sectionEnd = indexList[i + 1]
    243    sectionData = data[sectionStart:sectionEnd].strip()
    244    sectionTuple = sectionData.split("\n", 4)
    245
    246    subheader, minr, maxr = sectionTuple[:3]
    247    name = subheader.split("?")[0].strip()
    248    minT = float(minr.split("=")[1].strip())
    249    maxT = float(maxr.split("=")[1].strip())
    250
    251    tail = sectionTuple[3:]
    252
    253    return name, minT, maxT, sectionData, tail
    254
    255
    256def _buildEntries(sectionTuple):
    257    entries = []
    258    if len(sectionTuple) > 1:  # Has points
    259        npoints = float(sectionTuple[0].split("=")[1].strip())
    260        if npoints > 0:
    261            entries = _processSectionData(sectionTuple[1])
    262
    263    return entries
    264
    265
    266def _processSectionData(sectionData: str) -> List[Tuple[float, float]]:
    267    sectionData += "\n"
    268
    269    startI = 0
    270    tupleList = []
    271    while True:
    272        try:
    273            startI = sectionData.index("=", startI) + 1  # Past the equal sign
    274        except ValueError:  # No more data
    275            break
    276
    277        endI = sectionData.index("\n", startI)
    278        time = float(sectionData[startI:endI].strip())
    279
    280        startI = sectionData.index("=", endI) + 1  # Just past the '=' sign
    281        endI = sectionData.index("\n", startI)
    282        value = float(sectionData[startI:endI].strip())
    283
    284        startI = endI
    285        tupleList.append((time, value))
    286
    287    return tupleList

def openKlattgrid(fnFullPath: str) -> praatio.data_classes.klattgrid.Klattgrid: View Source

    39def openKlattgrid(fnFullPath: str) -> Klattgrid:
    40    try:
    41        with io.open(fnFullPath, "r", encoding="utf-16") as fd:
    42            data = fd.read()
    43    except UnicodeError:
    44        with io.open(fnFullPath, "r", encoding="utf-8") as fd:
    45            data = fd.read()
    46    data = data.replace("\r\n", "\n")
    47
    48    # Right now, can only open normal klatt grid and not short ones
    49    kg = _openNormalKlattgrid(data)
    50
    51    return kg

def wavToKlattgrid( praatEXE: str, inputFullPath: str, outputFullPath: str, timeStep: float = 0.005, numFormants: int = 5, maxFormantFreq: float = 5500.0, windowLength: float = 0.025, preEmphasis: float = 50.0, pitchFloor: float = 60.0, pitchCeiling: float = 600.0, minPitch: float = 50.0, subtractMean: bool = True, scriptFN: Optional[str] = None) -> None: View Source

    54def wavToKlattgrid(
    55    praatEXE: str,
    56    inputFullPath: str,
    57    outputFullPath: str,
    58    timeStep: float = 0.005,
    59    numFormants: int = 5,
    60    maxFormantFreq: float = 5500.0,
    61    windowLength: float = 0.025,
    62    preEmphasis: float = 50.0,
    63    pitchFloor: float = 60.0,
    64    pitchCeiling: float = 600.0,
    65    minPitch: float = 50.0,
    66    subtractMean: bool = True,
    67    scriptFN: Optional[str] = None,
    68) -> None:
    69    """Extracts the klattgrid from a wav file
    70
    71    The default values are the same as the ones used in praat
    72    """
    73
    74    if subtractMean is True:
    75        subtractMeanStr = "yes"
    76    else:
    77        subtractMeanStr = "no"
    78
    79    if scriptFN is None:
    80        scriptFN = join(utils.scriptsPath, "sound_to_klattgrid.praat")
    81
    82    utils.runPraatScript(
    83        praatEXE,
    84        scriptFN,
    85        [
    86            inputFullPath,
    87            outputFullPath,
    88            timeStep,
    89            numFormants,
    90            maxFormantFreq,
    91            windowLength,
    92            preEmphasis,
    93            pitchFloor,
    94            pitchCeiling,
    95            minPitch,
    96            subtractMeanStr,
    97        ],
    98    )

Extracts the klattgrid from a wav file

The default values are the same as the ones used in praat

def resynthesize( praatEXE: str, wavFN: str, klattFN: str, outputWavFN: str, doCascade: bool = True, scriptFN: Optional[str] = None) -> None: View Source

    101def resynthesize(
    102    praatEXE: str,
    103    wavFN: str,
    104    klattFN: str,
    105    outputWavFN: str,
    106    doCascade: bool = True,
    107    scriptFN: Optional[str] = None,
    108) -> None:
    109    if doCascade:
    110        method = "Cascade"
    111    else:
    112        method = "Parallel"
    113
    114    if scriptFN is None:
    115        scriptFN = join(utils.scriptsPath, "resynthesize_from_klattgrid.praat")
    116
    117    #  Praat crashes on exit after resynthesis with a klattgrid
    118    utils.runPraatScript(praatEXE, scriptFN, [wavFN, klattFN, outputWavFN, method])