File size: 2,081 Bytes
0b85fb9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import numpy as np
import pyworld
from numpy.typing import NDArray


def adjust_voice(
    fs: int,
    wave: NDArray[np.float32],
    pitch_scale: float = 1.0,
    intonation_scale: float = 1.0,
) -> tuple[int, NDArray[np.float32]]:
    """
    音声のピッチと抑揚を調整する。
    変更すると若干音質が劣化するので、どちらも初期値のままならそのまま返す。

    Args:
        fs (int): 音声のサンプリング周波数
        wave (NDArray[np.float32]): 音声データ
        pitch_scale (float, optional): ピッチの高さ. Defaults to 1.0.
        intonation_scale (float, optional): 抑揚の平均からの変更比率. Defaults to 1.0.

    Returns:
        tuple[int, NDArray[np.float32]]: 調整後の音声データのサンプリング周波数と音声データ
    """

    if pitch_scale == 1.0 and intonation_scale == 1.0:
        # 初期値の場合は、音質劣化を避けるためにそのまま返す
        return fs, wave

    # pyworld で f0 を加工して合成
    # pyworld よりもよいのがあるかもしれないが……

    # pyworld での処理のために double に変換 (念のため)
    wave_double = wave.astype(np.double)

    # 質が高そうだしとりあえずharvestにしておく
    f0, t = pyworld.harvest(wave_double, fs)

    sp = pyworld.cheaptrick(wave_double, f0, t, fs)
    ap = pyworld.d4c(wave_double, f0, t, fs)

    non_zero_f0 = [f for f in f0 if f != 0]

    # 非ゼロの f0 が存在しない場合(無音に近い場合など)は元の音声をそのまま返す
    if len(non_zero_f0) == 0:
        return fs, wave  # 元の (float32 の) wave を返す

    f0_mean = sum(non_zero_f0) / len(non_zero_f0)

    for i, f_val in enumerate(f0):
        if f_val == 0:
            continue
        f0[i] = pitch_scale * f0_mean + intonation_scale * (f_val - f0_mean)

    synthesized_wave_double = pyworld.synthesize(f0, sp, ap, fs)

    # 最終的に float32 に変換してから返す
    return fs, synthesized_wave_double.astype(np.float32)