顯示具有 Praat 標籤的文章。 顯示所有文章
顯示具有 Praat 標籤的文章。 顯示所有文章

2023年5月29日 星期一

好站 : Listen Lab 的 Praat 與語音分析教學影片

最近在解決網友詢問 Praat 的 spectrogram 是否可設定用不同顏色顯示能量範圍時找到 Youtube 上一個很棒的頻道 : 


此頻道版主是美國明尼蘇達大學語言與聽力科學系教授 Mathew Winn, 參考 :


作者的個人網站 : 


作者錄製了使用 Praat 做語音分析的系列影片 (缺 18/19), 非常值得參考 : 


另外還有 6 個聲學語音學系列教學影片 :


哇, 真是太棒了!

2023年5月25日 星期四

如何為 Praat 的聲譜圖 (spectrogram) 上色

昨天有網友留言詢問用 Praat 的聲譜圖時可否設定不同能量範圍用不同之顏色繪製? 我查了 Praat 介面並無所獲, 查詢線上使用手冊也沒找到, 看來 Praat 是固定用灰階來表示不同能量, 無法依照不同能量選擇不同顏色. 不過搜尋解決辦法時找到下面這個影片, 作者使用 R 程式將 Praat 匯出的聲譜資料進行後製處理, 利用 R 強大的繪圖功能來繪製彩色的聲譜圖 :





影片中的 R 程式原始碼放在 GitHub :


我用 ChatGPT 將此 R 程式轉換成相應的 Python 程式如下 :

# colored-spectroram.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

# Read Praat spectrogram functions
def convert_spectrogram_to_df():
    # Implementation of convert_spectrogram_to_df function

def pre_emphasize():
    # Implementation of pre_emphasize function

def constrain_dynamic_range(df, column, dynamic_range):
    # Implementation of constrain_dynamic_range function

# Set file paths
spectrogram_file_path = "C:\\Users\\Matt\\Documents\\R\\Read_praat_spectrograms"
my_spect_file = "lake.Spectrogram"

# Load data into DataFrame
df_spectrogram = convert_spectrogram_to_df(my_spect_file)
df_spectrogram = pre_emphasize(df_spectrogram)
df_spectrogram = constrain_dynamic_range(df_spectrogram, "Level_preemp", 120)

