CGプログラミング演習
10 より進んだ音の視覚化と最終課題の予備演習

1 より進んだ音の視覚化

  • 今回の講義では、より進んだ音の視覚化例を学びます。
  • これまでに較べると複雑なプログラムに接することになります。

2 サンプルプログラム

2.1 回転の動き

  • 前回講義で作成した棒グラフをアレンジし、回転する動作を加える
  • FFT解析した結果をもとに、周波数ごとに画面の中心の周囲を周回する軌道を描く
// OpenGL を使用する準備です
import processing.opengl.*;
import javax.media.opengl.GL; 

// Minim を使用する準備です
import ddf.minim.analysis.*;
import ddf.minim.*;

// Minim の最大音量を定めます
int BUFSIZE = 2048;

// Minim の変数を用意します。
Minim minim;

// オーディオ入力の変数を用意します
AudioInput in;

// FFTの変数を用意します
FFT fft;

// OpenGL の変数を用意します
GL gl;

// 回転角を保管する変数を用意します。
float[] rot = new float[BUFSIZE];
float[] rotSpeed = new float[BUFSIZE];

// プログラム開始時の事前準備です
void setup()
{
  // キャンバスサイズを定めるとともに、OpenGL の使用を宣言します
  size(600, 600, OPENGL);
  
  // Framerate を定めます
  frameRate(24);
  
  // カラーモードを HSB に指定します
  colorMode(HSB, 360, 100, 100, 100);
  
  // 描画内容をアンチエイリアシングします
  smooth();

  // Minim を生成します
  minim = new Minim(this);
  
  // minim からオーディオ入力源を取得します
  in = minim.getLineIn(Minim.STEREO, BUFSIZE);
  
  // FFT を生成します
  fft = new FFT(in.bufferSize(), in.sampleRate());

  // 回転角を保管する変数を用意します
  for (int i = 0; i < BUFSIZE; i++)
  {
    rot[i] = random(0, 360);
    rotSpeed[i] = 0;
  }

  // 背景を黒く塗りつぶします
  background(0);
}

// 描画内容を定めます
void draw()
{
  // 描画結果をフェードさせるように背景を塗りつぶします
  backgroundFade();
  
  // 描画内容を画面中央に移動します
  translate(width/2, height/2);
  
  // FFT を実行します
  fft.forward(in.mix);
  
  // FFTで解析した周波数幅を変数に保管します
  float specSize = fft.specSize();
  float getBand;
  
  // 周波数幅を for文で走査します
  for (int i = 0; i < specSize; i++)
  {
    // 周波数幅から、色相と x の位置を求めます。
    float h = map(i, 0, specSize, 0, 200);
    float x = map(i, 0, specSize, 0, width/1.5);

    // 現在見ている周波数の音量を変数に保存しておきます
    getBand = fft.getBand(i);

    // 音量を、0, 50 以内の数にマッピングします
    float l = map(getBand, 0, BUFSIZE/16, 0, 50);

    // 周波数ごとの音量をもとに、回転スピードを定めます
    // ★rotSpeed[i] = getBand;
    // ★rot[i] += rotSpeed[i];
    // ★pushMatrix();
    // ★rotate(radians(rot[i]));

    // 先ほど求めた色相を、線の色に設定します
    stroke(h, 50, 100, 50);
    
    // 四角形を描画します
    rect(x, 0, l/4, l);

    // ★popMatrix();
  }
}

// プログラム終了時の処理を定めます
void stop()
{
  minim.stop();
  super.stop();
}

// 背景の塗りつぶし方法を定めます
void backgroundFade()
{
  // 線を描きません
  noStroke();

  // 不透明度 16の黒を指定し
  fill(0, 16);

  // キャンバスサイズ一杯の四角形を描画します  
  rect(0, 0, width, height);
}

回転させるだけで、面白い効果が得られる。

2.2 同心円を描く

// OpenGL を使用する準備です
import processing.opengl.*;
import javax.media.opengl.GL; 

// Minim を使用する準備です
import ddf.minim.analysis.*;
import ddf.minim.*;

