Praat 脚本 | 不同发音人的音高数据规整与对比

作者:熊子瑜
脚本ID:Praat.XZY20220102.022
上传时间:2022年01月02日
简介:不同人朗读同一篇语料,其韵律节奏模式可能会存在一定差异,音节时长和停顿模式有时会有较大差别,在Praat程序的编辑窗口中很难直接对比观察较长片段的语音音高数据。为便于将不同发音人的音高数据放在一起进行比较,笔者设计并开发了此脚本程序,可针对不同发音人的音高数据进行时长和频率两个维度的规整处理,然后将二者放在一起作图展示。此脚本可适用于较长的篇章语料分析,用户需事先要做好音节标注。

查看详情 | 点此下载该脚本程序


#By XIONG Ziyu
#last modify: 2022/01/03
#Praat version: 6.1.05
#功能:针对不同发音人的音高数据进行时长和频率的规整处理,并将二者放在一起作图,
#      以便于比较二者的异同,还可计算规整后的音高相关系数以及二者之间的残差等数据;
#参数说明:
#      PitchTier_Filename_Of_Speaker_A:发音人A的某个音高数据文件(*.PitchTier),作为基准数据;
#      TextGrid_Filename_Of_Speaker_A: 与上一文件相关联的标注文件(*.TextGrid),用于获取音节时间信息;
#      PitchTier_Filename_Of_Speaker_B:发音人B对应的音高数据文件(*.PitchTier),用于规整处理;
#      TextGrid_Filename_Of_Speaker_B: 与上一文件相关联的标注文件(*.TextGrid),用于获取音节时间信息;
#      None_Syllable_Markers:标注中的非音节性成分,其音高数据将被忽略;
#      Syllable_Tier_Index:标注文件中音节所在的层级;
#      Pitch_Mean_for_New_PitchTier:拟合的音高数据均值,如果为0,则以基准音高数据的均值为拟合均值;如果为1,则不修改基频均值。
#      Pitch_Range_Factor_for_New_PitchTier:拟合的音高音域缩放系数,如果为1,则不缩放,
#                                            如果为0,则根据发音人A的音域自动缩放,其他可自行设置,建议在0.8-1.2之间
#                                            缩放时,音域下限基本保持不变,通过调节音域上限实现缩放目标。
#      Smooth_PitchTier:对音高数据进行平滑处理,设置相应的系数,如果为0,则不做平滑处理。
#      Top_Title:图片的顶部标题内容;
#      Pitch_Color_For_Speaker_B:发音人B的音高数据作图颜色;
#      Erase_all,设置为1时则清空作图窗口中原有的内容;

   

form 不同发音人的音高数据规整与对比
	sentence PitchTier_Filename_Of_Speaker_A E:\语音库\汉语语料\f001\f001001_01.PitchTier
	sentence TextGrid_Filename_Of_Speaker_A E:\语音库\汉语语料\f001\f001001_01.TextGrid
	sentence PitchTier_Filename_Of_Speaker_B E:\语音库\汉语语料\m001\m001001_01.PitchTier
	sentence TextGrid_Filename_Of_Speaker_B E:\语音库\汉语语料\m001\m001001_01.TextGrid
	sentence None_Syllable_Markers sil;silb;silv;tl
	positive Syllable_Tier_Index 2
	real Pitch_Mean_for_New_PitchTier_(Hz) 0
	real Pitch_Range_Factor_for_New_PitchTier 0
	real Smooth_PitchTier_(Hz) 12
	sentence Top_Title 灰色为基准发音人 A 的音高数据,其他颜色的数据是由脚本程序基于其他发音人的音高数据自动生成的:读同一段话,先基于音节边界信息将其他发音人的音高数据与基准发音人的音高数据进行时间对齐处理,然后根据基准发音人和其他发音人的音高均值对其他发音人的音高数据进行规整,以消除二者的音阶差异,便于比较其异同
	optionmenu Pitch_Color_For_Speaker_B: 2
		option Black
		option Blue
		option Red
		option Yellow
		option Green		
	boolean Erase_all 1
endform

Create TextGrid: 0, 1, "Mary John bell", "bell"
select all
Remove

Text reading preferences: "UTF-8"
Text writing preferences: "UTF-8"

colorList1$="black"
colorList2$="blue"
colorList3$="red"
colorList4$="yellow"
colorList5$="green"

