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

  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:
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:
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:
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])