promo.duration_morph API documentation

# promo.duration_morph

Created on Jun 5, 2013

@author: timmahrt

View Source

      1"""
      2Created on Jun 5, 2013
      3
      4@author: timmahrt
      5"""
      6
      7import os
      8from os.path import join
      9import copy
     10
     11from praatio import textgrid
     12from praatio import praat_scripts
     13from praatio import data_points
     14from praatio.utilities import constants
     15
     16from promo.morph_utils import utils
     17from promo.morph_utils import audio_scripts
     18from promo.morph_utils import plot_morphed_data
     19
     20# This value is used to differentiate a praat interval boundary that marks
     21# the start of one region and the end of another.
     22PRAAT_TIME_DIFF = 0.000001
     23
     24
     25class NoLabeledRegionFoundException(Exception):
     26    def __init__(self, tgFN):
     27        super(NoLabeledRegionFoundException, self).__init__()
     28        self.tgFN = tgFN
     29
     30    def __str__(self):
     31        return "No labeled region fitting the specified criteria for tg: " + self.tgFN
     32
     33
     34def changeDuration(
     35    fromWavFN,
     36    durationParameters,
     37    stepList,
     38    outputName,
     39    outputMinPitch,
     40    outputMaxPitch,
     41    praatEXE,
     42):
     43    """
     44    Uses praat to morph duration in one file to duration in another
     45
     46    Praat uses the PSOLA algorithm
     47    """
     48
     49    rootPath = os.path.split(fromWavFN)[0]
     50
     51    # Prep output directories
     52    outputPath = join(rootPath, "duration_resynthesized_wavs")
     53    utils.makeDir(outputPath)
     54
     55    durationTierPath = join(rootPath, "duration_tiers")
     56    utils.makeDir(durationTierPath)
     57
     58    fromWavDuration = audio_scripts.getSoundFileDuration(fromWavFN)
     59
     60    durationParameters = copy.deepcopy(durationParameters)
     61    # Pad any gaps with values of 1 (no change in duration)
     62
     63    # No need to stretch out any pauses at the beginning
     64    if durationParameters[0][0] != 0:
     65        tmpVar = (0, durationParameters[0][0] - PRAAT_TIME_DIFF, 1)
     66        durationParameters.insert(0, tmpVar)
     67
     68    # Or the end
     69    if durationParameters[-1][1] < fromWavDuration:
     70        durationParameters.append(
     71            (durationParameters[-1][1] + PRAAT_TIME_DIFF, fromWavDuration, 1)
     72        )
     73
     74    # Create the praat script for doing duration manipulation
     75    for stepAmount in stepList:
     76        durationPointList = []
     77        for start, end, ratio in durationParameters:
     78            percentChange = 1 + (ratio - 1) * stepAmount
     79            durationPointList.append((start, percentChange))
     80            durationPointList.append((end, percentChange))
     81
     82        outputPrefix = "%s_%0.3g" % (outputName, stepAmount)
     83        durationTierFN = join(durationTierPath, "%s.DurationTier" % outputPrefix)
     84        outputWavFN = join(outputPath, "%s.wav" % outputPrefix)
     85        durationTier = data_points.PointObject2D(
     86            durationPointList, constants.DataPointTypes.DURATION, 0, fromWavDuration
     87        )
     88        durationTier.save(durationTierFN)
     89
     90        praat_scripts.resynthesizeDuration(
     91            praatEXE,
     92            fromWavFN,
     93            durationTierFN,
     94            outputWavFN,
     95            outputMinPitch,
     96            outputMaxPitch,
     97        )
     98
     99
    100def getBareParameters(wavFN):
    101    wavDuration = audio_scripts.getSoundFileDuration(wavFN)
    102    return [
    103        (0, wavDuration, ""),
    104    ]
    105
    106
    107def getMorphParameters(fromTGFN, toTGFN, tierName, filterFunc=None, useBlanks=False):
    108    """
    109    Get intervals for source and target audio files
    110
    111    Use this information to find out how much to stretch/shrink each source
    112    interval.
    113
    114    The target values are based on the contents of toTGFN.
    115    """
    116
    117    if filterFunc is None:
    118        filterFunc = lambda entry: True  # Everything is accepted
    119
    120    fromEntryList = utils.getIntervals(
    121        fromTGFN, tierName, includeUnlabeledRegions=useBlanks
    122    )
    123    toEntryList = utils.getIntervals(
    124        toTGFN, tierName, includeUnlabeledRegions=useBlanks
    125    )
    126
    127    fromEntryList = [entry for entry in fromEntryList if filterFunc(entry)]
    128    toEntryList = [entry for entry in toEntryList if filterFunc(entry)]
    129
    130    assert len(fromEntryList) == len(toEntryList)
    131
    132    durationParameters = []
    133    for fromEntry, toEntry in zip(fromEntryList, toEntryList):
    134        fromStart, fromEnd = fromEntry[:2]
    135        toStart, toEnd = toEntry[:2]
    136
    137        # Praat will ignore a second value appearing at the same time as
    138        # another so we give each start a tiny offset to distinguish intervals
    139        # that start and end at the same point
    140        toStart += PRAAT_TIME_DIFF
    141        fromStart += PRAAT_TIME_DIFF
    142
    143        ratio = (toEnd - toStart) / float((fromEnd - fromStart))
    144        durationParameters.append((fromStart, fromEnd, ratio))
    145
    146    return durationParameters
    147
    148
    149def getManipulatedParamaters(tgFN, tierName, modFunc, filterFunc=None, useBlanks=False):
    150    """
    151    Get intervals for source and target audio files
    152
    153    Use this information to find out how much to stretch/shrink each source
    154    interval.
    155
    156    The target values are based on modfunc.
    157    """
    158
    159    fromExtractInfo = utils.getIntervals(tgFN, tierName, filterFunc, useBlanks)
    160
    161    durationParameters = []
    162    for fromInfoTuple in fromExtractInfo:
    163        fromStart, fromEnd = fromInfoTuple[:2]
    164        toStart, toEnd = modFunc(fromStart), modFunc(fromEnd)
    165
    166        # Praat will ignore a second value appearing at the same time as
    167        # another so we give each start a tiny offset to distinguish intervals
    168        # that start and end at the same point
    169        toStart += PRAAT_TIME_DIFF
    170        fromStart += PRAAT_TIME_DIFF
    171
    172        ratio = (toEnd - toStart) / float((fromEnd - fromStart))
    173
    174        ratioTuple = (fromStart, fromEnd, ratio)
    175        durationParameters.append(ratioTuple)
    176
    177    return durationParameters
    178
    179
    180def outputMorphTextgrids(fromTGFN, durationParameters, stepList, outputTGName):
    181    if outputTGName is not None:
    182        utils.makeDir(os.path.split(outputTGName)[0])
    183
    184    # Create the adjusted textgrids
    185    if outputTGName is not None:
    186        for stepFactor in stepList:
    187            stepDurationParameters = [
    188                (start, end, 1 + (ratio - 1) * stepFactor)
    189                for start, end, ratio in durationParameters
    190            ]
    191            adjustedTG = textgridManipulateDuration(fromTGFN, stepDurationParameters)
    192
    193            outputTGFN = "%s_%0.3g.TextGrid" % (outputTGName, stepFactor)
    194            adjustedTG.save(
    195                outputTGFN, format="short_textgrid", includeBlankSpaces=True
    196            )
    197
    198
    199def outputMorphPlot(
    200    fromTGFN, toTGFN, tierName, durationParameters, stepList, outputImageFN
    201):
    202    if outputImageFN is not None:
    203        utils.makeDir(os.path.split(outputImageFN)[0])
    204
    205    # Create the plot of the morph
    206    if outputImageFN is not None:
    207        _plotResults(
    208            durationParameters,
    209            fromTGFN,
    210            toTGFN,
    211            tierName,
    212            stepList,
    213            outputImageFN,
    214            None,
    215            False,
    216        )
    217
    218
    219def _plotResults(
    220    durationParameters,
    221    fromTGFN,
    222    toTGFN,
    223    tierName,
    224    stepList,
    225    outputPNGFN,
    226    filterFunc,
    227    includeUnlabeledRegions,
    228):
    229    # Containers
    230    fromDurList = []
    231    toDurList = []
    232    actDurList = []
    233    labelList = []
    234
    235    fromExtractInfo = utils.getIntervals(
    236        fromTGFN, tierName, filterFunc, includeUnlabeledRegions
    237    )
    238    toExtractInfo = utils.getIntervals(
    239        toTGFN, tierName, filterFunc, includeUnlabeledRegions
    240    )
    241
    242    # Get durations
    243    for fromInfoTuple, toInfoTuple in zip(fromExtractInfo, toExtractInfo):
    244        fromStart, fromEnd = fromInfoTuple[:2]
    245        toStart, toEnd = toInfoTuple[:2]
    246
    247        labelList.append(fromInfoTuple[2])
    248        fromDurList.append(fromEnd - fromStart)
    249        toDurList.append(toEnd - toStart)
    250
    251    # Get iterpolated values
    252    for stepAmount in stepList:
    253        tmpDurList = []
    254        for fromStart, fromEnd, ratio in durationParameters:
    255            dur = fromEnd - fromStart
    256            percentChange = 1 + (ratio - 1) * stepAmount
    257            tmpDurList.append(dur * percentChange)
    258
    259        actDurList.append(tmpDurList)
    260
    261    # Plot data
    262    plot_morphed_data.plotDuration(
    263        fromDurList, toDurList, actDurList, labelList, outputPNGFN
    264    )
    265
    266
    267def textgridMorphDuration(fromTGFN, toTGFN):
    268    """
    269    A convenience function.  Morphs interval durations of one tg to another.
    270
    271    This assumes the two textgrids have the same number of segments.
    272    """
    273    fromTG = textgrid.openTextgrid(fromTGFN, includeEmptyIntervals=False)
    274    toTG = textgrid.openTextgrid(toTGFN, includeEmptyIntervals=False)
    275    adjustedTG = textgrid.Textgrid()
    276
    277    for tierName in fromTG.tierNames:
    278        fromTier = fromTG.getTier(tierName)
    279        toTier = toTG.getTier(tierName)
    280        adjustedTier = fromTier.morph(toTier)
    281        adjustedTG.addTier(adjustedTier)
    282
    283    return adjustedTG
    284
    285
    286def textgridManipulateDuration(tgFN, ratioList):
    287    tg = textgrid.openTextgrid(tgFN, includeEmptyIntervals=False)
    288
    289    adjustedTG = textgrid.Textgrid()
    290
    291    for tierName in tg.tierNames:
    292        fromTier = tg.getTier(tierName)
    293
    294        adjustedTier = None
    295        if isinstance(fromTier, textgrid.IntervalTier):
    296            adjustedTier = _morphIntervalTier(fromTier, ratioList)
    297        elif isinstance(fromTier, textgrid.PointTier):
    298            adjustedTier = _morphPointTier(fromTier, ratioList)
    299
    300        assert adjustedTier is not None
    301        adjustedTG.addTier(adjustedTier)
    302
    303    return adjustedTG
    304
    305
    306def _getTimeDiff(start, stop, ratio):
    307    """Returns the time difference between interval and interval*ratio"""
    308    return (ratio - 1) * (stop - start)
    309
    310
    311def _morphPointTier(tier, ratioList):
    312    cumulativeAdjustAmount = 0
    313    i = 0
    314    newEntryList = []
    315    for timestamp, label in tier.entries:
    316        # Advance to the manipulation interval that coincides with the
    317        # current point or appears after it
    318        while i < len(ratioList) and timestamp > ratioList[i][1]:
    319            rStart, rStop, ratio = ratioList[i]
    320            cumulativeAdjustAmount += _getTimeDiff(rStart, rStop, ratio)
    321            i += 1
    322
    323        newTime = timestamp + cumulativeAdjustAmount
    324
    325        # Alter the time if the point is within a manipulation interval
    326        if i < len(ratioList):
    327            rStart, rStop, ratio = ratioList[i]
    328            if timestamp > rStart and timestamp <= rStop:
    329                newTime += _getTimeDiff(rStart, timestamp, ratio)
    330
    331        newEntryList.append((newTime, label))
    332
    333    maxT = tier.maxTimestamp + cumulativeAdjustAmount
    334    return tier.new(entries=newEntryList, maxTimestamp=maxT)
    335
    336
    337def _morphIntervalTier(tier, ratioList):
    338    cumulativeAdjustAmount = 0
    339    i = 0
    340    newEntryList = []
    341    for start, stop, label in tier.entries:
    342        # Syncronize the manipulation and data intervals so that they
    343        # are either overlapping or the manipulation interval is farther
    344        # in the future.  This accumulates the effect of past
    345        # manipulations so we know how much to offset timestamps
    346        # for the current interval.
    347        while (
    348            i < len(ratioList) and start > ratioList[i][0] and start >= ratioList[i][1]
    349        ):
    350            rStart, rStop, ratio = ratioList[i]
    351            cumulativeAdjustAmount = _getTimeDiff(rStart, rStop, ratio)
    352            i += 1
    353
    354        newStart = start + cumulativeAdjustAmount
    355        newStop = stop + cumulativeAdjustAmount
    356
    357        # Manipulate the interval further if there is overlap with a
    358        # manipulation interval
    359        while i < len(ratioList):
    360            rStart, rStop, ratio = ratioList[i]
    361
    362            # currAdjustAmount is the ratio modified by percent change
    363            # e.g. if the ratio is a boost of 1.2 but percent change is
    364            # 0.5, ratio = 1.1 (it loses half of its effect)
    365
    366            # Adjusting the start position based on overlap with the
    367            # current adjust interval
    368            if start <= rStart:
    369                pass
    370            elif start > rStart and start <= rStop:
    371                newStart += _getTimeDiff(rStart, start, ratio)
    372
    373            # Adjusting the stop position based on overlap with the
    374            # current adjust interval
    375            if stop >= rStop:
    376                newStop += _getTimeDiff(rStart, rStop, ratio)
    377            elif stop < rStop and stop >= rStart:
    378                newStop += _getTimeDiff(rStart, stop, ratio)
    379
    380            # If we are beyond the current manipulation interval,
    381            # then we need to move to the next one
    382            if stop >= rStop:
    383                cumulativeAdjustAmount += _getTimeDiff(rStart, rStop, ratio)
    384                i += 1
    385            # Otherwise, we are done manipulating the current interval
    386            elif stop < rStop:
    387                break
    388
    389        newEntryList.append((newStart, newStop, label))
    390
    391    newMax = tier.maxTimestamp + cumulativeAdjustAmount
    392    return tier.new(entries=newEntryList, maxTimestamp=newMax)

