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;
    }
  }

};

 

関連記事

fitbitの睡眠スコアを90弱で安定させる良い睡眠を続ける簡単な方法

m1 ipad pro 12.9 2021のusb-cハブはコレがベスト

Time Machine不要!Macを11.2.3にダウングレードして原神をm1 macbook airでプレイする

MH-Z19CとM5StickCで二酸化炭素濃度モニタリング

【神軽量HMD】Avegant Glyph 改造: 瓶詰堂さんのaltglyphを作った

PC、iPad、Android、switchもドックいらず!あまりに万能なusb-cハブが最強だった

コメント

コメントを返信する

メールアドレスが公開されることはありません。 が付いている欄は必須項目です