音声信号処理の Python 実現ガイド

【概要】音声信号処理の用語(60項目)とPython実装を示す。用語は音声信号の基本概念、周波数領域分析、スペクトル特徴量、基本周波数推定、フォルマント分析、時間領域特徴量、リズム・時間構造特徴量、環境音解析、信号処理技術、評価・検証手法に分類され、対応するPython関数を明示する。実装例として、音声ファイルの入出力、スペクトログラムの生成と表示、基本周波数(F0)推定、フォルマント分析、環境音の解析の5つのプログラムを提供する。

【目次】

  1. 音声信号処理の基本
  2. 用語リスト
  3. Pythonでの実現
  4. Pythonプログラム例

【サイト内のPython関連主要ページ】

【外部リソース】

音声信号処理の基本

用語リスト

音声信号の基本概念

周波数領域分析

スペクトル特徴量

基本周波数推定

フォルマント分析

時間領域特徴量

リズム・時間構造特徴量

環境音解析

信号処理技術

評価・検証手法

Pythonでの実現

音声信号処理の用語と関数の対応表を以下に示す。

音声信号の基本概念

用語 関数・メソッド 説明
サンプリング周波数 scipy.io.wavfile.read(filename) の戻り値(第1要素)、librosa.load(filename, sr=None) の戻り値(第2要素) WAVファイルからサンプリング周波数を取得
ビット深度 data.dtype 音声データ配列のデータ型から判定(int16は16ビット)
チャンネル数 data.shape 音声データ配列の形状から判定(1次元ならモノラル、2次元ならステレオ)
振幅 numpy.abs(signal) 信号の絶対値
時間長 len(data) / sampling_rate サンプル数をサンプリング周波数で除算

周波数領域分析

用語 関数・メソッド 説明
スペクトログラム scipy.signal.spectrogram(x, fs, window, nperseg, noverlap)librosa.stft(y, n_fft, hop_length, window) STFTによるスペクトログラム計算
短時間フーリエ変換(STFT) librosa.stft(y, n_fft, hop_length, window, center) 時間-周波数表現への変換
窓関数 scipy.signal.spectrogram()window='hann'librosa.stft()window='hann' ハン窓、ハミング窓、ブラックマン窓等を指定
フレーム長 scipy.signal.spectrogram()nperseglibrosa.stft()n_fft STFTの各区間の長さ
ホップ長 scipy.signal.spectrogram() では nperseg - noverlaplibrosa.stft()hop_length フレーム間の移動量
オーバーラップ率 scipy.signal.spectrogram()noverlap(オーバーラップ量を指定) 連続するフレーム間の重複部分
FFTサイズ librosa.stft()n_fftscipy.signal.spectrogram()nperseg 高速フーリエ変換のサンプル数
パワースペクトル密度(PSD) scipy.signal.welch(x, fs, nperseg) パワースペクトル密度の推定
スペクトル包絡 numpy.abs(librosa.stft(y)) STFTの振幅スペクトル

スペクトル特徴量

用語 関数・メソッド 説明
スペクトル重心 librosa.feature.spectral_centroid(y=y, sr=sr, n_fft=n_fft, hop_length=hop_length) パワースペクトルの重心周波数
スペクトル帯域幅 librosa.feature.spectral_bandwidth(y=y, sr=sr, n_fft=n_fft, hop_length=hop_length) スペクトル重心周りの周波数の広がり
スペクトルロールオフ librosa.feature.spectral_rolloff(y=y, sr=sr, n_fft=n_fft, hop_length=hop_length) 一定割合のパワーが含まれる周波数上限
スペクトル平坦度 librosa.feature.spectral_flatness(y=y, n_fft=n_fft, hop_length=hop_length) スペクトルの平坦さ

基本周波数推定

用語 関数・メソッド 説明
基本周波数(F0) librosa.pyin(y, fmin, fmax, sr) の戻り値(第1要素)、自己相関法では sr / peak_idx 確率的YIN法または自己相関法による基本周波数推定
有声音 librosa.pyin() の戻り値 voiced_flag が True 有声区間の判定結果
無声音 librosa.pyin() の戻り値 voiced_flag が False 無声区間の判定結果
自己相関法 scipy.signal.correlate(frame, frame, mode='full') でピーク検出により周期推定 信号の自己相関計算による周期性検出
ケプストラム法 対数パワースペクトルに numpy.fft.ifft() を適用してケプストラム計算 ケプストラム領域での周期性検出
PYIN librosa.pyin(y, fmin, fmax, sr) 確率的YIN法による基本周波数推定
オクターブ誤り F0推定結果に対する有効範囲チェック 50 <= f0 <= 2000 で検出 真の基本周波数の整数倍・整数分の1を誤推定する現象

