Processing で音に反応して動くアニメーションを作る(後編)
openFrameworks 等と比較して描画の遅い Processing を使って、音に反応して動くアニメーションを作る方法の第 2段です。
前回は音源を加工することで 24fps 程度のアニメーションを作ることが出来ました。
今回はさらに描画に時間がかかる絵によるアニメーションを実現させます!
我流で思いついた方法であって、「これが最適解だ!」というわけではありません。他にいい方法がありましたらお教えいただけると嬉しいです。
この記事は全文無料でお読みいただけます。もしお気に召しましたら投げ銭お願いしますね。😉✨
そもそも再生しながら描画することもないじゃろう
描画に時間のかかる Processing でリアルタイムに音源に反応させるのはそもそも無理のある話で、ここで考えているのもバッチ処理でアニメーションを作成する方法です。
だったら音源を再生しながら描画をする必要も無いですよね?
前回音源を再生していたのは音量などのデータを取得したいからであって、音を流したいわけではありません。
音源の再生と同時には描画せず、再生しながらデータだけを取得して、後でそのデータを元に描画するということもできるのでは?
🙅♀️ 音源 → [ 再生、 描画 ] → 画像
🙆♀️ 音源 → [ 再生、データ取得 ] → データ → [ 描画 ] → 画像
データ所得だけならほとんど時間はかからず、元音源にスロー加工などする必要もないはず。
さらにこれなら描画にどれだけ時間をかけてもいい!
音量データを csv ファイルに
ここでは音源の音量データを一曲まるまる一行の csv データにしてファイルに書き出すことにします。
/**
* Processing で音反応系の動画を作る(後編)
* 音源の音量データを csv に書き出し
*
* @author @deconbatch
* @version 0.1
* @license GPL Version 3 http://www.gnu.org/licenses/
* Processing 3.5.3
* 2020.10.17
*/
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.IOException;
import processing.sound.*;
int smplRate = 48000;
int frmRate = 24;
Sound snd;
SoundFile sfl;
Amplitude amp;
ArrayList<Float> ampValues = new ArrayList<Float>();
void setup() {
size(1, 1);
frameRate(frmRate);
snd = new Sound(this);
snd.sampleRate(smplRate);
sfl = new SoundFile(this, "your_sound_file.wav");
sfl.play();
amp = new Amplitude(this);
amp.input(sfl);
}
void draw() {
ampValues.add(amp.analyze());
if (!sfl.isPlaying()) {
try {
// 出力先を指定
FileWriter fw = new FileWriter("./data/your_sound_amp.csv", false);
PrintWriter pw = new PrintWriter(new BufferedWriter(fw));
// csv 形式で書き出し
for (float av : ampValues) {
pw.print(av);
pw.print(",");
}
pw.println();
pw.close();
} catch (IOException ex) {
ex.printStackTrace();
}
exit();
}
}
入力の音源は ./data/your_sound_file.wav、出力の csv データは ./data/your_sound_amp.csv です。
音源のサンプリングレートに合わせて(この場合 48kHz)
int smplRate = 48000;
フレームレート 24fps のアニメーション用にデータを取得しています。
int frmRate = 24;
csv ファイルのデータで描画
こちらは csv ファイルを読み込んで、そのデータを元に描画するサンプルコードです。
/**
* Processing で音反応系の動画を作る(後編)
* csv データを音量に見立てて描画
*
* @author @deconbatch
* @version 0.1
* @license GPL Version 3 http://www.gnu.org/licenses/
* Processing 3.5.3
* 2020.10.17
*/
import java.io.FileReader;
import java.io.BufferedReader;
import java.util.StringTokenizer;
import java.io.IOException;
Csv csv;
/**
* Csv
* csv ファイルを読み、その値を保持
* csv ファイルの全データは 0.0 から 1.0 の範囲内であることが前提
*/
class Csv {
private ArrayList<Float> csvValues = new ArrayList<Float>();
private float baseAv, multAv; // データ正規化のための値
public int length; // csv データの個数
Csv() {
// csv データ読み込み
try {
FileReader fr = new FileReader("./data/your_sound_amp.csv");
BufferedReader br = new BufferedReader(fr);
String ln;
while ((ln = br.readLine()) != null) {
StringTokenizer tk = new StringTokenizer(ln, ",");
while (tk.hasMoreTokens()) {
csvValues.add(Float.parseFloat(tk.nextToken()));
}
}
br.close();
} catch (IOException ex) {
ex.printStackTrace();
exit();
}
length = csvValues.size();
normalize();
}
/**
* normalize : データ全体を 0.0 から 1.0 に正規化するための値を算出
*/
void normalize() {
float minAv = 1.0;
float maxAv = 0.0;
for (float v : csvValues) {
minAv = (minAv < v) ? minAv : v;
maxAv = (maxAv > v) ? maxAv : v;
}
baseAv = minAv;
multAv = (maxAv - minAv == 0.0) ? 1.0 : 1.0 / (maxAv - minAv);
}
/**
* getValue : 当該フレームの値を返す
* @param f : 対応する値を得るフレーム
* @return : f フレーム目の正規化された音量値を返す
*/
float getValue(int f) {
float v = csvValues.get(f);
return (v - baseAv) * multAv;
}
/**
* getAverage : num フレーム分の平均値を返す
* @param f : 対応する値を得るフレーム
* @param num : num フレーム分の値を得る
* @return : f フレーム から num 個の値の正規化された平均値を返す
*/
float getAverage(int f, int num) {
float sum = 0.0;
for (int i = f; i < f + num; i++) {
sum += getValue(constrain(i, 0, csvValues.size() - 1));
}
return sum / num;
}
}
void setup() {
size(720, 720);
colorMode(HSB, 360, 100, 100, 100);
csv = new Csv();
}
void draw() {
for (int frameCnt = 0; frameCnt < csv.length; frameCnt++) {
drawSomething(frameCnt);
saveFrame("frames/" + String.format("%04d", frameCnt) + ".png");
}
exit();
}
void drawSomething(int f) {
float s = csv.getValue(f) * width * 0.5;
background(0.0, 0.0, 90.0, 100);
ellipse(width * 0.5, height * 0.5, s, s);
}
drawSomething() に描画のコードを書く仕組みで、この例では音量で円の大きさを変えるコードとなっています。
使わせていただいた音源はこちらです。
Pharmacy Party by Steve Combs is licensed under a Attribution 4.0 International (CC BY 4.0)
時間のかかるネタで描画
さて、準備が出来ました! いよいよ本題、描画にめちゃめちゃ時間がかかるもので試してみましょう。
私のコードとマシンだとこの画像一枚描画するのに 3秒程度かかります。0.3fps 換算ですね。 これを音源データに合わせて 24fps でアニメーションさせたのがこちらです。
うん!バッチリですね!
描画の自由度が上がる
このコードでは一曲まるごとの音量データが配列(ArrayList)に入っているので、描画の自由度が上がります。
例えば、最初の円の大きさが変わる例で 5フレーム分の音量の平均値を元に円の大きさを変えるようにするとこうなります。
float s = csv.getAverage(f, 5) * width * 0.5;
動きがちょっとおとなしく、滑らかになりました。
過去と未来の値を合わせて一度に描画することも出来ます。
最も太い線(赤)がフレームの音量、次に太い線(緑)がその先 5フレーム分の平均値、次(青)が 10フレーム分の平均値です。
音量を何段階かに区切って離散的な変化をさせても面白いですね。
さあ道具は揃った!
前回と今回の方法を使って、先日 Otodémie さんとコラボさせていただきました。
このコラボでまた別の課題が見えてきました。
・どんな絵なら面白いのか?
・どう動かしたら音によく追随しているように感じられるか?
・何より曲の主題を表現するにはどうすればいいのか?
音に反応するアニメーション作りの道具は手に入れたものの、これらの問題の答えとはなりません。
センス、経験、考察、試行錯誤…
音に反応して動くアニメーション作りの道はまだまだ続きそうです。💪🙂
この先には何もありません。この記事がお気に召しましたら投げ銭お願いします。😉✨
ここから先は
¥ 100
この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