# Plot spectrogram
fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(0, 5000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot segments in different colors
onset_l = 0.0324
onset_ei = 0.1254
onset_k = 0.306

df_spectrogram["segment"] = np.where(df_spectrogram["Time"] > onset_k, "k",
                                     np.where(df_spectrogram["Time"] > onset_ei, "eI",
                                              np.where(df_spectrogram["Time"] > onset_l, "l", np.nan)))

df_spectrogram["segment"] = pd.Categorical(df_spectrogram["segment"], categories=["l", "eI", "k"])

segment_colors = ["black", "#8C3B3B", "#1D2F50"]

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap=mcolors.ListedColormap(segment_colors))
ax.set_xlim(0, 0.56)
ax.set_ylim(0, 5000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot spectrogram with log-scaled Y axis
octave_breaks = [125, 250, 500, 1000, 2000, 4000]

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(50, 8000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot narrowband style spectrogram
my_spect_file_nb = "lake_narrowband.Spectrogram"

df_spectrogram_nb = convert_spectrogram_to_df(my
_spect_file_nb)
df_spectrogram_nb = pre_emphasize(df_spectrogram_nb)
df_spectrogram_nb = constrain_dynamic_range(df_spectrogram_nb, "Level_preemp", None, 95)

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram_nb["Time"], df_spectrogram_nb["Frequency"], df_spectrogram_nb["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(50, 8000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot "Mongoose" spectrogram
df_mongoose = convert_spectrogram_to_df("mongoose_12.Spectrogram")
df_mongoose = pre_emphasize(df_mongoose)
df_mongoose = constrain_dynamic_range(df_mongoose, "Level_preemp", 60)

onset_m = 0.0517
onset_a = 0.13
onset_ng = 0.2535
onset_g = 0.3575
onset_u = 0.4016
onset_s = 0.579
endpoint = 0.883

segment_times = [onset_m, onset_a, onset_ng, onset_g, onset_u, onset_s, endpoint]

df_mongoose["segment"] = pd.cut(df_mongoose["Time"], bins=segment_times, labels=False, right=False)
df_mongoose["segment"] = df_mongoose["segment"] + 1

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_mongoose["Time"], df_mongoose["Frequency"], df_mongoose["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.89)
ax.set_ylim(0, 6000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

不過裡面有幾個函式未實作, 有空再看看如何補全. 

2020年3月25日 星期三

在 GitHub 建立 praat-project 專案

計畫了好久想要用 tkinter 把網頁版的 Praat 語音資料處理套件改寫為 Python 版本, 希望徹底擺脫被 IE 綁架的窘境, 但我一直在忙網站與物聯網的東西, 所以只是停留在想的階段. 今天終於在 GitHub 建立了 praat-project 專案用來管理程式版本與進度 :

# https://github.com/tony1966/praat-project

目前先將原始的網頁版 project-v4 上傳, 放置在 /web-version 目錄下 :

# https://github.com/tony1966/praat-project/tree/master/web-version

接下來要配合 tkinter 的學習, 逐步完成移植作業. 這樣也能強迫早點學完 tkinter, 可說是一石二鳥之計. 計畫最怕沒進度, 哪怕是只前進一點點也好過紋風不動.

參考 :

# Praat 語音分析筆記索引
# project_v4

2019年12月9日 星期一

Praat 語音分析筆記索引

因 12/3 去彰師大講述 Praat 操作緣故, 為了讓文學院的學生更容易了解, 動手改寫了 2002 年寫的那本過於技術性之使用手冊, 重新整理為如下八篇筆記 :

# Praat 語音分析筆記 (一) : 簡介與軟體下載
# Praat 語音分析筆記 (二) : 錄音與存檔匯入
# Praat 語音分析筆記 (三) : SoundEditor 操作
# Praat 語音分析筆記 (四) : 音高分析
# Praat 語音分析筆記 (五) : 頻譜分析
# Praat 語音分析筆記 (六) : 共振峰分析
# Praat 語音分析筆記 (七) : 強度分析
# Praat 語音分析筆記 (八) : Praat Script

其實早在十年前便想重寫 Praat 使用手冊, 但一直都在忙專案而擱了下來, 這次終於一口氣把它寫完, 又重新複習了聲學語音學一遍. 本來想把 Picture 視窗的用法寫進去, 但製圖並非很重要, 且還要繼續趕專案, 所以在此別過吧!

2019年12月8日 星期日

Praat 語音分析筆記 (八) : Praat Script

Praat 最強大的功能是內建了 Praat Script, 可用來撰寫語音分析自動化的腳本程式. 本系列前面的文章參考 :

# Praat 語音分析筆記 (一) : 簡介與軟體下載
# Praat 語音分析筆記 (二) : 錄音與存檔匯入
# Praat 語音分析筆記 (三) : SoundEditor 操作
# Praat 語音分析筆記 (四) : 音高分析
# Praat 語音分析筆記 (五) : 頻譜分析
# Praat 語音分析筆記 (六) : 共振峰分析
# Praat 語音分析筆記 (七) : 強度分析

Praat Script 教學文件參考 :

# http://www.fon.hum.uva.nl/praat/manual/Scripting.html

本篇所使用的範例語音檔下載 (wav 檔之 zip 壓縮) :

# Praat_wav_files


八. Praat Script 與自動化分析

Praat 雖然提供了方便的圖形化界面讓使用者能快速地進行語音分析, 但由於語音具有多重變異性, 例如性別, 年齡, 與個人差異等, 實驗語音學中的聲學研究通常需透過田野調查採集大量語料進行統計分析, 成千上萬的語音數據若以人工方式分析將耗費大量人力, 且可能引進無法控制之操作或讀取錯誤, 造成統計分析結果失真. 利用 Praat Script 進行自動化分析可解決人力, 時間, 以及誤差等問題.

在學習 Praat Script 自動化技術之前, 先來學習 Praat 繼承自 Kay CSL 語音分析儀的紀錄檔 (log file) 功能, 這是一種半自動擷取語音數據的方法.


8.1 記錄檔 (log file) 功能 

所謂的紀錄檔功能是指, 在 Praat 人工分析語音時, 按下特定之功能鍵 (F12 與 Alt, Ctrl, 或 Shift 組合) 即可擷取預先指定之聲學特徵, 並自動儲存在指定的紀錄檔內, 這種半自動擷取數據的功能適合用在少量語料的人工分析, 或某些難以完全自動化, 需要先經過人工鑑別後再決定抓哪部分數據的情況.紀錄檔功能據說源自古早昂貴的 Kay CSL 語音分析儀.

紀錄檔功能放在 SoundEditor 視窗中, 因此必須先點選 Object 視窗中的聲音物件, 按右邊的 "Edit & View" 按鈕開啟 SoundEditor 視窗後, 再按 Query 選單點選 Log settings 功能項 :





Praat 提供四個紀錄檔 Log file 1~4, 其中 Log 1, 2 為以 format 欄位指定之格式抓取聲學特徵; 而 Log 3, 4 則以執行指定之 Praat Scrript 抓取. Log 1, 2 的參數設定在 Log 1 format 與 Log 2 format 兩欄位中. 預設 Log 1 擷取格式為 :

Time 'time:6' seconds, pitch 'f0:2' Hertz

即擷取游標所在時間 (小數點第 6 位) 與音高 (小數點第 2 位).

預設 Log 2 擷取格式為 :

't1:4''tab$''t2:4''tab$''f1:0''tab$''f2:0''tab$''f3:0'

即擷取游標所在時間 (小數點第 2 位) 與 F1~F3 共陣峰 (整數).

紀錄檔格式中放在單引號裡面的是聲學參數 (f0, f1, intensity 等) 或系統變數 (tab$ 與 editor$), 統稱為變數, 後面可用冒號再接一個整數, 表示取至小數點後第幾位 (對於數值變數而言), 其他沒有用單引號括起來的都是字串 :

'變數:小數點後第幾位'

例如下列擷取格式 :

time='time:5' F1='f1:0' F2='f2:0' F3='f3:0'

會抓出如下結果 :

time=0.12345 F1=523 F2=1024 F3=2781

可用的紀錄檔變數如下表 :


 紀錄檔變數 說明
 time 游標所在時間 (秒), 或選取區段中心點之時間
 freq 游標所在頻率 (Hz)
 t1 選取區段之起始時間
 t2 選取區段之結束時間
 dur 選取區段之時長
 f0 游標位置之音高, 或選取區段內之平均音高
 f1 游標位置之第一共振峰 F1, 或選取區段內 F1 之平均值
 f2 游標位置之第二共振峰 F2, 或選取區段內 F2 之平均值
 f3 游標位置之第三共振峰 F3, 或選取區段內 F3 之平均值
 f4 游標位置之第四共振峰 F4, 或選取區段內 F4 之平均值
 f5 游標位置之第五共振峰 F5, 或選取區段內 F5 之平均值
 b1 游標位置之第一共振峰 F1 之頻寬, 或選取區段內 F1 頻寬之平均值
 b2 游標位置之第二共振峰 F2 之頻寬, 或選取區段內 F2 頻寬之平均值
 b3 游標位置之第三共振峰 F3 之頻寬, 或選取區段內 F3 頻寬之平均值
 b4 游標位置之第四共振峰 F4 之頻寬, 或選取區段內 F4 頻寬之平均值
 b5 游標位置之第五共振峰 F5 之頻寬, 或選取區段內 F5 頻寬之平均值
 intensity 游標位置之強度 (dB), 或選取區段內強度之平均值
 power 游標位置之頻譜功率 (Pa2/Hz)
 tab$ TAB 鍵字元 (間隔用)
 editor$ 編輯器視窗的標題


因為預設的路徑可能不存在, 因此最好將預設的 Log file 紀錄檔位置更改為目前的工作目錄, 例如 D:\praat\ 下 :




擷取數據之方式 Log 1 為直接按下 F12, 其他紀錄檔則是 F12 與 Alt, Ctrl, 以及 Shift 之組合快捷鍵, 如下表所示 :


 紀錄檔 快捷鍵
 Log 1 F12
 Log 2 Shift + F12
 Log 3 (script) Alt + F12
 Log 4 (script) Ctrl + F12


首先在 SoundEditor 上點一下讓游標定在要擷取之時間點, 然後按 F12 鍵 (筆電需同時按 Fn +F12) 擷取 Log 1 數據, 擷取結果會顯示開啟的 Info 視窗中, 例如 :




紀錄檔功能雖然方便好用, 但只適合用來加速人工處理少量語料, 對於大量語料數據擷取, 必須借助 Praat Script 的強大威力來達成自動化, 原本需要數個月的人工處理時間可能只要數天之內即可完成.


8.2 Praat Script 編輯器 :

Praat 內建一個 Script 編輯器, 可直接編輯 Script 程式並執行. 在 Object 視窗按 File 選單點選 "New Praat script" 功能項, 這會開啟一個空白的 Script 編輯器視窗 (點選 "Open Praat script" 則是開啟既有之 Script 檔案) :




輸入如下指令後按 Run 即可執行, 因程式中有 printline 輸出指令, 因此它會開啟一個 Info 視窗顯示輸出資料 : :

a=1
str$="Hello World"
printline 'a'     
printline 'str$'





按編輯器的 File 按鈕點選 "Save as" 可將程式儲存到檔案統中, 也可以在 點選 "Open" 開啟既有之 Script 檔案, 或點選 "New" 開啟新檔, :





8.3 Praat Script  語法

Praat 中的人工操作動作均有相對應之指令, 直接撰寫指令集來代替人工分析, 這些指令集稱為 script, 類似 EXCEL 中的巨集與 VBA. 不過 Praat Script 的語法與一般程式語言如 Python 或 Java 等比起來要簡單好學多了.

所有的程式語言語法基本上都包含下列三要素 :
  • 指定 (assignment) : 賦予變數一個值
  • 判斷 (decision/branch) : 根據變數值決定執行方向
  • 迴圈 (loop) : 重複執行一段程式碼 
不管學哪一種程式語言都需先了解這語言的三種基本語法怎麼寫. 另外還要了解該語言的內建函數與資料結構 (例如陣列), 這樣就差不多學完一個程式語言了.


8.3.1 註解與跳行

Praat Script 使用 # 或 ! 號作為註解開頭, 而且是整行註解, 例如 :

#Author : Tony Y.H.Huang
!E-mail : tony1966@blabla.hinet.net
#Date : March 4,2009
#Praat version 5.1.2
clearinfo

注意, 不要在指令後面用 # 或 ! 號做註解, 例如在 echo 或 print 後面用 # 作註解會被當作輸出字串 :

str$="Hello World"
print 'str$'   #輸出 Hello World

此程式會輸出 "Hello World  #輸出 Hello World", 因此不要在指令後面註解, 要註解就用整列. 註解可用中文, 但在 Praat 中開啟 Script 時中文註解會變亂碼 (但不妨礙程式執行).

程式碼如果太長可跳行續寫, 但下一行開頭要用 ... (三個連續小數點) 表示此行程式碼接續上一行, 例如 :

fileappend "'info_out$'" Pitch Mean_Max_Min
... files path='percent_pitch_file_path$''newline$'

此例 第二行之 files path 事實上是接續上一行的指令.


8.3.2 資料型態與變數

Praat Script 資料型態很簡單, 只有數值與字串兩種, 布林值 (比較或邏輯運算子的輸出) 是用整數 0 與 1 代替, 分別代表 false 與 true. 數值包括整數與浮點數, 整數是像 123 或 2019 等沒有小數點的數; 而浮點數則是有小數點的數, 例如 3.14159, 709.23456 等等.

字串資料可以用單引號或雙引號括起來, 例如 "finished" 或 'finished' 均可. 但如果要用內建函數例如 echo 輸出變數, 則必須使用單引號, 不可用雙引號, 例如若變數 x 的值是 2, 則下列程式在 info 視窗輸出 'the value of x is 2' :

echo the value of x is 'x'

單引號字串具有代入功能, Praat Script 在執行時會先檢查單引號內是否有以存在之變數, 若有就會以其值代進去. 雙引號字串則無此功能.

變數用來在程式執行途中暫時儲存資料, 因此需為變數取一個名字, Praat Script 的變數名稱有如下規定 :
  • 必須以小寫英文字母開頭, 後面只能用英數字或底線.
  • 字串變數必須以 $ 結尾, 否則為數值變數.
  • 字串變數有三個保留字不能用 : newline$, tab$, hellDirectory$
因此 Pitch, _formant, $pitch 與 2time 都是不合法的變數名稱 (首字母須為小寫字母). 下列指定敘述也不合法 :

subject_name$=123 
the_date="2019-12-08"   

第一個指令錯誤在於 subject_name$ 以 $ 結尾, 此乃字串變數, 但卻指定一個數值給它; 第二個指令指派了一個字串給 the_date, 應該改成 the_date$ 才對.

Praat Script 沒有特別為布林值設一個資料型態, 而是直接用 1 代表 true, 用 0 代表 1.


8.3.3 運算子

Praat Script 的運算子如下 :

 算術運算子 說明
 a + b 加法, 5+2 得 7
 a - b 減法, 5-2 得 3
 a * b 乘法, 5*2 得 10
 a / b 除法 (浮點數除法), 5/2 得 2.5
 a ^ b a 的 b 次方, 2^4 得 16
 a div b a 除以 b 取整數 (整數除法), 5 div 2 得 2
 a mod b a 除以 b 取餘數, 5 mod 2 得 1

 比較運算子 說明
 > 大於, 傳回 1 或 0
 >= 大於等於, 傳回 1 或 0
 < 小於, 傳回 1 或 0
 <= 小於等於, 傳回 1 或 0
 = 等於, 傳回 1 或 0
 <> 不等於, 傳回 1 或 0

 邏輯運算子 說明
 and 邏輯且運算, a and b 需 a 與 b 均為 1 才傳回 1
 or 邏輯或運算, a or b 只要 a 或 b 為 1 即傳回 1
 not 邏輯非運算, not a 若 a 為 1 傳回 0, a 為 0 傳回 1  (反相)


+ 與 - 運算子除了作為算術運算子外, 若運算元為字串, 則做為字串串接與刪除運算, a$ - b$ 會從 a$ 字串最右邊刪除 b$ 字串, 例如 :

a$="Hello"
b$="World"
c$=a$ + " " + b$
echo 'c$'

此程式輸出 Hello World

a$="sound002.wav"
b$=a$ - ".wav"
echo 'b$'

此程式輸出 sound002, 亦即將副檔名刪除.

除了比較字串也可以用比較運算子進行比較,

a$=b$ : 需兩個字串雷同才會傳回 1
a$ <> b$ : 只要一個字元不同即傳回 1
a$ < b$ : 逐字元比較 Unicode 大小
a$ > b$ : 逐字元比較 Unicode 大小
a$ <= b$ : 逐字元比較 Unicode 大小
a$ >= b$ : 逐字元比較 Unicode 大小


8.3.4 內建常數

Praat Script 有三個內建數值常數 :
  • e : 自然指數, 其值約為 2.718
  • pi : 圓周率, 其值約為 3.14159
  • undefined : 未賦值的變數值
這三個常數可直接使用.


8.3.5 判斷

判斷也稱為分支 (branch), 是利用條件式是否符合來改變程式執行方向, 其語法為 if ... elsif ... else ... endif 結構 (注意, 是 elsif 不是 elseif), 例如 :

if pitch > 100 state$="over"
elsif pitch=100 state$="just"   
else state$="under"   
endif

此程式首先判斷變數 pitch 值是否大於100, 是則字串變數 state$ 被指定為 "over", 否則再判斷 pitch 是否剛好等於 100, 是則字串變數 state$ 被指定為 "just", 否則被指定為 "under".

注意, 若判斷後面要做的動作指令只有一個, 則可與 if 寫在同一行, 若有兩個以上指令則要跳行縮排寫 (縮排不是必要, 只是增加可讀性), 上面程式也可以這麼寫 :

if pitch > 100
    state$="over"
elsif pitch=100
    state$="just"   
else
    state$="under"   
endif


8.3.6 迴圈

迴圈用來重複執行一段程式碼, Praat Script 有三種迴圈指令 :
  • for 迴圈 (指定次數)
  • while 迴圈 (至少執行 0 次)
  • repeat 迴圈 (至少執行 1 次)
for 迴圈例如 :

for f from 100 to 900      
    Create Sound … tone 0 1 22050  0.5*(2*pi*f*x)      
endfor

此迴圈會從頻率 f=100 開始到 f=900 產生 22K 取樣的弦波聲音.

while 迴圈例如 :

x=10
while x > 0      
    x=x-1      
endwhile

此迴圈會在 x 大於 0 條件下繼續執行, 每次執行會將變數 x 減量 1.

repeat 迴圈例如 :

repeat      
    x=randomInteger(1,6) + randomInteger(1,6)      
until x=12

此例至少會執行一次, 每次會呼叫 Praat 內建函數 randomInteger() 來產生介於 1~6 之間的隨機整數, 若兩個隨機數和等於 12 就跳出結束迴圈, 否則繼續執行.

Praat Script 不支援陣列這種資料結構, 但可以用迴圈來模擬, 例如 :

for i from 1 to 3    
    square'i'=i*i    
endfor   
sum='square1' + 'square2' + 'square3'
echo 'sum'

此例利用單引號的代入功能來模擬陣列元素 square1, square2, 與 square3. 跳出迴圈後得到三個變數 square1=1, square2=4,  square3=9, 因此 sum=1+4+9=14.

此外, Praat 提供了許多好用的內建指令, 例如輸出, 表單, 以及檔案處理等等. 這些指令有的有參數, 參數就直接跟在指令後面.


8.3.7 輸出指令

Praat Script 內建輸出指令如下表 :


 輸出指令 說明
 clearinfo 清除 Info 視窗內容
 echo 先清除 Info 視窗內容後輸出資料 (取代)
 print 將資料輸出於 Info 視窗原內容後面 (append), 不跳行
 printtab 輸出一個 tab 字元到 Info 視窗原內容後面
 printline 輸出資料並跳行 (無輸出資料時則輸出空行並跳行)


輸出指令都會開啟一個 Info 視窗, 然後對此視窗輸出資料, 注意, 輸出變數要放在單引號中, 否則會被當作字串輸出, 例如 :

a=1
str$="Hello World"
printline a         
printline 'a'       
printline str$   
printline 'str$'   

printline a 會輸出 a  (把 a 當成字串); 而 printline 'a' 則輸出 1 (代入變數 a 之值 1); printline str$ 會輸出 str$ (把 str$ 當成字串); 而 printline 'str$'  才會輸出 Hello World.

可呼叫 clearinfo 清除 Info 視窗原有內容 (後面不需帶任何參數), 例如 :

clearinfo

echo 與 print 的差別是 echo 每次都會自動先清空 Info 視窗再輸出 (即 replace 的動作), 因此前一次輸出的資料會消失, 但 print 不會這麼做, 它會把資料接在上次輸出的資料後面 (即 append 的動作). 例如 :

result$="blablabla"

echo the result is 'result$'    #輸出 the result is blablabla

此指令會清空 Info 視窗並輸出 'the result is blablabla', 如果改用 print :

result$="blablabla"
print the result is ‘result$’ 

此指令會將 "the result is blablabla" 貼到 Info 視窗的原內容後面. 

printline 與 print 的差別在於 printline 輸出後會自動跳行, 但 print 不會. printline 後但若無輸出資料則輸出一個空行. printtab 會輸出一個 tab 字元, 這在控制輸出格式時很好用, 例如 : 

result$="blablabla"
printtab   
printline 
printline the result is 'result$' 

此程式會先插入一個 tab 字元, 然後跳行, 輸出 "the result is blablabla"


8.3.8 檔案處理指令

檔案處理指令如下表 :


 檔案處理指令 說明
 fileReadable("test.txt") 檢查檔案 test.txt 是否存在, 是傳回 1, 否傳回 0
 data$ < test,txt 讀取檔案 test.txt 內容到變數 data$
 data$ > test.txt 將資料 data$ 寫入檔案 test.txt (覆蓋)
 data$ >> test.txt 將資料 data$ 寫入檔案 test.txt 尾端 (append)
 fileappend test.txt data$ 將資料 data$ 寫入檔案 test.txt 尾端 (append)
 filedelete test.txt 刪除檔案 test.txt


下列程式利用 Strings 物件來儲存檔案名稱, 再以一個迴圈將檔案讀進 Object 視窗內.

directory$ = "d:\bin\pilot"
Create Strings as file list... list 'directory$'\*.wav
numberOfFiles = Get number of strings    
for ifile to numberOfFiles    
    select Strings list    
    fileName$ = Get string... ifile    
    Read from file... 'directory$'\'fileName$'    
endfor


8.3.9 Script 相關指令

Praat Script 中可用 include 載入其他程式到目前的 Script 中, 例如 :

include "c:\praat\script\fft.praat"       
include "..\script\fft.praat"   
include "script\fft.praat"

第一個 include 載入絕對目錄所指定之程式檔案; 其餘二個 include 使用相對路徑, 第二個 include 用 ".." 表示上一層目錄; 第三個 include 則是載入目前目錄下的 script 子目錄下的檔案. 注意, include 只能用路徑字串, 不可以使用字串變數, 例如下面指令是錯誤的 :

include 'file_path$'


8.4 輸入表單

Praat Script 執行時有時需要操作者輸入一些參數, 例如在音高分析時需要知道語者性別, 以便設定頻率的上下限. Praat 提供 form 表單來製作圖形化輸入表單, 其語法為 :

form  表單標題
integer 整數欄位名稱 預設值
real 浮點數欄位名稱 預設值
word 字串欄位名稱 預設值
endform

放在 form 與 endform 之間的就是表單中要輸入的欄位定義, 若輸入值為浮點數要宣告為 real; 若為整數宣告為 integer; 若為字串則宣告為 word. 欄位名稱格式為 a_b_note (用底線連接), 其中 a_b 就是程式中存取此輸入欄位的變數名稱. 表單變數資料型態如下表 :


 表單變數資料型態 說明
 word 字串
 real 浮點數
 integer 整數
 positive 正數


例如 :

form Specify the WAV/TXT directories
word Working_directory d:\project\pitch
integer minimum_pitch_(Hz) 75
integer maximum_pitch_(Hz) 600
real time_step_(second:0=auto) 0
endform

此表單定義了四個輸入欄位, 其中變數 minimum_pitch 與 maximum_pitch 為整數, 用來輸入音高上下限頻率; Working_directory 為字串, 用來輸入工作目錄; 而 time_step 為浮點數, 用來輸入時間步階值. 將上面程式碼貼到 Script 編輯器再按 Run 即可看到如下輸入視窗 :




注意, 欄位名稱中的底線在表單視窗中都會被改成空格.


8.5 取得人工操作的指令碼紀錄

Praat Script 提供了紀錄操作指令碼的功能 (類似 EXCEL 的錄製巨集), 如果想要將一連串的人工操作自動化, 不需要到使用手冊查詢相關指令 (Praat 的線上手冊雖然完整豐富, 但並非教學手冊), 直接在 Script 編輯器的 Edit 選單中點選 "Paste history" 貼上操作歷史即可.

以擷取音高為例, 首先於 Obect 視窗按 Open 點選載入聲音檔, 然後開啟 SoundEditor 視窗, 再按 Pitch 選單擷取游標所在時間點之音高值, 做完以上操作後, 回 Object 視窗按 File 選單點選 New Praat script 開啟 Script 編輯器, 然後按 Edit 選單中點選 "Paste history" :




結果如下 :

Read from file: "D:\Praat\this.wav"
View & Edit
Get pitch

這樣不須查手冊即可取得對應於操作之指令了. 不過在紀錄操作之前, 最好先用 Clear history 清除之前的操作指令紀錄.

Bingo! 忙了一周終於把 Praat 使用手冊重寫完成, 收工啦!

2019年12月7日 星期六

Praat 語音分析筆記 (七) : 強度分析

本篇探討如何使用 Praat 擷取聲音的強度 (intensity) 數據. 本系列前面的文章參考 :

# Praat 語音分析筆記 (一) : 簡介與軟體下載
# Praat 語音分析筆記 (二) : 錄音與存檔匯入
# Praat 語音分析筆記 (三) : SoundEditor 操作
# Praat 語音分析筆記 (四) : 音高分析
# Praat 語音分析筆記 (五) : 頻譜分析
# Praat 語音分析筆記 (六) : 共振峰分析


七 強度分析

相對於頻譜而言, 強度較好理解, 直覺上可以用聲音信號之振幅 (amplitude) 來理解聲音的強度 (intensity), 亦即波形中振幅越大, 強度也越大, 相對地聽起來就比較大聲 (較響亮).

但事實上人類聽覺對於聲音響度的感知並非與振幅呈線性正比關係, 而是一種非線性關係, 例如均方根值 (root mean square, 將分析窗內振幅平方後之平均值再開方根) 就比較貼近聽覺上聲音的響度 (loudness), 一些語音分析軟體就是計算均方根值來描繪音強曲線.

Praat 用來表示聲音強度的單位為分貝 (decibels,  dB), 這是一個相對能量的概念, 具體來說是將聲音氣流壓力平方後與一個參考值的平方相除再取 10 的對數, 結果再乘以 10, 定義如下 :


其中 v 為聲音氣壓的振幅, r 是一個參考氣壓, Praat 使用之參考壓力為 0.00002 Pascal.


7.1 使用 SoundEditor 擷取強度數據 

在 Object 視窗點選聲音物件, 按右方 "Edit & View" 鈕開啟 SoundEditor 視窗後, 首先必須按 View 選單點選 "Show abalyses" 功能項, 在彈出視窗中勾選 "Show intensity" 這一項, 這樣才能進行強度分析 :






這樣下半部圖形就會出現黃色的強度曲線了, 其量尺在右側, 顯示游標所在時間點的聲音強度 dB 值. 按 Intensity 鈕點選 "Show intensity" 會將游標所在時間點的強度顯示在 Info 視窗中 :





如果選取一個時段,  按 Intensity 鈕點選 "Listing intensity" 會將此時段內每一個分析窗所計算出來的強度輸出到 Info 視窗中 :





選取一個時段後按 Intensity 鈕點選 "Get minimum intensity" 與 "Get maximum intensity" 會分別輸出此時段內的最低與最高強度, 點選 "Draw visible intensity contour" 則會將強度曲線輸出到 Picture 視窗.

點選 "Extract visible intensity contour" 則會擷取強度曲線於 Object 視窗中建立強度物件, 點選此強度物件按右方的 Query 鈕, 可點選彈出選單知功能項擷取強度數據 :





7.2 直接從聲音物件建立強度物件

除了在 SoundEditor 視窗進行強度分析外, 也可以在 Object 視窗中點選聲音物件, 按右方 To intensity 鈕, 在彈出視窗中輸入此聲音物件的最低音高, 即可在 Object 視窗中建立一個強度物件 :





點選強度物件, 按右方的 Query 鈕即可查詢強度數據 :



Praat 語音分析筆記 (六) : 共振峰分析

本篇其實是頻譜分析的一部份, 但是因為共振峰是區別母音的最重要聲學特徵, 且 Praat 的 SoundEditor 視窗中有單獨的 Formant 選單, 故將其獨立為一篇. 本系列前面的文章參考 :

# Praat 語音分析筆記 (一) : 簡介與軟體下載
# Praat 語音分析筆記 (二) : 錄音與存檔匯入
# Praat 語音分析筆記 (三) : SoundEditor 操作
# Praat 語音分析筆記 (四) : 音高分析
# Praat 語音分析筆記 (五) : 頻譜分析


六. 共振峰分析 

Formant (共振峰) 是語音氣流通過發音腔 (vocal tract) 時, 音源 (聲帶) 的頻譜成分與發音腔的自然頻率產生共振, 使得聲波的振幅在這些頻率點增強 (源波與反射波因為相位一樣而形成疊加效應), 這在聲譜圖 (spectrogram) 上便呈現強度峰值 (peak), 特別是母音的共振效應特別明顯. 這些共振峰點峰峰相連成一長條帶, 頻帶由低到高有 F1, F2, F3,與 F4 等.

共振峰的位置反映了語者個人發音腔的動態物理結構與音源信號之間的時變交互作用. 特別是第一共振峰頻率 F1 及第二共振峰頻率F2 與母音發音位置有顯著之關聯. F1 反映了舌位高低 (舌位越高, F1 也越低); 而 F2 則反映了舌位前後 (舌位越前, F2 越高).

共振峰的成因可以從音源濾波器理論來說明, 欲理解此理論只要有上一篇頻譜分析中的頻域觀念即可, 不需要太多數學.


6.1 音源濾波器理論 

使用 Praat 觀察母音與有聲子音之聲譜圖都可看到明顯的共振峰現象, 其成因可用音源濾波器理論 (source-filter theory) 來解釋, 此理論從頻域角度來看信號, 聲帶做為語音的主動信號源, 為近似三角形之頻譜 X(w); 發音腔的模型為一多重帶通濾波器的轉移函數 T(w); 在不考慮唇部氣流發散的高頻增強效應下, 簡化的模型是兩者相乘即為輸出語音之頻譜 Y(w) :

Y(w)=X(w)*T(w)

其中 w 為角頻率=2*圓周率*頻率





共振峰主要是發音腔共振點所致, 發音腔就像一個動態變化的共振器, 發不同的母音時舌頭之動作與咽喉之形狀均不同, 所形塑之共振點不同, 因此不同母音所產生的共振峰也就不一樣, 這使得共振峰成為區別不同母音的最重要聲學特徵. 注意, 共振峰與基本頻率 fo 本質上的不同在於, 共振峰是被動 (passive) 形成的, 而 fo 則是聲帶音源主動 (active) 生成的.


6.2 使用 SoundEditor 擷取共振峰數據 

SoundEditor 視窗有一個 Formants 選單可用來擷取共振峰數據. 以前面從 this_is_a_book.wav 切音出來的 this.wav 的音節為例, 在 Object 視窗按 Open 選單點選 "Read from file" 將 this.wav 匯入物件視窗後, 點選此物件按右方 "Edit & View" 鈕開啟 SoundEditor 視窗, 然後按 Formant 選單點選 "Formant settings" 開啟共振峰設定視窗 :





需要設定的是上面三個欄位 :
  • Maximum formant : 共振峰之頻率上限 (Hz)
    女性語者此欄宜設為 5500, 男性語者, 宜設為 5000, 兒童則設為 8000.
  • Number of formants : 共振峰數目
    預設 5 表示計算 5 個共振峰 F1~F5. 
  • Window length : 分析窗寬度 (秒)
    頻譜分析之分析窗 (切片) 寬度, 寬頻頻譜設為 0.005 秒, 窄頻頻譜設為 0.03 秒, 計算共振峰通常使用寬頻頻譜, 故應設為 0.005 秒. 
另外還要做顯示 formants 之設定, 按 View 選單點選 "Show analyses", 在彈出的視窗中勾選 Show formants 與 Show spectrogram 兩項 :





這樣 SoundEditor 就只顯示聲譜與共振峰分析結果之圖形, 圖中縱向的紅點即為每一個分析窗切片經過演算法計算出來的共振峰, 位置都在頻譜分析的最高峰 (即顏色最暗, 能量最高之頂峰), 由下往上依序為 F1~F5, 沿著時間軸形成橫向的共振峰帶  :




在 SoundEditor 有共振峰出現的地方點一下 (設定游標), 按 Formants 選單點選 "Get first formant" 可取得該時間點第一共振峰 F1 頻率, 可見此 this.wav 在 0.132141 秒處之 F1 為 264 Hz :





其他的共振峰頻率可選取選單上之 second (F2), third (F3), fourth (F4) 等功能項取得, 雖然在共振峰設定中預設計算 5 個共振峰, 但 Formants 選單只提供 F1~F4 的功能項而已.

每一個共振峰都有其頻寬 (band width),  按 Formants 選單點選 "Get first bandwith" 可取得該時間點第一共振峰 F1 的頻寬, 可見此 this.wav 在 0.132141 秒處之 F1 頻寬為 56 Hz :   :





頻寬 (band width) 在信號處理上是指一個帶通濾波器 (band pass filter) 在中心頻率 (center frequency, fc) 左右往下 3dB 點的 fh 與 fl 之間的頻帶, 此頻帶內之功率是全部信號功率的一半, 故左右下 3dB 點又稱為半功率點 :




頻寬在物理上代表一種阻尼 (damping) 作用, 這是對運動的抑制或減弱現象 (例如摩擦力). 在語音聲學中, 共振峰的頻寬與發音腔的能量損耗 (power dissipation) 有關, 例如咽喉, 鼻腔, 口腔的軟組織都會在氣流能量流動時形成阻尼, 造成能量耗損. 共振峰頻寬越大表示能量損失越大, 因此幅射至外部的該頻段能量就越小.

參考 :

# https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4490178/
# http://support.ircam.fr/docs/AudioSculpt/3.0/co/Formant%20Analysis.html
# https://assta.org/proceedings/sst/sst2002/Papers/Millhouse070.pdf


如果要一次取出 F1~F4 全部共振峰, 則可在 Formant 選單點選 "Formant listing", 則該時間點之 F1~F4 會顯示於 Info 視窗中 :





輸出共振峰頻帶可點選 "Draw visible formant contour", 這會在 Picture 視窗中繪製共振峰分布圖 :





也可以點選 "Extract visible formant contour" 擷取共振峰曲線, 這樣會在 Object 視窗中產生一個共振峰物件, 點選此物件後可按右邊的 Querry 按鈕查詢共振峰數據 (頻率或頻寬), 或按 Draw 按鈕於 Picture 視窗繪圖 :





例如查詢與上面 SoundEditor 中時間點 0.132141 秒的 F1 得到一樣的 264 Hz 數據, 頻寬也是一樣 :





小數點後的誤差是因為時間點的小小差異, 頻率通常取整數即可.


6.3 從聲音物件直接產生共振峰物件 

上面關於共振峰的分析全部都是在 SoundEditor 視窗進行, 事實上也可以直接從聲音物件建立共振峰物件, 在 Object 視窗點選 this.wav 的聲音物件, 按右方 Analyse spectrum 鈕點選 "To formant(burg)" 功能項 :




在彈出視窗中同樣要設定三個參數, 其中 Max number of formants 用預設 5 即可, 但 Maximum formant 必須根據語者性別 (男 5000 Hz, 女 5500 Hz) 或年齡 (兒童 8000 Hz) 調整, 然後修改 Window length 為 0.005 秒以取得進行寬頻頻譜 :




這樣在 Object 視窗的物件列表中便出現了一個共振峰物件, 點選此 Formant 物件, 按右方的 Query 按鈕即可查詢共振峰頻率或頻寬等數據 :




也可按 Draw 鈕將共振峰圖形輸出到 Picture 視窗.


6.4 共振峰與母音的關係

前面曾提到共振峰是辨別母音的重要線索 (聲學特徵), 語音學者如 Fant, Peterson, 以及 Barney 等人利用語音合成方式操控共振峰進行母音辨識的研究時發現, 第一共振峰 F1 及第二共振峰 F2 與母音發音位置有顯著之關聯 :
  • F1 與舌位高低有關 (舌位越高 F1 越低)
  • F2 則與舌位前後有關 (舌位越前 F2 越高)
其關係如下圖所示 :




可見 F1 與 F2 的關係剛好與母音的舌位分布相當. 雖然共振峰頻率因人而異, 但不同語者的 F1 與 F2 關係呈現類似結果, 顯示共振峰確實是母音的區別性特徵. 另外, Peter Ladefoged 的研究則指出 F1 與 F2 的差距 F2-F1 更能看出舌位的前後位置 : F2-F1 越大表示母音位置越前面, 反之則越後面.

絕對值因人而異問題, 可以使用語音正規化 (normalization) 方法來消除, 這在語音感知研究中經常用到, 例如德國科學家 Eberhard Zwicker (1924~1990) 在其語音感知的心理學研究中所提出來的巴克量尺  (Bark scale), 用來量化人類聽覺感知中的類化現象.

巴克量尺類似信號理論中的信躁比概念 (SNR, signal noise ratio), 主要是探討人類聽覺系統在噪音的頻寬降低到一個臨界值時, 聽覺敏感度便提高到足以辨別語音, 這個噪音頻寬稱為臨界頻寬 (critical band width), 可用來解釋人耳內耳基底膜 (相當於多頻帶之帶通濾波器) 的聽覺感知功能.

Zwicker 提出一個計算臨界頻寬的公式, 其單位為 Bark :




其中 f 為信號頻率 (單位為 Hz), arctan 為數學上的反正切函數. 利用巴克量尺公式可將共振峰頻率代入 f  計算其巴克值來達成正規化. 根據 V.A. Kozhevnikov 與 L.A. Chistovich 於 1979 年的研究, 人耳區別母音差異的巴克臨界值為 3 巴克, 共振峰差異若低於 3 巴克時, 人耳聽覺將無法識別差異.

語音學家 A. K. Syrdal 與 H. S. Gopal 在 1986 年的研究中, 將美語的母音共振峰轉換成巴克值, 經過這個正規化程序, 發現 F1, F2, F3 與基本頻率 fo 的巴克差值可做為判別母音位置的指標 :




可見以巴克值為度量單位的話, 後母音可用 F3-F2 是否大於 3 巴克來判別;  而低母音則可用 F1-fo 是否大於 3 巴克來判別. 關於母音共振峰的田野調查聲學分析實例, 可參閱鍾榮富老師關於客家話母音的專題研究 :

# 台灣海陸家話的母音聲學研究

2019年12月5日 星期四

Praat 語音分析筆記 (五) : 頻譜分析

本篇主題是 Praat 語音分析中的頻譜分析, 在 SoundEditor 視窗中這些功能放在 Spectrum 選單內. 相較於音高分析, 頻譜較為複雜些, 但能觀察的聲學參數也較豐富. 本系列前面的文章參考 :

# Praat 語音分析筆記 (一) : 簡介與軟體下載
# Praat 語音分析筆記 (二) : 錄音與存檔匯入
# Praat 語音分析筆記 (三) : SoundEditor 操作
# Praat 語音分析筆記 (四) : 音高分析


五 頻譜分析

在使用 Praat 的頻譜分析功能前, 有必要對頻譜的理論基礎--傅立葉分析做個簡單的說明, 雖然牽涉一點點數學, 但只需要理解概念即可, 使用 Praat 操作頻譜分析並不需要任何複雜的數學計算.


4.1 傅立葉級數與傅立葉變換

所謂的頻譜 (spectrum) 是指信號 (例如語音) 在頻域 (frequency domain) 的能量分布, 以圖形來表示的話, 其橫軸是頻率 (單位為 Hz, 即每秒振動的次數), 縱軸為振幅 (amplitude, 即大小), 所以頻譜就是聲音中的能量隨頻率變化的情形.

頻譜分析的理論基礎來自數學中的傅立葉級數 (Fourier series), 這是法國數學家傅立葉 (Jean Baptiste Joseph Fourier, 1768~1830) 在研究熱傳導方程式時發展出來的解法, 這套數學後來被演進為更一般化的傅立葉變換 (Fourier transformation), 其應用非常廣泛 (例如 CT 掃描之影像處理), 也是現代數位信號處理的基礎.




傅立葉證明所有的函數都可以展開為三角函數之和 :




根據傅立葉的理論, 一個週期性函數 g(t) 可以拆解成許多不同頻率三角弦波之疊加, 這些弦波稱為此信號之諧波成分 (harmonic), 因此, 原本時域 (time domain) 的時間信號 g(t) 可以換一種方式用每一個諧波的頻率與其振幅表示, 其橫軸單位為頻率 (Hz), 縱軸為該頻率之振幅, 從這個角度來看信號就稱為頻域, 而從時間軸角度來看信號 g(t) 就稱為時域. 簡言之, 傅立葉級數是將週期函數從時域轉換到頻域的方法; 而傅立葉變換則是將非週期信號從時域轉換到頻域的方法.




上圖中紅色的波形就是時域中的信號波形, 藍色的弦波就是將其作傅立葉分解後的各諧波成分, 從右側的頻域來看, 這個近似方波的信號可以用諧波的振幅與其頻率來表示.

1965 年美國數學家 James Cooley 與 John Turkey 提出了快速傅立葉轉換 (Fast Fourier Transformation, FFT) 演算法, 使傅立葉轉換的計算速度獲得革命性的提升, Praat 的頻譜分析演算法也是快速傅立葉轉換的一種實作.

參考 :

# 如果看了此文你還不懂傅里葉變換那就


4.2 用傅立葉級數合成聲音信號

上面提到任何週期性信號都可以分解為不同頻率的弦波, 也就是說, 將這些弦波加起來就可以還原成原來的信號. 以方波 (square wave) 為例, 依據傅立葉分析公式可以分解為正弦波 (sine wave) 的無窮級數和 :





其中第一項中的 f 就是第一諧波頻率 (單位 Hz), 第二諧波頻率為 6f, 第三諧波頻率為 10f, .... 依此類推. 如果 f=100 Hz, 則第二諧波頻率為 200 Hz, 第三諧波頻率為 600 Hz. 參考 :

# Wiki : 方波

在 Praat 物件視窗的 New 選單中有一個 Sound 子選單, 點選其中的 "Create Sound from formula" 功能項可用來合成任何波形的信號 :




在彈出的選單中, 最底下有一個 formula 欄, 參考上面方波的正弦波級數和公式, 代入 f=100 Hz 轉成如下表示法 (只取前三項) :

4/pi*(sin(2*pi*100*x)+(1/3)*sin(6*pi*100*x)+(1/5)*sin(0*pi*100*x))

其中 pi 是 Praat 的內建常數, 其值為圓周率 3.1415926, 變數 x 為時間 (單位為秒), 注意, Praat 裡面變數要用 x, 不可用 t. 將此式子貼到 formula 欄中, 第一欄 Name 可取名為 square_wave, 按 OK 就會依據此數學式合成出一個近似方波的信號, 這時物件視窗會出現一個 square_wave 的聲音物件 :





按 "View & Edit" 鈕開啟 SoundEdit 視窗, 再按左下角 in 鈕數次放大波形就可看到這個近似方波的合成信號了. 按圖形底下的灰色長條鈕播放此聲音, 可聽到頻率為 100 Hz 的低沉哼哼聲.




注意, 這裡我們說近似而非還原是因為在方波的傅立葉無窮分解式中我們只取了前三個諧波來組合, 所以只能重建出近似方波的信號, 而無法還原為完美的方波, 如果加入更多諧波就能逼近完美之方波了.

在 Object 視窗中點選此近似方波之聲音物件, 按右方的 Draw 鈕, 在彈出的繪圖設定視窗中的 Time range 欄位填入上圖中波形的時間範圍 0.46875 與 0.53125 按 OK 即可將此時段內的近似方波輸出到 Picture 視窗 :






4.3 寬頻與窄頻聲譜

聲譜圖 (spectrogram) 又稱為語圖或聲紋圖, 在 SoundEditor 視窗中, 下半部圖形預設顯示寬頻聲譜圖 (wide band spectrogram) 與音高曲線, 這個聲譜圖就是 Praat 對聲音物件進行頻譜分析的結果.

Praat 在做頻譜分析時會像切吐司那樣, 將整個聲音物件切成連續等長的分析窗或時框 (window 或 time frame), 然後對每一片時框進行頻譜分析 (spectral analysis, 即快速傅立葉轉換) 得到該時框的頻譜 (spectrum).

以前面從 this_is_a_book.wav 切出來的第一音節 this.wav 為例, 按 Open 選單的 "Read from file" 匯入後, 點選此物件按 "Edit & View" 開啟 SoundEditor 視窗, 按 Spectrum 選單之 "Spectrogram settings" 功能項 :




彈出的設定視窗中, 第一欄為分析的頻譜範圍, 使用預設的 0~5000 Hz 即可. 可能需要調整的是第二個欄位 Window length (單位秒), 預設 0.005 秒表示這是寬頻頻譜 :




頻譜分析的原理簡單來講, 就是使用許多濾波器來鑑別頻率成分, 而寬頻頻譜與窄頻頻譜 (narrow band spetrum) 之差別在於進行頻譜分析時所使用之帶通濾波器 (band pass filter) 頻寬是大還是小, 使用大頻寬濾波器 (約 300 Hz) 去過濾原始信號得到的結果就是寬頻頻譜; 使用小頻寬濾波器 (約 45 Hz) 得到的結果就是窄頻頻譜.

帶通濾波器允許一個頻率範圍 (高低截止頻率 fL 與 fH 之間) 內之信號成分通過, 在此範圍外之頻率成分會被濾掉, 高低截止頻率之差即為其頻寬 (band width). 濾波器可以想像成篩子, 孔徑大 (大頻寬) 的允許更多東西過篩, 孔徑小 (小頻寬) 則只允許細物過篩, 其餘會被濾掉無法通過.




上面的聲譜圖設定視窗中的 Window length 欄位 (分析窗寬度) 就是用來控制濾波器的頻寬, 一般來說, 要得到寬頻頻譜的話, Window length 設為 0.005 秒 (5 毫秒); 而窄頻頻譜則設為 0.03 秒 (30 毫秒), 如下表所示 :




窄頻頻譜使用的濾波器頻寬小, 頻率解析度高, 因此能夠精細地解析出各次諧波成分, 也能夠看出基本頻率 fo (因為 fo 就是一次諧波); 反之, 寬頻頻譜使用的濾波器頻寬大, 頻率解析度低, 所以看不出諧波成分, 但能宏觀地看出共振峰 (formant, 又稱特強頻率帶) 的分布區域. 總之, 窄頻頻譜看細微 (見樹), 寬頻頻譜看大局 (見林).

窄頻與寬頻頻譜之差異可以用城門比喻來理解, 想像自己是管理城門進出的士兵, 若城門很窄 (窄頻), 只能容 1 或 2 人進出, 那麼就很容易鑑別出逃亡的朝廷要犯 (見樹); 反之若城門很寬 (寬頻), 同時進出的人太多, 就不容易鑑別出來了, 但是卻很容易察覺是進城的人潮多還是出城的人潮多 (見林).

SoundEditor 預設的寬頻聲譜圖與其 0.005 秒分析窗如下 :




此處是將 View 選單的 Show analyses 子選單設定只顯示 Show spectrogram 與 Show formant 這兩項, 顏色越深能量越高, 紅點所在之處是能量的頂峰, 稱為共振峰 (formant),  宛如山頂連成一線之稜線, 頻率由低到高標為 F1, F2, F3, 與 F4, 這是聲音氣流經過發音腔時產生共振所致, 共振點頻率能量獲得增強形成 F1~F4 共振峰現象.

在上面寬頻聲譜圖上點一下, 按 Spectrum 選單點選最底下的 "View spectrum slice" 可取出游標所在時間點之頻譜分析切片, 可較清楚看出該分析窗聲音切片的共振峰位置 :





此切片剖面顯示第一共振峰 F1 約在 304 Hz, F2 約在 1760 Hz, F3 約在 2430 Hz, 而 F4 約在 3020 Hz. 這只是游標所在時間點分析窗之寬頻頻譜圖而已, 隨著時間推移, 每一個分析窗的共振峰都不同.

聲譜圖實際上是三維的 (時間, 能量, 頻率), 但 Praat 目前僅能輸出二維 (時間, 頻率) 聲譜圖, 第三維的能量以顏色的灰階深淺表示. 一個 3D 聲譜的範例圖形如下 :




此圖朝向頁面為時間軸, 朝右為頻率軸, 朝上為強度 (能量) 軸, 圖中每一個切片就是用 0.005 秒分析窗寬度對聲音切片做頻譜分析的結果 (寬頻聲譜) , 隨著發聲時間由後往前逐片分析的結果排列起來就是 3D 聲譜圖, 其中由左向右清晰可見 F1~F5 五個共振峰, 是能量頂峰連成的稜線. 

若將 Window length 設為 0.03 秒可得到窄頻頻譜 :





此處是將 View 選單的 Show analyses 子選單設定只顯示 Show spectrogram 這一項, 可見下半部的聲譜圖呈現許多水平的橫紋, 這些就是聲音的諧波成分, 由下往上為一次, 二次, 三次, ... N 次諧波, 最底下的一次諧波也就是基本頻率 (fundamental frequency, 即音高).

在窄頻聲譜圖上點一下, 按 Spectrum 選單點選最底下的 "View spectrum slice" 可取出游標所在時間點之頻譜分析切片, 可較清楚看出該分析窗聲音切片的各次諧波成分 :





在彈出的頻譜分析視窗中可看到各次諧波 (請按左下角的 in 按鈕放大顯示), 其中最左邊的是一次諧波, 也就是基本頻率 (音高), 約 145 Hz, 其他較高次諧波頻率為此頻率之整數倍.

若將同一個聲音物件的某個切片之寬頻與窄頻頻譜套在一起, 可以看出寬頻頻譜其實是窄頻頻譜的外形 (envelop), 寬頻濾波器使得頻譜勻化了, 可以看出能量的大致分布情形; 而窄頻頻譜則顯示了精細的頻率成分 :




上圖中橘色為寬頻頻譜, 其峰值能量即 F1~F4 共振峰; 藍色為窄頻頻譜, 每一個脈衝代表一個諧波, 最左邊的一次諧波為基本頻率 Fo.


4.4 從 SoundEditor 視窗中取出聲譜圖

SoundEditor 視窗下半部的聲譜圖可以單獨取出來, 按 SoundEditor 視窗的 Spectrum 選單點選 "Extract visible spectrogram" :




取出的聲譜圖會放在 Object 視窗內, 點選後按右方的 View 按鈕就會開啟一個視窗呈現此聲譜圖 :





4.5 輸出聲譜圖

若要輸出 SoundEditor 視窗的聲譜圖至 Picture 視窗, 可按 Spectrum 選單點選 "Paint visible spectrogram" 功能項 :





可見輸出的是單純的聲譜圖, 沒有共振峰等資訊.


4.6 直接從聲音物件產生 Spectrogram 物件 

除了可 SoundEditor 視窗操作頻譜分析外, 也可以在物件視窗中直接產生 Spectrogram 物件, 首先在 SoundEditor 視窗點選聲音物件後, 按右方的 "To spectrogram" 按鈕 :




在彈出的視窗中, 將第一欄位 Window length 設定為 0.03 秒, 按 OK 鈕就會在 Object 視窗中產生一個窄頻聲譜之 Spectrogram 物件 :




點選此物件, 按右方 "Analyze" 鈕點選 "To spectrum(slice)" 會彈出一個設定視窗, 請在 Time 欄位輸入欲擷取之頻譜切片 :





此處輸入時間為 0.141432 秒, 表示要取出此時間點附近之切片, 按 OK 就會將擷取之頻譜切片放進 Object 視窗中, 點選此頻譜切片物件, 再按右邊的 "View & Edit" 鈕就就會顯示頻譜分析的結果了 :





由於分析窗框度 (Window lenght) 設為 0.03 秒, 故所建立的 Spectrogram 與 Spectrum 物件均為窄頻頻譜, 由上圖可清楚看出各諧波成分, 一次諧波頻率為 141 Hz, 即基本頻率 fo, 其餘高次諧波頻率為其整數倍.