フォルマント分析

用語 関数・メソッド 説明
フォルマント librosa.lpc() で係数を計算、numpy.roots() で根を求め、numpy.arctan2(numpy.imag(roots), numpy.real(roots)) * (sr / (2 * numpy.pi)) で周波数変換 声道共鳴により生じるスペクトル包絡のピーク周波数
声道 scipy.signal.lfilter(b, a, source) で共鳴フィルタを適用してモデル化 声帯から唇までの音声生成経路
線形予測分析(LPC) librosa.lpc(signal, order=order) LPC係数の計算
LPC係数 librosa.lpc() の戻り値 線形予測係数
予測次数 librosa.lpc()order パラメータ 使用する過去のサンプル数
バンド幅 numpy.roots() で求めた根に対し -1/2 * (sr/(2*numpy.pi)) * numpy.log(numpy.abs(roots)) で計算 フォルマントの周波数幅
のこぎり波 scipy.signal.sawtooth(2 * numpy.pi * f0 * t) 基本周波数と倍音を含む周期信号

時間領域特徴量

用語 関数・メソッド 説明
RMSエネルギー librosa.feature.rms(y=y, frame_length=frame_length, hop_length=hop_length) または numpy.sqrt(numpy.mean(y**2)) 二乗平均平方根エネルギー
ゼロ交差率(ZCR) librosa.feature.zero_crossing_rate(y=y, frame_length=frame_length, hop_length=hop_length) ゼロ交差の割合
クレストファクター numpy.max(numpy.abs(y)) / (numpy.sqrt(numpy.mean(y**2)) + 1e-10) ピーク値とRMS値の比
尖度(Kurtosis) scipy.stats.kurtosis(y, fisher=True) 確率分布の尖り具合

リズム・時間構造特徴量

用語 関数・メソッド 説明
オンセット librosa.onset.onset_detect(onset_envelope=onset_env, sr=sr) オンセット時刻の検出
オンセット強度包絡線 librosa.onset.onset_strength(y=y, sr=sr) オンセット強度の時系列
テンポグラム librosa.feature.tempogram(onset_envelope=onset_env, sr=sr) 時間-テンポ表現

環境音解析

用語 関数・メソッド 説明
環境音 scipy.signal.sawtooth()numpy.random.poisson()scipy.signal.lfilter() 等を組み合わせて生成 音声以外の音響信号
非定常性 librosa.stft() 等の時間窓による短時間分析で時間変化を捉える 信号の統計的性質の時間的変化
ノイズの色 scipy.signal.welch() でPSD計算、numpy.log10() で対数変換、scipy.stats.linregress() で傾き分析 ノイズのスペクトル特性の分類
スペクトル傾き scipy.stats.linregress(log_freqs, log_psd) の戻り値(傾き) 対数周波数軸上でのPSDの傾き

信号処理技術

用語 関数・メソッド 説明
正規化 numpy.clip(data, -1.0, 1.0) または data / (numpy.max(numpy.abs(data)) + 1e-10) 振幅を指定範囲に制限または最大値で正規化
減衰 numpy.exp(-t * decay_rate) 指数減衰の生成
共鳴 scipy.signal.lfilter(b, a, source) 共鳴フィルタの適用

評価・検証手法

用語 関数・メソッド 説明
閾値処理 corr[peak_idx] > threshold などの条件式 有声/無声判定やオンセット検出での閾値判定
有効範囲 50 <= f0 <= 2000 などの条件式 パラメータの妥当性確認

Pythonプログラム例

1. 音声ファイルの入出力

このコードは音声信号処理の基本となるファイル入出力を示す。scipyとlibrosaの2つのライブラリによるWAVファイルの読み込み方法を比較し、サンプリング周波数、ビット深度、チャンネル数、時間長などの基本パラメータを表示する。テスト信号として440Hzのサイン波を生成し、WAVファイルとして書き出す。正規化処理により振幅を-1.0から1.0の範囲に制限し、16ビット整数形式に変換する。波形プロットでは横軸に時間、縦軸に振幅を表示し、音声信号の時間領域表現を可視化する。この処理は用語リストの「音声信号の基本概念」に対応し、デジタル音声処理の基礎となる。


