Media Art ProgrammingWeek 14 · Period 02 Handout

p5.js / Media Art Programming

14주차 2교시 — FFT 주파수 분석 + 이미지/영상 + 기말 프로젝트 안내

Sound, Media, and Final Project 주제의 학생용 강의 자료입니다.

WEEK 14 PERIOD 02 P5.JS MEDIA ART

14주차 2교시 — FFT 주파수 분석 + 이미지/영상 + 기말 프로젝트 안내

강의 아웃라인: week-14-outline 이전 교시: week-14-period1-student (사운드 로드·재생·Amplitude·마이크) 다음 교시: week-14-period3-student (사운드 비주얼 실습 + 기말 구상)

항목 내용
대상 P5.js 입문자 (테크아트 전공, 1교시 Amplitude·마이크 학습 완료)
소요 시간 45분
핵심 도구 P5.js Web Editor, p5.sound (FFT), loadImage, createCapture
학습 목표
  • p5.FFT로 주파수 스펙트럼을 분석하고 이퀄라이저를 만들 수 있습니다
  • loadImage()·createCapture()로 이미지·웹캠을 통합할 수 있습니다
  • 사운드+파티클 결합 패턴으로 1교시 도입의 약속을 회수합니다
  • 기말 프로젝트의 제출물·일정·평가 기준을 이해할 수 있습니다

1교시 복습

복습 질문
  1. 사운드 파일을 로드할 때 사용하는 함수는? → loadSound()
  2. 사운드를 preload()에 넣는 이유는? → 용량이 크니까 setup() 전에 완전히 로드해야 해서
  3. 브라우저에서 소리가 안 나올 때 해결 방법은? → userStartAudio() + 클릭
  4. amp.getLevel()은 어떤 값을 돌려주나? → 0~1 사이의 음량

핵심 5번에서 마이크 입력도 다뤘습니다. setInput() 한 줄로 같은 Amplitude 패턴이 파일/마이크 둘 다 통한다는 점이 핵심입니다.

1교시 → 2교시 연결

1교시는 전체 음량 분석이었습니다. 숫자 하나 — 지금 이 순간 전체적으로 얼마나 큰지. 음악에는 저음(베이스 드럼, 쿵쿵), 중음(보컬, 기타), 고음(심벌, 찌이잉)이 있습니다. 이걸 따로 분석하면 훨씬 풍부한 비주얼이 가능합니다.

2교시는 FFT(주파수 분석)이미지·웹캠 통합사운드+파티클 통합 사례기말 프로젝트 안내 순서로 진행합니다.


심화 개념 1 — p5.FFT로 주파수 스펙트럼 분석

FFT란? (개념만 간단히)

FFT — Fast Fourier Transform. 수학적으로는 복잡하지만, 수학을 몰라도 됩니다. P5.js가 다 해줍니다.

이퀄라이저(EQ) 보신 적 있으면 떠올려 보세요. 음악 앱이나 스피커에 있는 막대 그래프. 저음 막대가 쿵쿵 움직이고, 고음 막대가 찌이잉 움직이고. FFT가 바로 그것입니다. 소리를 주파수별로 분해해서 지금 이 순간 저음·중음·고음이 각각 얼마인지 숫자로 알려줍니다.

1교시 p5.Amplitude 2교시 p5.FFT
소리의 전체 크기 소리의 주파수별 크기
숫자 1개 (0~1) 숫자 1024개 (배열, 각 0~255)

6주차에서 배운 배열이 여기서 쓰입니다. 1024개의 숫자가 담긴 배열입니다.

기본 사용법 — 이퀄라이저 바 차트

let song;
let fft;

function preload() {
  song = loadSound('assets/music.mp3');
}

function setup() {
  createCanvas(400, 400);
  colorMode(HSB, 255); // 3주차 HSB 색상
  fft = new p5.FFT();
}