// Minim の最大音量を定めます
int BUFSIZE = 128;

// Minim の変数を用意します。
Minim minim;

// オーディオ入力の変数を用意します
AudioInput in;

// FFTの変数を用意します
FFT fft;

// OpenGL の変数を用意します
GL gl;

// プログラム開始時の事前準備です
void setup()
{
  // キャンバスサイズを定めるとともに、OpenGL の使用を宣言します
  size(600, 600, OPENGL);

  // Framerate を定めます
  frameRate(24);

  // カラーモードを HSB に指定します
  colorMode(HSB, 360, 100, 100, 100);
  
  // 線を描画しません
  noStroke();

  // OpenGL を有効にします
  gl=((PGraphicsOpenGL)g).gl;
  gl.setSwapInterval(1);

  // Minim を生成します
  minim = new Minim(this);
  
  // minim からオーディオ入力源を取得します
  in = minim.getLineIn(Minim.STEREO, BUFSIZE);
  
  // FFT を生成します
  fft = new FFT(in.bufferSize(), in.sampleRate());

  // 背景を黒く塗りつぶします
  background(0);
}

// 描画内容を定めます
void draw()
{
  // 背景を黒く塗りつぶします
  background(0);

  // OpenGL を用いて描画内容を加算混色します
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);

  // 左側の同心円を描くため、座標位置を、下記の通り変更します;
  // x: 画面中央からキャンバスの8分の1左に
  // y: 画面中央に
  translate(width/2-width/8, height/2);

  // FFT を実行します(左チャンネル)
  fft.forward(in.left);
  
  // 左側の同心円を描画します
  for (int i = 0; i < fft.specSize(); i++)
  {
    // 周波数を色相にマッピングします
    float h = map(i, 0, fft.specSize(), 0, 300);
    
    // 周波数を円の直径にマッピングします
    float r = map(i, 0, fft.specSize(), 0, width);
    
    // 音量を透明度にマッピングします
    float a = map(fft.getBand(i), 0, in.bufferSize()/2.0, 0, 255);
    
    // 塗りつぶし色を指定します
    fill(h, 100, 100, a);
    
    // 円を描画します
    ellipse(0, 0, r, r);
  }
  
  // 右側の同心円を描くため、座標位置を、下記の通り変更します;
  // x: キャンバスの4分の1右に
  // y: 画面中央に
  translate(width/4, 0);

  // FFT を実行します(右チャンネル)
  fft.forward(in.right);

  // 右側の同心円を描画します
  for (int i = 0; i < fft.specSize(); i++)
  {
    // 周波数を色相にマッピングします
    float h = map(i, 0, fft.specSize(), 0, 300);
    
    // 周波数を円の直径にマッピングします
    float r = map(i, 0, fft.specSize(), 0, width);
    
    // 音量を透明度にマッピングします
    float a = map(fft.getBand(i), 0, in.bufferSize()/2.0, 0, 255);
    
    // 塗りつぶし色を指定します
    fill(h, 100, 100, a);
    
    // 円を描画します
    ellipse(0, 0, r, r);
  }
}

// プログラム終了時の処理を定めます
void stop()
{
  minim.stop();
  super.stop();
}

2.3 バネの動き

  • 周波数のスペクトル幅の数のパーティクルを生成
  • 画面の中心から「ばね」で繋がっている
  • スペクトルのレベルに応じてランダムな方向に力がかかる
  • パーティクルの大きさも、レベルに応じて変更
  • HSBモードで加法混色
// OpenGL を使用する準備です
import processing.opengl.*;
import javax.media.opengl.GL; 

// Minim を使用する準備です
import ddf.minim.analysis.*;
import ddf.minim.*;

// Minim の最大音量を定めます
int BUFSIZE = 512;

// Minim の変数を用意します。
Minim minim;

// オーディオ入力の変数を用意します
AudioInput in;

// FFTの変数を用意します
FFT fft;

// OpenGL の変数を用意します
GL gl;

