ここ最近は『ジェネラティブ・アート』を読んでいました。Processingを使って偶然性に頼ったプログラムによるアートをしてみよう、という内容です。Processingだと簡単にビジュアルを生成できて楽しいですね。ツールの選び方として「作業だけに没頭できるか?」の観点は大事だな、と思いました。
![](https://images-fe.ssl-images-amazon.com/images/I/41Eq81vSVPL._SL160_.jpg) | ビー・エヌ・エヌ新社 発売日 : 2014-11-21 |
今回はPythonで音楽ファイルを読み込んで再生時にアニメーションを表示する、スペクトルアナライザーを作ってみます。下の図みたいな動画でよく見かけるアレを作ります。『ジェネラティブ・アート』の前半くらいまでの内容と、オーディオを扱うためのライブラリー「Minim」の組み合わせで充分です。
ProcessingはJavaでコードを書くのが通常ですが、モードの切替えでPythonを使うこともできます。ただ、Pythonモードが実装されてから比較的に日が浅いからなのか、書籍もWebもPythonによるサンプルはあまり見かけません……。ライブラリーもサンプルはJavaだけが多いので、JavaのコードをPythonで読み替えていく作業が必要です(大したことではないけれど)。
コード
コード全体は下に載せてあるとおり。Minimは事前に追加されていることが前提です。音楽ファイルはProcessingのエディターにドロップしておく(プロジェクトファイルと同階層にフォルダーを作ってコピーされる)と、ファイル名だけを記述すれば良くなってラクです。
add_library('minim')
def setup() :
# 初期化
size(640, 480, P2D)
background(0)
smooth()
# 音楽取込み
global _minim
global _player
_minim = Minim(this)
_player = _minim.loadFile('test.mp3')
# 描画用変数
global _width_pad # 幅/y方向のウィンドウパディング
global _height_pad # 高/x方向のウィンドウパディング
_width_pad = 20
_height_pad = 80
global _width_div # 幅/y方向の分割数
global _height_div # 高/x方向の分割数
_width_div = 30
_height_div = 20
global _cell_width # セル幅
global _cell_height # セル高
_cell_width = (width - _width_pad * 2) // _width_div
_cell_height = (height - _height_pad * 2) // _height_div
# 周波数解析
# 10[Hz]〜サンプリングレートの1/2までをカウント
global _fft
global _log_band # _width_divに応じた対数の帯域
global _band_base # 10[Hz]
_fft = FFT(_player.bufferSize(), _player.sampleRate())
_band_base = 10.0
_log_band = pow((_player.sampleRate() / 2.0) / _band_base, 1.0 / _width_div)
def draw() :
background(0)
stroke(255)
# セットアップ
_fft.forward(_player.mix)
translate(_width_pad, height - _height_pad)
# 描画
for x in range(_width_div) :
# 列ごとの周波数帯域を計算して平均値を得る
band_start = _band_base * pow(_log_band, x)
band_end = band_start * _log_band
spec_ave = _fft.calcAvg(band_start, band_end)
# 対数[dB]に変換する
signal = -100
if spec_ave > 0.0 :
signal = 20.0 * log(spec_ave/255) / log(10)
# 高/x方向の分割数へマッピングする
pile_num = map(signal, -100, 0, 0, _height_div)
PileUpRect(_cell_width, _cell_height, 3, int(pile_num))
translate(_cell_width, 0)
def PileUpRect(width, height, padding, quantity) :
pushMatrix()
# quantityの数だけセルを積む
for q in range(quantity) :
rect(padding, -padding, width - padding, padding-height)
translate(0, -height)
popMatrix()
def keyPressed() :
# キーで再生オン/オフ
if _player.isPlaying() == True :
_player.pause()
else :
_player.play()