promo.f0_morph API documentation

# promo.f0_morph

Created on May 31, 2013

@author: timmahrt

Contains utilities for extracting, creating, and manipulating pitch files in praat.

View Source

      1"""
      2Created on May 31, 2013
      3
      4@author: timmahrt
      5
      6Contains utilities for extracting, creating, and manipulating pitch files in
      7praat.
      8"""
      9
     10from os.path import join
     11
     12from praatio import textgrid
     13from praatio import data_points
     14from praatio import praat_scripts
     15from praatio.utilities import utils as praatio_utils
     16from praatio.utilities import constants
     17
     18from promo.morph_utils import utils
     19from promo.morph_utils import audio_scripts
     20from promo.morph_utils import plot_morphed_data
     21from promo.morph_utils import morph_sequence
     22
     23
     24class MissingPitchDataException(Exception):
     25    def __str__(self):
     26        txt = (
     27            "\n\nNo data points available in a region for morphing.\n"
     28            "Two data points are needed in each region to do the morph\n"
     29            "Regions with fewer than two samples are skipped, which "
     30            "should be fine for some cases (e.g. unvoiced segments).\n"
     31            "If you need more data points, see "
     32            "promo.morph_utils.interpolation"
     33        )
     34        return txt
     35
     36
     37def getPitchForIntervals(data, tgFN, tierName):
     38    """
     39    Preps data for use in f0Morph
     40    """
     41    tg = textgrid.openTextgrid(tgFN, includeEmptyIntervals=False)
     42    data = tg.getTier(tierName).getValuesInIntervals(data)
     43    data = [dataList for _, dataList in data]
     44
     45    return data
     46
     47
     48def f0Morph(
     49    fromWavFN,
     50    pitchPath,
     51    stepList,
     52    outputName,
     53    doPlotPitchSteps,
     54    fromPitchData,
     55    toPitchData,
     56    outputMinPitch,
     57    outputMaxPitch,
     58    praatEXE,
     59    keepPitchRange=False,
     60    keepAveragePitch=False,
     61    sourcePitchDataList=None,
     62    minIntervalLength=0.3,
     63):
     64    """
     65    Resynthesizes the pitch track from a source to a target wav file
     66
     67    fromPitchData and toPitchData should be segmented according to the
     68    portions that you want to morph.  The two lists must have the same
     69    number of sublists.
     70
     71    Occurs over a three-step process.
     72
     73    This function can act as a template for how to use the function
     74    morph_sequence.morphChunkedDataLists to morph pitch contours or
     75    other data.
     76
     77    By default, everything is morphed, but it is possible to maintain elements
     78    of the original speaker's pitch (average pitch and pitch range) by setting
     79    the appropriate flag)
     80
     81    sourcePitchDataList: if passed in, any regions unspecified by
     82                         fromPitchData will be sampled from this list.  In
     83                         essence, this allows one to leave segments of
     84                         the original pitch contour untouched by the
     85                         morph process.
     86    """
     87
     88    fromDuration = audio_scripts.getSoundFileDuration(fromWavFN)
     89
     90    # Find source pitch samples that will be mixed in with the target
     91    # pitch samples later
     92    nonMorphPitchData = []
     93    if sourcePitchDataList is not None:
     94        timeList = sorted(fromPitchData)
     95        timeList = [(row[0][0], row[-1][0]) for row in timeList]
     96        endTime = sourcePitchDataList[-1][0]
     97        invertedTimeList = praatio_utils.invertIntervalList(timeList, endTime)
     98        invertedTimeList = [
     99            (start, stop)
    100            for start, stop in invertedTimeList
    101            if stop - start > minIntervalLength
    102        ]
    103
    104        for start, stop in invertedTimeList:
    105            pitchList = praatio_utils.getValuesInInterval(
    106                sourcePitchDataList, start, stop
    107            )
    108            nonMorphPitchData.extend(pitchList)
    109
    110    # Iterative pitch tier data path
    111    pitchTierPath = join(pitchPath, "pitchTiers")
    112    resynthesizedPath = join(pitchPath, "f0_resynthesized_wavs")
    113    for tmpPath in [pitchTierPath, resynthesizedPath]:
    114        utils.makeDir(tmpPath)
    115
    116    # 1. Prepare the data for morphing - acquire the segments to merge
    117    # (Done elsewhere, with the input fed into this function)
    118
    119    # 2. Morph the fromData to the toData
    120    try:
    121        finalOutputList = morph_sequence.morphChunkedDataLists(
    122            fromPitchData, toPitchData, stepList
    123        )
    124    except IndexError:
    125        raise MissingPitchDataException()
    126
    127    fromPitchData = [row for subList in fromPitchData for row in subList]
    128    toPitchData = [row for subList in toPitchData for row in subList]
    129
    130    # 3. Save the pitch data and resynthesize the pitch
    131    mergedDataList = []
    132    for i in range(0, len(finalOutputList)):
    133        outputDataList = finalOutputList[i]
    134
    135        if keepPitchRange is True:
    136            outputDataList = morph_sequence.morphRange(outputDataList, fromPitchData)
    137
    138        if keepAveragePitch is True:
    139            outputDataList = morph_sequence.morphAveragePitch(
    140                outputDataList, fromPitchData
    141            )
    142
    143        if sourcePitchDataList is not None:
    144            outputDataList.extend(nonMorphPitchData)
    145            outputDataList.sort()
    146
    147        stepOutputName = "%s_%0.3g" % (outputName, stepList[i])
    148        pitchFNFullPath = join(pitchTierPath, "%s.PitchTier" % stepOutputName)
    149        outputFN = join(resynthesizedPath, "%s.wav" % stepOutputName)
    150        pointObj = data_points.PointObject2D(
    151            outputDataList, constants.DataPointTypes.PITCH, 0, fromDuration
    152        )
    153        pointObj.save(pitchFNFullPath)
    154
    155        outputTime, outputVals = zip(*outputDataList)
    156        mergedDataList.append((outputTime, outputVals))
    157
    158        praat_scripts.resynthesizePitch(
    159            praatEXE,
    160            fromWavFN,
    161            pitchFNFullPath,
    162            outputFN,
    163            outputMinPitch,
    164            outputMaxPitch,
    165        )
    166
    167    # 4. (Optional) Plot the generated contours
    168    if doPlotPitchSteps:
    169        fromTime, fromVals = zip(*fromPitchData)
    170        toTime, toVals = zip(*toPitchData)
    171
    172        plot_morphed_data.plotF0(
    173            (fromTime, fromVals),
    174            (toTime, toVals),
    175            mergedDataList,
    176            join(pitchTierPath, "%s.png" % outputName),
    177        )