function draw() {
  background(0);

  let spectrum = fft.analyze(); // 배열 (길이 1024)

  noStroke();
  for (let i = 0; i < spectrum.length; i++) {
    // 인덱스 → x 위치
    let x = map(i, 0, spectrum.length, 0, width);
    // 값(0~255) → 막대 높이
    let h = map(spectrum[i], 0, 255, 0, height);
    // 인덱스 → 색상 (저주파=빨강, 고주파=보라)
    fill(map(i, 0, spectrum.length, 0, 255), 255, 255);
    rect(x, height - h, width / spectrum.length, h);
  }
}

function mousePressed() {
  userStartAudio();
  song.loop();
}

코드 한 줄씩.

코드 설명
colorMode(HSB, 255) 3주차 HSB 색상 모드
fft = new p5.FFT() FFT 분석기 생성
let spectrum = fft.analyze() 1024개 주파수 대역 에너지를 배열로 반환 (각 값 0~255)
map(i, 0, spectrum.length, 0, width) 배열 인덱스 → x 좌표
map(spectrum[i], 0, 255, 0, height) 에너지 값 → 막대 높이
fill(map(i, 0, spectrum.length, 0, 255), 255, 255) HSB의 Hue만 변화 → 무지개 색
rect(x, height - h, width/spectrum.length, h) 아래에서 위로 자라는 막대

3주차 for문 + 5주차 map() + 2주차 rect() + 3주차 colorMode(HSB). 지금까지 배운 것들이 전부 동원됩니다.

배열의 앞쪽(인덱스 0~100)이 저주파(베이스), 뒤쪽(인덱스 900~1023)이 고주파(심벌)입니다. 그래서 왼쪽 막대가 베이스에 반응하고, 오른쪽이 고음에 반응합니다.

실행하면 이퀄라이저 바 차트가 실시간으로 움직이고, 저음이 강한 구간에서는 왼쪽 빨간 막대가 높아지며, 고음이 강한 구간에서는 오른쪽 보라색 막대가 높아집니다.

에너지 밴드 추출 — getEnergy() (더 직관적인 방법)

1024개를 전부 다루기 부담스러우면, p5.sound가 미리 나눠놓은 밴드를 쓸 수 있습니다.

function draw() {
  colorMode(RGB, 255); // 앞선 HSB 예제와 이어 작성할 때 색상 기준 복귀
  background(0);
  fft.analyze(); // 반드시 먼저 호출!

  let bass = fft.getEnergy("bass");       // 저음 (0~255)
  let mid = fft.getEnergy("mid");         // 중음 (0~255)
  let treble = fft.getEnergy("treble");   // 고음 (0~255)

  // 저음 → 빨간 원
  let bassSize = map(bass, 0, 255, 10, 200);
  fill(255, 0, 0);
  noStroke();
  ellipse(100, height / 2, bassSize, bassSize);

  // 중음 → 초록 사각형
  let midSize = map(mid, 0, 255, 10, 150);
  fill(0, 255, 0);
  rectMode(CENTER);
  rect(200, height / 2, midSize, midSize);

  // 고음 → 파란 삼각형
  let trebleSize = map(treble, 0, 255, 10, 100);
  fill(0, 0, 255);
  triangle(
    300, height / 2 - trebleSize / 2,
    300 - trebleSize / 2, height / 2 + trebleSize / 2,
    300 + trebleSize / 2, height / 2 + trebleSize / 2
  );
}

저음 → 큰 빨간 원, 중음 → 초록 사각형, 고음 → 파란 삼각형. 각각 독립적으로 반응합니다.

getEnergy()에 넣을 수 있는 밴드.

밴드 주파수 범위 대응하는 소리
"bass" 20-140 Hz 베이스 드럼, 베이스 기타
"lowMid" 140-400 Hz 보컬 낮은 음역
"mid" 400-2600 Hz 보컬, 기타 멜로디
"highMid" 2600-5200 Hz 보컬 높은 음역
"treble" 5200-14000 Hz 심벌, 하이햇