import numpy as np
from scipy.io import wavfile
import librosa
import matplotlib.pyplot as plt

def read_wav_scipy(filename):
    """scipyによるWAVファイル読み込み"""
    sampling_rate, data = wavfile.read(filename)
    print("=== Scipyでの読み込み結果 ===")
    print(f"サンプリング周波数: {sampling_rate} Hz")
    print(f"チャンネル数: {data.shape[1] if len(data.shape) > 1 else 1}")
    print(f"サンプル数: {len(data)}")
    print(f"時間長: {len(data)/sampling_rate:.2f} 秒")
    return sampling_rate, data

def read_wav_librosa(filename):
    """librosaによるWAVファイル読み込み"""
    data, sampling_rate = librosa.load(filename, sr=None)
    print("\n=== Librosaでの読み込み結果 ===")
    print(f"サンプリング周波数: {sampling_rate} Hz")
    print(f"チャンネル数: {data.shape[1] if len(data.shape) > 1 else 1}")
    print(f"サンプル数: {len(data)}")
    print(f"時間長: {len(data)/sampling_rate:.2f} 秒")
    return sampling_rate, data

def write_wav(filename, sampling_rate, data):
    """WAVファイルの書き出し"""
    normalized_data = np.clip(data, -1.0, 1.0)
    int_data = (normalized_data * 32767).astype(np.int16)
    wavfile.write(filename, sampling_rate, int_data)

def generate_test_signal():
    """テスト用の音声信号生成(1秒のサイン波)"""
    duration = 1.0
    sampling_rate = 44100
    t = np.linspace(0, duration, int(sampling_rate * duration))
    signal = 0.5 * np.sin(2 * np.pi * 440 * t)
    return sampling_rate, signal

# テスト信号の生成と保存
sampling_rate, test_signal = generate_test_signal()
write_wav("test_signal.wav", sampling_rate, test_signal)

# 保存した音声ファイルの読み込みと情報表示
sr_scipy, data_scipy = read_wav_scipy("test_signal.wav")
sr_librosa, data_librosa = read_wav_librosa("test_signal.wav")

# 波形プロット
plt.figure(figsize=(12, 4))
time = np.arange(len(test_signal)) / sampling_rate
plt.plot(time, test_signal)
plt.xlabel('時間 [秒]')
plt.ylabel('振幅')
plt.title('テスト信号の波形')
plt.grid(True)
plt.show()

2. スペクトログラムの生成と表示

このコードは音声信号の時間-周波数表現であるスペクトログラムを生成する。チャープ信号(周波数が100Hzから4000Hzへ指数的に変化する信号)を用いて、短時間フーリエ変換(STFT)の特性を可視化する。scipyとlibrosaの両方の実装を比較し、窓関数(ハン窓)、フレーム長(2048サンプル)、ホップ長(1024サンプル)などのパラメータがスペクトログラムに与える影響を示す。上段に時間領域の波形、中段と下段に周波数領域のスペクトログラムを表示する。色の濃淡は信号の強度を表し、時間とともに周波数が変化する様子が確認できる。これは用語リストの「周波数領域分析」の実践例である。


import numpy as np
from scipy import signal
import librosa
import librosa.display
import matplotlib.pyplot as plt

def generate_chirp_signal():
    """チャープ信号の生成(周波数が時間とともに変化する信号)"""
    duration = 3.0
    sampling_rate = 44100
    t = np.linspace(0, duration, int(sampling_rate * duration))
    chirp = signal.chirp(t, f0=100, f1=4000, t1=duration, method='exponential')
    return sampling_rate, chirp

def calculate_spectrogram_scipy(audio_data, sampling_rate):
    """Scipyを使用したスペクトログラム計算"""
    nperseg = 2048
    noverlap = nperseg // 2
    frequencies, times, Sxx = signal.spectrogram(
        audio_data,
        fs=sampling_rate,
        window='hann',
        nperseg=nperseg,
        noverlap=noverlap,
        scaling='spectrum'
    )
    return frequencies, times, 10 * np.log10(Sxx + 1e-10)

