# generate.praat # Author: James Jodoin at The University of Texas at El Paso # # generate.praat is a script designed to interpolate between two audios. # # Audio files should be named like "name-neg" and "name-pos" # There must be a .TextGrid file for each Sound, named identically. # # Positive sounds with P100 and # Negative sounds with P0 should # sound like the original recording. # # TODO: # Use pitch method from neg on pos for better tracking # Intensity standard deviation is varying # Save sound objects to file # Consider a more advanced method of choosing ptierBase # # Sublime Text 3 with praat highlighting looks great. form Setup comment Choose a script mode: choice mode: 1 button Interpolate between two audios button Non configuration manipulations choice words: 1 button First and second word button First word only choice intON: 2 button Intensity manipulations ON button Intensity manipulations OFF comment All Sound pairs are in: word soundDir E:\Users\Jodoin\Documents\praat\interpolation\ comment All TextGrid files are in: word txtgDir E:\Users\Jodoin\Documents\praat\interpolation\textgrids\ comment Sound output should go in: word outDir E:\Users\Jodoin\Documents\praat\interpolation\interpolated\ endform appendInfoLine: "Looking in '", soundDir$, "' for sound files..." # Add 'neg' after the star to get only neg audios for entire audio mode soundFileNames = Create Strings as file list: "soundFileNames", soundDir$ + "/*.wav" numSoundFiles = Get number of strings # Non configuration manipulations i.e. the "Averaging" method if (mode == 2) for filei to numSoundFiles selectObject: soundFileNames sound$ = Get string: filei name$ = left$(sound$, index(sound$, "-")) appendInfoLine: sound$ sound = Read from file: soundDir$ + sound$ durTotal = Get total duration sound$ = selected$("Sound") # Max pitch must be accurate or mean readings wrong manip = To Manipulation: 0.005, 75, 400 negPitchTier = Extract pitch tier negMean = Get mean (points): 0, 0 selectObject: manip dTier = Extract duration tier selectObject: sound neutDuration = Get total duration intensity = To Intensity: 75, 0, "yes" meanInt = Get mean: 0, 0, "dB" iTier = Down to IntensityTier txtGridN = Read from file: soundDir$ + "textgrids\" + name$ + "neg.TextGrid" pos = Read from file: soundDir$ + name$ + "pos.wav" # Max pitch must be accurate or mean readings wrong posManip = To Manipulation: 0.005, 75, 400 posPitchTier = Extract pitch tier posMean = Get mean (points): 0, 0 pitchRatio = posMean / negMean txtGridP = Read from file: soundDir$ + "textgrids\" + name$ + "pos.TextGrid" # EXTRACT INTERVALS FROM NEG TEXTGRID # selectObject: txtGridN fstWrdStartN = Get start time of interval: 1, 2 fstWrdEndN = Get end time of interval: 1, 4 fstVwlStartN = Get start time of interval: 1, 3 fstVwlEndN = Get end time of interval: 1, 3 scndWrdStartN = Get start time of interval: 1, 5 scndWrdEndN = Get end time of interval: 1, 7 scndVwlStartN = Get start time of interval: 1, 6 scndVwlEndN = Get end time of interval: 1, 6 fstVwlDurN = fstVwlEndN - fstVwlStartN scndVwlDurN = scndVwlEndN - scndVwlStartN selectObject: txtGridP fstWrdStartP = Get start time of interval: 1, 2 fstWrdEndP = Get end time of interval: 1, 4 fstVwlStartP = Get start time of interval: 1, 3 fstVwlEndP = Get end time of interval: 1, 3 scndWrdStartP = Get start time of interval: 1, 5 scndWrdEndP = Get end time of interval: 1, 7 scndVwlStartP = Get start time of interval: 1, 6 scndVwlEndP = Get end time of interval: 1, 6 fstVwlDurP = fstVwlEndP - fstVwlStartP scndVwlDurP = scndVwlEndP - scndVwlStartP selectObject: pos posDuration = Get total duration durDif = posDuration - neutDuration durDif2 = scndVwlDurP - scndVwlDurN fstVwlDurRatN = (fstVwlDurN + durDif2) / fstVwlDurN scndVwlDurRatN = (scndVwlDurN + durDif2) / scndVwlDurN appendInfoLine: "Time difference second vowel:" appendInfoLine: durDif2 appendInfoLine: "1st, 2nd word vowel ratios:" appendInfoLine: fstVwlDurRatN appendInfoLine: scndVwlDurRatN selectObject: negPitchTier fstWrdMeanN = Get mean (points): fstWrdStartN, fstWrdEndN scndWrdMeanN = Get mean (points): scndWrdStartN, scndWrdEndN overallMeanN = Get mean (points): fstWrdStartN, scndWrdEndN selectObject: posPitchTier fstWrdMeanP = Get mean (points): fstWrdStartP, fstWrdEndP scndWrdMeanP = Get mean (points): scndWrdStartP, scndWrdEndP overallMeanP = Get mean (points): fstWrdStartP, scndWrdEndP fstWrdPitchRat = fstWrdMeanP / fstWrdMeanN scndWrdPitchRat = scndWrdMeanP / scndWrdMeanN overallPitchRat = overallMeanP / overallMeanN appendInfoLine: "Averages for neg:" appendInfoLine: fstWrdMeanN appendInfoLine: scndWrdMeanN appendInfoLine: overallMeanN appendInfoLine: "Averages for pos:" appendInfoLine: fstWrdMeanP appendInfoLine: scndWrdMeanP appendInfoLine: overallMeanP appendInfoLine: "1st, 2nd word pitch ratios:" appendInfoLine: fstWrdPitchRat appendInfoLine: scndWrdPitchRat appendInfoLine: overallPitchRat for i from 0 to 10 lvl = i / 10 selectObject: manip manipTmp = Copy: sound$ + "-AP" + string$(i) selectObject: negPitchTier pTierTmp = Copy: sound$ + "-AP" + string$(i) Multiply frequencies: fstWrdStartN, scndWrdEndN, 1 + ((2 ^ lvl) - 1) * (overallPitchRat - 1) #Multiply frequencies: fstWrdStartN, fstWrdEndN, 1 + ((2 ^ lvl) - 1) * (fstWrdPitchRat - 1) #Multiply frequencies: scndWrdStartN, scndWrdEndN, 1 + ((2 ^ lvl) - 1) * (scndWrdPitchRat - 1) selectObject: manipTmp, pTierTmp Replace pitch tier selectObject: manipTmp Get resynthesis (overlap-add) Rename: sound$ + "-AP" + string$(i) Save as WAV file: outDir$ + sound$ + "-AP" + string$(i) + ".wav" removeObject: manipTmp, pTierTmp endfor for i from 0 to 10 lvl = i / 10 selectObject: manip manipTmp = Copy: sound$ + "-AD" + string$(i) # Both first and second word if (words == 1) nameTag$ = "-AD" @buildDurationTier: dTier, lvl, fstVwlStartN, fstVwlEndN, 1 + (0.5 * (fstVwlDurRatN - 1)) dTierTmp = buildDurationTier.dTierTmp @buildDurationTier: dTierTmp, lvl, scndVwlStartN, scndVwlEndN, 1 + (0.5 * (scndVwlDurRatN - 1)) dTierTmp = buildDurationTier.dTierTmp else nameTag$ = "-AFD" @buildDurationTier: dTier, lvl, fstVwlStartN, fstVwlEndN, (fstVwlDurRatN) dTierTmp = buildDurationTier.dTierTmp endif selectObject: manipTmp, dTierTmp Replace duration tier selectObject: manipTmp Get resynthesis (overlap-add) Rename: sound$ + nameTag$ + string$(i) Save as WAV file: outDir$ + sound$ + nameTag$ + string$(i) + ".wav" removeObject: manipTmp, dTierTmp endfor # Flag for intensity manipulation (usually off) if (intON == 1) for i from 0 to 10 selectObject: sound sTmp = Copy: sound$ + "-AI" + string$(i) selectObject: iTier iTierTmp = Copy: sound$ + "-AI" + string$(i) coef = 5 * i nPts = Get number of points for pt to nPts time = Get time from index: pt int = Get value at index: pt Remove point: pt Add point: time, (coef * ((int - meanInt) / meanInt)) endfor selectObject: sTmp, iTierTmp Multiply: "no" Save as WAV file: outDir$ + sound$ + "-AI" + string$(i) + ".wav" removeObject: sTmp, iTierTmp endfor endif endfor else ; mode == 1 if (numSoundFiles mod 2 != 0) removeObject: soundFileNames exitScript: "Expecting file pairs, found odd number of files." endif txtgFileNames = Create Strings as file list: "txtgFileNames", txtgDir$ + "/*.TextGrid" numTxtgFiles = Get number of strings if (numSoundFiles != numTxtgFiles) removeObject: soundFileNames, txtgFileNames exitScript: "Mismatched number of sound and textgrid files." endif runSystem: "mkdir ", outDir$ numPT = 9 stepPT = 1 / (numPT - 1) numDT = 9 stepDT = 1 / (numDT - 1) numIT = 9 stepIT = 1 / (numIT - 1) # LOAD NEG AND POS PAIRS TOGETHER # appendInfoLine: "Manipulating sound pairs..." filei = 1 while filei <= numSoundFiles # LOAD NEG AUDIO # selectObject: soundFileNames soundN$ = Get string: filei soundN = Read from file: soundDir$ + "/" + soundN$ soundN$ = selected$ ("Sound") manipN = To Manipulation: 0.005, 75, 400 appendInfoLine: soundN$ + ".wav loaded" # LOAD NEG TEXTGRID # Read from file: txtgDir$ + soundN$ + ".TextGrid" txtgN$ = selected$ ("TextGrid") appendInfoLine: txtgN$ + ".TextGrid loaded" # EXTRACT INTERVALS FROM NEG TEXTGRID # selectObject: "TextGrid " + txtgN$ fstWrdStartN = Get start time of interval: 1, 2 fstWrdEndN = Get end time of interval: 1, 4 fstVwlStartN = Get start time of interval: 1, 3 fstVwlEndN = Get end time of interval: 1, 3 scndWrdStartN = Get start time of interval: 1, 5 scndWrdEndN = Get end time of interval: 1, 7 scndVwlStartN = Get start time of interval: 1, 6 scndVwlEndN = Get end time of interval: 1, 6 fstVwlDurN = fstVwlEndN - fstVwlStartN scndVwlDurN = scndVwlEndN - scndVwlStartN # EXTRACT DURATION FROM NEG AUDIO # appendInfoLine: "Extracting duration tier from neg sound" selectObject: manipN dtierN = Extract duration tier filei = filei + 1 # LOAD POS AUDIO # selectObject: soundFileNames soundP$ = Get string: filei soundP = Read from file: soundDir$ + "/" + soundP$ soundP$ = selected$ ("Sound") manipP = To Manipulation: 0.005, 75, 400 appendInfoLine: soundP$ + ".wav loaded" # LOAD POS TEXTGRID # Read from file: txtgDir$ + soundP$ + ".TextGrid" txtgP$ = selected$ ("TextGrid") appendInfoLine: txtgP$ + ".TextGrid loaded" # EXTRACT INTERVALS FROM POS TEXTGRID # selectObject: "TextGrid " + txtgP$ fstWrdStartP = Get start time of interval: 1, 2 fstWrdEndP = Get end time of interval: 1, 4 fstVwlStartP = Get start time of interval: 1, 3 fstVwlEndP = Get end time of interval: 1, 3 scndWrdStartP = Get start time of interval: 1, 5 scndWrdEndP = Get end time of interval: 1, 7 scndVwlStartP = Get start time of interval: 1, 6 scndVwlEndP = Get end time of interval: 1, 6 fstVwlDurP = fstVwlEndP - fstVwlStartP scndVwlDurP = scndVwlEndP - scndVwlStartP # Ratio must be greater than or equal to 1 # fstVwlDurRatN = fstVwlDurP / fstVwlDurN fstVwlDurRatP = fstVwlDurN / fstVwlDurP scndVwlDurRatN = scndVwlDurP / scndVwlDurN scndVwlDurRatP = scndVwlDurN / scndVwlDurP appendInfoLine: fstVwlDurRatN appendInfoLine: scndVwlDurRatN # EXTRACT DURATION FROM POS AUDIO # appendInfoLine: "Extracting duration tier from pos sound" selectObject: manipP dtierP = Extract duration tier filei = filei + 1 ########################DURATION######################## # CONSTRUCT 3 NEW DURATION TIERS FOR NEG AUDIO # appendInfoLine: "Building new duration tiers for vowels..." for d from 0 to (numDT - 1) lvl = d * stepDT @buildDurationTier: dtierN, lvl, fstVwlStartN, fstVwlEndN, fstVwlDurRatN dtierNs[d+1] = buildDurationTier.dTierTmp @buildDurationTier: dtierNs[d+1], lvl, scndVwlStartN, scndVwlEndN, scndVwlDurRatN dtierNs[d+1] = buildDurationTier.dTierTmp @buildDurationTier: dtierP, lvl, fstVwlStartP, fstVwlEndP, fstVwlDurRatP dtierPs[d+1] = buildDurationTier.dTierTmp @buildDurationTier: dtierPs[d+1], lvl, scndVwlStartP, scndVwlEndP, scndVwlDurRatP dtierPs[d+1] = buildDurationTier.dTierTmp endfor # REPLACE DURATION TIERS ON MANIPS # appendInfoLine: "Replacing duration tiers..." for d from 0 to (numDT - 1) selectObject: manipN mTmp = Copy: "" selectObject: dtierNs[d+1] d$ = selected$("DurationTier") selectObject: mTmp, dtierNs[d+1] Replace duration tier selectObject: mTmp Rename: soundN$ + d$ soundTmp = Get resynthesis (overlap-add) dManipNs[d+1] = To Manipulation: 0.005, 75, 400 #removeObject: mTmp, soundTmp, dtierNs[d+1] selectObject: manipP mTmp = Copy: "" selectObject: dtierPs[d+1] d$ = selected$("DurationTier") selectObject: mTmp, dtierPs[d+1] Replace duration tier selectObject: mTmp Rename: soundP$ + d$ soundTmp = Get resynthesis (overlap-add) dManipPs[d+1] = To Manipulation: 0.005, 75, 400 removeObject: mTmp, soundTmp, dtierPs[d+1] endfor ########################PITCH######################## # EXTRACT PITCH TIERS # appendInfoLine: "Extracting pitch tiers..." for d to numDT selectObject: dManipNs[d] pTierNs[d] = Extract pitch tier selectObject: dManipPs[d] pTierPs[d] = Extract pitch tier endfor # BUILD NEW PITCH TIERS AND REPLACE FOR ALL DURATIONS # appendInfoLine: "Building new pitch tiers..." nSounds = 1 for d to numDT # GENERALIZE VAR NAMES FOR PITCH # pTierBase = pTierNs[d] ; x val - time pTierTarget = pTierPs[d] ; y val - pitch selectObject: pTierBase nPtsN = Get number of points # ACCOUNT FOR ADDED TIME FROM DURATION CHANGE # selectObject: soundN originalLenN = Get total duration selectObject: pTierBase dManipLenN = Get total duration timeAddedN = dManipLenN - originalLenN adjustedEndTimeN = scndWrdEndN + timeAddedN # TRANSLATE TIME TO INDEX # startPtPitch1N = Get nearest index from time: fstWrdStartN endPtPitch1N = Get nearest index from time: fstWrdEndN startPtPitch2N = Get nearest index from time: scndWrdStartN endPtPitch2N = Get nearest index from time: adjustedEndTimeN # CALCULATE INTERVAL LENGTHS # word1DurN = fstWrdEndN - fstWrdStartN word2DurN = adjustedEndTimeN - scndWrdStartN # ACCOUNT FOR ADDED TIME FROM DURATION CHANGE # selectObject: soundP originalLenP = Get total duration selectObject: pTierTarget dManipLenP = Get total duration timeAddedP = dManipLenP - originalLenP adjustedEndTimeP = scndWrdEndP + timeAddedP # CALCULATE INTERVAL LENGTHS # word1DurP = fstWrdEndP - fstWrdStartP word2DurP = adjustedEndTimeP - scndWrdStartP # BUILD NEW PITCH TIERS # for p from 0 to (numPT - 1) lvl = p * stepPT p$ = "-P" + string$(round(100 * lvl)) # Paranoid copying selectObject: dManipNs[d] dManipTmpN = Copy: "" selectObject: dManipPs[d] dManipTmpP = Copy: "" selectObject: pTierBase #pTierTmpN = Copy: "" # Should this be pTierTarget? selectObject: pTierBase #pTierTmpP = Copy: "" # COMPUTE SCALED PITCH INTERVAL 1 # #appendInfoLine: "Building pitch interval 1" @buildPitchTier: pTierBase, pTierTarget, lvl, ... fstWrdStartN, fstWrdEndN, fstWrdStartP, fstWrdEndP firstWordPT = buildPitchTier.pTierTmp pTierTmpN = firstWordPT # COMPUTE SCALED PITCH INTERVAL 2 # #appendInfoLine: "Building pitch interval 2" @buildPitchTier: pTierTmpN, pTierTarget, lvl, ... scndWrdStartN, adjustedEndTimeN, scndWrdStartP, adjustedEndTimeP pTierTmpN = buildPitchTier.pTierTmp # BASIC ALGORITHM FOR ENTIRE POS SOUND # #appendInfoLine: "Building pitch interval for pos manip" @buildPitchTier: pTierTarget, pTierBase, (1 - lvl), ... fstWrdStartP, adjustedEndTimeP, fstWrdStartN, adjustedEndTimeN pTierTmpP = buildPitchTier.pTierTmp # REPLACE NEG PITCH TIER # selectObject: dManipTmpN, pTierTmpN Replace pitch tier selectObject: dManipTmpN dpSoundN = Get resynthesis (overlap-add) Rename: selected$("Sound") + p$ dpSoundsN[nSounds] = dpSoundN removeObject: dManipTmpN, firstWordPT, pTierTmpN # REPLACE POS PITCH TIER # selectObject: dManipTmpP, pTierTmpP Replace pitch tier selectObject: dManipTmpP dpSoundP = Get resynthesis (overlap-add) Rename: selected$("Sound") + p$ dpSoundsP[nSounds] = dpSoundP removeObject: dManipTmpP, pTierTmpP nSounds = nSounds + 1 endfor ; pitch removeObject: dManipNs[d], dManipPs[d], pTierNs[d], pTierPs[d] endfor ; duration nSounds = nSounds - 1 ; negate last increment # BUILD NEW INTENSITY TIERS AND MULTIPLY # if (intON == 1) appendInfoLine: "Extracting, building, and multiplying intensity tiers..." for s to nSounds for k from 0 to (numIT - 1) lvl = k * stepIT selectObject: dpSoundsN[s] sTmp = Copy: "" name$ = selected$("Sound") iTmp = To Intensity: 75, 0, "yes" @buildIntensityTier: iTmp, lvl, scndWrdStartN, scndWrdEndN selectObject: sTmp, buildIntensityTier.iTierTmp Multiply: "no" Rename: name$ + "-I" + string$(round(100 * lvl)) removeObject: sTmp, iTmp, buildIntensityTier.iTierTmp endfor for k from 0 to (numIT - 1) lvl = k * stepIT selectObject: dpSoundsP[s] sTmp = Copy: "" name$ = selected$("Sound") iTmp = To Intensity: 75, 0, "yes" @buildIntensityTier: iTmp, lvl, scndWrdStartP, scndWrdEndP selectObject: sTmp, buildIntensityTier.iTierTmp Multiply: "no" Rename: name$ + "-I" + string$(round(100 * lvl)) removeObject: sTmp, iTmp, buildIntensityTier.iTierTmp endfor #removeObject: dpSoundsN[s], dpSoundsP[s] endfor ; nSounds endif for i to nSounds selectObject: dpSoundsN[i] Save as WAV file: outDir$ + selected$("Sound") + ".wav" selectObject: dpSoundsP[i] Save as WAV file: outDir$ + selected$("Sound") + ".wav" endfor endwhile ; files endif # Flat increase in vowel interval # # dRatios between 0 and 1 unsupported # procedure buildDurationTier: .dtier, .lvl, .startT, .endT, .dRatio if (.dRatio > 1) .multiplier = (1 + (.lvl * (.dRatio - 1))) .lvlString$ = string$(round(100 * .lvl)) else if (.dRatio > 0) .multiplier = (1 + (.lvl * (.dRatio - 1))) .lvlString$ = string$(round(100 * (1 -.lvl))) else .multiplier = 1 endif endif selectObject: .dtier .dTierTmp = Copy: "-D" + .lvlString$ Add point: .startT, 1 Add point: .startT + 0.0001, .multiplier Add point: .endT - 0.0001, .multiplier Add point: .endT, 1 endproc # Builds new pTier scaling from each base pt to a target time and averaging pitch # # Does not work for positive audio, undefined percentage of dur # # Input .lvl needs to be between 0 and 1 # procedure buildPitchTier: .pTierBase, .pTierTarget, .lvl, .ivStartB, .ivEndB, .ivStartT, .ivEndT .p$ = "-P" + string$(round(100 * .lvl)) selectObject: .pTierBase .pTierTmp = Copy: .p$ .ivDurB = .ivEndB - .ivStartB .ivDurT = .ivEndT - .ivStartT .startPt = Get nearest index from time: .ivStartB .endPt = Get low index from time: .ivEndB for .pt from .startPt to .endPt selectObject: .pTierBase .timeB = Get time from index: .pt .freqB = Get value at index: .pt selectObject: .pTierTarget .timeT = .ivStartT + (((.timeB - .ivStartB) / .ivDurB) * .ivDurT) .freqT = Get value at time: .timeT #.newFreq = ((1 - .lvl) * .freqB) + ((.lvl) * .freqT) .percentOfPos = (2 ^ .lvl) - 1 .newFreq = ((1 - .percentOfPos) * .freqB) + ((.percentOfPos) * .freqT) selectObject: .pTierTmp Remove point near: .timeB Add point: .timeB, .newFreq endfor endproc # Copys then applies a formula to intensity tier # procedure buildIntensityTier: .intensity, .lvl, .startT, .endT selectObject: .intensity .meanInt = Get mean: .startT, .endT, "dB" .iTierTmp = Down to IntensityTier Rename: "-I" + string$(round(100 * .lvl)) .startPt = Get low index from time: .startT .endPt = Get high index from time: .endT .numPts = Get number of points for .pt to .numPts .pTime = Get time from index: .pt .int = Get value at index: .pt # INTENSITY ADJUSTMENT FORMULAS # .intensityFormula = (25 * ((.int - .meanInt) / .meanInt)) #.intensityFormula = (4 ^ ((.int - meanIntN) / meanIntN)) #.intensityFormula = 0.05 * .int selectObject: .iTierTmp Remove point: .pt if (.pt < .startPt || .pt > .endPt) .newInt = 0 else .newInt = lvl * .intensityFormula endif Add point: .pTime, .newInt endfor endproc