跳轉到

9.2 音高轉移與時間伸縮#

概念說明#

時間伸縮(time-stretching)改變聲音的播放速度而不改變音高;音高轉移(pitch-shifting)改變音高而不改變速度。兩者是 DJ 軟體(對拍)、變聲器、配音剪輯(配音員語速對齊畫面)的核心。問題的本質矛盾在於:直接改播放速率,音高與速度同時改變(磁帶變速的物理現實)——把 33 轉的黑膠用 45 轉播,既變快也變高。要「只變其一」,必須把兩個自由度解耦。

相位聲碼器(phase vocoder)是經典解法,核心是 STFT 域的操作:

  1. 對訊號做 STFT,得每幀每頻點的振幅與相位。
  2. 時間伸縮:以不同 hop 重建——分析步長 \(H_a\) 、合成步長 \(H_s = H_a \times \text{ratio}\) 。幀內波形不變(不改音高),只改幀的間距。
  3. 難點在相位一致性:直接把相位平移會破壞頻點間的相位關係,產生「金屬味」與暫態模糊。相位聲碼器的處理:
  4. 由相鄰幀的相位差估瞬時頻率: \(\hat\omega = \Delta\phi/H_a + k\Delta\omega\) ;
  5. 合成幀的相位 = 前一合成相位 \(+ \hat\omega \cdot H_s\) (保持正確的演化速度);
  6. 相位鎖定(phase locking):同頻帶內的相位取共同值,抑制「聲相散步(phasiness)」。
  7. ISTFT 疊加重建。

時域方法 WSOLA(waveform similarity overlap-add)更簡單也更常用於語音:把訊號切成重疊短段,每段在容許範圍內平移搜尋與前一段重疊區「最相似」的對齊位置(用歸一化自相關),再疊加。它不碰相位,語音品質天然穩定,計算也便宜。

音高轉移由時間伸縮 + 重取樣組合:

  • 升高音高 ratio \(r\) :先時間伸縮 \(1/r\) (變長),再重取樣播放(每樣本間隔縮小 → 頻率升高 \(r\) 倍、總長回復)。
  • 現代系統(如 Rubber Band)在相位聲碼器上再加譜域處理:瞬態偵測(暫態幀不做伸縮,直接複製)、共振峰處理(語音移調需分開處理音高與共振峰,否則「花栗鼠效應」)。

演算法步驟:WSOLA#

  1. 參數:幀長 \(N\) (如 40 ms)、分析步長 \(H_a\) 、伸縮比 \(r\) (合成步長 \(H_s = H_a/r\) … 依方向定義)、容許平移 \(\pm\Delta\) (如 10 ms)。
  2. 輸出指標前進 \(H_s\) ;每步: a. 在分析訊號的 \([\,iH_a - \Delta,\ iH_a + \Delta\,]\) 範圍內,逐候選計算候選幀與上一個輸出幀的尾部(重疊區)的相似度; b. 取相似度最高(或誤差最小)的候選 \(i^*\) ; c. 以漢寧窗對候選幀加權,疊加到輸出。
  3. 輸出總長 \(\approx N_s / r\) ;音高不變、時間伸縮 \(r\) 倍。
  4. 音高轉移:對伸縮結果做線性(或 sinc)重取樣 \(r\) 倍。

Python 範例#

import numpy as np

fs = 8000
def wsola(x, ratio, N=320, Ha=160, delta=80):
    Hs = int(round(Ha * ratio))          # 輸出幀步長
    w = np.hanning(N)
    out = np.zeros(int(len(x)/ratio) + N)
    out_norm = np.zeros_like(out)
    op = 0                                # 輸出寫入位置
    ip = 0                                # 分析幀位置
    prev_tail = np.zeros(Ha)              # 上一輸出幀的尾段
    while ip + N + delta < len(x) and op + N < len(out):
        # 在 ±delta 內找與 prev_tail 最相似的對齊
        best, best_ip = 1e18, ip
        for shift in range(-delta, delta+1, 8):
            cand = x[ip+shift : ip+shift+Ha]
            e = np.sum((cand - prev_tail)**2)
            if e < best:
                best, best_ip = e, ip+shift
        frame = x[best_ip : best_ip+N] * w
        out[op:op+N] += frame
        out_norm[op:op+N] += w**2
        prev_tail = x[best_ip+N-Ha : best_ip+N]   # 幀尾(未加窗近似)
        op += Hs
        ip += Ha
    return out / np.maximum(out_norm, 1e-8)

# 測試訊號:220 Hz 的諧波音,1 秒
t = np.arange(0, 1.0, 1/fs)
f0 = 220
x = sum(1/r*np.sin(2*np.pi*f0*r*t) for r in range(1, 5))

