๐ŸŽ“15์ฃผ์ฐจ: ์˜ค๋””์˜ค ๋ฐ˜์‘ (Audio-Reactive)

BamgasiJMยท2025๋…„ 11์›” 6์ผ

p5.js Art

๋ชฉ๋ก ๋ณด๊ธฐ
24/65
post-thumbnail

๋งˆ์ดํฌ/์‚ฌ์šด๋“œ ์ž…๋ ฅ โ†’ FFT/Amplitude ๋ถ„์„ โ†’ ํ˜•ํƒœ ๋ณ€ํ™”
ํŒŒํ‹ฐํด ์ƒ‰/์†๋„/ํฌ๊ธฐ/์ง„๋™์ด ๋ชจ๋‘ ์˜ค๋””์˜ค์— ๋ฐ˜์‘ํ•จ

โœ… ์˜ˆ์ œ 1 : Reactive Particle

// p5.sound ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•„์š”

let mic;
let fft;
let particles = [];
let audioStarted = false;

function setup() {
  createCanvas(800, 500);
  noStroke();
}

function draw() {
  background(12, 12, 12);

  // ์˜ค๋””์˜ค ์‹œ์ž‘ ์ „ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€
  if (!audioStarted) {
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(24);
    text("ํด๋ฆญํ•ด์„œ ์‹œ์ž‘", width / 2, height / 2);
    return;
  }

  // โœ… ์˜ค๋””์˜ค ๋ถ„์„
  let spectrum = fft.analyze();

  let bass = fft.getEnergy("bass") * 2; // ์ €์Œ 20โ€“140Hz
  let mid = fft.getEnergy("mid") * 2; // ์ค‘์Œ 400โ€“2600Hz
  let treble = fft.getEnergy("treble") * 2; // ๊ณ ์Œ 6000Hz~

  // โœ… ๊ฐ€์šด๋ฐ ์› โ€” ์ „์ฒด ์—๋„ˆ์ง€ ๋ฐ˜์‘(ํ™•์‹คํ•˜๊ฒŒ ๋ณด์ด๋„๋ก ๊ฐ•ํ™”)
  let level = fft.getEnergy("lowMid");
  let r = map(level, 0, 255, 30, 160);

  fill(255, 255, 255, 255);
  circle(width / 2, height / 2, r * 2);

  // โœ… ํŒŒํ‹ฐํด ์—…๋ฐ์ดํŠธ ๋ฐ ๋ Œ๋”
  particles.forEach((p) => {
    p.update(bass, mid, treble);
    p.show();
  });
}

// ํด๋ฆญ ์‹œ ์˜ค๋””์˜ค ์‹œ์ž‘
function mousePressed() {
  if (!audioStarted) {
    // โœ… ๋งˆ์ดํฌ ์ž…๋ ฅ ์‹œ์ž‘
    mic = new p5.AudioIn();
    mic.start();

    // โœ… FFT ์ดˆ๊ธฐํ™”
    fft = new p5.FFT(0.9, 256);
    fft.setInput(mic);

    // โœ… ํŒŒํ‹ฐํด ์ƒ์„ฑ
    for (let i = 0; i < 200; i++) {
      particles.push(new Particle());
    }

    audioStarted = true;
  }
}

// ---------------------------------------------
// โœ… Particle Class
// ์˜ค๋””์˜ค ์ฃผํŒŒ์ˆ˜ ๊ฐ’์— ๋”ฐ๋ผ ์†๋„ยท์ƒ‰ยทํฌ๊ธฐยทjitter ๋ชจ๋‘ ๋ฐ˜์‘
// ---------------------------------------------

class Particle {
  constructor() {
    this.pos = createVector(random(width), random(height));
    this.vel = p5.Vector.random2D().mult(random(0.5, 2));
    this.size = random(3, 6);

    // ์ƒ‰์ƒ ์ดˆ๊ธฐ๊ฐ’
    this.col = color(180, 150, 255, 200);
  }

