Particle Beatsみたいな動画を自分でも作りたいと思って、Processing + minimライブラリで実装してみました。
任意の画像を音楽に合わせてポンポン出すだけです。
ソースコードは以下。
import ddf.minim.analysis.*;
import ddf.minim.*;
int max_logo_index = 300;
int max_particle_index = 500;
int S_a1;
int current_logo_index;
int current_particle_index;
Minim minim;
AudioPlayer player;
FFT fft;
Logo[] logos = new Logo[max_logo_index];
Particle[] particles = new Particle[max_particle_index];
color c_logo = color(220, 220, 255, 100);
void setup()
{
size(1920, 1080);
stroke(0,0,255);
frameRate(30);
minim = new Minim(this);
S_a1 = 30;
// music
player = minim.loadFile("music.mp3", 512);
player.loop();
// Make FFT Object. bufferSize(): 1024, sampleRate: 44100Hz.
fft = new FFT(player.bufferSize(), player.sampleRate());
println("sampling reate is " +player.sampleRate());
println("spec size is " +fft.specSize());
println("bandwidth is: " +fft.getBandWidth());
// construct Logo instances
PImage img = loadImage("Laughing_man_logo.png");
for (int i=0; i<max_logo_index; i++) {
logos[i] = new Logo(img, c_logo);
}
// construct Particle instances
img = loadImage("particle.png");
for (int i=0; i<max_particle_index; i++) {
particles[i] = new Particle(img, c_logo);
}
current_logo_index = 0;
current_particle_index = 0;
imageMode(CENTER);
ellipseMode(CENTER);
}
void draw()
{
background(0, 0.9);
fft.forward(player.mix);
// logo appear
if (fft.getBand(1) > S_a1) {
logos[current_logo_index].activate();
current_logo_index += 1;
if (current_logo_index >= max_logo_index) {
current_logo_index = 0;
}
}
// draw logos
for (int i=0; i<logos.length; i++) {
logos[i].move();
}
// if collision between logos occurs, generate particle
for (int i=0; i<logos.length; i++) {
if (!logos[i].active) continue;
float x = logos[i].x;
float y = logos[i].y;
float size = logos[i].size;
float min_x = x - size / 2;
float max_x = x + size / 2;
float min_y = y - size / 2;
float max_y = y + size / 2;
for (int j=0; j<logos.length; j++) {
if (i == j) continue;
if (!logos[j].active) continue;
float obj_x = logos[j].x;
float obj_y = logos[j].y;
// if collision, generate particle
if (obj_x >= min_x && obj_x <= max_x) {
if (obj_y >= min_y && obj_y <= max_y) {
int particle_num = int(random(0, 100));
int max = current_particle_index + particle_num;
if (max > max_particle_index) {
current_particle_index = 0;
max = particle_num;
}
for (int k=current_particle_index; k<max; ++k) {
particles[k].activate(logos[i].x, logos[i].y);
}
}
}
}
}
// draw particles
for (int i=0; i<particles.length; i++) {
particles[i].move();
}
// for output movie
// saveFrame("frames/######.tif");
}
void stop()
{
player.close();
minim.stop();
super.stop();
}
class Logo
{
PImage img;
// status
boolean active;
float x, y; // position
float speed;
float speed_d; // spped decay
// draw arguments
color ripple_color;
float size;
float ellipse_size;
float ellipse_alpha;
float energy;
// rotation arguments
boolean right_handed_rotation;
float r; // distance from (x, y)
float theta; // angle of rotation
// lifetime arguments
int elapsed_time;
int life_span = 20;
/////////////////////////////////////////////////////////////////////////////
// constructor
/////////////////////////////////////////////////////////////////////////////
Logo(PImage _logo_img, color _ripple_color)
{
img = _logo_img;
active = false;
x = random(0, displayWidth);
y = random(0, displayHeight);
speed = 0;
speed_d = 0;
ripple_color = _ripple_color;
size = 0;
ellipse_size = 0;
ellipse_alpha = 0;
energy = 0;
r = random(10,200);
theta = random(0, 2*PI);
if (random(0, 1) >= 0.5) {
right_handed_rotation = true;
} else {
right_handed_rotation = false;
}
}
/////////////////////////////////////////////////////////////////////////////
// initialize parameter before appearance
/////////////////////////////////////////////////////////////////////////////
void activate()
{
if (active) return;
active = true;
speed = random(1, 1.5);
speed_d = 0.95;
size = random(0, 200);
if (size >= 180) {
size = random(500, 1000);
}
ellipse_size = size;
ellipse_alpha = 200;
energy = random(0.01, 0.1);
elapsed_time = 0;
}
/////////////////////////////////////////////////////////////////////////////
// update position and speed
/////////////////////////////////////////////////////////////////////////////
void move()
{
if (!active) size = 0;
// update position
if (right_handed_rotation) {
image(img, x + r*cos(2*PI - theta), y - r*sin(2*PI - theta), size, size);
} else {
image(img, x + r*cos(theta), y - r*sin(theta), size, size);
}
// draw
draw_logo();
// update speed
speed *= speed_d;
theta += PI / 100;
elapsed_time += 1;
// first increase size, then shrink
if (elapsed_time <= life_span / 10) {
size *= 1 + 4 * energy;
} else {
size *= 1 - energy;
}
if (elapsed_time >= life_span) {
active = false;
}
}
/////////////////////////////////////////////////////////////////////////////
// draw
/////////////////////////////////////////////////////////////////////////////
void draw_logo()
{
pushStyle();
color c_ripple = color(red(ripple_color), blue(ripple_color),
green(ripple_color), ellipse_alpha);
// draw a ripple
if (active) {
noFill();
strokeWeight(10);
stroke(c_ripple);
if (right_handed_rotation) {
ellipse(x + r*cos(2*PI - theta), y - r*sin(2*PI - theta),
ellipse_size * elapsed_time * 0.2,
ellipse_size * elapsed_time * 0.2);
} else {
ellipse(x + r*cos(theta), y - r*sin(theta),
ellipse_size * elapsed_time * 0.2,
ellipse_size * elapsed_time * 0.2);
}
}
ellipse_alpha -= 200 / life_span;
popStyle();
}
};
class Particle
{
PImage img;
color tint_color;
// status
boolean active;
float x, y;
float speed_x;
float speed_y;
float speed_d; // spped decay
float size;
float elapsed_time;
boolean right_handed_rotation;
int life_span = 50;
/////////////////////////////////////////////////////////////////////////////
// constructor
/////////////////////////////////////////////////////////////////////////////
Particle(PImage _logo_img, color _tint_color)
{
img = _logo_img;
tint_color = _tint_color;
active = false;
}
/////////////////////////////////////////////////////////////////////////////
// initialize parameter before appearance
/////////////////////////////////////////////////////////////////////////////
void activate(float x_start, float y_start)
{
if (active) return;
x = x_start;
y = y_start;
speed_x = random(0, 10);
speed_y = random(0, 10);
if (random(0, 1) >= 0.5) {
speed_x *= -1;
}
if (random(0, 1) >= 0.5) {
speed_y *= -1;
}
speed_d = 0.98;
active = true;
size = random(0, 20);
elapsed_time = 0;
}
/////////////////////////////////////////////////////////////////////////////
// update position and speed
/////////////////////////////////////////////////////////////////////////////
void move()
{
if (!active) size = 0;
// update position
x += speed_x;
y += speed_y;
// draw
pushStyle();
tint(tint_color);
image(img, x, y, size, size);
popStyle();
// update speed
speed_x *= speed_d;
speed_y *= speed_d;
elapsed_time += 1;
if (elapsed_time >= life_span) {
active = false;
}
}
};
コメント