NumpyのarrayからPydubのAudioSegmentを作成する

概要

Pythonでは、PydubのAudioSegmentを使って音声データを読み込み・加工したり、保存したりすることができます。ただし、Numpy等のデータの状態からAudioSegmentを作成する方法が分からなかったので調べて整理すると共に、音声データのスペクトルに乱数ノイズを加えた際に音声データにどのような影響が出るのかを検討しました。
音声ファイルは前回同様、スーパーマリオ64のウォーターランドの曲を使わせていただきました。

https://www.nintendo.co.jp/ds/series/dsi/menu/sound/download.html
前回の記事(AudioSegmentを用いた音声の読み込みやフーリエ変換を用いたスペクトログラムの作成)はこちら。

Pythonを使って音声データからスペクトログラムを作成する

ArrayからAudioSegmentの作成と保存

調べたら以下サイトに書いてありました。
https://github.com/jiaaro/pydub/blob/master/API.markdown

sound = AudioSegment(
    # raw audio data (bytes)
    data=b'…',

    # 2 byte (16 bit) samples
    sample_width=2,

    # 44.1 kHz frame rate
    frame_rate=44100,

    # stereo
    channels=2
)

どうやら配列をByte型に変換した物と、その他の設定値(bit数とフレームレート、ステレオ・モノラル)を指定すればいいようです。
また、保存は以下のようにexpertコマンドで可能のようです。

#簡単な方法
sound.export("/path/to/output.mp3", format="mp3")
#細かくいろいろ指定することも可能
sound.export("/path/to/output.mp3",
                   format="mp3",
                   bitrate="192k",
                   tags={"album": "The Bends", "artist": "Radiohead"},
                   cover="/path/to/albumcovers/radioheadthebends.jpg")

サンプルデータを使って試してみます。

#ファイル読み込み
sound = AudioSegment.from_file("ウォーターランド.m4a", "m4a")

#配列データ取り出し
samples = np.array(sound.get_array_of_samples())

#読み込んだ配列データから新しいAudioSegmentを作成
test = AudioSegment(
                    samples.astype("int16").tobytes(), 
                    sample_width=sound.sample_width, 
                    frame_rate=sound.frame_rate, 
                    channels=sound.channels,
                    )

#保存
test.export("test.mp3", format="mp3")

配列はsample_widthに合わせて、int16等の型に設定する必要があることに留意してください。保存できたファイルは以下。きちんと再現できていますね。

ノイズを加えた場合の検証

参考として、フーリエ変換して得られたスペクトルデータにノイズを乗せ、それを逆変換して得られる音声データがどのようになるかを検証しました。ノイズの幅であるn_ampとn_argを変えながら検証します。

#乱数のシードを設定
np.random.seed(0)
#ノイズの振幅
n_amp = 1.0
n_arg = 1.0

#音声データ読み込み
sound = AudioSegment.from_file("ウォーターランド.m4a", "m4a")
samples = np.array(sound.get_array_of_samples())
sample = samples[::sound.channels]

#fftでスペクトルに変換
spec = np.fft.fft(sample)

#振幅・偏角を取り出しノイズを印加
amp = np.abs(spec)
amp += np.random.randn(amp.shape[0])*amp.mean()*n_amp
arg = np.angle(spec) + np.random.randn(amp.shape[0])*n_arg

#ノイズを加えた結果をスペクトルに戻す
spec2 = np.exp(1j*arg)*amp

#フーリエ逆変換
sample2 = np.fft.ifft(spec2)

#AudioSegmentを作成し保存
test = AudioSegment(sample2.real.astype("int16").tobytes(), sample_width=sound.sample_width, frame_rate=sound.frame_rate, channels = 1)
test.export("test.mp3", format="mp3")

n_ampを変えた場合

まずは振幅についてだけノイズを加えてみます。後ろに雑音が乗るという感じですね。
n_amp=0.5, n_arg=0.0 の場合

n_amp=1.0, n_arg=0.0 の場合

n_amp=3.0, n_arg=0.0 の場合

n_argを変えた場合

次に偏角についてだけノイズを加えてみます。振幅の場合より影響が大きく、なんだか酔いそうな感じです。
n_amp=0.0, n_arg=0.5 の場合

n_amp=0.0, n_arg=1.0 の場合

n_amp=0.0, n_arg=3.0 の場合

n_ampとn_argの両方を変えた場合

最後に両方にノイズを加えてみます。両方の影響を重ねあわせたという感じです。
n_amp=0.5, n_arg=0.5 の場合

n_amp=1.0, n_arg=1.0 の場合

n_amp=3.0, n_arg=3.0 の場合

まとめ

Numpy等のデータの状態からAudioSegmentを作成する方法と、音声データのスペクトルに乱数ノイズを加えた際に音声データにどのような影響が出るのかを検討し整理しました。偏角のノイズが1.0を超えたあたりからだいぶ聞き取りにくくなるようですね。予測をしていく時は、偏角の誤差を小さくできるように(RMSE1.0以下)務めたほうが良さそうです。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA