p5.jsでサウンドイコライザー 1/29

p5.jsでサウンドイコライザーを作る。

手順は以下の通り!

1.ファイル名をsoudEQとして一回保存する。

2.index.htmlのheadに下記のコードを追加する。

<script src="https://cdn.jsdelivr.net/npm/p5@1.11.11/lib/addons/p5.sound.min.js"></script>

3.好きなサウンドファイル(sample.mp3)をアップロードする。

今回はコレ!

でもこれだとサイズが大きすぎるので好きなところで縮めてみる。
それとチェック用にマリオの音源や周波数を下から上げていく音声も追加してみた。

さて、いよいよプログラム編

4.下記のスクリプトをスケッチに貼り付ける。

let sound;   // 再生する音
let fft;     // 音のデータを取り出す道具

function preload() {
  sound = loadSound('FIRST_NOTE.mp3');
}

function setup() {
  createCanvas(400, 200);

  // ★波形データを1024個に固定して取り出す
  //   1024個のうち、前半512を「左」、後半512を「右」として扱う
  fft = new p5.FFT(0.8, 1024);

  textSize(16);
}

function draw() {
  background(240);

  // まだ再生していない時は案内だけ出す
  if (!sound.isPlaying()) {
    fill(80);
    textAlign(CENTER, CENTER);
    text('クリックで再生 / 停止', 200, 100);
    return;
  }

  // 波形(音の振れ方)を配列でもらう(長さは1024)
  // 値はだいたい -1.0 ~ +1.0 の範囲
  let wave = fft.waveform();

  // 左右の「音量っぽい値」を作るための合計
  let leftSum = 0;
  let rightSum = 0;
  for (let i = 0; i < 512; i++) {
    leftSum += abs(wave[i]);
  }
  for (let i = 512; i < 1024; i++) {
    rightSum += abs(wave[i]);
  }
  let leftLevel = leftSum / 512;
  let rightLevel = rightSum / 512;

  // ★円の大きさ(半径)を作る
  let leftRadius = 10 + leftLevel * 250;
  let rightRadius = 10 + rightLevel * 250;

  // ★大きくなりすぎないように上限を決める
  if (leftRadius > 80) leftRadius = 80;
  if (rightRadius > 80) rightRadius = 80;

  // ★小さくなりすぎないように下限を決める
  if (leftRadius < 10) leftRadius = 10;
  if (rightRadius < 10) rightRadius = 10;


  noStroke();
  fill(100, 150, 255);
  ellipse(100, 100, leftRadius * 2, leftRadius * 2);

  // 右の円(中心は決め打ちで300,100)
  fill(255, 120, 120);
  ellipse(300, 100, rightRadius * 2, rightRadius * 2);

  // 数値を表示
  fill(50);
  textAlign(CENTER, CENTER);
  text('L', 100, 170);
  text('R', 300, 170);

  let leftValue = int(leftLevel * 100);
  let rightValue = int(rightLevel * 100);

  text(leftValue, 100, 190);
  text(rightValue, 300, 190);
}

function mousePressed() {
  
  // クリックで再生/停止できるようにする
  if (sound.isPlaying()) {
    sound.pause();
  } else {
    sound.loop(); // ループ再生(繰り返し)
  }
}

ここからアレンジを加える。

色々と試したところ、泡っぽく見せるのがかっこいいなと感じたので小さい方から大きい周波数になるほどcircleの大きさも大きく、今のままでは周波数が多すぎるので、3回に一回に調整して泡の数を少なくした。

let sound;   // 再生する音
let fft;     // 音のデータを取り出す道具

function preload() {
  sound = loadSound('FIRST_NOTE (mp3cut.net).mp3');
}

function setup() {
  createCanvas(512, 300);
  // ★波形データを1024個に固定して取り出す
  //   1024個のうち、前半512を「左」、後半512を「右」として扱う
  fft = new p5.FFT(0.8, 1024);
  textSize(16);
  twosecond = 0;
}