전부 쓸 필요는 없습니다. 보통 bass, mid, treble 세 개면 충분합니다.

확인 질문

1교시의 p5.Amplitude와 2교시의 p5.FFT는 어떤 차이가 있나요?

: Amplitude는 전체 음량 숫자 하나, FFT는 주파수별로 나눠 분석.


심화 개념 2 — 이미지 활용: loadImage()

사운드 다음으로, 이미지를 P5.js에 가져오는 방법입니다.

let img;

function preload() {
  img = loadImage('assets/photo.jpg');
}

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

function draw() {
  image(img, 0, 0, width, height);
}

11주차 preload() 패턴과 완전히 같습니다. loadJSON()loadSound()loadImage(). 전부 같은 구조입니다.

  • image(img, x, y) — 원래 크기로 표시
  • image(img, x, y, w, h) — 지정한 크기로 표시

tint()로 이미지 색조 변경

function draw() {
  tint(255, 0, 0);     // 빨간 틴트
  image(img, 0, 0, width, height);
}
function draw() {
  tint(255, 128);      // 반투명 (알파값)
  image(img, 0, 0, width, height);
}

사운드 + 이미지 결합 — 음량에 따라 이미지가 펄럭이는 효과

let song, amp, img;

function preload() {
  song = loadSound('assets/music.mp3');
  img = loadImage('assets/photo.jpg');
}

function setup() {
  createCanvas(400, 400);
  amp = new p5.Amplitude();
}

function draw() {
  background(0);

  let level = amp.getLevel();
  let s = map(level, 0, 1, 100, 400);
  let alpha = map(level, 0, 1, 100, 255);

  tint(255, alpha);
  image(img, width / 2 - s / 2, height / 2 - s / 2, s, s);
}

function mousePressed() {
  userStartAudio();
  song.loop();
}

음량이 크면 이미지가 커지고 선명해지고, 작으면 작아지고 투명해집니다. 사진이 숨 쉬는 것처럼 펄럭거리는 효과입니다.

13주차 연결 — 이미지 파티클

13주차 파티클 시스템의 display()에서 ellipse(this.position.x, this.position.y, 12)image(img, this.position.x - 8, this.position.y - 8, 16, 16)로 교체하면, 작은 그림이 분수처럼 쏟아지는 이미지 파티클이 됩니다. 13주차 2교시에서 예고했던 그것입니다. 기말에서 별·하트·로고 이미지로 바꾸면 곧장 작품이 됩니다.


심화 개념 3 — 웹캠 활용: createCapture()

이미지는 정적인 파일이었는데, 실시간 영상도 가져올 수 있습니다. 바로 웹캠입니다.

let video;

function setup() {
  createCanvas(400, 400);
  video = createCapture(VIDEO);
  video.size(400, 400);
  video.hide(); // 캔버스 밑에 별도로 표시되는 HTML 요소 숨기기
}

function draw() {
  image(video, 0, 0, width, height);
}

실행하면 브라우저가 카메라 접근 권한을 묻습니다. ’허용’을 눌러야 동작합니다.

video.hide()가 필요한 이유

createCapture()는 캔버스 외에 HTML <video> 요소를 하나 더 만듭니다. video.hide()를 안 넣으면 캔버스 밑에 영상이 하나 더 보입니다. hide()로 숨기고 draw() 안에서 image()로만 표시합니다.

웹캠 + 사운드 결합

function draw() {
  image(video, 0, 0, width, height);

  let level = amp.getLevel();
  let size = map(level, 0, 1, 0, 300);

  fill(255, 100);
  noStroke();
  ellipse(width / 2, height / 2, size, size);
}

웹캠 영상 위에 음량에 따라 도형을 오버레이하면 — 자기 모습 위에 사운드 이펙트가 올라가는 인터랙티브 미디어아트가 됩니다.