def calculate_spectrogram_librosa(audio_data, sampling_rate):
    """Librosaを使用したスペクトログラム計算"""
    n_fft = 2048
    hop_length = n_fft // 2
    D = librosa.stft(audio_data, n_fft=n_fft, hop_length=hop_length,
                    window='hann', center=True)
    S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)
    return S_db

# チャープ信号の生成
sampling_rate, chirp = generate_chirp_signal()

# Scipyによるスペクトログラム
freqs, times, spec_scipy = calculate_spectrogram_scipy(chirp, sampling_rate)

# Librosaによるスペクトログラム
spec_librosa = calculate_spectrogram_librosa(chirp, sampling_rate)

# 結果の可視化
plt.figure(figsize=(15, 10))

# 波形
plt.subplot(3, 1, 1)
time = np.arange(len(chirp)) / sampling_rate
plt.plot(time, chirp)
plt.xlabel('時間 [秒]')
plt.ylabel('振幅')
plt.title('チャープ信号の波形')
plt.grid(True)

# Scipyによるスペクトログラム
plt.subplot(3, 1, 2)
plt.pcolormesh(times, freqs, spec_scipy, shading='gouraud')
plt.ylabel('周波数 [Hz]')
plt.xlabel('時間 [秒]')
plt.title('Scipyによるスペクトログラム')
plt.colorbar(label='パワー [dB]')

# Librosaによるスペクトログラム
plt.subplot(3, 1, 3)
librosa.display.specshow(spec_librosa, sr=sampling_rate, x_axis='time',
                       y_axis='hz', hop_length=1024)
plt.ylabel('周波数 [Hz]')
plt.xlabel('時間 [秒]')
plt.title('Librosaによるスペクトログラム')
plt.colorbar(label='振幅 [dB]')

plt.tight_layout()
plt.show()

3. 基本周波数(F0)推定

このコードは音声の高さを決定する基本周波数を推定する3つの手法を比較する。440Hzの基本波とその倍音を含むテスト信号に対し、自己相関法、ケプストラム法、PYIN法を適用する。自己相関法は信号の周期性を相関計算により検出する。ケプストラム法は対数パワースペクトルに逆フーリエ変換を適用する。PYIN法はlibrosaに実装された高精度な確率的手法である。各手法の推定結果を時系列でプロットし、真のF0(440Hz)と比較する。有声音判定の閾値処理や有効範囲(50-2000Hz)のチェックにより、オクターブ誤りを防ぐ。これは用語リストの「基本周波数推定」に対応する。


import numpy as np
from scipy import signal
import librosa
import matplotlib.pyplot as plt

def generate_test_signal(f0=440, duration=1.0, sr=44100):
    """基本周波数を持つテスト信号の生成"""
    t = np.linspace(0, duration, int(sr * duration))
    # 基本波と倍音を含む信号
    signal_data = 0.5 * np.sin(2 * np.pi * f0 * t) + \
            0.25 * np.sin(2 * np.pi * 2 * f0 * t) + \
            0.125 * np.sin(2 * np.pi * 3 * f0 * t)
    return signal_data, sr