function draw() {
  background(24, 119, 149);

  // まだ再生していない時は案内だけ出す
  if (!sound.isPlaying()) {
    fill(255);
    textSize(16);
    textAlign(CENTER, CENTER);
    text('クリックで再生 / 停止', 256, 150);
    return;
  }

  // 波形(音の振れ方)を配列でもらう(長さは1024)
  // 値はだいたい -1.0 ~ +1.0 の範囲
  let wave = fft.analyze(1024);

  // 左右の「音量っぽい値」を作るための合計
  let Sum = [];

  for (let i = 0; i < 8; i++) 
  {
    Sum[i] = 0;
  }
  if (frameCount % 120 === 0) {
    twosecond += 1;
  }
  for (let i = 0; i < 1024; i++) 
  {
    // rect(i,200,1,-1*abs(wave[i]));
    if (abs(wave[i]) != 0 && abs(wave[i]) % 3 == 0)
    {
      fill(113, 191, 234);
      // if (abs(wave[i] % 50 == 0))
      //   {
      //     // fill(96/twosecond * 12, 191/twosecond * 12, 59/twosecond * 12);
      //     fill(255)
        // }
      // else if (abs(wave[i] % 50+random(50) == 0))
      //   {
      //     fill(160, 68, 40);
      //   }
      // ellipse(i + 5 * random(),200 -1* abs(wave[i]),10,10);
      ellipse(i,300 -1* abs(wave[i]),abs(wave[i])*abs(wave[i]/1000) ,abs(wave[i])*abs(wave[i])/1000);
      fill(255)
      ellipse(i,300 -1* abs(wave[i]),abs(wave[i])*abs(wave[i]/1200) ,abs(wave[i])*abs(wave[i])/1200);
      
    }
    if (i > 0 && i < 1024/8)
      {
        Sum[0] += abs(wave[i]);
      }
    else if(i < 1024/8 * 2)
      {
        Sum[1] += abs(wave[i]);
      }
    else if(i < 1024/8 * 3)
      {
        Sum[2] += abs(wave[i]);
      }
    else if(i < 1024/8 * 4)
      {
        Sum[3] += abs(wave[i]);
      }
    else if(i < 1024/8 * 5)
      {
        Sum[4] += abs(wave[i]);
      }
    else if(i < 1024/8 * 6)
      {
        Sum[5] += abs(wave[i]);
      }
    else if(i < 1024/8 *7)
      {
        Sum[6] += abs(wave[i]);
      }
    else
      {
        Sum[7] += abs(wave[i]);
      }
  }
  

  // let leftLevel = leftSum / 512;
  // let rightLevel = rightSum / 512;

  // // ★円の大きさ(半径)を作る
  // let leftRadius = 10 + leftLevel * 250;
  // let rightRadius = 10 + rightLevel * 250;

//   // ★大きくなりすぎないように上限を決める
//   if (leftRadius > 80) leftRadius = 80;
//   if (rightRadius > 80) rightRadius = 80;

//   // ★小さくなりすぎないように下限を決める
//   if (leftRadius < 10) leftRadius = 10;
//   if (rightRadius < 10) rightRadius = 10;


  noStroke();
  textSize(16);
  fill(231, 136, 106);

  // for (let i = 0; i < 8; i++) 
  //   {
  //     rect(400/8 * i,200-int(Sum[i]),400/8,int(Sum[i]));
  //   }
  // let leftValue = int(leftLevel * 100);
  // let rightValue = int(rightLevel * 100);
  
  textSize(40);
  fill(255, 155, 61);
  text("BLUE GIANT",120,280)
}

function mousePressed() {
  
  // クリックで再生/停止できるようにする
  if (sound.isPlaying()) {
    sound.pause();
  } else {
    sound.loop(); // ループ再生(繰り返し)
  }
}

炭酸水みたいになった。実行結果は下記のとおり

ここから歌詞を入れたバージョンを作ってみます。
正規表現(/(\d+)こんな感じ)を使って
[00:22.05]お別れしたのはもっと 前の事だったような
この元の歌詞から分、秒、歌詞の三つに分けます。

let sound;   // 再生する音
let fft;     // 音のデータを取り出す道具
let lrc;

function preload() {
  sound = loadSound('videoplayback.m4a');
  lrc = loadStrings('lyrics ray.txt');
}

function setup() {
  createCanvas(550, 300);
  // ★波形データを1024個に固定して取り出す
  //   1024個のうち、前半512を「左」、後半512を「右」として扱う
  fft = new p5.FFT(0.8, 1024);
  textSize(16);
  twosecond = 0;
  parseLRC();
  
}
let lyrics = [];
function parseLRC() {
  for (let line of lrc) {
    let match = line.match(/\[(\d+):(\d+\.\d+)\](.*)/)
    
    if (match) {
      let min = int(match[1]);
      print(match);
      let sec = float(match[2]);
      let time = min * 60 + sec;
      let text = match[3];

      lyrics.push({ time, text });
    }
  }
}

function draw() {
  background(24, 119, 149);

    noStroke();
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(25);
    textStyle(BOLD);

    let t = sound.currentTime();
    let current = "";

    for (let i = 0; i < lyrics.length; i++)
    {
      if (t >= lyrics[i].time) {
        current = lyrics[i].text;
      }
    }

    text(current, width/2, height*0.8);
}

こんな感じ!

let match = line.match(/\[(\d+):(\d+\.\d+)\](.*)/)