심화 개념 4 — 사운드+파티클 통합 사례

1교시 도입에서 본 ‘음악에 반응하는 파티클’ 데모, 어떻게 만들어지는지 풀어 봅니다. 13주차 파티클 시스템 + 1교시 Amplitude의 결합입니다.

핵심 아이디어는 Emitter의 addParticle() 호출 빈도를 음량에 비례시키는 것입니다. 13주차 Emitter는 run() 안에서 addParticle()을 매 프레임 한 번 부르는데, 이번 버전에서는 run() 안의 자동 생성 한 줄만 빼고, 외부에서 음량만큼 addParticle()을 호출합니다.

class Emitter {
  // ... 13주차와 동일: constructor, addParticle ...

  // 13주차 run()에서 this.addParticle() 자동 호출만 제거
  run() {
    for (let i = this.particles.length - 1; i >= 0; i--) {
      this.particles[i].update();
      this.particles[i].display();
      if (this.particles[i].isDead()) {
        this.particles.splice(i, 1);
      }
    }
  }
}

draw()는 음량에 비례해 파티클을 방출한 뒤, run()으로 업데이트·표시·소멸 처리를 이어갑니다.

let song, amp, emitter;

function preload() {
  song = loadSound('assets/music.mp3');
}

function setup() {
  createCanvas(640, 360);
  amp = new p5.Amplitude();
  emitter = new Emitter(width / 2, height / 2);
}

function draw() {
  background(0, 25); // 잔상 효과

  let level = amp.getLevel();
  let count = floor(map(level, 0, 0.5, 0, 10)); // 음량 비례 방출량

  for (let i = 0; i < count; i++) {
    emitter.addParticle();
  }

  emitter.run();
}

function mousePressed() {
  userStartAudio();
  song.loop();
}

비트가 강할 때 파티클이 폭발하듯 쏟아지고, 조용해지면 잦아드는 화면이 나옵니다. 1교시 도입에서 본 그 데모가 이렇게 만들어진 것입니다.

베이스 추출로 더 인상적으로

베이스만 추출해서 파티클 크기·속도에 매핑하면 더 인상적입니다 — fft.getEnergy("bass") (심화 1번에서 다룬 그것).

기말 프로젝트의 가장 강력한 조합 중 하나 — 사운드 리액티브 파티클입니다. 13주차 + 14주차 두 주차 내용 결합의 모범 사례라, 기말 평가 기준의 ’기술 2가지 이상 활용’이 자연스럽게 충족됩니다.


기말 프로젝트 안내

1. 기말 프로젝트 개요

항목 내용
과제명 인터랙티브 미디어 콘텐츠 제작
비중 40% (학기 전체 성적의 가장 큰 부분)
주제 자유 (1~14주차 내용 활용)
15주차 프로젝트 작업 + 1:1 피드백
16주차 최종 제출

제출물.

  1. P5.js 소스코드 — Web Editor 링크 또는 파일
  2. 시연영상 — 30초~1분 길이, 화면 녹화

15주차에 개별 피드백 시간이 있습니다. 완성하고 오지 않아도 됩니다. 진행 중인 상태로 가져오면 같이 방향을 잡고 기술적 도움을 드립니다. 16주차에 최종 제출 — 시연영상과 소스코드 두 개를 제출합니다.

2. 주제 선정 가이드 — 1~14주차 총정리

주차 핵심 기술 기말 활용 예시
1-2 setup/draw, 도형, 변수 기본 캔버스 구성, 움직이는 도형
3 색상(RGB/HSB), for문 패턴 생성, 그라데이션, 반복 도형
4 조건문, 마우스/키보드 인터랙티브 반응, 상태 전환
5 애니메이션, random, map 움직임, 랜덤 효과, 값 매핑
6 함수, 배열 코드 구조화, 다수 요소 관리
7 클래스, 객체 배열 독립 객체 설계, 대량 생성
9 벡터, 삼각함수 물리 기반 움직임, 원운동
10 힘, 물리 시뮬레이션 중력, 마찰, 바람 효과
11 JSON/CSV 데이터 데이터 시각화
12 Perlin Noise, Flow Field 유기적·자연스러운 움직임
13 파티클 시스템 폭발·비·눈·불꽃 이펙트
14 사운드·이미지·웹캠 멀티미디어 결합
권장

