Praat 脚本 | 单字调音高数据均值分析并作图

作者:熊子瑜
脚本ID:Praat.XZY20211008.020
上传时间:2021年10月08日
简介:此脚本可基于音高数据文件(allData_PitchTier.txt)自动测算并绘制出单字调音高数据图,结果大致如下所示(作图区域大小由用户提前圈定),并将给出各个点的均值及标准差等数据。注意,此脚本仅适用于单个发音人的音高数据,每次针对一位发音人的数据进行操作。除了可直接使用Hz标度外,还支持 Mel、Erb、Bark、Log、SemiTone、Log z-score 等几种音高换算和规整方式。

500

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


#By XIONG Ziyu
#last modify: 2021/09/28
#Praat version: 6.1.05
#功能:根据用户指定的声调音高数据文件进行均值分析并作图,仅适用于单个发音人的音高数据;
#注意:数据文件必须以UTF-8文本编码格式,制表符分隔的数据文件:allData_PitchTier.txt
#      数据文件应含有韵母调类、音高值1、音高值2、……、音高值10、音高段时长等数据列
#      如果前后两个音高点的音高间距大于2.5个半音,则剔除该音节的音高数据。
#
#对话框,接受用户输入参数内容
#PitchTier_DataFileName: 声调音高数据文件:allData_PitchTier.txt
#Color_index_and_list:输入颜色名称字符串,作图时按照输入顺序循环使用线条颜色。
#Diaolei_title:输入调类列数据的列标题。
#Pitch_Point_Number: 音高值的个数。
#Without_following_tones:用英文分号分隔的一些关键字,如果调类中含有某个给定的关键字,则跳过该调类的数据
#Keyword_In_tones:调类中所包含的关键字,如果调类名称中不包含给定的关键字,则跳过该调类的数据。
#addMarker_of_Tones:在各调类名称前添加一个公共的关键字,如“T”。
#Top_Title:顶部标题。
#Hz_to_Pitch:音高换算方式。
#Pitch_Max:输入发音人的音域最大值,单位为Hz,若大于等于99999则自动获取。
#Pitch_Min:输入发音人的音域最小值,单位为Hz,若小于等于-99999则自动获取。
#Duration_Max:输入声调最大时长值,单位为秒,若小于等于0则自动获取。


###########################################################
#以下内容请勿随意修改
###########################################################

form 分析声调音高数据均值并作图
	sentence PitchTier_DataFileName(数据文件名) C:\rrr\allData_PitchTier.txt
	sentence Diaolei_title(调类列标题) 韵母调类
	sentence Without_following_tones(剔除以下调类) ;TX;TY;TZ;
	sentence Keyword_In_tones(调类名称关键字) 
	sentence AddMarker_of_Tones(调类名称前缀) T
	sentence Top_Title(图标题) 声调音高数据图
	sentence Color_index_and_list(颜色顺序) Red Blue Black Green Grey Yellow Purple Magenta Olive Silver Lime Teal Pink Navy Cyan Maroon
	optionmenu Hz_to_Pitch(音高换算方式) 1
		option None (不换算)
		option Hertz To Mel
		option Hertz To Erb
		option Hertz To Log
		option Hertz To Semitones(ref=100Hz)
		option log z-score
		option Hertz To Bark
	positive Pitch_Point_Number(音高点数) 10
	real Data_Isok(标准差范围) 3
	boolean clean_all 1
endform

#将Praat读取和存储文本文件的编码格式设定为UTF8,避免汉字乱码
Text reading preferences: "UTF-8"
Text writing preferences: "UTF-8"

#清空主窗口对象列表中的数据对象
Create TextGrid... 0 1 "Mary John bell" bell
select all
Remove

if diaolei_title$=""
	msg$="请正确输入调类列数据的列标题!"
	call exitMsg
endif

if fileReadable(pitchTier_DataFileName$)=0
	msg$="未找到下一指定的数据文件,运行已结束!"+newline$+newline$+pitchTier_DataFileName$
	call exitMsg
