ここ最近は『ジェネラティブ・アート』を読んでいました。Processingを使って偶然性に頼ったプログラムによるアートをしてみよう、という内容です。Processingだと簡単にビジュアルを生成できて楽しいですね。ツールの選び方として「作業だけに没頭できるか?」の観点は大事だな、と思いました。
今回は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()