def resynthesizePitch(praatEXE, inputWavFN, pitchFN, outputWavFN, minPitch, maxPitch, scriptFN=None, pointList=None): ''' Resynthesizes the pitch in a wav file with the given pitch contour file The pitch track to use can optionally be passed in as pointList. If so, it will be saved as pitchFN for praat to be able to use. ''' if scriptFN is None: scriptFN = join(utils.scriptsPath, "resynthesize_pitch.praat") if pointList is not None: dur = audioio.WavQueryObj(inputWavFN).getDuration() pointObj = dataio.PointObject2D(pointList, dataio.PITCH, 0, dur) pointObj.save(pitchFN) utils.runPraatScript( praatEXE, scriptFN, [inputWavFN, pitchFN, outputWavFN, minPitch, maxPitch])
def changeDuration(fromWavFN, durationParameters, stepList, outputName, outputMinPitch, outputMaxPitch, praatEXE): ''' Uses praat to morph duration in one file to duration in another Praat uses the PSOLA algorithm ''' rootPath = os.path.split(fromWavFN)[0] # Prep output directories outputPath = join(rootPath, "duration_resynthesized_wavs") utils.makeDir(outputPath) durationTierPath = join(rootPath, "duration_tiers") utils.makeDir(durationTierPath) fromWavDuration = audio_scripts.getSoundFileDuration(fromWavFN) durationParameters = copy.deepcopy(durationParameters) # Pad any gaps with values of 1 (no change in duration) # No need to stretch out any pauses at the beginning if durationParameters[0][0] != 0: tmpVar = (0, durationParameters[0][0] - PRAAT_TIME_DIFF, 1) durationParameters.insert(0, tmpVar) # Or the end if durationParameters[-1][1] < fromWavDuration: durationParameters.append( (durationParameters[-1][1] + PRAAT_TIME_DIFF, fromWavDuration, 1)) # Create the praat script for doing duration manipulation for stepAmount in stepList: durationPointList = [] for start, end, ratio in durationParameters: percentChange = 1 + (ratio - 1) * stepAmount durationPointList.append((start, percentChange)) durationPointList.append((end, percentChange)) outputPrefix = "%s_%0.3g" % (outputName, stepAmount) durationTierFN = join(durationTierPath, "%s.DurationTier" % outputPrefix) outputWavFN = join(outputPath, "%s.wav" % outputPrefix) durationTier = dataio.PointObject2D(durationPointList, dataio.DURATION, 0, fromWavDuration) durationTier.save(durationTierFN) praat_scripts.resynthesizeDuration(praatEXE, fromWavFN, durationTierFN, outputWavFN, outputMinPitch, outputMaxPitch)
outputWavFN = "mary1_accented.wav" outputPitchFN = "mary1_accented.pitch" minPitch = 75 maxPitch = 450 if not os.path.exists(rootOutputPath): os.mkdir(rootOutputPath) # 1st - get pitch piList = pitch_and_intensity.extractPI(join(root, wavFN), join(rootOutputPath, pitchIntensityFN), praatEXE, minPitch, maxPitch) pitchList = [(timeV, pitchV) for timeV, pitchV, _ in piList] dur = audioio.WavQueryObj(join(root, wavFN)).getDuration() pointObj = dataio.PointObject2D(pitchList, dataio.PITCH, 0, dur) pointObj.save(join(rootOutputPath, originalPitchFN)) # 2nd - get region to manipulate. Let's make the subject more emphatic! tg = tgio.openTextgrid(join(root, "mary1.TextGrid")) tier = tg.tierDict["words"] start, stop, _ = tier.entryList[0] # Getting info for the first word targetPitchList = [(timeV, pitchV) for timeV, pitchV in pitchList if timeV >= start and timeV <= stop] # 3rd - make manipulation accent = modify_pitch_accent.PitchAccent(targetPitchList) accent.addPlateau(0.05) # Peak is dragged out for 0.05 seconds accent.adjustPeakHeight(60) # Plateau is raised by 60 hz accent.shiftAccent(-0.03) # Recenter the plateau around the original peak
def f0Morph(fromWavFN, pitchPath, stepList, outputName, doPlotPitchSteps, fromPitchData, toPitchData, outputMinPitch, outputMaxPitch, praatEXE, keepPitchRange=False, keepAveragePitch=False): ''' 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) ''' fromDuration = audio_scripts.getSoundFileDuration(fromWavFN) # Iterative pitch tier data path pitchTierPath = join(pitchPath, "pitchTiers") resynthesizedPath = join(pitchPath, "f0_resynthesized_wavs") for tmpPath in [pitchTierPath, resynthesizedPath]: utils.makeDir(tmpPath) # 1. Prepare the data for morphing - acquire the segments to merge # (Done elsewhere, with the input fed into this function) # 2. Morph the fromData to the toData try: finalOutputList = morph_sequence.morphChunkedDataLists( fromPitchData, toPitchData, stepList) except IndexError: raise MissingPitchDataException() fromPitchData = [row for subList in fromPitchData for row in subList] toPitchData = [row for subList in toPitchData for row in subList] # 3. Save the pitch data and resynthesize the pitch mergedDataList = [] for i in range(0, len(finalOutputList)): outputDataList = finalOutputList[i] if keepPitchRange is True: outputDataList = morph_sequence.morphRange(outputDataList, fromPitchData) if keepAveragePitch is True: outputDataList = morph_sequence.morphAveragePitch( outputDataList, fromPitchData) stepOutputName = "%s_%0.3g" % (outputName, stepList[i]) pitchFNFullPath = join(pitchTierPath, "%s.PitchTier" % stepOutputName) outputFN = join(resynthesizedPath, "%s.wav" % stepOutputName) pointObj = dataio.PointObject2D(outputDataList, dataio.PITCH, 0, fromDuration) pointObj.save(pitchFNFullPath) outputTime, outputVals = list(zip(*outputDataList)) mergedDataList.append((outputTime, outputVals)) praat_scripts.resynthesizePitch(praatEXE, fromWavFN, pitchFNFullPath, outputFN, outputMinPitch, outputMaxPitch) # 4. (Optional) Plot the generated contours if doPlotPitchSteps: fromTime, fromVals = list(zip(*fromPitchData)) toTime, toVals = list(zip(*toPitchData)) plot_morphed_data.plotF0((fromTime, fromVals), (toTime, toVals), mergedDataList, join(pitchTierPath, "%s.png" % outputName))
def f0Morph(fromWavFN, pitchPath, stepList, outputName, doPlotPitchSteps, fromPitchData, toPitchData, outputMinPitch, outputMaxPitch, praatEXE, keepPitchRange=False, keepAveragePitch=False, sourcePitchDataList=None, minIntervalLength=0.3): ''' 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. ''' fromDuration = audio_scripts.getSoundFileDuration(fromWavFN) # Find source pitch samples that will be mixed in with the target # pitch samples later nonMorphPitchData = [] if sourcePitchDataList is not None: timeList = sorted(fromPitchData) timeList = [(row[0][0], row[-1][0]) for row in timeList] endTime = sourcePitchDataList[-1][0] invertedTimeList = praatio_utils.invertIntervalList(timeList, endTime) invertedTimeList = [(start, stop) for start, stop in invertedTimeList if stop - start > minIntervalLength] for start, stop in invertedTimeList: pitchList = praatio_utils.getValuesInInterval( sourcePitchDataList, start, stop) nonMorphPitchData.extend(pitchList) # Iterative pitch tier data path pitchTierPath = join(pitchPath, "pitchTiers") resynthesizedPath = join(pitchPath, "f0_resynthesized_wavs") for tmpPath in [pitchTierPath, resynthesizedPath]: utils.makeDir(tmpPath) # 1. Prepare the data for morphing - acquire the segments to merge # (Done elsewhere, with the input fed into this function) # 2. Morph the fromData to the toData try: finalOutputList = morph_sequence.morphChunkedDataLists( fromPitchData, toPitchData, stepList) except IndexError: raise MissingPitchDataException() fromPitchData = [row for subList in fromPitchData for row in subList] toPitchData = [row for subList in toPitchData for row in subList] # 3. Save the pitch data and resynthesize the pitch mergedDataList = [] for i in range(0, len(finalOutputList)): outputDataList = finalOutputList[i] if keepPitchRange is True: outputDataList = morph_sequence.morphRange(outputDataList, fromPitchData) if keepAveragePitch is True: outputDataList = morph_sequence.morphAveragePitch( outputDataList, fromPitchData) if sourcePitchDataList is not None: outputDataList.extend(nonMorphPitchData) outputDataList.sort() stepOutputName = "%s_%0.3g" % (outputName, stepList[i]) pitchFNFullPath = join(pitchTierPath, "%s.PitchTier" % stepOutputName) outputFN = join(resynthesizedPath, "%s.wav" % stepOutputName) pointObj = dataio.PointObject2D(outputDataList, dataio.PITCH, 0, fromDuration) pointObj.save(pitchFNFullPath) outputTime, outputVals = zip(*outputDataList) mergedDataList.append((outputTime, outputVals)) praat_scripts.resynthesizePitch(praatEXE, fromWavFN, pitchFNFullPath, outputFN, outputMinPitch, outputMaxPitch) # 4. (Optional) Plot the generated contours if doPlotPitchSteps: fromTime, fromVals = zip(*fromPitchData) toTime, toVals = zip(*toPitchData) plot_morphed_data.plotF0((fromTime, fromVals), (toTime, toVals), mergedDataList, join(pitchTierPath, "%s.png" % outputName))