に注目して正規表現の話をします。
正規表現は言語によって書き方が少々変わるらしいです。
p5js, javaの場合はこう書きます。
まずスラッシュ(/)で正規表現の範囲を指定します。
()で囲まれている中をmatchのリストの中に入れていく仕組みです。
バックスラッシュ(\)は普通使われない文字を入れるときに使います!例えば[、]、d、.など。
この[.]はもともと
[00:22.05]お別れしたのはもっと 前の事だったような
にも入っている記号を表しており、ここにリストに代入してほしくない記号を書いておくと、リストにその記号が入らなくなります。
このd+ と .* についてです。
d+のdは数字を表しており、+は1文字以上の指定、.*はそれ以外の何か、を表しています。
もしこのdをアバウトに.+ただ一文字以上とってきてね!という命令にしてしまうと分しか扱うつもりがないのに間違った文字の情報が入ってきてしまうので、なるべく厳格に表します。
また、正規表現の仕組みとしてリストの0番目には必ず拾ってくる列のすべてを代入します。つまり、この例を入れた結果は
[“[00:22.05]お別れしたのはもっと 前の事だったような”, “00”, “22.05”, “お別れしたのはもっと 前の事だったような”]
と取ってくることができます。なんて便利なんでしょう!!!
これを踏まえてプログラムを改良すると

let sound;   // 再生する音
let fft;     // 音のデータを取り出す道具
let lrc;

function preload() {
  sound = loadSound('videoplayback.m4a');
  lrc = loadStrings('lyrics ray.txt');
}

function setup() {
  createCanvas(550, 300);
  // ★波形データを1024個に固定して取り出す
  //   1024個のうち、前半512を「左」、後半512を「右」として扱う
  fft = new p5.FFT(0.8, 1024);
  textSize(16);
  twosecond = 0;
  parseLRC();
  
}
let lyrics = [];
function parseLRC() {
  for (let line of lrc) {
    let match = line.match(/\[(\d+):(\d+\.\d+)\](.*)/)
    
    if (match) {
      let min = int(match[1]);
      print(match);
      let sec = float(match[2]);
      let time = min * 60 + sec;
      let text = match[3];

      lyrics.push({ time, text });
    }
  }
}

function draw() {
  background(24, 119, 149);

  // まだ再生していない時は案内だけ出す
  if (!sound.isPlaying()) {
    fill(255);
    textSize(16);
    textAlign(CENTER, CENTER);
    text('クリックで再生 / 停止', 256, 150);
    return;
  }
  
  
  let wave = fft.analyze(1024);
  let Sum = [];
  for (let i = 0; i < 8; i++) 
  {
    Sum[i] = 0;
  }
  if (frameCount % 120 === 0) {
    twosecond += 1;
  }
  for (let i = 0; i < 1024; i++) 
  {
    if (abs(wave[i]) != 0 && abs(wave[i]) % 3 == 0)
    {
      fill(113, 191, 234);
      ellipse(i,300 -1* abs(wave[i]),abs(wave[i])*abs(wave[i]/1000) ,abs(wave[i])*abs(wave[i])/1000);
      fill(255)
      ellipse(i,300 -1* abs(wave[i]),abs(wave[i])*abs(wave[i]/1200) ,abs(wave[i])*abs(wave[i])/1100);
    }
    if (i > 0 && i < 1024/8)
      {
        Sum[0] += abs(wave[i]);
      }
    else if(i < 1024/8 * 2)
      {
        Sum[1] += abs(wave[i]);
      }
    else if(i < 1024/8 * 3)
      {
        Sum[2] += abs(wave[i]);
      }
    else if(i < 1024/8 * 4)
      {
        Sum[3] += abs(wave[i]);
      }
    else if(i < 1024/8 * 5)
      {
        Sum[4] += abs(wave[i]);
      }
    else if(i < 1024/8 * 6)
      {
        Sum[5] += abs(wave[i]);
      }
    else if(i < 1024/8 *7)
      {
        Sum[6] += abs(wave[i]);
      }
    else
      {
        Sum[7] += abs(wave[i]);
      }
  }
    noStroke();
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(25);
    textStyle(BOLD);

    let t = sound.currentTime();
    let current = "";

    for (let i = 0; i < lyrics.length; i++)
    {
      if (t >= lyrics[i].time) {
        current = lyrics[i].text;
      }
    }

    text(current, width/2, height*0.8);
}

function mousePressed() {
  
  // クリックで再生/停止できるようにする
  if (sound.isPlaying()) {
    sound.pause();
  } else {
    sound.loop(); // ループ再生(繰り返し)
  }
}

となります!実行結果はこちら!

どやぁ~

コメントする