def autocorrelation_f0(signal_data, sr, frame_length=2048, hop_length=512):
    """自己相関法によるF0推定"""
    f0_values = []
    times = []

    for i in range(0, len(signal_data) - frame_length, hop_length):
        frame = signal_data[i:i + frame_length]
        # 自己相関の計算
        corr = signal.correlate(frame, frame, mode='full')
        corr = corr[len(corr)//2:]

        # 最初のピークを探す(直流成分を除く)
        peak_idx = np.argmax(corr[50:]) + 50
        if corr[peak_idx] > 0.1:  # 有声音の閾値
            f0 = sr / peak_idx
            if 50 <= f0 <= 2000:  # 有効なF0範囲
                f0_values.append(f0)
            else:
                f0_values.append(0)
        else:
            f0_values.append(0)

        times.append(i / sr)

    return np.array(f0_values), np.array(times)

def cepstrum_f0(signal_data, sr, frame_length=2048, hop_length=512):
    """ケプストラム法によるF0推定"""
    f0_values = []
    times = []

    for i in range(0, len(signal_data) - frame_length, hop_length):
        frame = signal_data[i:i + frame_length]

        # パワースペクトルの計算
        spectrum = np.fft.fft(frame)
        log_spectrum = np.log(np.abs(spectrum) + 1e-10)

        # ケプストラムの計算
        cepstrum = np.fft.ifft(log_spectrum).real

        # 有効なF0範囲でピーク検出
        start_idx = int(sr / 2000)  # 2000Hz対応
        end_idx = int(sr / 50)      # 50Hz対応
        peak_idx = np.argmax(cepstrum[start_idx:end_idx]) + start_idx

        if peak_idx > 0:
            f0 = sr / peak_idx
            if 50 <= f0 <= 2000:
                f0_values.append(f0)
            else:
                f0_values.append(0)
        else:
            f0_values.append(0)

        times.append(i / sr)

    return np.array(f0_values), np.array(times)

def librosa_f0(signal_data, sr):
    """librosaを使用したF0推定(PYIN法)"""
    f0, voiced_flag, voiced_probs = librosa.pyin(
        signal_data,
        fmin=librosa.note_to_hz('C2'),
        fmax=librosa.note_to_hz('C7'),
        sr=sr
    )
    return f0, voiced_flag

# テスト信号の生成と分析
signal_data, sr = generate_test_signal(f0=440)

# 自己相関法によるF0推定
f0_autocorr, times_autocorr = autocorrelation_f0(signal_data, sr)

# ケプストラム法によるF0推定
f0_cepstrum, times_cepstrum = cepstrum_f0(signal_data, sr)

# librosaによるF0推定
f0_librosa, voiced_flag = librosa_f0(signal_data, sr)
times_librosa = librosa.times_like(f0_librosa, sr=sr)

# 結果の可視化
plt.figure(figsize=(12, 10))

# 波形
plt.subplot(4, 1, 1)
t = np.linspace(0, len(signal_data)/sr, len(signal_data))
plt.plot(t, signal_data)
plt.xlabel('時間 [秒]')
plt.ylabel('振幅')
plt.title('テスト信号の波形')
plt.grid(True)

# 自己相関法によるF0
plt.subplot(4, 1, 2)
plt.plot(times_autocorr, f0_autocorr, 'o-', label='推定F0')
plt.axhline(y=440, color='r', linestyle='--', label='真のF0')
plt.xlabel('時間 [秒]')
plt.ylabel('周波数 [Hz]')
plt.title('自己相関法によるF0推定結果')
plt.legend()
plt.grid(True)

# ケプストラム法によるF0
plt.subplot(4, 1, 3)
plt.plot(times_cepstrum, f0_cepstrum, 'o-', label='推定F0')
plt.axhline(y=440, color='r', linestyle='--', label='真のF0')
plt.xlabel('時間 [秒]')
plt.ylabel('周波数 [Hz]')
plt.title('ケプストラム法によるF0推定結果')
plt.legend()
plt.grid(True)

# librosaによるF0
plt.subplot(4, 1, 4)
plt.plot(times_librosa[voiced_flag], f0_librosa[voiced_flag],
         'o-', label='推定F0(有声区間)')
plt.axhline(y=440, color='r', linestyle='--', label='真のF0')
plt.xlabel('時間 [秒]')
plt.ylabel('周波数 [Hz]')
plt.title('PYIN(librosa)によるF0推定結果')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

4. フォルマント分析

このコードは母音の音色を特徴づけるフォルマント(声道の共鳴周波数)を分析する。母音/a/と/i/を、基本周波数130Hzののこぎり波に共鳴フィルタを適用して合成する。線形予測分析(LPC)により音声信号からLPC係数を計算し、その根から周波数変換してフォルマントを推定する。さらに無声音をランダムノイズに高域通過フィルタを適用して生成し、有声音との違いを可視化する。各音声の波形とスペクトルを表示し、推定されたフォルマント(赤破線)と真のフォルマント(緑点線)を比較する。バンド幅も計算され、共鳴のシャープさを定量化する。これは用語リストの「フォルマント分析」と「有声音・無声音」に対応する。


import numpy as np
from scipy import signal
import librosa
import matplotlib.pyplot as plt

def generate_vowel(formants, duration=1.0, sr=16000):
    """フォルマントを持つ母音の生成"""
    t = np.linspace(0, duration, int(sr * duration))
    f0 = 130  # 基本周波数

    # 声帯波源(のこぎり波)
    source = signal.sawtooth(2 * np.pi * f0 * t)

    # フォルマントフィルタの作成
    vowel = np.zeros_like(t)
    for formant in formants:
        bw = formant * 0.1  # バンド幅
        w0 = 2 * np.pi * formant / sr
        r = np.exp(-np.pi * bw / sr)

        # 共鳴フィルタ
        a = [1, -2*r*np.cos(w0), r**2]
        b = [1]
        vowel += signal.lfilter(b, a, source)

    return vowel / np.max(np.abs(vowel)), sr

def generate_unvoiced(duration=1.0, sr=16000):
    """無声音の生成"""
    # ランダムノイズを生成
    noise = np.random.randn(int(sr * duration))

    # 高域通過フィルタで無声子音を模擬
    sos = signal.butter(4, 2000, 'hp', fs=sr, output='sos')
    unvoiced = signal.sosfilt(sos, noise)

    return unvoiced / np.max(np.abs(unvoiced)), sr

def estimate_formants(signal_data, sr, order=12):
    """LPCによるフォルマント推定"""
    # LPC係数の計算
    a = librosa.lpc(signal_data, order=order)

    # 根を求める
    roots = np.roots(a)
    roots = roots[np.imag(roots) >= 0]  # 正の虚部を持つ根のみ

    # 角周波数から周波数へ変換
    angles = np.arctan2(np.imag(roots), np.real(roots))
    freqs = angles * (sr / (2 * np.pi))

    # バンド幅の計算
    bandwidth = -1/2 * (sr/(2*np.pi)) * np.log(np.abs(roots))

    # 周波数でソート
    sorted_idx = np.argsort(freqs)
    freqs = freqs[sorted_idx]
    bandwidth = bandwidth[sorted_idx]

    return freqs, bandwidth

# 母音/a/のフォルマント
formants_a = [730, 1090, 2440]
# 母音/i/のフォルマント
formants_i = [270, 2290, 3010]

# 母音の生成
vowel_a, sr = generate_vowel(formants_a)
vowel_i, _ = generate_vowel(formants_i)

# 無声音の生成
unvoiced, _ = generate_unvoiced()

# フォルマント推定
freqs_a, bw_a = estimate_formants(vowel_a, sr)
freqs_i, bw_i = estimate_formants(vowel_i, sr)

# 結果の可視化
plt.figure(figsize=(15, 12))

# 母音/a/の波形とスペクトル
plt.subplot(3, 2, 1)
plt.plot(vowel_a)
plt.title('母音/a/の波形(有声音)')
plt.xlabel('サンプル')
plt.ylabel('振幅')

plt.subplot(3, 2, 2)
freq, spec = signal.welch(vowel_a, sr, nperseg=2048)
plt.plot(freq, 10 * np.log10(spec))
plt.vlines(freqs_a[:3], plt.ylim()[0], plt.ylim()[1],
          colors='r', linestyles='--', label='推定フォルマント')
plt.vlines(formants_a, plt.ylim()[0], plt.ylim()[1],
          colors='g', linestyles=':', label='真のフォルマント')
plt.title('母音/a/のスペクトル')
plt.xlabel('周波数 [Hz]')
plt.ylabel('パワー [dB]')
plt.legend()

# 母音/i/の波形とスペクトル
plt.subplot(3, 2, 3)
plt.plot(vowel_i)
plt.title('母音/i/の波形(有声音)')
plt.xlabel('サンプル')
plt.ylabel('振幅')

plt.subplot(3, 2, 4)
freq, spec = signal.welch(vowel_i, sr, nperseg=2048)
plt.plot(freq, 10 * np.log10(spec))
plt.vlines(freqs_i[:3], plt.ylim()[0], plt.ylim()[1],
          colors='r', linestyles='--', label='推定フォルマント')
plt.vlines(formants_i, plt.ylim()[0], plt.ylim()[1],
          colors='g', linestyles=':', label='真のフォルマント')
plt.title('母音/i/のスペクトル')
plt.xlabel('周波数 [Hz]')
plt.ylabel('パワー [dB]')
plt.legend()

# 無声音の波形とスペクトル
plt.subplot(3, 2, 5)
plt.plot(unvoiced)
plt.title('無声音の波形')
plt.xlabel('サンプル')
plt.ylabel('振幅')

plt.subplot(3, 2, 6)
freq, spec = signal.welch(unvoiced, sr, nperseg=2048)
plt.plot(freq, 10 * np.log10(spec))
plt.title('無声音のスペクトル')
plt.xlabel('周波数 [Hz]')
plt.ylabel('パワー [dB]')

plt.tight_layout()
plt.show()

5. 環境音の解析

このコードは非定常な環境音(雨音)を合成し、様々な特徴量で解析する。大中小の雨滴をポアソン分布に従って生成し、各雨滴を減衰正弦波としてモデル化する。スペクトル特徴量(重心、帯域幅、ロールオフ、平坦度)により音色の明るさや複雑さを定量化する。時間領域特徴量(RMSエネルギー、ゼロ交差率、クレストファクター、尖度)により強度やインパルス性を評価する。リズム特徴量(オンセット、テンポグラム)により時間構造を捉え、パワースペクトル密度の傾きからノイズの色を分類する。6つのサブプロットで波形、スペクトログラム、各種特徴量の時間変化を可視化する。これは用語リストの「環境音解析」「スペクトル特徴量」「時間領域特徴量」に対応する。


import numpy as np
import librosa
import librosa.display
import scipy.stats
from scipy.signal import welch
from scipy import signal
import matplotlib.pyplot as plt

def compute_spectral_features(y, sr, n_fft=2048, hop_length=512):
    """スペクトル特徴量の計算"""
    if np.all(y == 0):
        return {
            'centroid': np.zeros(1),
            'bandwidth': np.zeros(1),
            'rolloff': np.zeros(1),
            'flatness': np.zeros(1)
        }

    spectral_centroid = librosa.feature.spectral_centroid(
        y=y, sr=sr, n_fft=n_fft, hop_length=hop_length
    )[0]

    spectral_bandwidth = librosa.feature.spectral_bandwidth(
        y=y, sr=sr, n_fft=n_fft, hop_length=hop_length
    )[0]

    spectral_rolloff = librosa.feature.spectral_rolloff(
        y=y, sr=sr, n_fft=n_fft, hop_length=hop_length
    )[0]

    spectral_flatness = librosa.feature.spectral_flatness(
        y=y, n_fft=n_fft, hop_length=hop_length
    )[0]

    return {
        'centroid': spectral_centroid,
        'bandwidth': spectral_bandwidth,
        'rolloff': spectral_rolloff,
        'flatness': spectral_flatness
    }

def compute_temporal_features(y, sr, frame_length=2048, hop_length=512):
    """時間領域特徴量の計算"""
    if np.all(y == 0):
        return {
            'rms': np.zeros(1),
            'zcr': np.zeros(1),
            'crest_factor': 0,
            'kurtosis': 0
        }

    # RMSエネルギー
    rms = librosa.feature.rms(
        y=y, frame_length=frame_length, hop_length=hop_length
    )[0]

    # ゼロ交差率
    zero_crossing_rate = librosa.feature.zero_crossing_rate(
        y=y, frame_length=frame_length, hop_length=hop_length
    )[0]

    # 波形の統計量
    rms_val = np.sqrt(np.mean(y**2)) + 1e-10
    crest_factor = np.max(np.abs(y)) / rms_val
    kurtosis = scipy.stats.kurtosis(y, fisher=True)

    return {
        'rms': rms,
        'zcr': zero_crossing_rate,
        'crest_factor': crest_factor,
        'kurtosis': kurtosis
    }

def compute_rhythm_features(y, sr):
    """リズム特徴量の計算"""
    # テンポグラム
    onset_env = librosa.onset.onset_strength(y=y, sr=sr)
    tempogram = librosa.feature.tempogram(
        onset_envelope=onset_env,
        sr=sr
    )

    # オンセット検出
    onset_frames = librosa.onset.onset_detect(
        onset_envelope=onset_env,
        sr=sr
    )

    return {
        'tempogram': tempogram,
        'onset_frames': onset_frames
    }

def analyze_noise_color(y, sr):
    """ノイズの色分析(パワースペクトル密度の傾き)"""
    freqs, psd = welch(y, sr, nperseg=2048)
    # 対数変換
    log_freqs = np.log10(freqs[1:])  # 0Hzを除外
    log_psd = np.log10(psd[1:])
    # 線形回帰で傾きを計算
    slope, _, _, _, _ = scipy.stats.linregress(log_freqs, log_psd)
    return slope, freqs[1:], psd[1:]

def simulate_rain(duration=5.0, sr=44100):
    """雨音のシミュレーション"""
    t = np.linspace(0, duration, int(sr * duration))
    rain = np.zeros_like(t)

    # 雨滴の種類(大小)を考慮
    drop_types = {
        'small': {
            'freq_range': (2000, 4000),
            'duration_range': (0.01, 0.03),
            'amplitude_range': (0.1, 0.3)
        },
        'medium': {
            'freq_range': (1000, 2000),
            'duration_range': (0.03, 0.06),
            'amplitude_range': (0.3, 0.6)
        },
        'large': {
            'freq_range': (500, 1000),
            'duration_range': (0.06, 0.1),
            'amplitude_range': (0.6, 1.0)
        }
    }

    # 各種類の雨滴を生成
    for drop_type, params in drop_types.items():
        # 雨滴の数(ポアソン分布に従う)
        n_drops = np.random.poisson(duration *
                 (100 if drop_type == 'small' else
                  50 if drop_type == 'medium' else 20))

        for _ in range(n_drops):
            # 雨滴の時刻
            t_drop = np.random.random() * duration
            idx = int(t_drop * sr)

            # 雨滴のパラメータ
            freq = np.random.uniform(*params['freq_range'])
            drop_duration = np.random.uniform(*params['duration_range'])
            amplitude = np.random.uniform(*params['amplitude_range'])

            # 雨滴の波形生成
            n_samples = int(drop_duration * sr)
            if idx + n_samples >= len(rain):
                continue

            # 減衰正弦波として雨滴を生成
            t_drop = np.arange(n_samples) / sr
            envelope = np.exp(-t_drop * 30)  # 指数減衰
            drop = amplitude * envelope * np.sin(2 * np.pi * freq * t_drop)

            # ランダムなフィルタリングで音色を変化
            drop = signal.lfilter([1], [1, -0.99], drop)

            rain[idx:idx+n_samples] += drop

    # クリッピングを防ぎつつ正規化
    rain = rain / (np.max(np.abs(rain)) + 1e-10)

    return rain, sr

# 解析の実行
y, sr = simulate_rain()

# 各種特徴量の計算
spectral_features = compute_spectral_features(y, sr)
temporal_features = compute_temporal_features(y, sr)
rhythm_features = compute_rhythm_features(y, sr)
noise_slope, freqs_psd, psd = analyze_noise_color(y, sr)

# 結果の可視化
plt.figure(figsize=(15, 10))

# 波形
plt.subplot(3, 2, 1)
plt.plot(np.linspace(0, len(y)/sr, len(y)), y)
plt.title('雨音波形')
plt.xlabel('時間 [秒]')
plt.ylabel('振幅')

# スペクトログラム
plt.subplot(3, 2, 2)
D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('スペクトログラム')

# スペクトル特徴量の時間変化
plt.subplot(3, 2, 3)
times = np.linspace(0, len(y)/sr, len(spectral_features['centroid']))
plt.plot(times, librosa.amplitude_to_db(spectral_features['flatness']),
         label='平坦度')
plt.plot(times, spectral_features['centroid']/sr, label='重心')
plt.legend()
plt.title('スペクトル特徴量の時間変化')
plt.xlabel('時間 [秒]')

# パワースペクトル密度(ノイズの色分析)
plt.subplot(3, 2, 4)
plt.loglog(freqs_psd, psd)
plt.title(f'パワースペクトル密度(傾き: {noise_slope:.2f})')
plt.xlabel('周波数 [Hz]')
plt.ylabel('PSD')

# テンポグラム
plt.subplot(3, 2, 5)
librosa.display.specshow(rhythm_features['tempogram'],
                       sr=sr, x_axis='time')
plt.title('テンポグラム')
plt.xlabel('時間 [秒]')
plt.ylabel('テンポ [BPM]')

# エネルギーとゼロ交差率
plt.subplot(3, 2, 6)
times_rms = np.linspace(0, len(y)/sr, len(temporal_features['rms']))
plt.plot(times_rms, temporal_features['rms'], label='RMS')
plt.plot(times_rms, temporal_features['zcr'], label='ZCR')
plt.legend()
plt.title('時間領域特徴量')
plt.xlabel('時間 [秒]')

plt.tight_layout()
plt.show()

# 統計値の表示
print(f"クレストファクター: {temporal_features['crest_factor']:.2f}")
print(f"尖度: {temporal_features['kurtosis']:.2f}")
print(f"ノイズの色(スペクトル傾き): {noise_slope:.2f}")