PRAAT_TIME_DIFF = 1e-06
class NoLabeledRegionFoundException(builtins.Exception): View Source

    26class NoLabeledRegionFoundException(Exception):
    27    def __init__(self, tgFN):
    28        super(NoLabeledRegionFoundException, self).__init__()
    29        self.tgFN = tgFN
    30
    31    def __str__(self):
    32        return "No labeled region fitting the specified criteria for tg: " + self.tgFN

Common base class for all non-exit exceptions.

NoLabeledRegionFoundException(tgFN) View Source

    27    def __init__(self, tgFN):
    28        super(NoLabeledRegionFoundException, self).__init__()
    29        self.tgFN = tgFN

tgFN

##### Inherited Members

builtins.BaseException
  with_traceback
  add_note
  args

def changeDuration( fromWavFN, durationParameters, stepList, outputName, outputMinPitch, outputMaxPitch, praatEXE): View Source

    35def changeDuration(
    36    fromWavFN,
    37    durationParameters,
    38    stepList,
    39    outputName,
    40    outputMinPitch,
    41    outputMaxPitch,
    42    praatEXE,
    43):
    44    """
    45    Uses praat to morph duration in one file to duration in another
    46
    47    Praat uses the PSOLA algorithm
    48    """
    49
    50    rootPath = os.path.split(fromWavFN)[0]
    51
    52    # Prep output directories
    53    outputPath = join(rootPath, "duration_resynthesized_wavs")
    54    utils.makeDir(outputPath)
    55
    56    durationTierPath = join(rootPath, "duration_tiers")
    57    utils.makeDir(durationTierPath)
    58
    59    fromWavDuration = audio_scripts.getSoundFileDuration(fromWavFN)
    60
    61    durationParameters = copy.deepcopy(durationParameters)
    62    # Pad any gaps with values of 1 (no change in duration)
    63
    64    # No need to stretch out any pauses at the beginning
    65    if durationParameters[0][0] != 0:
    66        tmpVar = (0, durationParameters[0][0] - PRAAT_TIME_DIFF, 1)
    67        durationParameters.insert(0, tmpVar)
    68
    69    # Or the end
    70    if durationParameters[-1][1] < fromWavDuration:
    71        durationParameters.append(
    72            (durationParameters[-1][1] + PRAAT_TIME_DIFF, fromWavDuration, 1)
    73        )
    74
    75    # Create the praat script for doing duration manipulation
    76    for stepAmount in stepList:
    77        durationPointList = []
    78        for start, end, ratio in durationParameters:
    79            percentChange = 1 + (ratio - 1) * stepAmount
    80            durationPointList.append((start, percentChange))
    81            durationPointList.append((end, percentChange))
    82
    83        outputPrefix = "%s_%0.3g" % (outputName, stepAmount)
    84        durationTierFN = join(durationTierPath, "%s.DurationTier" % outputPrefix)
    85        outputWavFN = join(outputPath, "%s.wav" % outputPrefix)
    86        durationTier = data_points.PointObject2D(
    87            durationPointList, constants.DataPointTypes.DURATION, 0, fromWavDuration
    88        )
    89        durationTier.save(durationTierFN)
    90
    91        praat_scripts.resynthesizeDuration(
    92            praatEXE,
    93            fromWavFN,
    94            durationTierFN,
    95            outputWavFN,
    96            outputMinPitch,
    97            outputMaxPitch,
    98        )

