音声信号処理の Python 実現ガイド
【概要】音声信号処理の用語(60項目)とPython実装を示す。用語は音声信号の基本概念、周波数領域分析、スペクトル特徴量、基本周波数推定、フォルマント分析、時間領域特徴量、リズム・時間構造特徴量、環境音解析、信号処理技術、評価・検証手法に分類され、対応するPython関数を明示する。実装例として、音声ファイルの入出力、スペクトログラムの生成と表示、基本周波数(F0)推定、フォルマント分析、環境音の解析の5つのプログラムを提供する。
【目次】
【サイト内のPython関連主要ページ】
- Windows AI支援Python開発環境構築ガイド: 別ページ »で説明
- AIエディタ Windsurf の活用: 別ページ »で説明
- AIエディタCursorガイド: 別ページ »で説明
- Google Colaboratory: 別ページ »で説明
- Python(Google Colaboratoryを含む)のまとめ: 別ページ »で説明
- 機械学習の Python 実現ガイド: 別ページ »で説明
- 行列計算の Python 実現ガイド: 別ページ »で説明
- 統計分析のPython での実現ガイド: 別ページ »で説明
- 音声信号処理の Python 実現ガイド: 別ページ »で説明
- カラー画像処理の Python 実現ガイド: 別ページ »で説明
- Python 言語によるとても簡単なアドベンチャーゲーム(変数,式,if,while,関数,print,time.sleep, def, global を使用): 別ページ »で説明
- Pythonプログラミング講座:基礎から応用まで(授業資料,全15回): 別ページ »で説明
- Pythonプログラミングの例と実践ガイド: 別ページ »で説明
【外部リソース】
- Pythonの公式サイト: https://www.python.org
- 東京大学の「Pythonプログラミング入門」: https://utokyo-ipp.github.io/IPP_textbook.pdf
- ITmedia社の「Pythonチートシート」の記事: https://atmarkit.itmedia.co.jp/ait/articles/2004/20/news015.html
音声信号処理の基本
アナログ信号・デジタル信号:信号の表現形式の違いである。アナログ信号は時間的に連続した値をとる信号であり、デジタル信号は離散的な時刻に離散的な値をとる信号である。音声処理ではアナログ信号をデジタル信号に変換(A/D変換)して処理を行う。
周波数:信号の周期的な変化の速さを表す物理量である。単位はHz(ヘルツ)で、1秒あたりの振動回数を示す。周波数が高いほど音は高く聞こえる。
時間領域・周波数領域:信号を表現する2つの視点である。時間領域は信号の振幅が時間とともにどう変化するかを表し、周波数領域は信号にどの周波数成分がどれだけ含まれるかを表す。
フーリエ変換:時間領域の信号を周波数領域の表現に変換する数学的手法である。信号に含まれる周波数成分とその強度を抽出できる。逆変換により周波数領域から時間領域へ戻すことも可能である。
周波数分解能・時間分解能:信号分析における精度のトレードオフである。周波数分解能は周波数をどれだけ細かく識別できるかを示し、時間分解能は時間変化をどれだけ細かく追跡できるかを示す。一方を高めると他方が低下する関係にある。
スペクトル:信号の周波数成分とその強度の分布である。横軸に周波数、縦軸に振幅またはパワーをとったグラフで表現される。
周期性:信号が一定の時間間隔で繰り返されるパターンを持つ性質である。音声では有声音が周期性を持つ。
定常性・非定常性:信号の統計的性質の時間的変化の有無である。定常信号は統計的性質が時間によらず一定であり、非定常信号は時間とともに変化する。音声信号や環境音は非定常信号である。
ノイズ:目的とする信号に混入する不要な成分である。ランダムな性質を持つことが多く、信号対雑音比(SNR)により信号品質を評価する。
時間窓:信号を短い区間に分割するための時間範囲である。短時間フーリエ変換では時間窓を移動させながら各区間で周波数分析を行う。
用語リスト
音声信号の基本概念
サンプリング周波数:アナログ信号をデジタル信号に変換する際の1秒あたりのサンプル数である。単位はHz(ヘルツ)である。音声信号の周波数帯域を決定する重要なパラメータであり、44.1kHzや48kHzが一般的に使用される。
ビット深度:各サンプルの振幅を表現するビット数である。16ビットや24ビットが一般的である。ビット深度が大きいほど、より細かい振幅の変化を表現でき、ダイナミックレンジが広がる。
チャンネル数:音声信号の独立した信号系列の数である。モノラル(1チャンネル)、ステレオ(2チャンネル)、マルチチャンネル(5.1チャンネルなど)がある。
振幅:音声信号の大きさを表す値である。音の大きさや強さに対応する物理量である。
時間長:音声信号の継続時間である。サンプル数をサンプリング周波数で除算することで算出される。
周波数領域分析
スペクトログラム:音声信号の時間-周波数表現である。横軸に時間、縦軸に周波数、色の濃淡でパワーまたは振幅を表示する。音声の時間的変化と周波数成分を同時に可視化できる。
短時間フーリエ変換(STFT):時間的に変化する信号を短い時間窓で区切り、各区間でフーリエ変換を適用する手法である。非定常信号である音声の周波数成分の時間変化を分析する基本的な手法である。
窓関数:信号を短い区間に分割する際に適用する関数である。ハン窓、ハミング窓、ブラックマン窓などがある。窓関数の選択は周波数分解能と時間分解能のトレードオフに影響する。
フレーム長:STFTにおいて信号を分割する各区間の長さである。サンプル数で表現される。フレーム長が長いほど周波数分解能が高くなるが、時間分解能は低下する。
ホップ長:STFTにおいて連続するフレーム間の移動量である。フレーム長よりも短く設定することでフレーム間のオーバーラップを実現する。
オーバーラップ率:連続するフレーム間で重複する部分の割合である。通常50%(ホップ長がフレーム長の半分)や75%が使用される。オーバーラップにより時間分解能を向上させる。
FFTサイズ:高速フーリエ変換(FFT)で使用するサンプル数である。2のべき乗(512、1024、2048など)が一般的である。FFTサイズが大きいほど周波数分解能が高くなる。
パワースペクトル密度(PSD):単位周波数あたりのパワーを表す指標である。信号の周波数成分の強度分布を示す。
スペクトル包絡:スペクトルの全体的な形状を表す曲線である。声道の共鳴特性を反映し、音色の特徴を表現する。
スペクトル特徴量
スペクトル重心:パワースペクトルの重心となる周波数である。音色の明るさを表す指標である。高い値ほど高周波成分が多いことを示す。
スペクトル帯域幅:スペクトル重心を中心とした周波数の広がりである。音色の豊かさや複雑さを表す指標である。
スペクトルロールオフ:全パワーの一定割合(通常85%や95%)が含まれる周波数の上限である。高周波成分の広がりを示す指標である。
スペクトル平坦度:スペクトルの平坦さを表す指標である。幾何平均と算術平均の比として算出される。値が1に近いほどホワイトノイズに近く、0に近いほどトーン性が高い。
基本周波数推定
基本周波数(F0):音声の周期的な振動の基本となる周波数である。音声の高さを決定する重要なパラメータである。人間の音声では男性が約100-150Hz、女性が約180-250Hzの範囲にある。
有声音:声帯の周期的な振動により生成される音声である。母音や有声子音が該当する。明確な基本周波数を持つ。
無声音:声帯の振動を伴わず、気流の乱れにより生成される音声である。無声子音が該当する。基本周波数を持たない。
自己相関法:信号と時間シフトした信号自身との相関を計算し、周期性を検出する手法である。基本周波数推定の古典的手法である。
ケプストラム法:対数パワースペクトルに逆フーリエ変換を適用して得られるケプストラム領域で周期性を検出する手法である。
PYIN:確率的YIN法である。基本周波数推定の高精度な手法であり、各時刻での基本周波数の確率分布を推定する。librosaで実装されている。
オクターブ誤り:基本周波数推定において、真の基本周波数の整数倍または整数分の1の値を誤って推定する現象である。
フォルマント分析
フォルマント:声道の共鳴により生じるスペクトル包絡のピーク周波数である。母音の音色を特徴づける重要なパラメータである。通常、第1フォルマント(F1)から第4フォルマント(F4)までを分析対象とする。
声道:声帯から唇までの音声生成経路である。共鳴管として機能し、音源スペクトルを変形させる。
線形予測分析(LPC):過去のサンプル値の線形結合により現在のサンプル値を予測するモデルである。音声信号の声道特性をモデル化し、フォルマント推定に広く使用される。
LPC係数:線形予測分析で得られる予測係数である。声道の共鳴特性を表現する。
予測次数:線形予測分析で使用する過去のサンプル数である。通常、サンプリング周波数(kHz単位)の2倍程度に設定される。
バンド幅:フォルマントの周波数幅である。共鳴のシャープさを表す指標であり、減衰の速さに関係する。
のこぎり波:基本周波数とその整数倍の倍音を含む周期信号である。有声音の音源モデルとして使用される。
時間領域特徴量
RMSエネルギー:信号の二乗平均平方根である。音声の強度を表す基本的な指標である。
ゼロ交差率(ZCR):信号が0を横切る回数の割合である。高周波成分の多さを示す指標であり、有声音と無声音の判別に使用される。
クレストファクター:信号のピーク値とRMS値の比である。信号の波高率を表し、インパルス性の強さを示す指標である。
尖度(Kurtosis):確率分布の尖り具合を表す統計量である。正規分布からの逸脱度を示し、インパルス性やノイズ性の評価に使用される。
リズム・時間構造特徴量
オンセット:音の立ち上がり時刻である。音符の開始時刻や打楽器の打撃時刻に対応する。
オンセット強度包絡線:各時刻でのオンセットの強さを表す時系列である。スペクトルの変化量から算出される。
テンポグラム:時間-テンポ表現である。各時刻での周期性の強さをテンポ(BPM)の関数として表示する。
環境音解析
環境音:音声以外の音響信号の総称である。自然音(雨、風、鳥の鳴き声など)、人工音(機械音、交通音など)、生活音などを含む。
非定常性:信号の統計的性質が時間とともに変化する性質である。環境音の多くは非定常な特性を持つ。
ノイズの色:ノイズのスペクトル特性を色で表現する概念である。ホワイトノイズ(全周波数で均等なパワー)、ピンクノイズ(周波数に反比例するパワー)、ブラウンノイズ(周波数の二乗に反比例するパワー)などがある。
スペクトル傾き:対数周波数軸上でのパワースペクトル密度の傾きである。ノイズの色を定量化する指標であり、線形回帰により算出される。
信号処理技術
正規化:信号の振幅を一定の範囲(通常-1から1)に収める処理である。信号が表現可能な範囲を超えることを防ぐために重要である。
減衰:信号の振幅が時間とともに減少する現象である。指数減衰が自然音の特徴として広く見られる。
共鳴:特定の周波数で振幅が増幅される現象である。声道のフォルマントや楽器の音色形成に重要な役割を果たす。
評価・検証手法
閾値処理:信号や特徴量が一定の閾値を超えるか否かで判定を行う処理である。有声/無声判定やオンセット検出で使用される。
有効範囲:パラメータが妥当とされる値の範囲である。基本周波数推定では通常50-2000Hzが人間の音声の有効範囲とされる。
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() の nperseg、librosa.stft() の n_fft |
STFTの各区間の長さ |
| ホップ長 | scipy.signal.spectrogram() では nperseg - noverlap、librosa.stft() の hop_length |
フレーム間の移動量 |
| オーバーラップ率 | scipy.signal.spectrogram() の noverlap(オーバーラップ量を指定) |
連続するフレーム間の重複部分 |
| FFTサイズ | librosa.stft() の n_fft、scipy.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}")