class MissingPitchDataException(builtins.Exception): View Source

    25class MissingPitchDataException(Exception):
    26    def __str__(self):
    27        txt = (
    28            "\n\nNo data points available in a region for morphing.\n"
    29            "Two data points are needed in each region to do the morph\n"
    30            "Regions with fewer than two samples are skipped, which "
    31            "should be fine for some cases (e.g. unvoiced segments).\n"
    32            "If you need more data points, see "
    33            "promo.morph_utils.interpolation"
    34        )
    35        return txt

Common base class for all non-exit exceptions.

##### Inherited Members

builtins.Exception
  Exception
builtins.BaseException
  with_traceback
  add_note
  args

def getPitchForIntervals(data, tgFN, tierName): View Source

    38def getPitchForIntervals(data, tgFN, tierName):
    39    """
    40    Preps data for use in f0Morph
    41    """
    42    tg = textgrid.openTextgrid(tgFN, includeEmptyIntervals=False)
    43    data = tg.getTier(tierName).getValuesInIntervals(data)
    44    data = [dataList for _, dataList in data]
    45
    46    return data

Preps data for use in f0Morph

def f0Morph( fromWavFN, pitchPath, stepList, outputName, doPlotPitchSteps, fromPitchData, toPitchData, outputMinPitch, outputMaxPitch, praatEXE, keepPitchRange=False, keepAveragePitch=False, sourcePitchDataList=None, minIntervalLength=0.3): View Source

     49def f0Morph(
     50    fromWavFN,
     51    pitchPath,
     52    stepList,
     53    outputName,
     54    doPlotPitchSteps,
     55    fromPitchData,
     56    toPitchData,
     57    outputMinPitch,
     58    outputMaxPitch,
     59    praatEXE,
     60    keepPitchRange=False,
     61    keepAveragePitch=False,
     62    sourcePitchDataList=None,
     63    minIntervalLength=0.3,
     64):
     65    """
     66    Resynthesizes the pitch track from a source to a target wav file
     67
     68    fromPitchData and toPitchData should be segmented according to the
     69    portions that you want to morph.  The two lists must have the same
     70    number of sublists.
     71
     72    Occurs over a three-step process.
     73
     74    This function can act as a template for how to use the function
     75    morph_sequence.morphChunkedDataLists to morph pitch contours or
     76    other data.
     77
     78    By default, everything is morphed, but it is possible to maintain elements
     79    of the original speaker's pitch (average pitch and pitch range) by setting
     80    the appropriate flag)
     81
     82    sourcePitchDataList: if passed in, any regions unspecified by
     83                         fromPitchData will be sampled from this list.  In
     84                         essence, this allows one to leave segments of
     85                         the original pitch contour untouched by the
     86                         morph process.
     87    """
     88
     89    fromDuration = audio_scripts.getSoundFileDuration(fromWavFN)
     90
     91    # Find source pitch samples that will be mixed in with the target
     92    # pitch samples later
     93    nonMorphPitchData = []
     94    if sourcePitchDataList is not None:
     95        timeList = sorted(fromPitchData)
     96        timeList = [(row[0][0], row[-1][0]) for row in timeList]
     97        endTime = sourcePitchDataList[-1][0]
     98        invertedTimeList = praatio_utils.invertIntervalList(timeList, endTime)
     99        invertedTimeList = [
    100            (start, stop)
    101            for start, stop in invertedTimeList
    102            if stop - start > minIntervalLength
    103        ]
    104
    105        for start, stop in invertedTimeList:
    106            pitchList = praatio_utils.getValuesInInterval(
    107                sourcePitchDataList, start, stop
    108            )
    109            nonMorphPitchData.extend(pitchList)
    110
    111    # Iterative pitch tier data path
    112    pitchTierPath = join(pitchPath, "pitchTiers")
    113    resynthesizedPath = join(pitchPath, "f0_resynthesized_wavs")
    114    for tmpPath in [pitchTierPath, resynthesizedPath]:
    115        utils.makeDir(tmpPath)
    116
    117    # 1. Prepare the data for morphing - acquire the segments to merge
    118    # (Done elsewhere, with the input fed into this function)
    119
    120    # 2. Morph the fromData to the toData
    121    try:
    122        finalOutputList = morph_sequence.morphChunkedDataLists(
    123            fromPitchData, toPitchData, stepList
    124        )
    125    except IndexError:
    126        raise MissingPitchDataException()
    127
    128    fromPitchData = [row for subList in fromPitchData for row in subList]
    129    toPitchData = [row for subList in toPitchData for row in subList]
    130
    131    # 3. Save the pitch data and resynthesize the pitch
    132    mergedDataList = []
    133    for i in range(0, len(finalOutputList)):
    134        outputDataList = finalOutputList[i]
    135
    136        if keepPitchRange is True:
    137            outputDataList = morph_sequence.morphRange(outputDataList, fromPitchData)
    138
    139        if keepAveragePitch is True:
    140            outputDataList = morph_sequence.morphAveragePitch(
    141                outputDataList, fromPitchData
    142            )
    143
    144        if sourcePitchDataList is not None:
    145            outputDataList.extend(nonMorphPitchData)
    146            outputDataList.sort()
    147
    148        stepOutputName = "%s_%0.3g" % (outputName, stepList[i])
    149        pitchFNFullPath = join(pitchTierPath, "%s.PitchTier" % stepOutputName)
    150        outputFN = join(resynthesizedPath, "%s.wav" % stepOutputName)
    151        pointObj = data_points.PointObject2D(
    152            outputDataList, constants.DataPointTypes.PITCH, 0, fromDuration
    153        )
    154        pointObj.save(pitchFNFullPath)
    155
    156        outputTime, outputVals = zip(*outputDataList)
    157        mergedDataList.append((outputTime, outputVals))
    158
    159        praat_scripts.resynthesizePitch(
    160            praatEXE,
    161            fromWavFN,
    162            pitchFNFullPath,
    163            outputFN,
    164            outputMinPitch,
    165            outputMaxPitch,
    166        )
    167
    168    # 4. (Optional) Plot the generated contours
    169    if doPlotPitchSteps:
    170        fromTime, fromVals = zip(*fromPitchData)
    171        toTime, toVals = zip(*toPitchData)
    172
    173        plot_morphed_data.plotF0(
    174            (fromTime, fromVals),
    175            (toTime, toVals),
    176            mergedDataList,
    177            join(pitchTierPath, "%s.png" % outputName),
    178        )

Resynthesizes the pitch track from a source to a target wav file

fromPitchData and toPitchData should be segmented according to the portions that you want to morph. The two lists must have the same number of sublists.

Occurs over a three-step process.

This function can act as a template for how to use the function morph_sequence.morphChunkedDataLists to morph pitch contours or other data.

By default, everything is morphed, but it is possible to maintain elements of the original speaker's pitch (average pitch and pitch range) by setting the appropriate flag)

sourcePitchDataList: if passed in, any regions unspecified by fromPitchData will be sampled from this list. In essence, this allows one to leave segments of the original pitch contour untouched by the morph process.