Uses praat to morph duration in one file to duration in another

Praat uses the PSOLA algorithm

def getBareParameters(wavFN): View Source

    101def getBareParameters(wavFN):
    102    wavDuration = audio_scripts.getSoundFileDuration(wavFN)
    103    return [
    104        (0, wavDuration, ""),
    105    ]

def getMorphParameters(fromTGFN, toTGFN, tierName, filterFunc=None, useBlanks=False): View Source

    108def getMorphParameters(fromTGFN, toTGFN, tierName, filterFunc=None, useBlanks=False):
    109    """
    110    Get intervals for source and target audio files
    111
    112    Use this information to find out how much to stretch/shrink each source
    113    interval.
    114
    115    The target values are based on the contents of toTGFN.
    116    """
    117
    118    if filterFunc is None:
    119        filterFunc = lambda entry: True  # Everything is accepted
    120
    121    fromEntryList = utils.getIntervals(
    122        fromTGFN, tierName, includeUnlabeledRegions=useBlanks
    123    )
    124    toEntryList = utils.getIntervals(
    125        toTGFN, tierName, includeUnlabeledRegions=useBlanks
    126    )
    127
    128    fromEntryList = [entry for entry in fromEntryList if filterFunc(entry)]
    129    toEntryList = [entry for entry in toEntryList if filterFunc(entry)]
    130
    131    assert len(fromEntryList) == len(toEntryList)
    132
    133    durationParameters = []
    134    for fromEntry, toEntry in zip(fromEntryList, toEntryList):
    135        fromStart, fromEnd = fromEntry[:2]
    136        toStart, toEnd = toEntry[:2]
    137
    138        # Praat will ignore a second value appearing at the same time as
    139        # another so we give each start a tiny offset to distinguish intervals
    140        # that start and end at the same point
    141        toStart += PRAAT_TIME_DIFF
    142        fromStart += PRAAT_TIME_DIFF
    143
    144        ratio = (toEnd - toStart) / float((fromEnd - fromStart))
    145        durationParameters.append((fromStart, fromEnd, ratio))
    146
    147    return durationParameters

