音声パワーと基本周波数をリアルタイムでモニタリングするスクリプトをPythonで書いた話

はじめに

「音」の勉強を始めたビギナーにとっては、 そもそも音響関係のPythonモジュールを使ったプログラミングの経験も少ないだろうと思われる。 本記事はそのようなビギナー向けにプログラムの実例をひとつ提供するものである。

さて以前、こんな記事を書いたことがあった。

tam5917.hatenablog.com

記事の中で、発話区間検出(Voice Activity Detection)をしながら音声認識をかけるスクリプトを紹介している。

実行イメージを下図に示す。音声の短時間パワーおよび認識された発話内容が表示されている。

図1:asr_streaming_vad.pyの実行例

音声入力を活用したアプリケーションの実装を考えた場合、音声認識機能をひとまず除外し、パワー(音量)だけではなく声の高さ(基本周波数)もモニタリングできたほうが色々と捗るのでは、と考えた。

そこで本記事ではそのようなデモンストレーションを行うスクリプトを実装したので、紹介する。

実装

実装へのリンクを示す。音量監視機能付き音声認識スクリプトの大元がKoji Inoue 氏のコードであるため、コピーライトを書いておいた。

主に使うモジュールはsounddeviceである。このモジュールを活用することで、マイクから音声データを取得し、自身のプログラム内で活用できる。

さて音声データから音声の短時間パワーを計算しているのはこのあたり:

audio = struct.unpack(f"{len(indata) / 2:.0f}h", indata)  # 2Byte単位でunpackする
audio = np.array(audio).astype(np.float64)

# 音声のパワー(音声データの二乗平均)を計算する
rms = math.sqrt(np.square(audio).mean())
power = 20 * math.log10(rms) if rms > 0.0 else -math.inf  # RMSからデシベルへ

indata は ストリームから取得した生のバイナリデータであり、それをstructモジュールのunpack関数を用いて short int へと変換する。 unpackした直後はtupleになっているため、numpyのarray へと変換する。パワーの計算方法については音響学・音声分析の教科書を見て欲しい。

基本周波数を計算しているのはこのあたり:

fo, _ = pyworld.dio(audio, self.rate)
nonzero_ind = np.nonzero(fo.astype(int))[0]
fo = fo[nonzero_ind]  # foが非ゼロの部分を取り出すことで、推定をロバストにする
if len(fo) > 0:
    fo = fo.mean()  # フレーム平均
else:
    fo = 0.0  # 空っぽだったら 0.0 Hz

PyWorldのdio関数により基本周波数を計算している。基本周波数が非ゼロのフレームのみを取り出して、フレーム平均により推定している。

音声をマイクから取得し、パワーと基本周波数を表示するやり方は以下の通り。 sounddeviceの作法は少し独特だが、「入力ストリーム」から音声取得→音声分析→パワーたちを表示、をひたすら繰り返す動作となっている。

try:
    print("<収録開始>")
    mic_stream.open_stream()  # 入力ストリームを開く準備
    with mic_stream.input_stream:  # 入力ストリームから音声取得
        audio_generator = mic_stream.generator()  # 音声データ(のカタマリ)
        for data in audio_generator:  # チャンクごとに情報を表示してモニタリング
            power, fo = mic_stream.compute_power_fo(data)  # 音声パワーと基本周波数を取得
            print(
                "\r" + f"音声パワー {power:5.1f}[dB] " + f"基本周波数 {fo:5.1f}[Hz]",
                end="",
            )
            continue
    except KeyboardInterrupt:  # Ctrl-C で強制終了
        print("\n<収録終了>")

なおプログラム実行終了時のイメージはこちら。実行中は音量と声の高さに応じて、表示が逐次変わっていく。

図2:短時間パワーと基本周波数をリアルタイムでモニタリングした様子(プログラム終了時)

今回のプログラムではCtrl-Cにより強制終了する形とした。本記事の読者ならば、さらに改良できるだろう。

おわりに

sounddeviceモジュールの活用例として、パワー(音量)と声の高さ(基本周波数)をモニタリングするPythonスクリプトの実装を紹介した。件のモジュールの仕様を筆者はすべて把握しているわけではないのだが、公式のサンプルプログラムを動かしたりすることで、マイクやスピーカーからの音声入出力がリアルタイムで行われるのはやはり面白いと感じる。

sounddevice 公式の 実装例たちを見ると、リアルタイムで音声波形をグラフにプロットしたりするスクリプトが紹介されている。ぜひ試してみると良いだろう。またasyncioモジュールを活用した実装例も紹介されている。 今回の実装をasyncioモジュールによってリファクタリングすることは今後の課題としたい。

おまけ

sounddeviceを用いた先人の記事たち。matplotlibのFuncAnimationと組み合わせるのが良いらしい。