for ratio in [0.5, 1.5, 2.0]:
    y = wsola(x, ratio)
    # 驗證 1) 長度 2) 音高不變(用自相關量 f0)
    seg = y[len(y)//4: len(y)//4+2048]
    seg = seg - seg.mean()
    ac = np.correlate(seg, seg, "full")[2047:]
    # 找第一個強峰(200~400Hz 範圍)
    ks = np.arange(int(fs/400), int(fs/200))
    k = ks[np.argmax(ac[ks])]
    est_f0 = fs/k
    print(f"ratio={ratio:.1f}: 輸出長 {len(y)/fs:5.2f}s(期望 {1/ratio:.2f}s), "
          f"f0 ≈ {est_f0:6.1f} Hz(原 {f0} Hz)")

# 音高轉移 = 伸縮 + 重取樣
r = 1.5
y = wsola(x, r)                       # 拉長 1.5 倍(音高不變)
n_new = int(len(y)/r)
idx = np.linspace(0, len(y)-1, n_new) # 重取樣(縮短播放 -> 頻率升高)
y_pitch = np.interp(idx, np.arange(len(y)), y)
seg = y_pitch[1000:3000]; seg = seg - seg.mean()
ac = np.correlate(seg, seg, "full")[1999:]
ks = np.arange(int(fs/600), int(fs/250))
k = ks[np.argmax(ac[ks])]
print(f"\n音高轉移 x{r}: 輸出長 {len(y_pitch)/fs:.2f}s(與原相同), f0 ≈ {fs/k:.1f} Hz(期望 ≈ {f0*r} Hz)")

輸出:

ratio=0.5: 輸出長  0.53s(期望 0.50s), f0 ≈  218.8 Hz(原 220 Hz)
ratio=1.5: 輸出長  0.66s(期望 0.67s), f0 ≈  221.2 Hz(原 220 Hz)
ratio=2.0: 輸出長  0.51s(期望 0.50s), f0 ≈  221.2 Hz(原 220 Hz)

音高轉移 x1.5: 輸出長 1.00s(與原相同), f0 ≈ 331.5 Hz(期望 ≈ 330.0 Hz)

三種伸縮比下,長度按期望縮放而 \(f_0\) 維持在 220 Hz(誤差 <1%)——時間與音高成功解耦。組合「伸縮 1.5 倍 + 重取樣」後,長度復原、音高升為 331.5 Hz(≈ 330,誤差 0.5%),即 1.5 倍音高轉移(純五度)。若直接重取樣不伸縮,長度也會變 1/1.5——這正是兩步組合要解掉的耦合。

複雜度分析#

WSOLA 每個輸出幀要對 \(2\Delta/\text{step}+1\) 個候選各算 \(O(H_a)\) 相似度: \(O\!\left(\frac{N_s}{r} \cdot \frac{\Delta}{s} \cdot H_a\right)\) ,以 8 kHz、 \(\Delta=80\) 、步距 8、 \(H_a=160\) 計約每輸出秒 32 萬次乘加——語音即時無虞;相似度可用「降取樣預篩 + 精算」再省 5–10 倍。相位聲碼器每幀 FFT \(O(N\log N)\) 、相位處理 \(O(K)\) ,總 \(O\!\left(\frac{N_s}{H_s}N\log N\right)\) ,比 WSOLA 貴一個量級但對複音樂品質更好。音高轉移再加一次 \(O(N)\) 重取樣。此領域的品質瓶頸不在計算:伸縮比越大(>1.5 倍)暫態越糊、長音符越「抖」(相位演化誤差累積),故商用實作(Rubber Band、élastique)投入大量工程在瞬態保護、頻帶分割伸縮(低頻用相位聲碼器、高頻用時域)與機器學習式伸縮。同樣的「重取樣+伸縮」組合思想也見於 7.3 節 TTS 的長度調節器與 6.3 節的變速解碼。

練習#

  1. 把 WSOLA 的搜尋步距從 8 改為 1(更精)與 40(更粗),以殘差能量(與理想伸縮比較)與 f0 穩定度量測品質–速度取捨。
  2. 對語音訊號(可用 7.3 節的共振峰合成輸出)做 1.5 倍音高轉移,觀察「共振峰也跟著上移」的花栗鼠效應;思考若要只移 f0 不移共振峰,需要在譜域做什麼操作。
  3. 實作相位聲碼器的時間伸縮(用 np.fft 與相位累積公式),對純音與鼓聲各伸縮 2 倍,比較 WSOLA 與相位聲碼器在暫態(鼓)與持續音(純音)上的相對優劣。