Pythonを使用した音声変換プログラムの作成


Photo by Jake Kaminski on Unsplash

おんりょうちょうせい


人の声の中で最も重要な要素であるトーンをどのように調整するかを理解してみましょう.

リサンプリングによるピッチ調整


最も簡単な方法は、音源ファイルをダウンサンプリングすると音の色調が低下し、逆にアップサンプリングすると音の色調が向上します.
import librosa 
import samplerate
import IPython.display as ipd

data, sr = librosa.load('example.wav', sr=None)
out_data1 = samplerate.resample(data, 0.5, 'sinc_best')
out_data2 = samplerate.resample(data, 1.5, 'sinc_best')

print('Higher pitch:')
ipd.display(ipd.Audio(out_data1, rate=sr))
print('Lower pitch:')
ipd.display(ipd.Audio(out_data2, rate=sr))
しかし、この方法を使うと、話すスピードにも影響があり、不自然に聞こえます.したがって、同じ速度でトーンを変更したい場合は、「マッチング同期ゼロオーバーと追加」アルゴリズムを使用する必要があります.

PSOLAアルゴリズムによるピッチ調整


まず、フレーム(Frame)単位で音声ファイルを分割し、同じ語速とトーンのみを変更します.この関数をframeizeに設定してみます.
def frameize(x: np.array, N: int, H_a: int, hfilt: np.array) -> list:
    """Truncate audio sample into frames.
    
    Params
    ------
    x: audio array
    N: segment size
    H_a: analysis hop size
    hfilt: windowing filter
    
    Returns
    -------
    frames: segments of audio sample
    """
    frames = []
    idx = 0 
    
    while True:
        try: frames += [hfilt*x[H_a*idx:H_a*idx+N]]
        except: break   
        idx += 1
    
    return frames
ここで、H_aは音源部分間の間隔であり、Nは各部分の長さである.
これで、フレーム間隔を調整するだけで、サウンドファイルの時間を変更し、フレーム内のフィーチャーを保持できます.これをtime-stratingといい、distort_time関数にしてみます.
def distort_time(x: np.array, N: int, H_a: int,
                 hfilt: np.array, alpha: float) -> np.array:
    """Distort time of audio sample by given ratio.
    
    Params
    ------
    x: audio data
    N: segment size
    H_a: analysis hop size
    hfilt: windowing filter
    alpha: time-scaling factor
    
    Returns
    -------
    out_x: time-scaled data 
    """
    # put into frames
    frames = frameize(x, N, H_a, hfilt)
    
    H_s = int(np.round(H_a*alpha))
    interval = 200 # search area for best match
    out_x = np.zeros(len(frames)*H_s+N)
        
    # time-distorting
    for i, frame in enumerate(frames):
        # end parts
        if i == len(frames) - 1:
            hfilt_norm = find_hfilt_norm(hfilt, H_s)
        # start, middle parts
        else:
            hfilt_norm = find_hfilt_norm(hfilt, H_s)

        out_x[i*H_s:i*H_s+N] += frame/hfilt_norm
    
    return out_x
ここで、find_hfilt_norm関数は、分割時に適用されたフィルタを再消去する役割を果たす.
その後、再サンプリングが1/(フレーム間隔パーセント)の場合、時間は元の水平に戻り、間隔は変化します.このメソッドは、関数synthesize_pitchに表示されます.
def synthesize_pitch(x: np.array, sr: int, N: int, H_a: int,
                      hfilt: np.array, alpha: float) -> np.array:
    """Synthesize sound sample into new one with different pitch using PSOLA algorithm.
    
    Params
    ------
    x: audio data
    sr: sampling rate
    N: segment size
    H_a: analysis hop size
    hfilt: windowing filter
    alpha: pitch factor
    
    Returns
    -------
    syn_x: synthesized data
    """
    syn_data = distort_time(x, N, H_a, hfilt, alpha)

    # resampling
    syn_data = samplerate.resample(syn_data, 1/alpha, 'sinc_best')
    syn_data = syn_data/np.max(abs(syn_data))
        
    return syn_data
この場合、周囲にフレーム間の溝がある必要があり、時間が長くなると切断された音が聞こえなくなり、フレーム間隔を変更すると溝に間欠が発生するため、この問題を解決するために、溝間隔をさらに調整して、中断の程度を最小限に抑えることができる.

ピッチ調整機能付きFrequencesStretching


ピッチをある程度高くすると、音はヘリウム音になります.この問題を緩和するために、β=α1/3\beta=\alpha^{1/3}β=α1/3の割合で増加すると、よりナチュラルな色合いになります.こちらです.α\alphaα pitch因子値です.β\betaβ はstretching factor値です.この式は数回の実験によって確立され,この論文で見ることができる.
N = 1024 # segment size for sampling rate 44100 Hz
H_a = int(N*0.5) # analysis hop size between 0.5 ~ 1
hfilt = np.hanning(N) # filter type

# input 
data, sr = librosa.load('example.wav', sr=None)
ipd.display(ipd.Audio(data, rate=sr, normalize=False))
alpha = 1.6 # pitch

# pitch increase
data = synthesize_pitch(data, sr, N, H_a, hfilt, alpha=alpha)

# frequency stretching
S1 = librosa.stft(data, n_fft=512, hop_length=64)
S2 = warp_spectrum(S1, alpha**(1/3))
data = librosa.istft(S2, hop_length=64, win_length=512)

ipd.display(ipd.Audio(data, rate=sr, normalize=True))
完全なコードは羽根で記述されている.
最後に、より自然に音を変えるためには、トーンだけでなく、他の特性も調整しなければならない.その部分は次の授業で紹介しましょう.