Get intervals for source and target audio files

Use this information to find out how much to stretch/shrink each source interval.

The target values are based on the contents of toTGFN.

def getManipulatedParamaters(tgFN, tierName, modFunc, filterFunc=None, useBlanks=False): View Source

    150def getManipulatedParamaters(tgFN, tierName, modFunc, filterFunc=None, useBlanks=False):
    151    """
    152    Get intervals for source and target audio files
    153
    154    Use this information to find out how much to stretch/shrink each source
    155    interval.
    156
    157    The target values are based on modfunc.
    158    """
    159
    160    fromExtractInfo = utils.getIntervals(tgFN, tierName, filterFunc, useBlanks)
    161
    162    durationParameters = []
    163    for fromInfoTuple in fromExtractInfo:
    164        fromStart, fromEnd = fromInfoTuple[:2]
    165        toStart, toEnd = modFunc(fromStart), modFunc(fromEnd)
    166
    167        # Praat will ignore a second value appearing at the same time as
    168        # another so we give each start a tiny offset to distinguish intervals
    169        # that start and end at the same point
    170        toStart += PRAAT_TIME_DIFF
    171        fromStart += PRAAT_TIME_DIFF
    172
    173        ratio = (toEnd - toStart) / float((fromEnd - fromStart))
    174
    175        ratioTuple = (fromStart, fromEnd, ratio)
    176        durationParameters.append(ratioTuple)
    177
    178    return durationParameters

