14주차 3교시 — 사운드 비주얼 실습 + 기말 프로젝트 구상
강의 아웃라인: week-14-outline 이전 교시: week-14-period2-student (FFT + 이미지/웹캠 + 기말 안내)
| 항목 | 내용 |
|---|---|
| 대상 | P5.js 입문자 (테크아트 전공, 1·2교시 Amplitude·FFT·미디어 학습 완료) |
| 소요 시간 | 45분 (Part A 25 + Part B 15 + 마무리 5) |
- 1~2교시 사운드 분석을 직접 구현해 비주얼에 매핑할 수 있습니다
- 기말 프로젝트 주제·방향·기술을 정해 한 장으로 구상할 수 있습니다
제출·채점하는 과제가 아닌 수업 내 실습입니다. 14주차는 학기 마지막 정규 수업이며, 결과물은 기말 프로젝트의 출발점으로 재활용할 수 있습니다.
실습 환경 안내
3교시는 두 파트로 나뉩니다. Part A 25분 — 사운드 비주얼을 직접 만들기, Part B 15분 — 기말 프로젝트를 구상하기, 마지막 마무리 5분.
| 파트 | 시간 | 내용 |
|---|---|---|
| Part A | 25분 | 사운드 비주얼 실습 — 1~2교시 내용 직접 구현 |
| Part B | 15분 | 기말 프로젝트 구상 — 주제·방향·기술 선정 |
| 마무리 | 5분 | 저장 + 기말 일정 재확인 + 학기 회고 |
사운드 파일 준비
Web Editor에 음악 파일을 업로드합니다. 자기가 좋아하는 곡(MP3, 30초~1분 권장)이 있으면 가져와도 되고, 없으면 강사가 준비한 샘플 파일을 씁니다. 비트감 있고 음량 변화가 뚜렷한 곡이 효과 확인에 좋습니다.
마이크 사용을 원하면: 1교시 핵심 5에서 본
p5.AudioIn 패턴을 그대로 씁니다. 브라우저 마이크 권한
허용이 필요합니다. 노이즈가 많은 환경에서는 파일 기반이 더
안정적입니다.
1교시 완성 코드를 복사해 붙여넣고 거기서 시작하는 게 가장 안전합니다. 빈 캔버스부터 시작하지 말고, 돌아가는 코드 위에서 수정합니다.
방향 A/B/C 중 하나를 골라 시작합니다. 가장 쉬운 건 방향 A이고, 막막하면 A부터 시작합니다.
Part A — 사운드 비주얼 실습
방향 A 스타터: 음량
반응 비주얼 (p5.Amplitude)
1교시에서 만든 코드를 기반으로 시작합니다.
// === 방향 A: 음량 반응 비주얼 ===
// 음량에 따라 원의 크기와 색상이 변합니다
// 여기서 시작해서 자유롭게 수정하세요
let song;
let amp;
function preload() {
// 자신의 음악 파일명으로 바꾸세요
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
amp = new p5.Amplitude();
}
function draw() {
background(0);
let level = amp.getLevel(); // 0.0 ~ 1.0
// 음량 → 크기
let size = map(level, 0, 1, 10, 400);
// 음량 → 색상
let r = map(level, 0, 1, 50, 255);
let b = map(level, 0, 1, 255, 50);
fill(r, 50, b);
noStroke();
ellipse(width / 2, height / 2, size, size);
}
function mousePressed() {
userStartAudio();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}도전 옵션.
- 원 대신 다른 도형(사각형·삼각형) 사용
- 잔상 효과 추가:
background(0, 0, 0, 30)— 5주차에서 배운 것 - 원을 여러 개 그리기:
for문(3주차) 활용 - 음량에 따라 원의 위치도 변하게 하기
- HSB 색상 모드에서
frameCount로 색상 순환:fill(frameCount % 360, 80, 100)
방향 B 스타터:
이퀄라이저 바 차트 (p5.FFT)
// === 방향 B: 이퀄라이저 바 차트 ===
// 주파수 스펙트럼을 막대 차트로 시각화합니다
let song;
let fft;
function preload() {
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
colorMode(HSB, 255);
fft = new p5.FFT();
}
function draw() {
background(0);
let spectrum = fft.analyze();
noStroke();
for (let i = 0; i < spectrum.length; i++) {
let x = map(i, 0, spectrum.length, 0, width);
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();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}도전 옵션.
- 막대 개수 줄이기:
new p5.FFT(0.8, 64)— 64개 막대로 더 굵게 - 원형 이퀄라이저:
translate(width/2, height/2)+rotate()+ 막대를 원형 배치 - 잔상 효과 추가
- 막대 대신 원으로 표현:
ellipse(x, height - h, 5, 5)
방향 C 스타터:
저음/중음/고음 분리 (fft.getEnergy())
// === 방향 C: 저음/중음/고음 분리 비주얼 ===
// bass, mid, treble을 각각 다른 도형에 매핑합니다
let song;
let fft;
function preload() {
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
fft = new p5.FFT();
}
function draw() {
background(0);
fft.analyze(); // 반드시 먼저 호출
let bass = fft.getEnergy("bass");
let mid = fft.getEnergy("mid");
let treble = fft.getEnergy("treble");
// 저음 → 빨간 원 (왼쪽)
let bassSize = map(bass, 0, 255, 10, 200);
fill(255, 50, 50);
noStroke();
ellipse(width * 0.2, height / 2, bassSize, bassSize);
// 중음 → 초록 사각형 (가운데)
let midSize = map(mid, 0, 255, 10, 150);
fill(50, 255, 50);
rectMode(CENTER);
rect(width * 0.5, height / 2, midSize, midSize);
// 고음 → 파란 삼각형 (오른쪽)
let trebleSize = map(treble, 0, 255, 10, 100);
fill(50, 50, 255);
triangle(
width * 0.8, height / 2 - trebleSize / 2,
width * 0.8 - trebleSize / 2, height / 2 + trebleSize / 2,
width * 0.8 + trebleSize / 2, height / 2 + trebleSize / 2
);
}
function mousePressed() {
userStartAudio();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}도전 옵션.
- 각 도형에 투명도 추가:
fill(255, 50, 50, map(bass, 0, 255, 50, 200)) - 도형 더 추가:
lowMid·highMid밴드 활용 - 저음에 반응하는 배경색:
background(map(bass, 0, 255, 0, 50), 0, 0) - 13주차 파티클과 결합 (2교시 심화 4 참고)
Step-by-Step 가이드 (방향 A 기준)
Step 1 — 사운드 로드 및 재생 (7분)
목표: 음악 파일이 정상적으로 재생되는지 확인.
- P5.js Web Editor에서 새 스케치 생성
- 음악 파일을 Web Editor에 업로드 (파일 탭 → Upload file)
- 방향 A 스타터 코드 복사·붙여넣기
- 파일명을 자신의 파일명으로 수정:
loadSound('자신의파일명.mp3') - 실행 → 캔버스 클릭 → 소리가 나는지 확인
userStartAudio()가 mousePressed() 안에
있는지 → 캔버스(검은 화면)를 클릭했는지 → 파일명 철자·확장자 확인 → 콘솔
에러 메시지 확인. 업로드·권한 지연을 가정해 이 단계에 2분 여유를
두었습니다.
실행하고 캔버스를 클릭하면 음악이 흘러나오고, 음량에 따라 원이 커졌다 작아집니다.
Step 2 — 분석 객체 연결 (8분)
목표: 분석값(amp.getLevel())이 실제 0~1
사이 값으로 들어오는지 확인하고, 곡에 맞춰 매핑 범위를 조정합니다.
콘솔에 음량 값을 찍어 확인합니다.
function draw() {
background(0);
let level = amp.getLevel();
console.log(level); // 콘솔에서 값 확인
// ... 기존 그리기 ...
}콘솔을 열어 값이 흘러가는 걸 봅니다. 보통 조용한 부분은
0.01~0.05, 큰 부분은 0.2~0.5 정도.
곡마다 최대값이 다르다는 점이 핵심입니다.
매핑 범위 조정: 원의 변화가 너무 미미하면
map()의 입력 최대값을 줄입니다.
// 변화가 잘 안 보이면 1 → 0.5나 0.3으로
let size = map(level, 0, 0.3, 10, 400);방향 B/C로 갈 경우: 여기서
fft = new p5.FFT() + fft.analyze()로 객체를
바꾸고 콘솔로 spectrum 배열을 확인합니다.
콘솔에 0~0.5 사이 숫자가 실시간으로 흐르고, 원의 변화 폭이 곡에 맞게 자연스럽습니다.
Step 3 — 비주얼 매핑 (10분)
목표: 분석값을 시각적 속성(크기·색상·위치·잔상)에 자유롭게 연결합니다.
아래 순서로 한 겹씩 쌓습니다.
3-1. 잔상 효과
// background(0); 을 아래로 교체
background(0, 0, 0, 30); // 반투명 배경 → 잔상 효과3-2. 원을 여러 개 — 동심원
function draw() {
background(0, 0, 0, 30);
let level = amp.getLevel();
for (let i = 0; i < 5; i++) {
let size = map(level, 0, 0.5, 10, 400 - i * 70);
let alpha = map(i, 0, 4, 255, 50);
fill(map(level, 0, 0.5, 50, 255), 50, map(level, 0, 0.5, 255, 50), alpha);
noStroke();
ellipse(width / 2, height / 2, size, size);
}
}3-3. HSB 색상 + frameCount로 색상
순환
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100);
amp = new p5.Amplitude();
}
function draw() {
background(0, 0, 0, 30);
let level = amp.getLevel();
let size = map(level, 0, 0.5, 10, 400);
// frameCount로 색상 순환 + 음량으로 채도 조절
fill(frameCount % 360, map(level, 0, 0.5, 30, 100), 100);
noStroke();
ellipse(width / 2, height / 2, size, size);
}3-4. 인터랙션 추가 (옵션)
// 마우스 위치로 원 위치 변경
ellipse(mouseX, mouseY, size, size);
// 클릭으로 재생/일시정지 토글
function mousePressed() {
userStartAudio();
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}자신만의 색상·크기·잔상·인터랙션 조합으로 1분 안에 음악에 반응하는 비주얼이 화면을 가득 채웁니다.
Part B — 기말 프로젝트 구상
종이나 노트를 준비합니다.
Step 1 — 주제 선정 (5분)
먼저 이번 학기에서 가장 재미있었던 수업을 떠올려 봅니다.
질문 가이드.
- 어떤 주차 실습이 제일 재밌었나?
- 만들어보고 싶었는데 시간이 부족했던 건?
- OpenProcessing이나 YouTube에서 본 작품 중 따라 해보고 싶었던 게 있는가?
키워드 3개를 종이에 적어 봅니다. 예: ‘파티클 + 마우스 + 색상’ 또는 ‘소리 + 노이즈 + 원형’.
방향 예시 (2교시 안내 복습).
- A. 사운드 리액티브 비주얼
- B. 인터랙티브 시뮬레이션 (물리, 중력)
- C. 데이터 아트
- D. 제너러티브 아트 (노이즈 기반)
- E. 자유 주제
Step 2 — 컨셉 스케치 (7분)
키워드를 정했으면, 대략적인 화면 구성을 그려 봅니다. 예쁘게 안 그려도 됩니다. 네모 하나 그리고 그 안에 무엇이 있을지 스케치하면 됩니다.
스케치에 포함할 것.
- 캔버스에 어떤 요소가 있는지 (도형? 파티클? 이미지?)
- 어떤 인터랙션이 있는지 (마우스 클릭? 키보드? 소리?)
- 시간에 따라 어떻게 변하는지
아래 체크리스트에서 쓸 기술 2가지 이상을 체크합니다.
기술 체크리스트
Step 3 — 프로토타입 시작 (선택, 3분)
여유가 되면 P5.js Web Editor에서 새 스케치를 열고, 기본 구조를 잡습니다.
// 기말 프로젝트 프로토타입
// 프로젝트명: ___________
// 활용 기술: ___________
function setup() {
createCanvas(600, 600);
// TODO: 초기화 코드
}
function draw() {
background(0);
// TODO: 메인 비주얼
}
// TODO: 인터랙션이 구조에 변수 선언, 클래스 정의, preload() 등을 추가해
갑니다. 오늘 다 완성할 필요 없습니다.
마무리
스케치를 꼭 저장합니다. Ctrl+S (Mac은 Cmd+S).
기말 프로젝트 일정 재확인
| 주차 | 할 일 | 비고 |
|---|---|---|
| 14주차 (오늘) | 주제 구상 시작 | 키워드 + 스케치 |
| 15주차 | 작업 + 1:1 피드백 | 진행 중 상태로 가져오기 |
| 16주차 | 최종 제출 | 시연영상 + 소스코드 |
완성하지 않아도 됩니다. 진행 중인 상태로 가져오면 피드백을 받을 수 있습니다. 방향이 안 잡혔어도 괜찮습니다. 핵심은 다음 주에 무언가를 가져오는 것입니다. 빈 캔버스에 원 하나 그린 상태라도 좋습니다. 거기서부터 같이 발전시키면 됩니다.
14주차 학기 회고
1주차에서 ellipse(200, 200, 50, 50) 한 줄로 원을 그렸던
게 기억나나요?
이제 여러분은 소리를 분석하고, 데이터를 시각화하고, 수백 개의 파티클을 관리하고, 물리 법칙을 시뮬레이션하는 코드를 작성할 수 있습니다. 1주차의 그 원 하나에서 시작해서, 14주차의 사운드 리액티브 비주얼까지 온 것입니다.
기말 프로젝트에서 그 모든 것을 자신만의 작품으로 만들어 봅시다.
선택 사전 학습 영상
- The Coding Train “17.1 Loading and Playing Sound” (약 15분)
- The Coding Train “17.9 Sound Visualization” (약 18분)
- OpenProcessing에서 “sound reactive p5js” 검색
자기점검 기준
제출·채점하지 않는 수업 내 실습입니다. 아래는 스스로 완성도를 가늠하는 기준입니다.
Part A 기본 완료 기준 - 사운드가 정상 재생되고
getLevel() 또는 analyze()로 분석값을 받아옴 -
분석값이 map()으로 시각 요소(크기·색상·위치 중 1개 이상)에
연결됨 - 소리 변화에 따라 비주얼이 실시간으로 반응
Part A 확장 기준 - 잔상 효과, HSB 색상 순환, 다중 도형 등 2가지 이상 적용 - 인터랙션(마우스·키보드) 1개 이상 추가 - 13주차 파티클이나 이미지·웹캠과 결합
Part B 기본 완료 기준 - 주제와 방향이 키워드 3개로 정해짐 - 활용할 기술(2가지 이상)이 체크리스트에서 결정됨 - 대략적인 화면 구성이 종이에 스케치됨
선택 확장 과제
Part A를 빠르게 마친 뒤 더 해보고 싶다면 도전합니다.
- 파티클 + 사운드 결합 — 13주차 파티클 시스템을
가져와
amp.getLevel()에 따라 파티클 생성 개수를 조절 (2교시 심화 4 통합 사례 참고) - 마이크 입력으로 전환 —
loadSound()대신p5.AudioIn()을 써서 교실 소리에 반응 (1교시 핵심 5 참고) - 이미지 + 사운드 —
loadImage()로 이미지를 불러와 음량에 따라 크기·투명도 변화 - 이미지 파티클 — 13주차 파티클의
ellipse를image로 교체 (2교시 심화 2 회수) - 원형 이퀄라이저 — 방향 B 완성 예제 참고,
translate()+rotate()+cos()/sin()활용
완성 예제 (방향별)
방향 A 완성 예제 — 음량 반응 동심원 + 잔상
let song;
let amp;
function preload() {
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
colorMode(HSB, 360, 100, 100, 100);
amp = new p5.Amplitude();
}
function draw() {
background(0, 0, 0, 10); // 잔상 효과
let level = amp.getLevel();
// 동심원 5개: 음량에 따라 크기 변화
for (let i = 4; i >= 0; i--) {
let size = map(level, 0, 0.5, 10, 350 - i * 60);
size = max(size, 5); // 최소 크기 보장
let hue = (frameCount + i * 40) % 360; // 색상 순환
let alpha = map(i, 0, 4, 80, 20);
fill(hue, 80, 100, alpha);
noStroke();
ellipse(width / 2, height / 2, size, size);
}
// 음량 표시 (디버그용 — 완성 시 삭제 가능)
fill(0, 0, 100);
noStroke();
textSize(12);
text('Level: ' + nf(level, 1, 3), 10, 20);
}
function mousePressed() {
userStartAudio();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}방향 B 완성 예제 — 원형 이퀄라이저
let song;
let fft;
function preload() {
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
colorMode(HSB, 255);
angleMode(DEGREES);
fft = new p5.FFT(0.8, 128); // 128개 밴드
}
function draw() {
background(0);
translate(width / 2, height / 2);
let spectrum = fft.analyze();
for (let i = 0; i < spectrum.length; i++) {
let angle = map(i, 0, spectrum.length, 0, 360);
let r = map(spectrum[i], 0, 255, 50, 180); // 반지름
let x = r * cos(angle);
let y = r * sin(angle);
fill(map(i, 0, spectrum.length, 0, 255), 255, 255);
noStroke();
ellipse(x, y, 5, 5);
}
// 가운데 원: 전체 음량 평균
let level = 0;
for (let i = 0; i < spectrum.length; i++) {
level += spectrum[i];
}
level = level / spectrum.length;
let centerSize = map(level, 0, 128, 20, 80);
fill(255);
ellipse(0, 0, centerSize, centerSize);
}
function mousePressed() {
userStartAudio();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}방향 C 완성 예제 — 저음/중음/고음 분리 + 배경 반응
let song;
let fft;
function preload() {
song = loadSound('assets/music.mp3');
}
function setup() {
createCanvas(400, 400);
fft = new p5.FFT();
}
function draw() {
fft.analyze();
let bass = fft.getEnergy("bass");
let mid = fft.getEnergy("mid");
let treble = fft.getEnergy("treble");
// 배경색도 저음에 반응
background(map(bass, 0, 255, 0, 30), 0, map(bass, 0, 255, 0, 20));
// 저음 → 빨간 원들 (크기 + 투명도 변화)
let bassSize = map(bass, 0, 255, 20, 250);
for (let i = 0; i < 3; i++) {
fill(255, 50, 50, map(i, 0, 2, 150, 30));
noStroke();
ellipse(width * 0.25, height / 2, bassSize + i * 30, bassSize + i * 30);
}
// 중음 → 초록 사각형 (회전)
let midSize = map(mid, 0, 255, 10, 150);
push();
translate(width * 0.5, height / 2);
rotate(frameCount * 0.02);
fill(50, 255, 50, 180);
noStroke();
rectMode(CENTER);
rect(0, 0, midSize, midSize);
pop();
// 고음 → 파란 삼각형 (깜빡임)
let trebleSize = map(treble, 0, 255, 10, 120);
let trebleAlpha = map(treble, 0, 255, 30, 255);
fill(50, 80, 255, trebleAlpha);
noStroke();
triangle(
width * 0.75, height / 2 - trebleSize / 2,
width * 0.75 - trebleSize / 2, height / 2 + trebleSize / 2,
width * 0.75 + trebleSize / 2, height / 2 + trebleSize / 2
);
// 범례
fill(255);
noStroke();
textSize(12);
text('BASS', width * 0.2, height - 20);
text('MID', width * 0.47, height - 20);
text('TREBLE', width * 0.68, height - 20);
}
function mousePressed() {
userStartAudio();
// 재생 토글 — 반복 클릭 시 중복 재생 방지
if (song.isPlaying()) {
song.pause();
} else {
song.loop();
}
}