2가지 이상의 주차 내용을 결합하세요. 하나만 쓰면 실습과제 수준입니다. 두 가지 이상을 조합해야 기말 프로젝트의 깊이가 나옵니다.

주제 방향 예시.

방향 설명 활용 주차
A. 사운드 리액티브 비주얼 음악에 반응하는 파티클·도형·패턴 14 + 13 + 3
B. 인터랙티브 시뮬레이션 물리 법칙 기반 놀이·작품 9-10 + 4 + 7
C. 데이터 아트 데이터를 활용한 시각화 작품 11 + 3 + 5
D. 제너러티브 아트 노이즈·알고리즘으로 생성되는 작품 12 + 5 + 7
E. 자유 주제 위 내용을 자유롭게 조합 자유

A~D는 예시일 뿐입니다. 여기 없는 조합도 얼마든지 가능합니다. 핵심은 본인이 흥미로운 주제를 찾는 것입니다.

3. 평가 기준

항목 비중 세부 내용
기술 구현 40% 수업에서 배운 기법 2가지 이상 활용
인터랙션 20% 마우스·키보드·사운드 등 사용자 상호작용 포함
작품성 20% 시각적 완성도, 컨셉의 일관성
발표 자료 20% 시연영상 품질, 코드 정리 상태

기술 구현이 40%로 가장 크지만, ’코드가 완벽해야 한다’는 뜻이 아닙니다. 수업에서 배운 기법을 실제로 활용했는지가 중요합니다.

완벽한 코드보다 본인만의 아이디어가 중요합니다. 기술적으로 완벽하지 않아도, 컨셉이 흥미롭고 직접 시도한 흔적이 보이면 좋은 점수를 받을 수 있습니다.

인터랙션 20% — 반드시 사용자가 무언가를 할 수 있어야 합니다. 단순히 자동으로 돌아가는 영상이 아니라, 클릭·키보드·소리 등으로 작품에 관여할 수 있어야 합니다.

시연영상은 화면 녹화 30초~1분이면 됩니다. 별도 편집은 필요 없고, 작품이 어떻게 동작하는지 보여주면 됩니다.

15주차 마음가짐

완성 안 해도 됩니다. 진행 중인 상태로 가져오면 1:1 피드백을 받을 수 있습니다. 방향이 안 잡혔어도 괜찮습니다. 핵심은 다음 주에 무언가를 가져오는 것입니다.


3교시 실습 안내

3교시는 세 부분입니다.

파트 시간 내용
Part A 25분 사운드 비주얼 실습 — 1~2교시 내용 직접 구현
Part B 15분 기말 프로젝트 구상 — 주제·방향·기술 선정
마무리 5분 저장 + 일정 재확인 + 학기 회고

Part A는 세 가지 방향 중 하나를 골라 시작합니다.

먼저 A부터 시작하고, 잘 되면 B나 C로 넘어갑니다. 스타터 코드는 3교시에서 받습니다. 사운드 파일은 강사가 준비한 샘플을 쓰거나, 자기 곡(MP3, 30초~1분)을 가져와도 됩니다.

Part B는 기말 주제를 구체적으로 구상하는 시간입니다. 종이에 스케치하고, 쓸 기술을 정하고, 여유가 되면 프로토타입을 시작합니다.

시작 힌트

Part A에서는 먼저 1교시 완성 코드를 복사해 붙여넣고, 거기서 시작합니다. 코드가 돌아가는 상태에서 수정하는 게 가장 안전합니다.