  update(bass, mid, treble) {
    // โœ… ์˜ค๋””์˜ค ๋ฐ˜์‘ ๊ฐ•๋„ ์กฐ์ ˆ(ํ™•์‹คํ•˜๊ฒŒ ๋ณด์ด๋„๋ก ๊ณผ์žฅ)
    let bassAmp = map(bass, 0, 255, 0, 8); // ํฌ๊ณ  ๊ฐ•๋ ฌํ•œ ๋ฐ˜์‘
    let midAmp = map(mid, 0, 255, 0, 4); // ์ด๋™ ์†๋„
    let trebleAmp = map(treble, 0, 255, 0, 3); // ์ƒ‰ ๋ฐ˜์ง์ž„

    // โœ… ์†๋„ ์ฆ๊ฐ€ โ€” mid์— ๊ฐ•ํ•˜๊ฒŒ ๋ฐ˜์‘
    this.pos.add(this.vel.copy().mult(0.4 + midAmp * 1.3));

    // โœ… jitter โ€” ๊ณ ์Œ treble ๊ธฐ๋ฐ˜ ์ž‘์€ ์ง„๋™
    this.pos.add(p5.Vector.random2D().mult(trebleAmp * 3.5));

    // โœ… ํ™”๋ฉด ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€๋ฉด wrap-around
    if (this.pos.x > width) this.pos.x = 0;
    if (this.pos.x < 0) this.pos.x = width;
    if (this.pos.y > height) this.pos.y = 0;
    if (this.pos.y < 0) this.pos.y = height;

    // โœ… ์ƒ‰์ƒ ๋ฐ˜์‘ โ€” bass/mid/treble ๋ชจ๋‘ ๋ฐ˜์˜
    this.col = color(
      map(bass, 0, 255, 0, 255), // ์ €์Œ โ†’ ๋นจ๊ฐ• ์ตœ๋Œ€์น˜๊นŒ์ง€
      map(mid, 0, 255, 50, 255), // ์ค‘์Œ โ†’ ์ดˆ๋ก ๊ฐ•ํ•˜๊ฒŒ ๋ฐ˜์˜
      map(treble, 0, 255, 100, 255), // ๊ณ ์Œ โ†’ ํŒŒ๋ž€ ๋ฐ˜์ง์ž„
      240
    );

    // โœ… ํฌ๊ธฐ โ€” bass์— ๋ฐ˜์‘
    this.currentSize = this.size + bassAmp * 2.2;
  }

  show() {
    fill(this.col);
    circle(this.pos.x, this.pos.y, this.currentSize);
  }
}

โœ… ๊ตฌ์กฐ ์„ค๋ช…
1) ๋งˆ์ดํฌ ์ž…๋ ฅ

mic = new p5.AudioIn();
mic.start();

2) FFT ๋ถ„์„

fft = new p5.FFT(0.8, 128);
fft.setInput(mic);
  • smoothing: 0.8
  • bins: 128 (์ ๋‹นํžˆ ๊ฐ€๋ฒผ์šด ํ•ด์ƒ๋„)

3) ์ฃผํŒŒ์ˆ˜ ๋Œ€์—ญ๊ฐ’ ์ถ”์ถœ

fft.getEnergy("bass");
fft.getEnergy("mid");
fft.getEnergy("treble");

4) ๊ฐ ์ฃผํŒŒ์ˆ˜ ๋Œ€์—ญ โ†’ ํ˜•ํƒœ ๋ณ€ํ™” ๋งคํ•‘

  • bass(์ €์Œ) โ†’ ํฌ๊ธฐ / ํŒฝ์ฐฝ / ์ง„๋™
  • mid(์ค‘์Œ) โ†’ ํŒŒํ‹ฐํด ์ด๋™
  • treble(๊ณ ์Œ) โ†’ jitter / sparkle ํšจ๊ณผ

5) ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ

  • ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์˜ค๋””์˜ค์˜ ์—๋„ˆ์ง€์— ๋”ฐ๋ผ ์†๋„, ๋ฐฉํ–ฅ, ํฌ๊ธฐ ๋ณ€๋™

โœ… ํ™•์žฅ ์•„์ด๋””์–ด
1) ์ŠคํŽ™ํŠธ๋Ÿผ ๋ฐ”(Visualizer) ์Šคํƒ€์ผ

  • music player์ฒ˜๋Ÿผ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๊ฐ€ ํ”๋“ค๋ฆผ

2) Circles / Lines / Mesh vibrating

  • ์ €์—ญ/์ค‘์—ญ/๊ณ ์—ญ์„ ํ˜•ํƒœ deformation์œผ๋กœ ์—ฐ๊ฒฐ

3) ์‰์ด๋”(Shaders)์™€ ๊ฒฐํ•ฉํ•œ Audio-Reactive

  • WebGL + FFT โ†’ ๊ฐ•๋ ฅํ•œ VJ ์Šคํƒ€์ผ ๊ตฌํ˜„

4) ์˜ค๋””์˜ค ํŒŒ์ผ ์—…๋กœ๋“œ (.mp3)

5) 3D ์˜ค๋””์˜ค ๋ฐ˜์‘ Visual (Three.js)

  • Box height = FFT
  • Point cloud vibration

โœ… ์˜ˆ์ œ 2 : Reactive Spiral Spectrum

let mic, fft;
let audioStarted = false;

function setup() {
  createCanvas(600, 600);
  angleMode(DEGREES);
}

function draw() {
  background(5);
  translate(width / 2, height / 2);

  // ์˜ค๋””์˜ค ์‹œ์ž‘ ์ „ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€
  if (!audioStarted) {
    fill(255);
    textAlign(CENTER, CENTER);
    textSize(24);
    text("ํด๋ฆญํ•ด์„œ ์‹œ์ž‘", 0, 0);
    return;
  }

  let spectrum = fft.analyze();
  let rotateSpeed = map(fft.getEnergy("bass"), 0, 255, 0, 9);

  rotate(frameCount * -0.5 + rotateSpeed);

  noStroke();

  for (let i = 0; i < spectrum.length; i++) {
    let r = map(i, 0, spectrum.length, 40, 250);
    let angle = i * 6;

    let amp = spectrum[i];

    let x = r * cos(angle);
    let y = r * sin(angle);

    fill(
      map(amp, 0, 255, 100, 255),
      map(i, 0, spectrum.length, 50, 200),
      255,
      200
    );
    circle(x, y, map(amp, 0, 255, 2, 72));
  }
}

// ํด๋ฆญ ์‹œ ์˜ค๋””์˜ค ์‹œ์ž‘
function mousePressed() {
  if (!audioStarted) {
    mic = new p5.AudioIn();
    mic.start();

    fft = new p5.FFT(0.8, 512);
    fft.setInput(mic);

    audioStarted = true;
  }
}

โœ… ์˜ˆ์ œ 3 : Reactive Undulating Circle

let mic, fft;
let particles = [];
let audioStarted = false;

function setup() {
  createCanvas(600, 600);
  angleMode(DEGREES);
  colorMode(RGB, 255);

  mic = new p5.AudioIn();
  fft = new p5.FFT(0.8, 64);
  fft.setInput(mic);

  for (let i = 0; i < 160; i++) {
    particles.push(new Particle());
  }

  noStroke();
}

function draw() {
  background(15, 15, 20, 10);

  if (!audioStarted) {
    displayStartMessage();
    return;
  }

  fft.analyze();
  let bass = fft.getEnergy("bass");
  let mid = fft.getEnergy("mid");
  let treble = fft.getEnergy("treble");

  renderUndulatingCircle(bass, mid, treble);
  updateParticles(bass, mid, treble);
}

function displayStartMessage() {
  fill(220);
  textSize(24);
  textAlign(CENTER, CENTER);
  noStroke();
  text("ํด๋ฆญํ•ด์„œ ์‹œ์ž‘", width / 2, height / 2);
}

function mousePressed() {
  if (getAudioContext().state !== "running") {
    getAudioContext()
      .resume()
      .then(() => {
        mic.start();
        audioStarted = true;
      })
      .catch((err) => {
        console.error("Error starting audio context:", err);
      });
  } else if (!audioStarted) {
    mic.start();
    audioStarted = true;
  }
}