endif
pitchTier_DataFileName$=replace$(pitchTier_DataFileName$,"/","\",0)
preMarker$=addMarker_of_Tones$

#读入声调音高数据文件
Read Table from tab-separated file: "'pitchTier_DataFileName$'"
Rename: "data"
rNum=Get number of rows
cNum=Get number of columns
for c from 1 to cNum
	cName$=Get column label: 'c'
	if left$(cName$,3)="音高值" or left$(cName$,3)="音高段"
		Set column label (index): 'c', "X'cName$'"
	endif
endfor

#获取线条颜色名称
color_index_and_list$=color_index_and_list$+" "
colorOk$=" Red Blue Black Green Grey Yellow Purple Magenta Olive Silver Lime Teal Pink Navy Cyan Maroon "
colorNum=0
while index(color_index_and_list$," ")>0
	ltxt$=left$(color_index_and_list$,index(color_index_and_list$," ")-1)
	color_index_and_list$=right$(color_index_and_list$,length(color_index_and_list$)-index(color_index_and_list$," "))
	if ltxt$ != "" and index(colorOk$," "+ltxt$+" ")>0
		colorNum=colorNum+1
		color'colorNum'$=ltxt$
	endif
endwhile

#获取拟剔除的调类关键字
without_following_tones$=without_following_tones$+";"
without_following_tones$=replace$(without_following_tones$,",",";",0)
without_following_tones$=replace$(without_following_tones$," ",";",0)
noKeyNum=0
while index(without_following_tones$,";")>0
	ltxt$=left$(without_following_tones$,index(without_following_tones$,";")-1)
	without_following_tones$=right$(without_following_tones$,length(without_following_tones$)-index(without_following_tones$,";"))
	if ltxt$ != ""
		noKeyNum=noKeyNum+1
		noKey'noKeyNum'$=ltxt$
	endif
endwhile

#获取拟保留的调类关键字
keyword_In_tones$=keyword_In_tones$+";"
keyword_In_tones$=replace$(keyword_In_tones$,",",";",0)
keyword_In_tones$=replace$(keyword_In_tones$," ",";",0)
okKeyNum=0
while index(keyword_In_tones$,";")>0
	ltxt$=left$(keyword_In_tones$,index(keyword_In_tones$,";")-1)
	keyword_In_tones$=right$(keyword_In_tones$,length(keyword_In_tones$)-index(keyword_In_tones$,";"))
	if ltxt$ != ""
		okKeyNum=okKeyNum+1
		okKey'okKeyNum'$=ltxt$
	endif
endwhile

#检查是否缺少基础数据列
selectObject: "Table data"
call checkDataColume 'diaolei_title$'
call checkDataColume X音高段时长
for i from 1 to pitch_Point_Number
	call checkDataColume X音高值'pitch_Point_Number'
endfor

#根据用户给定的调类关键字、拟排除的调类等信息筛选数据
selectObject: "Table data"
rNum=Get number of rows
for r from 1 to rNum
	diaoLei$=Get value: 'r', "'diaolei_title$'"
	for i from 1 to noKeyNum
		if index(diaoLei$,noKey'i'$)>0
			diaoLei$=""
			i=999
		endif
	endfor

	if okKeyNum>0
		isok=0
		for i from 1 to okKeyNum
			if index(diaoLei$,okKey'i'$)>0
				isok=1
				i=999
			endif
		endfor
		if isok=0
			diaoLei$=""
		endif
	endif

	for i from 1 to pitch_Point_Number
		vv$=Get value: 'r', "X音高值'i'"
		v=extractNumber(vv$,"")
		if v<50 or vv$="" or vv$="?" or v=undefined
			diaoLei$=""
			i=999
		endif
	endfor

	if diaoLei$="?" or diaoLei$="" or diaoLei$="NULL" or diaoLei$="NONE" or index(diaoLei$,"UNK")>0
		diaoLei$="000ERR"
		Set string value: 'r', "'diaolei_title$'", "'diaoLei$'"
	endif
endfor

diaoNum=0
diaolei_List$=";"
Sort rows: "'diaolei_title$'"
for r from 1 to rNum
	diaoLei$=Get value: 'r', "'diaolei_title$'"
	if diaoLei$="000ERR"
		Remove row: 'r'
		r=r-1
		rNum=rNum-1
	else
		if index(diaolei_List$,";"+diaoLei$+";")=0
			diaolei_List$=diaolei_List$+diaoLei$+";"
			diaoNum=diaoNum+1
			diao'diaoNum'$=diaoLei$
		endif
	endif
endfor

#根据全部音高数据的均值和标准差,剔除偏离N个标准差的数据
maxStdv=data_Isok
if maxStdv>0 and maxStdv<=5
	err=1
	while err=1
		call DelData
	endwhile
endif

#音高数据换算
call ModData

selectObject: "Table data"
rNum=Get number of rows

#测算调类均值
if hz_to_Pitch=1
	shuju$="'newline$'Hertz 标度'newline$''newline$'"
endif
if hz_to_Pitch=2
	shuju$="'newline$'Mel 标度'newline$''newline$'"
endif
if hz_to_Pitch=3
	shuju$="'newline$'Erb 标度'newline$''newline$'"
endif
if hz_to_Pitch=4
	shuju$="'newline$'对数标度(Log10)'newline$''newline$'"
endif
if hz_to_Pitch=5
	shuju$="'newline$'半音标度(ref=100Hz)'newline$''newline$'"
endif
if hz_to_Pitch=6
	shuju$="'newline$'Log Z-score 标度'newline$''newline$'"
endif
if hz_to_Pitch=7
	shuju$="'newline$'Bark 标度'newline$''newline$'"
endif

shuju$=shuju$+"调类"
for i from 1 to pitch_Point_Number
	shuju$=shuju$+tab$+"X音高值'i'"
endfor
shuju$=shuju$+"'tab$'样本数'tab$'X音高段时长(s)"+newline$

maxDur=0
maxPitch=-999999999
minPitch=999999999
call GetValues

pitch_Min=minPitch
pitch_Max=maxPitch
duration_Max=maxDur
beginPause: "设置发音人的音高和时长阈值"
	real: "Pitch_Max", pitch_Max
	real: "Pitch_Min", pitch_Min
	real: "Duration_Max", duration_Max
clicked=endPause: "Cancel","OK", 2, 1
if clicked=1
	exitScript:"脚本程序已被终止运行!'newline$'"
endif

maxPitch=pitch_Max
minPitch=pitch_Min
maxDur=duration_Max
maxDur='maxDur:3'
minPitch='minPitch:3'
maxPitch='maxPitch:3'

if hz_to_Pitch=5 or hz_to_Pitch=6 or hz_to_Pitch=7
	minPitch=minPitch+100
	maxPitch=maxPitch+100
endif

for d from 1 to diaoNum
	ltxt$=diao'd'$
	if ltxt$ != ""
		diaolei$=ltxt$
		call DrawTone
		diaoDur'd'=dur
	endif
endfor

for d from 1 to diaoNum
	dur=diaoDur'd'
	diao$=diao'd'$
	cMin=minPitch
	cMax=maxPitch
	selectObject: "PitchTier 'diao$'"
	if hz_to_Pitch=5 or hz_to_Pitch=6 or hz_to_Pitch=7
		Formula: "self+100"
	endif
	cMin=floor(cMin)
	cMax=ceiling(cMax)
	To Pitch: 0.005, 'cMin', 'cMax'
	Formula: "if x>'dur' then self*0 else self endif"
endfor

clearinfo
if clean_all=1
	Erase all
endif
Axes: 0, 1, 0, 1
Font size: 10
Speckle size: 1
usedTone$=""
for d from 1 to diaoNum
	color$=color'd'$
	Colour: "'color$'"
	diao$=diao'd'$
	usedTone$=usedTone$+diao$+" "
	dur=diaoDur'd'
	selectObject: "Pitch 'diao$'"
	du=dur-0.01
	v=Get value at time: 'du', "Hertz", "Linear"
	Speckle: 0, 'maxDur', 'minPitch', 'maxPitch', "no"
	du=du+0.04
	if du>maxDur
		du=maxDur-0.04
	endif
	Text special: 'du', "Centre", 'v', "Bottom", "Times", 10, "0", "'preMarker$''diao$'"
endfor

Axes: 0, 1, 0, 1
if hz_to_Pitch=1
	Text left: "yes", "Pitch (Hz)"
endif
if hz_to_Pitch=2
	Text left: "yes", "Pitch (Mel)"
endif
if hz_to_Pitch=3
	Text left: "yes", "Pitch (Erb)"
endif
if hz_to_Pitch=4
	Text left: "yes", "Pitch (Log10)"
endif
if hz_to_Pitch=5
	Text left: "yes", "Pitch (Semitones re 100Hz)"
endif
if hz_to_Pitch=6
	Text left: "yes", "Pitch (Log Z-score)"
endif
if hz_to_Pitch=7
	Text left: "yes", "Pitch (Bark)"
endif
Text bottom: "yes", "Time (s)"
Axes: 0, 'maxDur', 'minPitch', 'maxPitch'
Colour: "grey"
Line width: 0.5
Draw inner box
if diaoNum<9
	for
 d from 1 to diaoNum
		color$=color'd'$
		Colour: "'color$'"
		diao$=diao'd'$
		ypos=0.98-(d-1)/(diaoNum)*0.4
		Axes: 0, 1, 0, 1
		Text special: 1.10, "Centre", 'ypos', "Half", "Times", 10, "0", "'preMarker$''diao$' --- "
	endfor
endif

Colour: "black"
if hz_to_Pitch=5 or hz_to_Pitch=6 or hz_to_Pitch=7
	minPitch=minPitch-100
	maxPitch=maxPitch-100
endif
Axes: 0, 'maxDur', 'minPitch', 'maxPitch'
Marks left: 6, "yes", "yes", "yes"
Colour: "grey"
Marks bottom: 6, "yes", "yes", "yes"
Text top: "no", "'top_Title$'"

Copy to clipboard
sfn$=left$(pitchTier_DataFileName$,rindex(pitchTier_DataFileName$,"."))+"PNG"
filedelete 'sfn$'
Save as 300-dpi PNG file: "'sfn$'"

echo 'shuju$''newline$'在当前这些声调的均值数据中,'newline$'音高最大值:'maxPitch:3''newline$'音高最小值:'minPitch:3''newline$'时长最大值(秒):'maxDur:3''newline$'

Create TextGrid: 0, 1, "Mary John bell", "bell"
select all
Remove
msg$="作图过程已经结束!"
exitScript: "'msg$'"+newline$+newline$+"########## 以下信息不用查看 ##########"+newline$

procedure checkDataColume colName$
	selectObject: "Table data"
	colPos=Get column index: "'colName$'"
	if colPos=0
		msg$="缺少“'colName$'”数据,请核查!"
		call exitMsg
	endif
endproc

procedure GetValues
	for d from 1 to diaoNum
		ltxt$=diao'd'$
		if ltxt$ != ""
			diaolei$=ltxt$
			selectObject: "Table data"
			Extract rows where column (text): "'diaolei_title$'", "is equal to", "'diaolei$'"
			Rename: "'diaolei$'"
			dur=Get mean: "X音高段时长"
			if maxDur<dur
				maxDur=dur
			endif
			selectObject: "Table 'diaolei$'"
			for i from 1 to pitch_Point_Number
				mv=Get mean: "X音高值'i'"
				if maxPitch<mv
					maxPitch=mv
				endif
				if minPitch>mv
					minPitch=mv
				endif
			endfor
			selectObject: "Table 'diaolei$'"
			Remove
		endif
	endfor
endproc

procedure DrawTone
	selectObject: "Table data"
	Extract rows where column (text): "'diaolei_title$'", "is equal to", "'diaolei$'"
	Rename: "'diaolei$'"
	dur=Get mean: "X音高段时长"
	stdv=Get standard deviation: "X音高段时长"
	Create PitchTier: "'diaolei$'", 0, 'maxDur'
	selectObject: "Table 'diaolei$'"
	yangbenshu=Get number of rows
	shuju$=shuju$+preMarker$+diaolei$
	for i from 1 to pitch_Point_Number
		selectObject: "Table 'diaolei$'"
		mv'd'_'i'=Get mean: "X音高值'i'"
		stdv'd'_'i'=Get standard deviation: "X音高值'i'"
		selectObject: "PitchTier 'diaolei$'"
		t=dur*(i-1)/(pitch_Point_Number-1)
		mv=mv'd'_'i'
		stdv=stdv'd'_'i'
		Add point: 't', 'mv'
		shuju$=shuju$+tab$+"'mv:3'/'stdv:3'"
	endfor
	shuju$=shuju$+tab$+"'yangbenshu:0'"+tab$+"'dur:3'/'stdv:3'"+newline$
endproc

procedure exitMsg
	Create TextGrid: 0, 1, "Mary John bell", "bell"
	select all
	Remove
	echo 'msg$'
	exitScript: "'msg$'"+newline$+newline$+"########## 以下信息不用查看 ##########"+newline$
endproc

procedure DelData
	err=0
	selectObject: "Table data"
	rNum=Get number of rows

	k=0
	vMean=0
	for r from 1 to rNum
		for i from 1 to pitch_Point_Number
			v=Get value: 'r', "X音高值'i'"
			vMean=vMean+v
			k=k+1
		endfor
	endfor
	vMean=vMean/k

	k=0
	vStdv=0
	for r from 1 to rNum
		for i from 1 to pitch_Point_Number
			v=Get value: 'r', "X音高值'i'"
			vStdv=vStdv+(v-vMean)^2
			k=k+1
		endfor
	endfor
	vStdv=(vStdv/(k-1))^0.5

	for r from 1 to rNum
		del=0
		for i from 1 to pitch_Point_Number
			v=Get value: 'r', "X音高值'i'"
			if abs((v-vMean)/vStdv)>maxStdv
				del=1
				i=999
			endif
		endfor

		if del=1
			diaoLei$="000ERR"
			Set string value: 'r', "'diaolei_title$'", "'diaoLei$'"
		endif
	endfor

	diaoNum=0
	diaolei_List$=";"
	Sort rows: "'diaolei_title$'"
	for r from 1 to rNum
		diaoLei$=Get value: 'r', "'diaolei_title$'"
		if diaoLei$="000ERR"
			Remove row: 'r'
			r=r-1
			rNum=rNum-1
			err=1
		else
			if index(diaolei_List$,";"+diaoLei$+";")=0
				diaolei_List$=diaolei_List$+diaoLei$+";"
				diaoNum=diaoNum+1
				diao'diaoNum'$=diaoLei$
			endif
		endif
	endfor
	rNum=Get number of rows
endproc

procedure ModData
	selectObject: "Table data"
	for i from 1 to pitch_Point_Number
		if hz_to_Pitch=2
			Formula: "X音高值'i'", "hertzToMel(self)"
		endif
		if hz_to_Pitch=3
			Formula: "X音高值'i'", "hertzToErb(self)"
		endif
		if hz_to_Pitch=4
			Formula: "X音高值'i'", "log10(self)"
		endif
		if hz_to_Pitch=5
			Formula: "X音高值'i'", "hertzToSemitones(self)"
		endif
		if hz_to_Pitch=6
			Formula: "X音高值'i'", "log10(self)"
		endif
		if hz_to_Pitch=7
			Formula: "X音高值'i'", "hertzToBark(self)"
		endif
	endfor

	if hz_to_Pitch=6
		selectObject: "Table data"
		rNum=Get number of rows
		k=0
		vMean=0
		for r from 1 to rNum
			for i from 1 to pitch_Point_Number
				v=Get value: 'r', "X音高值'i'"
				vMean=vMean+v
				k=k+1
			endfor
		endfor
		vMean=vMean/k

		k=0
		vStdv=0
		for r from 1 to rNum
			for i from 1 to pitch_Point_Number
				v=Get value: 'r', "X音高值'i'"
				vStdv=vStdv+(v-vMean)^2
				k=k+1
			endfor
		endfor
		vStdv=(vStdv/(k-1))^0.5

		for i from 1 to pitch_Point_Number
			Formula: "X音高值'i'", "(self-'vMean')/'vStdv'"
		endfor
	endif
endproc