float[] posX = new float[BUFSIZE];
float[] posY = new float[BUFSIZE];
float[] speedX = new float[BUFSIZE];
float[] speedY = new float[BUFSIZE];
float[] angle = new float[BUFSIZE];

// バネの硬さを定めます
float stiffness = 0.4;

// バネの摩擦係数を定めます(減衰の程度)
float damping = 0.9;

// 円の重さを定めます
float mass = 12.0;

// プログラム開始時の事前準備です
void setup()
{
  // キャンバスサイズを定めるとともに、OpenGL の使用を宣言します
  size(600, 600, OPENGL);

  // Framerate を定めます
  frameRate(24);

  // カラーモードを HSB に指定します
  colorMode(HSB, 360, 100, 100, 100);
  
  // 線を描画しません
  noStroke();

  // OpenGL を有効にします
  gl=((PGraphicsOpenGL)g).gl;
  gl.setSwapInterval(1);

  // Minim を生成します
  minim = new Minim(this);
  
  // minim からオーディオ入力源を取得します
  in = minim.getLineIn(Minim.STEREO, BUFSIZE);
  
  // FFT を生成します
  fft = new FFT(in.bufferSize(), in.sampleRate());

  for (int i = 0; i < BUFSIZE; i++) {
    posX[i] = 0;
    posY[i] = 0;
    speedX[i] = 0;
    speedY[i] = 0;
    angle[i] = radians(random(0, 360));
  }

  // 背景を黒く塗りつぶします
  background(0);
}

// 描画内容を定めます
void draw()
{
  // 描画結果をフェードさせるように背景を塗りつぶします
  backgroundFade();
  
  // OpenGL を利用して加算混色します
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
  
  // 描画内容を画面中央に描きます
  translate(width/2, height/2);
  
  // FFT を実行します
  fft.forward(in.mix);
  
  // FFTで解析した周波数幅を変数に保管します
  float specSize = fft.specSize();
  float getBand;
  
  // 周波数ごとに円を描画します
  for (int i = 0; i < specSize; i++)
  {
    // 音量を取得しておきます
    getBand = fft.getBand(i);
  
    // 音量に基づいてバネにかかる力を定めます
    float addFroce = getBand* i * width/float(BUFSIZE)/2.0;
    // 外側に向かう方向(角度)をランダムにします
    float direction = radians(random(0, 360));
    // 力と角度に基づいて、円の中心位置を取得します
    float addX = cos(direction) * addFroce;
    float addY = sin(direction) * addFroce;

    
    float forceX = stiffness * -posX[i] + addX;
    float accelerationX = forceX / mass;
    speedX[i] = damping * (speedX[i] + accelerationX);
    float forceY = stiffness * -posY[i] + addY;
    float accelerationY = forceY / mass;
    speedY[i] = damping * (speedY[i] + accelerationY);
    posX[i] += speedX[i];
    posY[i] += speedY[i];

    fill(255, 20);
    float h = map(i, 0, specSize, 0, 360);
    float r = getBand * i / 4.0 + 30.0;
    fill(h, 100, 100, 10);
    ellipse(posX[i], posY[i], r, r);
  }
}

// プログラム終了時の処理を定めます
void stop()
{
  minim.stop();
  super.stop();
}

// 背景の塗りつぶし方法を定めます
void backgroundFade()
{
  // OpenGL を利用して減色混合とします
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
  gl.glBlendEquation(GL.GL_FUNC_ADD);

  // 線を描きません
  noStroke();
  
  // 不透明度 5 の黒を指定し、
  fill(0, 5);
  
  // キャンバスサイズ一杯の四角形を描画します  
  rect(0, 0, width, height);
}

3 最終課題に向けて

  • 最終課題での作業ですが、こちらであらかじめ用意したライブラリを使用してもらおうと思っています。
  • まずはライブラリの使用感を試してみましょう!

3.1 サンプルプログラムのダウンロード

  • 下記リンクよりサンプルプログラムをダウンロードしてください:
    kadai2_prototype.zip
  • プログラムを開いて実行すると、先ほどのばねを使ったサンプルプログラムと全く同じ描画結果が得られます
  • プログラムの内容を確認してみましょう。
