【Python】音声を離散フーリエ変換して周波数帯ごとにビジュアライズする
はじめに
この記事の時点で音声に関してあやふやだったことが多いので改めて書くことにした。今回はwavファイルを読み込んでフーリエ変換し、周波数ごとに振幅を表示したい。
wavファイルを読み込む
import wave import numpy as np #Wave_readオブジェクトとして得る wf = wave.open("440Hz.wav", "rb") #チャンネルの番号、1:モノラル、2:ステレオ channels_no = wf.getnchannels() #サンプルサイズ、2ならバイト? sample_width = wf.getsampwidth() #サンプリング周波数 sampling_frequency = wf.getframerate() #全体のフレーム数 frames = wf.getnframes() #bytesオブジェクトとして最大[引数]フレーム取得する。 buf = wf.readframes(wf.getnframes()) data = np.frombuffer(buf, dtype = "int16") #ステレオだとdataには[左, 右, 左, 右, ...]のように入っているので、スライスして右だけ取り出す。別に左でもいいけどなんとなく。 if(channels_no == 2): data = data[::2] print("Channel No:", channels_no) print("Sample Width:", sample_width) print("Sampling Frequency:", sampling_frequency) print("Frames:", frames) """Standart output Channel No: 1 Sample Width: 2 Sampling Frequency: 44100 Frames: 220500 """
周波数ごとに表示する
import os import time #コンソールクリアのラムダ式 cclear = lambda : os.system('cls' if os.name=='nt' else 'clear') #表示する行数 line = 50 #離散フーリエするデータの幅(1/24秒) delta = int(sampling_frequency / 24) fft_data = [] for i in range(int(len(data) / delta)): #deltaでスライスして高速離散フーリエ変換 fft_data.append(np.fft.fft(data[i * delta:(i + 1) * delta])) #周波数帯ごとに振幅を表示する。 for j in range(line): #まとめる周波数帯をスライスで取得 width = fft_data[i][j * int(delta / line):(j + 1) * int(delta / line)] #振幅の絶対値の平均をとる s = 0 for w in width: s += abs(w) y = s / len(width) #大きすぎるので小さくするため定数で割る length = int(y / 30000) #それでも大きい場合制限する if(length > 100): length = 100 #その周波数帯の先頭と振幅を視覚的に表示する print({0:05d}.format(int(j * delta / line * 24)) + "*" * length)) #0.2秒待機 time.sleep(0.2) #コンソールクリア cclear()
実行結果
440Hzが流れるwavファイルを読み込んだ様子
10kHzが流れるwavファイルを読み込んだ様子
2つ目のfor文の中のdeltaを100とかにすれば100区切りで表示できる。
参考
PythonでWAVファイルを読み込む - 音楽プログラミングの超入門(仮)
22.4. wave — Read and write WAV files — Python 3.6.3 documentation