if none_Syllable_Markers$!=""
	none_Syllable_Markers$=replace$(none_Syllable_Markers$,",",";",0)
	none_Syllable_Markers$=replace$(none_Syllable_Markers$," ",";",0)
	none_Syllable_Markers$=replace$(none_Syllable_Markers$,",",";",0)
	none_Syllable_Markers$=replace$(none_Syllable_Markers$,";",";",0)
	none_Syllable_Markers$=replace$(none_Syllable_Markers$,"、",";",0)
endif
none_Syllable_Markers$=";"+none_Syllable_Markers$+";"

pitchTier_A$=pitchTier_Filename_Of_Speaker_A$
pitchTier_A$=replace$(pitchTier_A$,"/","\",0)
pitchTier_B$=pitchTier_Filename_Of_Speaker_B$
pitchTier_B$=replace$(pitchTier_B$,"/","\",0)
textGrid_A$=textGrid_Filename_Of_Speaker_A$
textGrid_A$=replace$(textGrid_A$,"/","\",0)
textGrid_B$=textGrid_Filename_Of_Speaker_B$
textGrid_B$=replace$(textGrid_B$,"/","\",0)

Read from file: "'pitchTier_A$'"
adur=Get total duration
Rename: "A"

Create TextGrid: 0, 'adur', "SYLLABLE", ""
Rename: "A"
Read from file: "'textGrid_A$'"
Rename: "TEMP"
aNums=Get number of intervals: 'syllable_Tier_Index'
preTime=0
for i from 1 to aNums
	selectObject: "TextGrid TEMP"
	st=Get start time of interval: 'syllable_Tier_Index', 'i'
	et=Get end time of interval: 'syllable_Tier_Index', 'i'
	lab$=Get label of interval: 'syllable_Tier_Index', 'i'
	if index(none_Syllable_Markers$,";"+lab$+";")=0 and lab$!=""
		selectObject: "TextGrid A"
		if st>preTime and st<adur
			Insert boundary: 1, 'st'
			preTime=st
		endif
		if et>preTime and st<adur
			Insert boundary: 1, 'et'
			preTime=et
		endif
		cpos=Get number of intervals: 1
		cpos=cpos-1
		Set interval text: 1, 'cpos', lab$
	else
		selectObject: "PitchTier A"
		cst=st+0.001
		cet=et-0.001
		Remove points between: 'cst', 'cet'
	endif
endfor
selectObject: "TextGrid TEMP"
Remove

selectObject: "PitchTier A"
opNums_A=Get number of points
opStdv_A=Get standard deviation (points): 0, 0
opMean_A=Get mean (points): 0, 0
pStdv_A=opStdv_A
pMean_A=opMean_A
To Pitch: 0.01, 60, 600
opMin_A=Get minimum: 0, 0, "Hertz", "Parabolic"
opMax_A=Get maximum: 0, 0, "Hertz", "Parabolic"
opRange_A=12*log10(opMax_A/opMin_A)/log10(2)
Remove

Read from file: "'pitchTier_B$'"
bdur=Get total duration
Rename: "B"

Create TextGrid: 0, 'bdur', "SYLLABLE", ""
Rename: "B"
Read from file: "'textGrid_B$'"
Rename: "TEMP"
bNums=Get number of intervals: 'syllable_Tier_Index'
preTime=0
for i from 1 to bNums
	selectObject: "TextGrid TEMP"
	st=Get start time of interval: 'syllable_Tier_Index', 'i'
	et=Get end time of interval: 'syllable_Tier_Index', 'i'
	lab$=Get label of interval: 'syllable_Tier_Index', 'i'
	if index(none_Syllable_Markers$,";"+lab$+";")=0 and lab$!=""
		selectObject: "TextGrid B"
		if st>preTime and st<bdur
			Insert boundary: 1, 'st'
			preTime=st
		endif
		if et>preTime and st<bdur
			Insert boundary: 1, 'et'
			preTime=et
		endif
		cpos=Get number of intervals: 1
		cpos=cpos-1
		Set interval text: 1, 'cpos', lab$
	else
		selectObject: "PitchTier B"
		cst=st+0.001
		cet=et-0.001
		Remove points between: 'cst', 'cet'
	endif
endfor
selectObject: "TextGrid TEMP"
Remove

selectObject: "PitchTier B"
opNums_B=Get number of points
opMean_B=Get mean (points): 0, 0
opStdv_B=Get standard deviation (points): 0, 0
pMean_B=opMean_B
pStdv_B=opStdv_B
To Pitch: 0.01, 60, 600
opMin_B=Get minimum: 0, 0, "Hertz", "Parabolic"
opMax_B=Get maximum: 0, 0, "Hertz", "Parabolic"
opRange_B=12*log10(opMax_B/opMin_B)/log10(2)
Remove

pRangefactor=pitch_Range_Factor_for_New_PitchTier
if pRangefactor=0
	pRangefactor=opRange_A/opRange_B
endif

selectObject: "PitchTier B"
if pRangefactor!=1 and pRangefactor>0
	Formula: "10^(log10(self/opMin_B)*pRangefactor)*opMin_B"
endif
rpMean_B=Get mean (points): 0, 0
rpStdv_B=Get standard deviation (points): 0, 0
pMean_B=rpMean_B
pStdv_B=rpStdv_B
rpNums_B=Get number of points
To Pitch: 0.01, 60, 600
rpMin_B=Get minimum: 0, 0, "Hertz", "Parabolic"
rpMax_B=Get maximum: 0, 0, "Hertz", "Parabolic"
rpRange_B=12*log10(rpMax_B/rpMin_B)/log10(2)
rdur=Get total duration
Remove

selectObject: "TextGrid A"
aNums=Get number of intervals: 1
for i from 1 to aNums
	ost'i'=Get start time of interval: 1, 'i'
	oet'i'=Get end time of interval: 1, 'i'
	lab$=Get label of interval: 1, 'i'
	osd'i'$=""
	while index("0123456789 ",right$(lab$,1))>0 and lab$!=""
		if index("0123456789",right$(lab$,1))>0
			osd'i'$=right$(lab$,1)
		endif
		lab$=left$(lab$,length(lab$)-1)
	endwhile
	if right$(lab$,1)="r" and lab$!="er"
		lab$=left$(lab$,length(lab$)-1)
	endif
	olab'i'$=lab$
endfor

if pitch_Mean_for_New_PitchTier>0
	pMean_A=pitch_Mean_for_New_PitchTier
endif
if pitch_Mean_for_New_PitchTier=1
	pMean_A=pMean_B
endif

Create PitchTier: "B2A", 0, 'adur'
syl=0
selectObject: "TextGrid B"
nNums=Get number of intervals: 1
for i from 1 to nNums
	selectObject: "TextGrid B"
	nst'i'=Get start time of interval: 1, 'i'
	net'i'=Get end time of interval: 1, 'i'
	lab$=Get label of interval: 1, 'i'
	while index("0123456789 ",right$(lab$,1))>0 and lab$!=""
		lab$=left$(lab$,length(lab$)-1)
	endwhile
	if right$(lab$,1)="r" and lab$!="er"
		lab$=left$(lab$,length(lab$)-1)
	endif
	nlab'i'$=lab$

	selectObject: "PitchTier B"
	st=nst'i'
	et=net'i'
	spos=Get high index from time: 'st'
	epos=Get low index from time: 'et'
	cpos=0
	if epos>=spos and nlab'i'$!=""
		k=0
		for j from syl+1 to aNums
			if olab'j'$=nlab'i'$
				syl=j
				cpos=j
				j=999999
			endif
			k=k+1
			if k>4
				j=999999
			endif
		endfor
	endif
	if cpos>0
		for k from spos to epos
			selectObject: "PitchTier B"
			v=Get value at index: 'k'
			t=Get time from index: 'k'
			ct=(t-st)/(et-st)
			cst=ost'cpos'
			cet=oet'cpos'
			t=cst+(cet-cst)*ct
			selectObject: "PitchTier B2A"
			v=10^(log10(v/pMean_B))*pMean_A
			Add point: 't', 'v:1'
		endfor
	endif
endfor

selectObject: "PitchTier B2A"
npMean_B=Get mean (points): 0, 0
npStdv_B=Get standard deviation (points): 0, 0
ndur=Get total duration
npNums_B=Get number of points
To Pitch: 0.01, 60, 600
npMin_B=Get minimum: 0, 0, "Hertz", "Parabolic"
npMax_B=Get maximum: 0, 0, "Hertz", "Parabolic"
npRange_B=12*log10(npMax_B/npMin_B)/log10(2)

if smooth_PitchTier>0
	selectObject: "PitchTier B2A"
	Remove
	selectObject: "Pitch B2A"
	Smooth: 'smooth_PitchTier'
	Down to PitchTier
	Rename: "TMP"
	Create PitchTier: "B2A", 0, 'adur'
	selectObject: "PitchTier A"
	for i from 1 to opNums_A
		selectObject: "PitchTier A"
		t=Get time from index: 'i'
		selectObject: "PitchTier TMP"
		v=Get value at time: 't'
		selectObject: "PitchTier
 B2A"
		Add point: 't', 'v:1'
	endfor
	selectObject: "Pitch B2A"
	plusObject: "Pitch B2A"
	plusObject: "PitchTier TMP"
	Remove
endif

selectObject: "PitchTier B2A"
spMean_B=Get mean (points): 0, 0
spStdv_B=Get standard deviation (points): 0, 0
sdur=Get total duration
spNums_B=Get number of points
To Pitch: 0.01, 60, 600
spMin_B=Get minimum: 0, 0, "Hertz", "Parabolic"
spMax_B=Get maximum: 0, 0, "Hertz", "Parabolic"
spRange_B=12*log10(spMax_B/spMin_B)/log10(2)
Remove

Create Table with column names: "TMP", 0, "TIME_A SYLLABLE Pitch_A Pitch_B"
selectObject: "PitchTier A"
pNums=Get number of points
cMax=0
cMin=10000
syl=1
rNum=0
diff=0
for p from 1 to pNums
	selectObject: "PitchTier A"
	v=Get value at index: 'p'
	t=Get time from index: 'p'
	selectObject: "PitchTier B2A"
	n=Get value at time: 't'
	cpos=0
	syll$=""
	for j from syl to aNums
		if t>ost'j' and t<=oet'j' and cpos=0
			syl=j
			cpos=j
			j=999999
		endif
	endfor
	if cpos>0
		syll$=olab'cpos'$+osd'cpos'$
	endif
	if syll$!=""
		diff=diff+(n-v)^2
		selectObject: "Table TMP"
		Append row
		rNum=rNum+1
		Set numeric value: 'rNum', "TIME_A", 't:3'
		Set numeric value: 'rNum', "Pitch_A", 'v:1'
		Set numeric value: 'rNum', "Pitch_B", 'n:1'
		Set string value: 'rNum', "SYLLABLE", "'syll$'"
		if cMax<v
			cMax=v
		endif
		if cMax<n
			cMax=n
		endif
		if cMin>v
			cMin=v
		endif
		if cMin>n
			cMin=n
		endif
	endif
endfor
diff=(diff/(rNum-1))^0.5

res$=Report correlation (Pearson r): "Pitch_A", "Pitch_B", 0.025
corr=extractNumber(res$,"=")

cMax=round(cMax)+10
cMin=round(cMin)-10

sfn$=pitchTier_B$
sfn$=left$(pitchTier_B$,rindex(pitchTier_B$,".")-1)
sfn$=right$(sfn$,length(sfn$)-rindex(sfn$,"\"))
sfn$=left$(pitchTier_A$,rindex(pitchTier_A$,".")-1)+"-"+sfn$+"('pitch_Mean_for_New_PitchTier'-'pitch_Range_Factor_for_New_PitchTier'-'smooth_PitchTier').txt"
selectObject: "Table TMP"
Save as tab-separated file: "'sfn$'"

txt$="'newline$'"
txt$=txt$+"发音人A的Pitch数据文件:'newline$''pitchTier_A$''newline$'"
txt$=txt$+"发音人A的TextGrid数据文件:'newline$''textGrid_A$''newline$'"
txt$=txt$+"发音人B的Pitch数据文件:'newline$''pitchTier_B$''newline$'"
txt$=txt$+"发音人B的TextGrid数据文件:'newline$''textGrid_B$''newline$'"
txt$=txt$+"TextGrid数据文件中的音节层级为:'syllable_Tier_Index''newline$'"
txt$=txt$+"TextGrid数据文件中被剔除出的标注有:'none_Syllable_Markers$''newline$'"
txt$=txt$+"'newline$'基础数据如下:'newline$'"
txt$=txt$+"发音人A的文件时长为:'adur:3'秒'newline$'"
txt$=txt$+"发音人A的音高点数为:'opNums_A'个'newline$'"
txt$=txt$+"发音人A的音高均值为:'opMean_A:1'Hz'newline$'"
txt$=txt$+"发音人A的音高标准差为:'opStdv_A:1'Hz'newline$'"
txt$=txt$+"发音人A的音高最大值为:'opMax_A:1'Hz'newline$'"
txt$=txt$+"发音人A的音高最小值为:'opMin_A:1'Hz'newline$'"
txt$=txt$+"发音人A的音域范围为:'opRange_A:1'St'newline$''newline$'"
txt$=txt$+"发音人B的文件时长为:'bdur:3'秒'newline$'"
txt$=txt$+"发音人B的音高点数为:'opNums_B'个'newline$'"
txt$=txt$+"发音人B的音高均值(原)为:'opMean_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高标准差(原)为:'opStdv_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最大值(原)为:'opMax_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最小值(原)为:'opMin_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音域范围(原)为:'opRange_B:1'St'newline$''newline$'"
txt$=txt$+"音高范围修改系数为:'pRangefactor:3''newline$'"
txt$=txt$+"发音人B的文件时长(修改音域后)为:'rdur:3'秒'newline$'"
txt$=txt$+"发音人B的音高点数(修改音域后)为:'rpNums_B'个'newline$'"
txt$=txt$+"发音人B的音高均值(修改音域后)为:'rpMean_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高标准差(修改音域后)为:'rpStdv_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最大值(修改音域后)为:'rpMax_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最小值(修改音域后)为:'rpMin_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音域范围(修改音域后)为:'rpRange_B:1'St'newline$''newline$'"
txt$=txt$+"目标音高均值为:'pMean_A:1'Hz'newline$'"
txt$=txt$+"发音人B的文件时长(修改均值后)为:'ndur:3'秒'newline$'"
txt$=txt$+"发音人B的音高点数(修改均值后)为:'npNums_B'个'newline$'"
txt$=txt$+"发音人B的音高均值(修改均值后)为:'npMean_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高标准差(修改均值后)为:'npStdv_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最大值(修改均值后)为:'npMax_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最小值(修改均值后)为:'npMin_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音域范围(修改均值后)为:'npRange_B:1'St'newline$''newline$'"
txt$=txt$+"音高平滑阈值:'smooth_PitchTier:1'Hz'newline$'"
txt$=txt$+"发音人B的文件时长(平滑后)为:'sdur:3'秒'newline$'"
txt$=txt$+"发音人B的音高点数(平滑后)为:'spNums_B'个'newline$'"
txt$=txt$+"发音人B的音高均值(平滑后)为:'spMean_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高标准差(平滑后)为:'spStdv_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最大值(平滑后)为:'spMax_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音高最小值(平滑后)为:'spMin_B:1'Hz'newline$'"
txt$=txt$+"发音人B的音域范围(平滑后)为:'spRange_B:1'St'newline$''newline$'"
txt$=txt$+"发音人A和发音人B的音高数据(规整后)相关系数为:'corr:3''newline$'"
txt$=txt$+"发音人A和发音人B的各点音高数据(规整后)残差为:'diff:1'Hz'newline$'"
echo 'txt$'
info$=txt$

wid=adur*3.6
Select outer viewport: 0, 'wid', 0, 2.5
if erase_all=1
	Erase all
	selectObject: "PitchTier A"
	Grey
	Speckle size: 3
	Draw: 0, 0, 'cMin', 'cMax', "yes", "lines and speckles"
endif

Speckle size: 2
Colour: colorList'pitch_Color_For_Speaker_B'$
selectObject: "PitchTier B2A"
Draw: 0, 0, 'cMin', 'cMax', "yes", "lines and speckles"

if erase_all=1
	Black
	Select outer viewport: 0, 'wid', 0, 3
	Axes: 0, 1, 0, 1
	Paint rectangle: "white", 0, 1, 0, 0.15
	selectObject: "TextGrid A"
	Draw: 0, 0, "no", "no", "no"
	Select outer viewport: 0, 'wid', 0, 3
	Axes: 0, 1, 0, 1
	if top_Title$!=""
		txt$=top_Title$
		txt$=replace$(txt$,"\","/",0)
		txt$=replace$(txt$,"_","-",0)
		Text top: "no", "'txt$'"
	endif
endif

Select outer viewport: 0, 'wid', 0, 3
png$=sfn$-".txt"+".png"
Save as 300-dpi PNG file: "'png$'"
pfn$=sfn$-".txt"+".PitchTier"
selectObject: "PitchTier B2A"
Save as text file: "'pfn$'"
ini$=sfn$-".txt"+".ini"
filedelete 'ini$'
fileappend "'ini$'" 'info$'

exitScript: "已完成,请查阅以下数据文件:'newline$''sfn$''newline$''png$''newline$''pfn$''newline$'"