// OpenGL を使用する準備です
import processing.opengl.*;
import javax.media.opengl.GL; 

// Minim を使用する準備です
import ddf.minim.analysis.*;
import ddf.minim.*;

// Minim の最大音量を定めます
int BUFSIZE = 512;

// Minim の変数を用意します。
Minim minim;

// オーディオ入力の変数を用意します
AudioInput in;

// FFTの変数を用意します
FFT fft;

// OpenGL の変数を用意します
GL gl;

// 最終課題(kadai2)ライブラリを使用するための変数を用意します
K2 k2;

// プログラム開始時の事前準備です
void setup()
{
  // キャンバスサイズを定めるとともに、OpenGL の使用を宣言します
  size(600, 600, OPENGL);

  // Framerate を定めます
  frameRate(24);

  // カラーモードを HSB に指定します
  colorMode(HSB, 360, 100, 100, 100);
  
  // 線を描画しません
  noStroke();

  // OpenGL を有効にします
  gl = ((PGraphicsOpenGL)g).gl;
  gl.setSwapInterval(1);

  // Minim を生成します
  minim = new Minim(this);
  
  // minim からオーディオ入力源を取得します
  in = minim.getLineIn(Minim.STEREO, BUFSIZE);
  
  // FFT を生成します
  fft = new FFT(in.bufferSize(), in.sampleRate());

  // 背景を黒く塗りつぶします
  background(0);

  // 最終課題ライブラリを使用可能にします。
  // この時、あらかじめ定めておいたMinim の最大音量(BUFSIZE)を引数として与えます
  k2 = new K2(BUFSIZE);
}

// 描画内容を定めます
void draw()
{
  // 最終課題ライブラリを利用して、描画結果をフェードさせるように背景を塗りつぶします
  // 引数として与えるのは、塗りつぶしたい幅と高さ(width, height)、塗りつぶしの際の透明度です
  k2.backgroundFade(width, height, 5);
  
  // OpenGL を利用して加算混色します
  gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
  
  // 描画内容を画面中央に描きます
  translate(width/2, height/2);
  
  // FFT を実行します
  fft.forward(in.mix);
  
  // FFTで解析した周波数幅を変数に保管します
  float specSize = fft.specSize();
  float getBand;
  
  // 周波数ごとに円を描画します
  for (int i = 0; i < specSize; i++)
  {
    // 音量を取得しておきます
    getBand = fft.getBand(i);

    // 最終課題ライブラリを使用して、次に描く円の位置を求めます
    // 円の位置(x, y)は、pos という変数に格納されます
    // 実際に x, y の値を取り出すには、pos.x, pos.y とします
    Position pos = k2.spring(width, height, i, specSize, getBand);
  
    // 塗りつぶしの色を定めます
    fill(255, 20);
    
    // 音量に応じて色相を定めます
    float h = map(i, 0, specSize, 0, 360);

    // 円の大きさを定めます
    float r = getBand * i / 4.0 + 30.0;
    
    // 塗りつぶしの色を定めます
    fill(h, 100, 100, 10);

    // 円を描画します
    // 円の描画位置は、さきほど最終課題ライブラリで算出した pos の中から取り出します
    ellipse(pos.x, pos.y, r, r);
  }
}

// プログラム終了時の処理を定めます
void stop()
{
  minim.stop();
  super.stop();
}

3.2 ライブラリの効能

  • さきほどのサンプルプログラムに較べ、複雑だった円の描画位置の決定部分が 1行にまとめられました。
  • ライブラリを使用するといっても、それほど複雑な方法をとるわけではありません。
  • 今回配布した最終課題ライブラリには、バネの動きと背景の塗りつぶし機能の 2つだけが用意されていましたが、木曜日に、さらにバージョンアップしたものを配布します。
  • ライブラリに含まれる機能を組み合わせながら、最終課題を制作してほしいと考えています。

4 演習

  • 上の 4つのサンプルプログラムをアレンジしてください。
  • 音の視覚化に少しでも慣れることが目的です。