function renderUndulatingCircle(bass, mid, treble) {
  push();
  translate(width / 2, height / 2);

  // ๋…ธ์ด์ฆˆ ๊ธฐ๋ฐ˜ ์–ธ๋“ˆ๋ ˆ์ดํŒ… ์„œํด
  // ์ˆ˜์น˜๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ๋ณ€ํ™”๊ฐ€ ๋” ์ปค์ง
  let baseRadius = 180;
  let noiseScale = map(mid, 0, 255, 0.01, 0.5);  // ์ค‘์Œ์— ๋”ฐ๋ผ ๋…ธ์ด์ฆˆ ๊ฐ•๋„ ๋ณ€ํ™”
  let amplitude = map(bass, 0, 255, 30, 200);    // ์ €์Œ์— ๋”ฐ๋ผ ์ง„ํญ ๋ณ€ํ™”

  // ์ƒ‰์ƒ - ๊ณ ์Œ๊ณผ ์ค‘์Œ์— ๋”ฐ๋ผ ๋” ํ™”๋ คํ•˜๊ฒŒ ๋ณ€ํ™”
  let hue = (frameCount * 0.5 + treble * 0.3) % 360;
  let r = map(sin(hue), -1, 1, 80, 255);
  let g = map(sin(hue + 120), -1, 1, 100, 255);
  let b = map(sin(hue + 240), -1, 1, 150, 255);

  // ์ค‘์Œ์— ๋”ฐ๋ผ ์ฑ„๋„ ๊ฐ•๋„ ์กฐ์ ˆ
  let saturation = map(mid, 0, 255, 0.3, 1);
  r = lerp(150, r, saturation);
  g = lerp(150, g, saturation);
  b = lerp(150, b, saturation);

  fill(r, g, b, 200);

  beginShape();
  for (let angle = 0; angle < 360; angle += 2) {
    let xoff = cos(angle) * noiseScale;
    let yoff = sin(angle) * noiseScale;

    // ๋…ธ์ด์ฆˆ ๊ฐ’ ๊ณ„์‚ฐ - 0.01์„ ๋†’์ด๋ฉด ๋…ธ์ด์ฆˆ ๋นจ๋ผ์ง
    let n = noise(xoff + frameCount * 0.01, yoff + frameCount * 0.01);
    let offset = map(n, 0, 1, -amplitude, amplitude);

    let r = baseRadius + offset;
    let x = r * cos(angle);
    let y = r * sin(angle);

    vertex(x, y);
  }
  endShape(CLOSE);

  pop();
}

function updateParticles(bass, mid, treble) {
  for (let p of particles) {
    p.update(bass, mid, treble);
    p.show();
  }
}

class Particle {
  constructor() {
    this.reset();
  }

  reset() {
    this.pos = createVector(width / 2, height / 2);
    this.vel = p5.Vector.random2D().mult(random(0.3, 1.0));
    this.size = random(4, 8); // ํฌ๊ธฐ ์ฆ๊ฐ€
    this.life = random(150, 250);
    this.maxLife = this.life;
  }

  update(bass, mid, treble) {
    this.life -= 1;

    let speedBoost = 0.4 + mid * 0.004;
    this.pos.add(this.vel.copy().mult(speedBoost));

    // ๋ฏธ์„ธํ•œ ํ”๋“ค๋ฆผ
    this.pos.add(p5.Vector.random2D().mult(treble * 0.002));

    if (this.life <= 0) {
      this.reset();
    }
  }

  show() {
    let alpha = map(this.life, 0, this.maxLife, 0, 220);

    // ํŒŒํ‹ฐํด
    noStroke();
    fill(200, 220, 255, alpha);
    circle(this.pos.x, this.pos.y, this.size);

    // ๊ธ€๋กœ์šฐ ํšจ๊ณผ
    fill(200, 220, 255, alpha * 0.3);
    circle(this.pos.x, this.pos.y, this.size * 1.8);
  }
}
profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€