This Praat script creates an F0 continuum for two segmentally matching words (e.g., SUBject vs. subJECT). First, it matches the two words in duration and then interpolates the F0 contour linearly in 11 steps (steps 1 and 11 being the original contours), controlling for intensity.
You can also download the script as a .praat file.
#____________________________________ HEADER __________________________________####
# date: 31.01.2023, run in Praat 6.2.12 on Windows 11
# author: Ronny Bujok (adapted from Hans Rutger Bosker)
# email: Ronny.Bujok@mpi.nl
# filename: F0_stress continuum_interpolation.praat
# project: Audiovisual Perception of Lexical Stress
# license: CC BY-NC 4.0
####################################################################################################################
# This script takes recordings of segmentally identical, disyllabic minimal stress pairs with lexical stress on the first
# (strong-weak; SW) or second (weak-strong; WS) syllable (e.g., VOORnaam vs. voorNAAM) and interpolates their F0-contours linearly.
# The manipulated F0-contours are applied to the SW recording to create an F0-based lexical stress continuum.
# Duration and Intensity are set to mean, ambiguous values.
#
# This script requires minimal pairs to match in their name. Stress pattern in input files is denoted with "_sw" or "_ws"
# (e.g., voornaam_sw.wav & voornaam_ws.wav). No other underscore characters are allowed.
# Textgrids for each recording are required, with boundaries at word onset, second syllable onset and word offset.
#
# Note that the parameters for pitch estimation are set to match a male voice. When working with a female voice,
# please adjust the arguments of the functions "Lengthen (overlap-add)" and "To Manipulation".
####################################################################################################################
# Define input and output directory
input_directory$ = "C:\input folder"
output_directory$= "C:\output folder"
# create file list of all files with the ending "_sw.wav" (just sound files, one per minimal pair (sw))
Create Strings as file list... list 'input_directory$'\*_sw.wav
n = Get number of strings
# _________________________________ CREATE FILELIST ____________________________________________________
# create list again because it removes itself after every iteration
for x from 1 to n
Create Strings as file list... list 'input_directory$'\*_sw.wav
# get only the word name without extension or stress identifier (sw or ws)
select Strings list
current_file$ = Get string... x
idx = rindex (current_file$, "_")
word$ = left$(current_file$,idx-1)
#_____________________________________ DURATION ___________________________________________________________
# To interpolate F0 contours, recordings must be of equal length. So duration must be manipulated first.
#---------------------------------- get values from SW word ---------------------------------------------
# open SW word and convert to mono
wavfile_sw$ = "'word$'_sw"
Read from file... 'input_directory$'\'wavfile_sw$'.Textgrid
Read from file... 'input_directory$'\'wavfile_sw$'.wav
Convert to mono
Rename: "mono_sw"
wav_sw_id = selected()
# select the mono file and the corresponding textgrid and extract all intervals
selectObject: "TextGrid 'wavfile_sw$'"
plusObject: "Sound mono_sw"
Extract all intervals: 1, "no"
# select the second interval (first syllable) and get the total duration and the intensity
select (wav_sw_id+2)
sw_dur1= Get total duration
sw_int1= Get intensity (dB)
Rename: "sw_1"
# select the third interval (second syllable) and get the total duration and the intensity
select (wav_sw_id+3)
sw_dur2= Get total duration
sw_int2= Get intensity (dB)
Rename: "sw_2"
# select and rename the first and last interval (silences) for future reference, to concatenate the final audio
select (wav_sw_id+1)
Rename: "pre_silence_sw"
select (wav_sw_id+4)
Rename: "post_silence_sw"
#---------------------------------- get values from WS word -----------------------------------------
# open WS word and convert to mono
wavfile_ws$ = "'word$'_ws"
Read from file... 'input_directory$'\'wavfile_ws$'.Textgrid
Read from file... 'input_directory$'\'wavfile_ws$'.wav
Convert to mono
Rename: "mono_ws"
wav_ws_id = selected()
# select the mono file and the corresponding textgrid and extract all intervals
selectObject: "TextGrid 'wavfile_ws$'"
plusObject: "Sound mono_ws"
Extract all intervals: 1, "no"
# select the second interval (first syllable) and get the total duration and the intensity
select (wav_ws_id+2)
ws_dur1= Get total duration
ws_int1= Get intensity (dB)
Rename: "ws_1"
# select the third interval (second syllable) and get the total duration and the intensity
select (wav_ws_id+3)
ws_dur2= Get total duration
ws_int2= Get intensity (dB)
Rename: "ws_2"
#---------------------------------- Manipulate duration ------------------------------------------
# Set the duration of syllable 1 of the SW word to the average, ambiguous duration
select Sound sw_1
targetdur_1 = ((sw_dur1+ws_dur1)/2)
Lengthen (overlap-add)... 75 250 (targetdur_1/sw_dur1)
Rename: "sw_1_durmatched"
# Set the duration of syllable 2 of the SW word to the average, ambiguous duration
select Sound sw_2
targetdur_2 = ((sw_dur2+ws_dur2)/2)
Lengthen (overlap-add)... 75 250 (targetdur_2/sw_dur2)
Rename: "sw_2_durmatched"
# Set the duration of syllable 1 of the WS word to the average, ambiguous duration
select Sound ws_1
Lengthen (overlap-add)... 75 250 (targetdur_1/ws_dur1)
Rename: "ws_1_durmatched"
# Set the duration of syllable 2 of the WS word to the average, ambiguous duration
select Sound ws_2
Lengthen (overlap-add)... 75 250 (targetdur_2/ws_dur2)
Rename: "ws_2_durmatched"
# Concatenate duration-manipulated SW word
select Sound sw_1_durmatched
plusObject: "Sound sw_2_durmatched"
Concatenate recoverably
select Sound chain
Rename: "durmatched_sw"
# Concatenate duration-manipulated WS word
select Sound ws_1_durmatched
plusObject: "Sound ws_2_durmatched"
Concatenate recoverably
select Sound chain
Rename: "durmatched_ws"
# save TextGrid with the boundary between the syllables. Necessary for intensity manipulation
select TextGrid chain
Rename: "syl_boundary"
#__________________________________________ F0 INTERPOLATION ________________________________________________
# Extract the pitch tiers
select Sound durmatched_sw
s1_dur = Get total duration
To Manipulation... 0.01 50 300
Extract pitch tier
select Sound durmatched_ws
s2_dur = Get total duration
To Manipulation... 0.01 50 300
Extract pitch tier
# create 10 ms time bins for interpolation
timebinsize = 0.01
nbins = floor(s1_dur/timebinsize)
for currentbin from 1 to 'nbins'
currentbin_start = (currentbin*timebinsize)-timebinsize
currentbin_end = (currentbin*timebinsize)
currentbin_mid = (currentbin_start + currentbin_end)/2
select PitchTier durmatched_sw
currentbin_f0_s1 = Get value at time... currentbin_mid
f0_bin_s1 [currentbin] = currentbin_f0_s1
select PitchTier durmatched_ws
currentbin_f0_s2 = Get value at time... currentbin_mid
f0_bin_s2 [currentbin] = currentbin_f0_s2
endfor
#define number of steps for interpolation
nsteps = 11
step_ratio = 1/('nsteps'-1)
# This means that...
# the stepsize is 10% of the difference between SW and WS;
# step 1 of the continuum is the original SW contour;
# step 11 of the continuum is the original WS contour.
# interpolate the pitch tiers
for currentstep from 1 to 'nsteps'
select PitchTier durmatched_sw
Copy... interpol_'currentstep'
# first remove all original F0 points
Remove points between... 0 's1_dur'
# now add a point for each time bin
for currentbin from 1 to 'nbins'
currentbin_start = (currentbin*timebinsize)-timebinsize
currentbin_end = (currentbin*timebinsize)
currentbin_mid = (currentbin_start + currentbin_end)/2
currentbin_f0_s1 = f0_bin_s1 [currentbin]
currentbin_f0_s2 = f0_bin_s2 [currentbin]
currentbin_f0_diff = currentbin_f0_s1 - currentbin_f0_s2
currentbin_f0_ratio = currentbin_f0_diff * 'step_ratio'
currentbin_f0_interpol = currentbin_f0_s1 - (currentbin_f0_ratio * ('currentstep'-1))
Add point... 'currentbin_mid' 'currentbin_f0_interpol'
endfor
# select original audio (in this case SW recording) and replace the pitch contour
select Manipulation durmatched_sw
Copy... interpol_'currentstep'
plusObject: "PitchTier interpol_'currentstep'"
Replace pitch tier
minusObject: "PitchTier interpol_'currentstep'"
Get resynthesis (overlap-add)
interval_id = selected()
#__________________________________________ INTENSITY____________________________________________________
# extract each syllable again for intensity manipulation
plusObject: "TextGrid syl_boundary"
Extract all intervals: 1, "no"
# set (average) target intensity per syllable
targetint_1 = ((sw_int1+ws_int1)/2)
targetint_2 = ((sw_int2+ws_int2)/2)
# adjust the intensities to an average/ambigous value
select (interval_id+1)
Scale intensity... targetint_1
Rename: "final_syl_1"
select (interval_id+2)
Scale intensity... targetint_2
Rename: "final_syl_2"
#______________________________________ CONCATENATE & SAVE ______________________________________________
# copy final segment of the recording (silence post word offset), so Praat concatenates in the correct order
select Sound post_silence_sw
Copy... post_silence_sw_copy
# select remaining segments
plusObject: "Sound pre_silence_sw"
plusObject: "Sound final_syl_1"
plusObject: "Sound final_syl_2"
plusObject: "post_silence_sw_copy"
# concatenate
Concatenate recoverably
select Sound chain
Rename: "'word$'_step_'currentstep'"
Save as WAV file... 'output_directory$'\'word$'_step_'currentstep'.wav
select TextGrid chain
Save as text file... 'output_directory$'\'word$'_step_'currentstep'.TextGrid
endfor
select all
Remove
endfor
################################################################################
# End of script
################################################################################