Get intervals for source and target audio files

Use this information to find out how much to stretch/shrink each source interval.

The target values are based on modfunc.

def outputMorphTextgrids(fromTGFN, durationParameters, stepList, outputTGName): View Source

    181def outputMorphTextgrids(fromTGFN, durationParameters, stepList, outputTGName):
    182    if outputTGName is not None:
    183        utils.makeDir(os.path.split(outputTGName)[0])
    184
    185    # Create the adjusted textgrids
    186    if outputTGName is not None:
    187        for stepFactor in stepList:
    188            stepDurationParameters = [
    189                (start, end, 1 + (ratio - 1) * stepFactor)
    190                for start, end, ratio in durationParameters
    191            ]
    192            adjustedTG = textgridManipulateDuration(fromTGFN, stepDurationParameters)
    193
    194            outputTGFN = "%s_%0.3g.TextGrid" % (outputTGName, stepFactor)
    195            adjustedTG.save(
    196                outputTGFN, format="short_textgrid", includeBlankSpaces=True
    197            )

def outputMorphPlot( fromTGFN, toTGFN, tierName, durationParameters, stepList, outputImageFN): View Source

    200def outputMorphPlot(
    201    fromTGFN, toTGFN, tierName, durationParameters, stepList, outputImageFN
    202):
    203    if outputImageFN is not None:
    204        utils.makeDir(os.path.split(outputImageFN)[0])
    205
    206    # Create the plot of the morph
    207    if outputImageFN is not None:
    208        _plotResults(
    209            durationParameters,
    210            fromTGFN,
    211            toTGFN,
    212            tierName,
    213            stepList,
    214            outputImageFN,
    215            None,
    216            False,
    217        )

def textgridMorphDuration(fromTGFN, toTGFN): View Source

    268def textgridMorphDuration(fromTGFN, toTGFN):
    269    """
    270    A convenience function.  Morphs interval durations of one tg to another.
    271
    272    This assumes the two textgrids have the same number of segments.
    273    """
    274    fromTG = textgrid.openTextgrid(fromTGFN, includeEmptyIntervals=False)
    275    toTG = textgrid.openTextgrid(toTGFN, includeEmptyIntervals=False)
    276    adjustedTG = textgrid.Textgrid()
    277
    278    for tierName in fromTG.tierNames:
    279        fromTier = fromTG.getTier(tierName)
    280        toTier = toTG.getTier(tierName)
    281        adjustedTier = fromTier.morph(toTier)
    282        adjustedTG.addTier(adjustedTier)
    283
    284    return adjustedTG

A convenience function. Morphs interval durations of one tg to another.

This assumes the two textgrids have the same number of segments.

def textgridManipulateDuration(tgFN, ratioList): View Source

    287def textgridManipulateDuration(tgFN, ratioList):
    288    tg = textgrid.openTextgrid(tgFN, includeEmptyIntervals=False)
    289
    290    adjustedTG = textgrid.Textgrid()
    291
    292    for tierName in tg.tierNames:
    293        fromTier = tg.getTier(tierName)
    294
    295        adjustedTier = None
    296        if isinstance(fromTier, textgrid.IntervalTier):
    297            adjustedTier = _morphIntervalTier(fromTier, ratioList)
    298        elif isinstance(fromTier, textgrid.PointTier):
    299            adjustedTier = _morphPointTier(fromTier, ratioList)
    300
    301        assert adjustedTier is not None
    302        adjustedTG.addTier(adjustedTier)
    303
    304    return adjustedTG