๐ต ์๋ฐ์คํฌ๋ฆฝํธ ์น ์ค๋์ค API: ๋ธ๋ผ์ฐ์ ์์ ์์ ๋ง๋ค๊ธฐ ๐ถ

์๋ ํ์ธ์, ์์ ์ ์ฌ๋ํ๋ ๊ฐ๋ฐ์ ์ฌ๋ฌ๋ถ! ์ค๋์ ์ ๋ง ํฅ๋ฏธ์ง์งํ ์ฃผ์ ๋ก ์ฌ๋ฌ๋ถ์ ์ฐพ์์์ต๋๋ค. ๋ฐ๋ก ์๋ฐ์คํฌ๋ฆฝํธ ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ์์ ์ง์ ์์ ์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณผ ๊ฑฐ์์. ๐
์ฌ๋ฌ๋ถ, ํน์ ๋ธ๋ผ์ฐ์ ๊ฐ ๋จ์ํ ์น ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋๊ตฌ๋ผ๊ณ ๋ง ์๊ฐํ์ จ๋์? ๊ทธ๋ ๋ค๋ฉด ์ค๋ ์ด ๊ธ์ ํตํด ๋ธ๋ผ์ฐ์ ์ ์จ๊ฒจ์ง ์ฌ๋ฅ์ ๋ฐ๊ฒฌํ๊ฒ ๋ ๊ฑฐ์์! ์ฐ๋ฆฌ์ ์น๊ตฌ ๋ธ๋ผ์ฐ์ ๋ ์ฌ์ค ๊ฝค๋ ํ๋ฅญํ ์์ ๊ฐ์ด๊ธฐ๋ ํ๋ต๋๋ค. ๐
์ด ๊ธ์ ํตํด ์ฌ๋ฌ๋ถ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ์์ ๋ค์ํ ์๋ฆฌ๋ฅผ ๋ง๋ค๊ณ , ์กฐ์ํ๊ณ , ์ฌ์ง์ด ์ ๋ฌธ๊ฐ ์์ค์ ์์ ์ํ์ ๋ง๋ค์ด๋ด๋ ๋ฐฉ๋ฒ๊น์ง ๋ฐฐ์ฐ๊ฒ ๋ ๊ฑฐ์์. ๋ง์น ๋ธ๋ผ์ฐ์ ๋ผ๋ ๋ฌด๋ ์์์ ์ฌ๋ฌ๋ถ์ด ์งํ์๊ฐ ๋์ด ์น ์ค๋์ค ์ค์ผ์คํธ๋ผ๋ฅผ ์งํํ๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ฃ ! ๐ญ๐ผ
์, ๊ทธ๋ผ ์ด์ ์ฐ๋ฆฌ์ ์์ ์ ์ฌ์ ์ ์์ํด๋ณผ๊น์? ์ค๋น๋์ จ๋์? ์ฌ๋ฌ๋ถ์ ์ฐฝ์๋ ฅ๊ณผ ์ฝ๋ฉ ์ค๋ ฅ์ ํ๋ฐ ๋ชจ์, ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์ธ์์ ๋จ ํ๋๋ฟ์ธ ์ฌ๋ฌ๋ถ๋ง์ ์์ ์ ์ ๋ณด์ผ ์๊ฐ์ ๋๋ค!
๐ ํ์ต ๋ชฉํ:
- ์น ์ค๋์ค API์ ๊ธฐ๋ณธ ๊ฐ๋ ์ดํดํ๊ธฐ
- ์ค๋์ค ์ปจํ ์คํธ ์์ฑ ๋ฐ ๊ด๋ฆฌํ๊ธฐ
- ๋ค์ํ ์ค๋์ค ์์ค ๋ค๋ฃจ๊ธฐ
- ํจ๊ณผ์ ํํฐ๋ฅผ ์ฌ์ฉํด ์๋ฆฌ ๊ฐ๊ณตํ๊ธฐ
- ๊ฐ๋จํ ์ ๋์ฌ์ด์ ๋ถํฐ ๋ณต์กํ ์ค๋์ค ์๊ฐํ๊น์ง ๊ตฌํํ๊ธฐ
์ด ๋ชจ๋ ๊ณผ์ ์ ํตํด, ์ฌ๋ฌ๋ถ์ ๋จ์ํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋์ด์ ์ง์ ํ ์๋ฏธ์ '์๋ฆฌ'๋ฅผ ํ๋ก๊ทธ๋๋ฐํ๊ฒ ๋ ๊ฑฐ์์. ๋ง์น ์ฌ๋ฅ๋ท์์ ๋ค์ํ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋ฏ์ด, ์ฐ๋ฆฌ๋ ์ค๋ '์๋ฆฌ'๋ผ๋ ์ฌ๋ฅ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ๊ฑฐ๋ํ๊ฒ ๋ ๊ฒ๋๋ค. ํฅ๋ฏธ์ง์งํ์ง ์๋์? ๐
์, ๊ทธ๋ผ ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์น ์ค๋์ค์ ์ธ๊ณ๋ก ๋ฐ์ด๋ค์ด๋ณผ๊น์? ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ผ๊ณ , ์ฝ๋ฉ์ ์์ํ ์ค๋น๋ฅผ ํด์ฃผ์ธ์. ์ฐ๋ฆฌ์ ์์ ์ ๋ชจํ์ด ์์๋ฉ๋๋ค! ๐๐ต
๐ง ์น ์ค๋์ค API: ์๋ฆฌ์ ๋์งํธ ์ธ๊ณ๋ก์ ์ด๋
์ฌ๋ฌ๋ถ, ํน์ '์๋ฆฌ'๊ฐ ๋ฌด์์ธ์ง ์๊ฐํด๋ณด์ ์ ์๋์? ๋ฌผ๋ฆฌํ์ ์ผ๋ก ์๋ฆฌ๋ ๊ณต๊ธฐ์ ์ง๋์ด๊ณ , ์ฐ๋ฆฌ์ ๊ท๋ ์ด ์ง๋์ ๊ฐ์งํด ๋๋ก ์ ๋ฌํ์ฃ . ๊ทธ๋ฐ๋ฐ ์ด ์๋ ๋ก๊ทธ ์ธ๊ณ์ ์๋ฆฌ๋ฅผ ์ด๋ป๊ฒ ๋์งํธ๋ก ํํํ ์ ์์๊น์? ๋ฐ๋ก ์ฌ๊ธฐ์ ์น ์ค๋์ค API์ ๋ง๋ฒ์ด ์์๋ฉ๋๋ค! ๐งโโ๏ธโจ
์น ์ค๋์ค API๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค๋์ค๋ฅผ ์ฒ๋ฆฌํ๊ณ ํฉ์ฑํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ์ด๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋ธ๋ผ์ฐ์ ์์ ์ง์ ์๋ฆฌ๋ฅผ ๋ง๋ค๊ณ , ์กฐ์ํ๊ณ , ์ฌ์ํ ์ ์์ด์. ๋ง์น ๋์งํธ ์ธ๊ณ์ ์์ ์์ ์คํ๋์ค๋ฅผ ๊ฐ์ง ๊ฒ๊ณผ ๊ฐ์ฃ !
๐ต ์น ์ค๋์ค API์ ์ฃผ์ ํน์ง:
- ์ค์๊ฐ ์ค๋์ค ์ฒ๋ฆฌ
- ๋ค์ํ ์ค๋์ค ์์ค ์ง์ (ํ์ผ, ์คํธ๋ฆผ, ์์ฑ๋ ์๋ฆฌ ๋ฑ)
- ๋ณต์กํ ์ค๋์ค ๋ผ์ฐํ ๊ฐ๋ฅ
- ์ ๋ฐํ ์๊ฐ ์ ์ด
- ๋ค์ํ ์ค๋์ค ํจ๊ณผ ์ ์ฉ ๊ฐ๋ฅ
์ด์ ์ฐ๋ฆฌ๋ ์ด ๋๋ผ์ด ๋๊ตฌ๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์์ ์ ์ธ๊ณ๋ก ๋ฐ์ด๋ค ์ค๋น๊ฐ ๋์์ต๋๋ค. ๋ง์น ์ฌ๋ฅ๋ท์์ ์๋ก์ด ์ฌ๋ฅ์ ๋ฐ๊ฒฌํ๋ฏ์ด, ์ฐ๋ฆฌ๋ ๋ธ๋ผ์ฐ์ ์ ์จ๊ฒจ์ง ์์ ์ ์ฌ๋ฅ์ ๋ฐ๊ฒฌํ๊ฒ ๋ ๊ฑฐ์์! ๐
๐ผ ์ค๋์ค ์ปจํ ์คํธ: ์๋ฆฌ์ ๋์ดํฐ
์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํ ๋ ๊ฐ์ฅ ๋จผ์ ๋ง๋๊ฒ ๋๋ ๊ฒ์ด ๋ฐ๋ก '์ค๋์ค ์ปจํ ์คํธ(Audio Context)'์ ๋๋ค. ์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์๋ฆฌ๋ฅผ ๋ค๋ฃจ๋ ๋ชจ๋ ์์ ์ด ์ผ์ด๋๋ ๊ณต๊ฐ์ด์์. ๋ง์น ์์ ๊ฐ๋ค์ด ๋ น์์ค์์ ์์ ํ๋ฏ์ด, ์ฐ๋ฆฌ๋ ์ด ์ค๋์ค ์ปจํ ์คํธ ์์์ ์๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ์กฐ์ํ๊ฒ ๋ฉ๋๋ค.
์ค๋์ค ์ปจํ ์คํธ๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ์์ฃผ ๊ฐ๋จํด์. ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ๋ฉ๋๋ค:
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
์ด๋ ๊ฒ ์์ฑ๋ ์ค๋์ค ์ปจํ ์คํธ๋ ์ฐ๋ฆฌ์ ์์ ์์ ์ค์ด ๋๋ ๊ฑฐ์ฃ . ์ด์ ์ด ์์ ์ค ์์์ ์ฐ๋ฆฌ๋ ๋ค์ํ ์ ๊ธฐ(์๋ฆฌ ์์ค)๋ฅผ ๋ฐฐ์นํ๊ณ , ํจ๊ณผ๋ฅผ ๋ํ๊ณ , ์๋ฆฌ์ ํ๋ฆ์ ์ ์ดํ ์ ์๊ฒ ๋ฉ๋๋ค.
๐น ๋ ธ๋(Node): ์๋ฆฌ์ ๋น๋ฉ ๋ธ๋ก
์น ์ค๋์ค API์์ ๋ชจ๋ ์๋ฆฌ ์ฒ๋ฆฌ๋ '๋ ธ๋'๋ผ๋ ๊ฐ๋ ์ ํตํด ์ด๋ฃจ์ด์ง๋๋ค. ๋ ธ๋๋ ์๋ฆฌ์ ์์ฑ, ์ฒ๋ฆฌ, ๋ถ์ ๋ฑ ํน์ ๊ธฐ๋ฅ์ ๋ด๋นํ๋ ์์ ๋จ์๋ผ๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค. ๋ง์น ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ, ์ด ๋ ธ๋๋ค์ ์ฐ๊ฒฐํด ๋ณต์กํ ์ค๋์ค ์์คํ ์ ๊ตฌ์ถํ ์ ์์ฃ .
๐งฉ ์ฃผ์ ๋ ธ๋ ์ ํ:
- ์์ค ๋ ธ๋: ์ค๋์ค ์์ค๋ฅผ ๋ํ๋ (์: ์ค์ค๋ ์ดํฐ, ์ค๋์ค ํ์ผ)
- ํจ๊ณผ ๋ ธ๋: ์๋ฆฌ๋ฅผ ๋ณํ์ํด (์: ๊ฒ์ธ, ํํฐ, ์ปจ๋ณผ๋ฒ)
- ๋ถ์ ๋ ธ๋: ์ค๋์ค ๋ฐ์ดํฐ๋ฅผ ๋ถ์ (์: ์ ๋๋ผ์ด์ )
- ๋ชฉ์ ์ง ๋ ธ๋: ์ต์ข ์ถ๋ ฅ์ ๋ด๋น (์: ์คํผ์ปค)
์ด ๋ ธ๋๋ค์ ์ฐ๊ฒฐํด ์๋ฆฌ์ ํ๋ฆ์ ๋ง๋๋ ๊ณผ์ ์ ๋ง์น ํ์ดํ๋ฅผ ์ฐ๊ฒฐํ๋ ๊ฒ๊ณผ ๋น์ทํด์. ์๋ฆฌ๊ฐ ์์ค ๋ ธ๋์์ ์์ํด ์ฌ๋ฌ ํจ๊ณผ ๋ ธ๋๋ฅผ ๊ฑฐ์ณ ์ต์ข ์ ์ผ๋ก ๋ชฉ์ ์ง ๋ ธ๋(๋ณดํต ์คํผ์ปค)๋ก ํ๋ฌ๊ฐ๋ ๊ฑฐ์ฃ .
๐ ํ๋ผ๋ฏธํฐ: ์๋ฆฌ์ ์กฐ์ ์์ก์ด
๊ฐ ๋ ธ๋๋ ๋ค์ํ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ง๊ณ ์์ด์. ์ด ํ๋ผ๋ฏธํฐ๋ค์ ์กฐ์ ํจ์ผ๋ก์จ ์ฐ๋ฆฌ๋ ์๋ฆฌ์ ํน์ฑ์ ์ ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ค์ค๋ ์ดํฐ ๋ ธ๋์ ์ฃผํ์๋ฅผ ์กฐ์ ํ๊ฑฐ๋, ๊ฒ์ธ ๋ ธ๋์ ๋ณผ๋ฅจ์ ์กฐ์ ํ๋ ์์ด์ฃ .
๋๋ถ๋ถ์ ํ๋ผ๋ฏธํฐ๋ ์๊ฐ์ ๋ฐ๋ผ ๋ณํ๋ฅผ ์ค ์ ์์ด์. ์ด๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋์ ์ด๊ณ ํํ๋ ฅ ์๋ ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ผ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ด ๋ณผ๋ฅจ์ ์์ํ ๋์ด๋ ํจ๊ณผ๋ฅผ ์ค ์ ์์ฃ :
const gainNode = audioContext.createGain();
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2);
์ด ์ฝ๋๋ 0์ด์์ ์์ํด 2์ด ๋์ ๋ณผ๋ฅจ์ 0์์ 1๋ก ์ ํ์ ์ผ๋ก ์ฆ๊ฐ์ํต๋๋ค. ๋ง์น ํ์ด๋ ์ธ(fade in) ํจ๊ณผ๋ฅผ ์ฃผ๋ ๊ฒ๊ณผ ๊ฐ์ฃ !
๐ฐ๏ธ ์๊ฐ: ์๋ฆฌ์ ๋ค ๋ฒ์งธ ์ฐจ์
์น ์ค๋์ค API์์ ์๊ฐ์ ๋งค์ฐ ์ค์ํ ๊ฐ๋ ์ ๋๋ค. ๋ชจ๋ ์๋ฆฌ ์ด๋ฒคํธ๋ ์ ํํ ์๊ฐ์ ๋ง์ถฐ ์คํ๋์ด์ผ ํ๋๊น์. ์ค๋์ค ์ปจํ ์คํธ๋ ์์ฒด์ ์ธ ์๊ณ๋ฅผ ๊ฐ์ง๊ณ ์์ด์, ์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ชจ๋ ์ค๋์ค ์ด๋ฒคํธ์ ํ์ด๋ฐ์ ์ ์ดํฉ๋๋ค.
audioContext.currentTime์ ํตํด ํ์ฌ ์ค๋์ค ์ปจํ ์คํธ์ ์๊ฐ์ ์ ์ ์๊ณ , ์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฏธ๋์ ์ค๋์ค ์ด๋ฒคํธ๋ฅผ ์ ํํ๊ฒ ์ค์ผ์ค๋งํ ์ ์์ด์. ์ด๋ ์ ํํ ๋ฆฌ๋ฌ๊ณผ ํ์ด๋ฐ์ด ์ค์ํ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํนํ ์ ์ฉํฉ๋๋ค.
๐ญ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ: ๋ชจ๋๋ฅผ ์ํ ์์
์น ์ค๋์ค API๋ ํ๋์ ๋๋ถ๋ถ์ ๋ธ๋ผ์ฐ์ ์์ ์ง์๋์ง๋ง, ์ผ๋ถ ์ค๋๋ ๋ธ๋ผ์ฐ์ ์์๋ ์ง์๋์ง ์์ ์ ์์ด์. ๋ฐ๋ผ์ ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค.
๋คํํ๋, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ผ๋ก ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค:
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
์ด๋ ๊ฒ ํ๋ฉด ํ์ค AudioContext์ ์นํท ๊ธฐ๋ฐ ๋ธ๋ผ์ฐ์ ์ webkitAudioContext๋ฅผ ๋ชจ๋ ์ปค๋ฒํ ์ ์์ฃ .
์, ์ด์ ์ฐ๋ฆฌ๋ ์น ์ค๋์ค API์ ๊ธฐ๋ณธ์ ์ธ ๊ฐ๋ ๋ค์ ์ดํด๋ณด์์ด์. ์ด๊ฒ๋ค์ด ์ฐ๋ฆฌ๊ฐ ๋ธ๋ผ์ฐ์ ์์ ์์ ์ ๋ง๋ค ๋ ์ฌ์ฉํ ๊ธฐ๋ณธ ์ฌ๋ฃ๋ค์ด์์. ๋ง์น ํ๊ฐ๊ฐ ํ๋ ํธ ์์ ๋ฌผ๊ฐ์ ์ค๋นํ๋ ๊ฒ์ฒ๋ผ, ์ฐ๋ฆฌ๋ ์ด์ ์๋ฆฌ์ ํ๋ ํธ๋ฅผ ์ค๋นํ ์ ์ด์ฃ .
๋ค์ ์น์ ์์๋ ์ด ๊ฐ๋ ๋ค์ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ป๊ฒ ํ๋ฉด ๋ฉ์ง ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ผ ์ ์๋์ง ์์ธํ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๊ฐ ๊ณง ๋ฉ์ง ์์ ์คํ๋์ค๋ก ๋ณ์ ํ ์ค๋น๊ฐ ๋์๋์? ๊ทธ๋ผ ๊ณ์ํด์ ์ฐ๋ฆฌ์ ์์ ์ ์ฌ์ ์ ์ด์ด๊ฐ๋ณผ๊น์? ๐ถ๐
๐บ ์๋ฆฌ ๋ง๋ค๊ธฐ: ์ค์ค๋ ์ดํฐ์ ์น๊ตฌ๋ค
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ ๋๋ค! ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์๋ฆฌ๋ฅผ ์์ฑํ ์ ์์ด์. ๊ทธ ์ค์์๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ด๊ณ ๊ฐ๋ ฅํ ๋๊ตฌ๊ฐ ๋ฐ๋ก '์ค์ค๋ ์ดํฐ(Oscillator)'์ ๋๋ค. ์ค์ค๋ ์ดํฐ๋ ์ฃผ๊ธฐ์ ์ธ ํํ์ ์์ฑํ๋ ์ฅ์น๋ก, ๊ธฐ๋ณธ์ ์ธ ์์์ ๋ง๋๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๋ง์น ์ ์ ํค๋ณด๋์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ์๋ฆฌ๋ฅผ ๋ ์ฌ๋ฆฌ์๋ฉด ๋ ๊ฒ ๊ฐ์์. ๐น
๐ ์ค์ค๋ ์ดํฐ: ์๋ฆฌ์ ๊ธฐ๋ณธ ์ฌ๋ฃ
์ค์ค๋ ์ดํฐ๋ ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ณธ์ ์ธ ํํ๋ค์ ์์ฑํ ์ ์์ต๋๋ค:
- ์ฌ์ธํ (Sine wave): ๊ฐ์ฅ ์์ํ๊ณ ๋ถ๋๋ฌ์ด ์๋ฆฌ
- ์ฌ๊ฐํ (Square wave): ๋ ์นด๋กญ๊ณ ๊ฑฐ์น ์๋ฆฌ
- ์ผ๊ฐํ (Triangle wave): ๋ถ๋๋ฝ์ง๋ง ์ฌ์ธํ๋ณด๋ค๋ ์ฝ๊ฐ ๋ ์นด๋ก์ด ์๋ฆฌ
- ํฑ๋ํ (Sawtooth wave): ๊ฑฐ์น ๊ณ ํ์ฑํ ์๋ฆฌ
์ค์ค๋ ์ดํฐ๋ฅผ ์์ฑํ๊ณ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// ์ค์ค๋ ์ดํฐ ์์ฑ
const oscillator = audioContext.createOscillator();
// ์ฃผํ์ ์ค์ (440Hz๋ A4 ์)
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
// ํํ ์ค์ (๊ธฐ๋ณธ๊ฐ์ 'sine')
oscillator.type = 'sine'; // 'square', 'triangle', 'sawtooth' ์ค ์ ํ ๊ฐ๋ฅ
// ์ค๋์ค ์ถ๋ ฅ์ ์ฐ๊ฒฐ
oscillator.connect(audioContext.destination);
// ์ค์ค๋ ์ดํฐ ์์
oscillator.start();
// 5์ด ํ์ ์ค์ค๋ ์ดํฐ ์ ์ง
setTimeout(() => oscillator.stop(), 5000);
์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด, 440Hz์ A4 ์์ด 5์ด ๋์ ์ฌ์๋ฉ๋๋ค. ๋ง์น ํผ์๋ ธ์ A ํค๋ฅผ ๋๋ฅธ ๊ฒ๊ณผ ๊ฐ์ ํจ๊ณผ์ฃ !
๐ญ ๊ฒ์ธ ๋ ธ๋: ๋ณผ๋ฅจ์ ๋ง๋ฒ์ฌ
์ค์ค๋ ์ดํฐ๋ง์ผ๋ก๋ ์๋ฆฌ์ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ๊ธฐ ์ด๋ ค์์. ์ด๋ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฐ๋ก '๊ฒ์ธ ๋ ธ๋(Gain Node)'์ ๋๋ค. ๊ฒ์ธ ๋ ธ๋๋ ์๋ฆฌ์ ๋ณผ๋ฅจ์ ์กฐ์ ํ๋ ์ญํ ์ ํฉ๋๋ค. ๋ง์น ์คํผ์ปค์ ๋ณผ๋ฅจ ์กฐ์ ๊ธฐ์ ๊ฐ์ ์ญํ ์ ํ๋ค๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
๊ฒ์ธ ๋ ธ๋๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
// ๊ฒ์ธ ๋
ธ๋ ์์ฑ
const gainNode = audioContext.createGain();
// ๊ฒ์ธ ๊ฐ ์ค์ (0์์ 1 ์ฌ์ด์ ๊ฐ, 1์ด ์๋ ๋ณผ๋ฅจ)
gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
// ์ค์ค๋ ์ดํฐ๋ฅผ ๊ฒ์ธ ๋
ธ๋์ ์ฐ๊ฒฐ
oscillator.connect(gainNode);
// ๊ฒ์ธ ๋
ธ๋๋ฅผ ์ค๋์ค ์ถ๋ ฅ์ ์ฐ๊ฒฐ
gainNode.connect(audioContext.destination);
// ๋ณผ๋ฅจ ํ์ด๋ ์ธ ํจ๊ณผ
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2);
์ด ์ฝ๋๋ฅผ ์ฌ์ฉํ๋ฉด, ์๋ฆฌ๊ฐ ์์ํ ์ปค์ง๋ ํ์ด๋ ์ธ ํจ๊ณผ๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ๋ง์น ์์ ์ ์์ ๋ถ๋ถ์์ ๋ณผ๋ฅจ์ด ์ ์ ์ปค์ง๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ฃ !
๐จ ๋ฒํผ ์์ค: ๋๋ง์ ์๋ฆฌ ๋ง๋ค๊ธฐ
์ค์ค๋ ์ดํฐ๋ ๊ธฐ๋ณธ์ ์ธ ํํ๋ง์ ์ ๊ณตํ์ง๋ง, ๋๋ก๋ ๋ ๋ณต์กํ๊ณ ๋ ํนํ ์๋ฆฌ๊ฐ ํ์ํ ๋๊ฐ ์์ต๋๋ค. ์ด๋ด ๋ ์ฌ์ฉํ๋ ๊ฒ์ด '๋ฒํผ ์์ค(Buffer Source)'์ ๋๋ค. ๋ฒํผ ์์ค๋ฅผ ์ฌ์ฉํ๋ฉด ์ฐ๋ฆฌ๊ฐ ์ง์ ์ ์ํ ํํ์ ์ฌ์ํ ์ ์์ด์.
๋ค์์ ๋ฒํผ ์์ค๋ฅผ ์ฌ์ฉํด ๊ฐ๋จํ ์๋ฆฌ๋ฅผ ๋ง๋๋ ์์ ์ ๋๋ค:
// ๋ฒํผ ์์ฑ (1์ด ๊ธธ์ด์ ๋ฒํผ)
const bufferSize = audioContext.sampleRate;
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
// ๋ฒํผ์ ๋ฐ์ดํฐ ์ฑ์ฐ๊ธฐ
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
// ์ฌ๊ธฐ์๋ ๊ฐ๋จํ ์ฌ์ธํ๋ฅผ ์์ฑํฉ๋๋ค
data[i] = Math.sin(2 * Math.PI * 440 * i / audioContext.sampleRate);
}
// ๋ฒํผ ์์ค ๋
ธ๋ ์์ฑ
const bufferSource = audioContext.createBufferSource();
bufferSource.buffer = buffer;
// ์ค๋์ค ์ถ๋ ฅ์ ์ฐ๊ฒฐ
bufferSource.connect(audioContext.destination);
// ์ฌ์ ์์
bufferSource.start();
์ด ์ฝ๋๋ 1์ด ๊ธธ์ด์ 440Hz ์ฌ์ธํ๋ฅผ ์์ฑํ๊ณ ์ฌ์ํฉ๋๋ค. ํ์ง๋ง ๋ฒํผ ์์ค์ ์ง์ ํ ํ์ ์ด๋ณด๋ค ํจ์ฌ ๋ ๋ณต์กํ๊ณ ๋ ํนํ ์๋ฆฌ๋ฅผ ๋ง๋ค ์ ์๋ค๋ ์ ์ ๋๋ค. ์ฌ๋ฌ๋ถ์ ์์๋ ฅ์ด ๊ณง ์๋ฆฌ๊ฐ ๋๋ ๊ฑฐ์ฃ !
๐ต ํ์ ๋ง๋ค๊ธฐ: ์ฌ๋ฌ ์๋ฆฌ์ ์กฐํ
์ง๊ธ๊น์ง๋ ๋จ์ผ ์์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์ดํด๋ดค๋๋ฐ์, ์ด์ ์ฌ๋ฌ ์์ ์กฐํฉํด ํ์์ ๋ง๋ค์ด๋ณผ๊น์? ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ์ค์ค๋ ์ดํฐ๋ฅผ ๋์์ ์ฌ์ฉํด ํ์ฑํ ํ์์ ๋ง๋ค ์ ์์ต๋๋ค.
๋ค์์ ๊ฐ๋จํ C ๋ฉ์ด์ ์ฝ๋(C, E, G)๋ฅผ ๋ง๋๋ ์์ ์ ๋๋ค:
function playChord() {
const frequencies = [261.63, 329.63, 392.00]; // C4, E4, G4์ ์ฃผํ์
const oscillators = [];
frequencies.forEach(freq => {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.frequency.setValueAtTime(freq, audioContext.currentTime);
osc.connect(gain);
gain.connect(audioContext.destination);
// ๋ถ๋๋ฌ์ด ์์๊ณผ ๋์ ์ํ ํ์ด๋ ์ธ/์ ์ ํจ๊ณผ
gain.gain.setValueAtTime(0, audioContext.currentTime);
gain.gain.linearRampToValueAtTime(0.2, audioContext.currentTime + 0.1);
gain.gain.linearRampToValueAtTime(0, audioContext.currentTime + 2);
osc.start();
osc.stop(audioContext.currentTime + 2);
oscillators.push(osc);
});
}
// ์ฝ๋ ์ฌ์
playChord();
์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด, C ๋ฉ์ด์ ์ฝ๋๊ฐ ๋ถ๋๋ฝ๊ฒ ์์๋์ด 2์ด ๋์ ์ฌ์๋ ํ ํ์ด๋ ์์๋ฉ๋๋ค. ๋ง์น ํผ์๋ ธ๋ก ์ฝ๋๋ฅผ ๋ถ๋๋ฝ๊ฒ ์ฐ์ฃผํ๋ ๊ฒ๊ณผ ๊ฐ์ ํจ๊ณผ๋ฅผ ๋ผ ์ ์์ฃ !
๐๏ธ ํํฐ: ์๋ฆฌ์ ์กฐ๊ฐ๊ฐ
๋๋ก๋ ์๋์ ์๋ฆฌ๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ, ํน์ ์ฃผํ์ ๋์ญ์ ๊ฐ์กฐํ๊ฑฐ๋ ์ ๊ฑฐํ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค. ์ด๋ด ๋ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฐ๋ก 'ํํฐ(Filter)'์ ๋๋ค. ํํฐ๋ ์๋ฆฌ์ ํน์ ์ฃผํ์ ์ฑ๋ถ์ ๋ณํ์์ผ ์์์ ๋ฐ๊พธ๋ ์ญํ ์ ํฉ๋๋ค.
์น ์ค๋์ค API์์๋ ๋ค์ํ ์ข ๋ฅ์ ํํฐ๋ฅผ ์ ๊ณตํฉ๋๋ค:
- ๋ก์ฐํจ์ค ํํฐ: ๋ฎ์ ์ฃผํ์๋ ํต๊ณผ์ํค๊ณ ๋์ ์ฃผํ์๋ ์ฐจ๋จ
- ํ์ดํจ์ค ํํฐ: ๋์ ์ฃผํ์๋ ํต๊ณผ์ํค๊ณ ๋ฎ์ ์ฃผํ์๋ ์ฐจ๋จ
- ๋ฐด๋ํจ์ค ํํฐ: ํน์ ์ฃผํ์ ๋์ญ๋ง ํต๊ณผ์ํค๊ณ ๋๋จธ์ง๋ ์ฐจ๋จ
- ๋ ธ์น ํํฐ: ํน์ ์ฃผํ์ ๋์ญ๋ง ์ฐจ๋จํ๊ณ ๋๋จธ์ง๋ ํต๊ณผ
๋ค์์ ๋ก์ฐํจ์ค ํํฐ๋ฅผ ์ฌ์ฉํ๋ ์์ ์ ๋๋ค:
// ์ค์ค๋ ์ดํฐ ์์ฑ
const oscillator = audioContext.createOscillator();
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
// ํํฐ ์์ฑ
const filter = audioContext.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1000, audioContext.currentTime);
// ์ฐ๊ฒฐ: ์ค์ค๋ ์ดํฐ -> ํํฐ -> ์ถ๋ ฅ
oscillator.connect(filter);
filter.connect(audioContext.destination);
// ์์
oscillator.start();
// ํํฐ ์ฃผํ์๋ฅผ ์๊ฐ์ ๋ฐ๋ผ ๋ณ๊ฒฝ
filter.frequency.linearRampToValueAtTime(100, audioContext.currentTime + 2);
์ด ์ฝ๋๋ ํฑ๋ํ ์ค์ค๋ ์ดํฐ์ ์๋ฆฌ๋ฅผ ๋ก์ฐํจ์ค ํํฐ๋ก ๊ฑธ๋ฌ๋ ๋๋ค. ํํฐ์ ์ฐจ๋จ ์ฃผํ์๊ฐ 2์ด์ ๊ฑธ์ณ 1000Hz์์ 100Hz๋ก ๋ฎ์์ง๋ฏ๋ก, ์๋ฆฌ๊ฐ ์ ์ ๋ํํด์ง๋ ๊ฒ์ ๋ค์ ์ ์์ต๋๋ค. ๋ง์น ์ฐ์ฃผ์ ์ด ๋ฉ์ด์ง๋ ๋ฏํ ํจ๊ณผ๋ฅผ ๋ผ ์ ์์ฃ !
๐ญ ์ปจ๋ณผ๋ฒ: ๊ณต๊ฐ๊ฐ์ ๋ง์ ์ฌ
๋ง์ง๋ง์ผ๋ก ์๊ฐํ ๊ฒ์ '์ปจ๋ณผ๋ฒ(Convolver)' ๋ ธ๋์ ๋๋ค. ์ปจ๋ณผ๋ฒ๋ ์๋์ ์๋ฆฌ์ ํน์ ํ ์ํฅ ํน์ฑ์ ์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์์ ๋ฐฉ์์ ๋ น์๋ ์๋ฆฌ๋ฅผ ํฐ ์ฝ์ํธํ์์ ์ฐ์ฃผ๋ ๊ฒ์ฒ๋ผ ๋ค๋ฆฌ๊ฒ ๋ง๋ค ์ ์์ฃ .
์ปจ๋ณผ๋ฒ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด '์ํ์ค ์๋ต(Impulse Response)'์ด๋ผ๋ ํน๋ณํ ์ค๋์ค ์ํ์ด ํ์ํฉ๋๋ค. ์ด๋ ํน์ ๊ณต๊ฐ์ ์ํฅ ํน์ฑ์ ๋ด๊ณ ์๋ ์งง์ ์ค๋์ค ํ์ผ์ ๋๋ค.
// ์ํ์ค ์๋ต ๋ก๋
fetch('concert-hall.wav')
.then(response => response.arrayBuffer())
.then(arrayBuffer => audioContext.decodeAudioData(arrayBuffer))
.then(audioBuffer => {
// ์ปจ๋ณผ๋ฒ ๋
ธ๋ ์์ฑ
const convolver = audioContext.createConvolver();
convolver.buffer = audioBuffer;
// ์ค๋์ค ์์ค (์ฌ๊ธฐ์๋ ์ค์ค๋ ์ดํฐ ์ฌ์ฉ)
const oscillator = audioContext.createOscillator();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(440, audioContext.currentTime);
// ์ฐ๊ฒฐ: ์ค์ค๋ ์ดํฐ -> ์ปจ๋ณผ๋ฒ -> ์ถ๋ ฅ
oscillator.connect(convolver);
convolver.connect(audioContext.destination);
// ์์
oscillator.start();
});
์ด ์ฝ๋๋ ๋จ์ํ ์ฌ์ธํ ์๋ฆฌ๋ฅผ ์ฝ์ํธํ์์ ์ฐ์ฃผ๋๋ ๊ฒ์ฒ๋ผ ๋ค๋ฆฌ๊ฒ ๋ง๋ญ๋๋ค. ๋ง์น ์์ ์คํผ์ปค๋ก ํฐ ๊ณต์ฐ์ฅ์ ์ํฅ์ ์ฌํํ๋ ๊ฒ๊ณผ ๊ฐ์ฃ !
์, ์ด์ ์ฐ๋ฆฌ๋ ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํด ๋ค์ํ ์๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ์กฐ์ํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ ์ต๋๋ค. ์ค์ค๋ ์ดํฐ๋ก ๊ธฐ๋ณธ ์์ ๋ง๋ค๊ณ , ๊ฒ์ธ ๋ ธ๋๋ก ๋ณผ๋ฅจ์ ์กฐ์ ํ๊ณ , ํํฐ๋ก ์์์ ๋ฐ๊พธ๊ณ , ์ปจ๋ณผ๋ฒ๋ก ๊ณต๊ฐ๊ฐ์ ๋ํ์ฃ . ์ด ๋ชจ๋ ์์๋ค์ ์กฐํฉํ๋ฉด, ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ์์์ ํ๋ถํ๊ณ ๋ค์ฑ๋ก์ด ์ฌ์ด๋ ์๋๋ฅผ ๋ง๋ค์ด๋ผ ์ ์์ต๋๋ค!
๋ค์ ์น์ ์์๋ ์ด๋ฌํ ๊ธฐ๋ณธ ์์๋ค์ ์ด๋ป๊ฒ ์กฐํฉํด ์ค์ ์์ ์ด๋ ์ฌ์ด๋ ์ดํํธ๋ฅผ ๋ง๋ค ์ ์๋์ง ์์๋ณด๊ฒ ์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ์ฐฝ์๋ ฅ์ ๋ง์๊ป ๋ฐํํ ์ค๋น๊ฐ ๋์ จ๋์? ๊ทธ๋ผ ๊ณ์ํด์ ์ฐ๋ฆฌ์ ์น ์ค๋์ค ์ฌํ์ ์ด์ด๊ฐ๋ณผ๊น์? ๐ถ๐
๐ผ ์์ ๋ง๋ค๊ธฐ: ์น ์ค๋์ค API์ ์์ ์ ํ์ฉ
์ง๊ธ๊น์ง ์ฐ๋ฆฌ๋ ์น ์ค๋์ค API์ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์ฑ ์์๋ค์ ์ดํด๋ณด์์ต๋๋ค. ์ด์ ์ด ์์๋ค์ ์กฐํฉํด ์ค์ ์์ ์ ๋ง๋ค์ด๋ณผ ์ฐจ๋ก์ ๋๋ค. ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ ์์ ์คํ๋์ค๋ก ๋ณ์ ํ ์ค๋น๊ฐ ๋์๋์? ๊ทธ๋ผ ์์ํด๋ณผ๊น์! ๐น๐ธ๐ฅ
๐ต ๊ฐ๋จํ ์ํ์ ๋ง๋ค๊ธฐ
๋จผ์ , ๊ฐ๋จํ ์ํ์๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์ํ์๋ ๋ฏธ๋ฆฌ ์ ํด์ง ์์๋๋ก ์์ ์ฌ์ํ๋ ์ฅ์น์ ๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ๋จํ ๋ฉ๋ก๋๋ ๋ฆฌ๋ฌ์ ๋ง๋ค ์ ์์ฃ .
const notes = [
{ note: 'C4', duration: 0.5 },
{ note: 'D4', duration: 0.5 },
{ note: 'E4', duration: 0.5 },
{ note: 'F4', duration: 0.5 },
{ note: 'G4', duration: 1 },
{ note: 'A4', duration: 1 },
{ note: 'B4', duration: 1 },
{ note: 'C5', duration: 2 }
];
const noteFrequencies = {
'C4': 261.63, 'D4': 293.66, 'E4': 329.63, 'F4': 349.23,
'G4': 392.00, 'A4': 440.00, 'B4': 493.88, 'C5': 523.25
};
function playSequence() {
let time = audioContext.currentTime;
notes.forEach(note => {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.frequency.setValueAtTime(noteFrequencies[note.note], time);
gain.gain.setValueAtTime(0.5, time);
osc.connect(gain);
gain.connect(audioContext.destination);
osc.start(time);
osc.stop(time + note.duration);
time += note.duration;
});
}
// ์ํ์ค ์ฌ์
playSequence();
์ด ์ฝ๋๋ C ๋ฉ์ด์ ์ค์ผ์ผ์ ์ฐ์ฃผํฉ๋๋ค. ๊ฐ ์์ ๊ธธ์ด๋ฅผ ๋ค๋ฅด๊ฒ ์ค์ ํด ๊ฐ๋จํ ๋ฉ๋ก๋๋ฅผ ๋ง๋ค์ด๋์ฃ . ๋ง์น ์์ ์์ ์์๋ฅผ ์ด์ ๊ฒ ๊ฐ์ ๋๋์ด ๋ค์ง ์๋์?
๐ฅ ๋๋ผ ๋จธ์ ๋ง๋ค๊ธฐ
๋ค์์ผ๋ก, ๊ฐ๋จํ ๋๋ผ ๋จธ์ ์ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์ด๋ฒ์๋ ์ค์ค๋ ์ดํฐ ๋์ ์ค๋์ค ๋ฒํผ๋ฅผ ์ฌ์ฉํด ๋ ์ค์ ์ ๊ธฐ์ ๋น์ทํ ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์.
const kickBuffer = await loadSample('kick.wav');
const snareBuffer = await loadSample('snare.wav');
const hihatBuffer = await loadSample('hihat.wav');
const drumPattern = [
['kick', 'hihat'],
['hihat'],
['snare', 'hihat'],
['hihat'],
['kick', 'hihat'],
['hihat'],
['snare', 'hihat'],
['hihat']
];
function playDrums() {
let time = audioContext.currentTime;
const tempo = 120; // BPM
const beatDuration = 60 / tempo;
drumPattern.forEach(beat => {
beat.forEach(instrument => {
const source = audioContext.createBufferSource();
source.buffer = instrument === 'kick' ? kickBuffer :
instrument === 'snare' ? snareBuffer : hihatBuffer;
source.connect(audioContext.destination);
source.start(time);
});
time += beatDuration;
});
}
// ๋๋ผ ํจํด ์ฌ์
playDrums();
์ด ์ฝ๋๋ ๊ธฐ๋ณธ์ ์ธ 8๋นํธ ๋๋ผ ํจํด์ ๋ง๋ค์ด๋ ๋๋ค. ํฅ, ์ค๋ค์ด, ํ์ดํ์ ์กฐํฉํด ๋ฆฌ๋ฌ๊ฐ ์๋ ๋นํธ๋ฅผ ๋ง๋ค์ด๋์ฃ . ์ด์ ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ ๋๋ผ ๋จธ์ ์ผ๋ก ๋ณ์ ํ์ต๋๋ค!
๐ธ ๊ธฐํ ์๋ฎฌ๋ ์ด์
์ด๋ฒ์๋ ์กฐ๊ธ ๋ ๋ณต์กํ ์ ๊ธฐ์ธ ๊ธฐํ๋ฅผ ์๋ฎฌ๋ ์ด์ ํด๋ณด๊ฒ ์ต๋๋ค. ๊ธฐํ์ ํน์ง์ ์ธ ์๋ฆฌ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์ฌ๋ฌ ๊ฐ์ ์ค์ค๋ ์ดํฐ์ ํํฐ, ๊ทธ๋ฆฌ๊ณ ์๋ฒจ๋กํ๋ฅผ ์ฌ์ฉํ ๊ฑฐ์์.
function playGuitar(frequency, duration) {
const oscillators = [];
const gains = [];
// 6๊ฐ์ ์ค์ค๋ ์ดํฐ๋ก ๊ธฐํ ์คํธ๋ง ์๋ฎฌ๋ ์ด์
for (let i = 0; i < 6; i++) {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(frequency * (1 + i * 0.01), audioContext.currentTime);
gain.gain.setValueAtTime(0.5, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration);
osc.connect(gain);
gain.connect(audioContext.destination);
oscillators.push(osc);
gains.push(gain);
}
// ํํฐ ์ถ๊ฐ
const filter = audioContext.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(5000, audioContext.currentTime);
filter.frequency.exponentialRampToValueAtTime(500, audioContext.currentTime + 0.5);
gains.forEach(gain => gain.connect(filter));
filter.connect(audioContext.destination);
// ์ค์ค๋ ์ดํฐ ์์
oscillators.forEach(osc => osc.start());
// ์ค์ค๋ ์ดํฐ ์ ์ง
setTimeout(() => {
oscillators.forEach(osc => osc.stop());
}, duration * 1000);
}
// E ์ฝ๋ ์ฐ์ฃผ
playGuitar(329.63, 2); // E4 ์
setTimeout(() => playGuitar(493.88, 2), 500); // B4 ์
setTimeout(() => playGuitar(659.25, 2), 1000); // E5 ์
์ด ์ฝ๋๋ E ์ฝ๋๋ฅผ ์ฐ์ฃผํฉ๋๋ค. ์ฌ๋ฌ ๊ฐ์ ์ค์ค๋ ์ดํฐ๋ฅผ ์ฌ์ฉํด ๊ธฐํ ์ค์ ์ง๋์ ์๋ฎฌ๋ ์ด์ ํ๊ณ , ํํฐ๋ฅผ ์ฌ์ฉํด ๊ธฐํ์ ํน์ง์ ์ธ ์์์ ๋ง๋ค์ด๋์ต๋๋ค. ๋ง์น ์ค์ ๊ธฐํ๋ฅผ ์ฐ์ฃผํ๋ ๊ฒ ๊ฐ์ ๋๋์ด ๋ค์ง ์๋์?
๐น ์ ๋์ฌ์ด์ ๋ง๋ค๊ธฐ
๋ง์ง๋ง์ผ๋ก, ๊ฐ๋จํ ์ ๋์ฌ์ด์ ๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์ ๋์ฌ์ด์ ๋ ๋ค์ํ ํํ๊ณผ ์๋ฒจ๋กํ, ํํฐ ๋ฑ์ ์กฐํฉํด ํ๋ถํ ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋ด๋ ์ ๊ธฐ์ ๋๋ค.
class Synth {
constructor(audioContext) {
this.audioContext = audioContext;
this.oscillator = this.audioContext.createOscillator();
this.gainNode = this.audioContext.createGain();
this.filter = this.audioContext.createBiquadFilter();
this.oscillator.type = 'sawtooth';
this.filter.type = 'lowpass';
this.filter.frequency.setValueAtTime(1000, this.audioContext.currentTime);
this.oscillator.connect(this.filter);
this.filter.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
this.oscillator.start();
}
playNote(frequency, duration) {
const now = this.audioContext.currentTime;
// ์ฃผํ์ ์ค์
this.oscillator.frequency.setValueAtTime(frequency, now);
// ์๋ฒจ๋กํ ์ ์ฉ
this.gainNode.gain.cancelScheduledValues(now);
this.gainNode.gain.setValueAtTime(0, now);
this.gainNode.gain.linearRampToValueAtTime(1, now + 0.01);
this.gainNode.gain.exponentialRampToValueAtTime(0.01, now + duration);
// ํํฐ ์ค์
this.filter.frequency.cancelScheduledValues(now);
this.filter.frequency.setValueAtTime(100, now);
this.filter.frequency.exponentialRampToValueAtTime(3000, now + 0.5);
}
}
const synth = new Synth(audioContext);
// C ๋ฉ์ด์ ์ฝ๋ ์ฐ์ฃผ
synth.playNote(261.63, 1); // C4
setTimeout(() => synth.playNote(329.63, 1), 250); // E4
setTimeout(() => synth.playNote(392.00, 1), 500); // G4
์ด ์ฝ๋๋ ๊ฐ๋จํ ์ ๋์ฌ์ด์ ๋ฅผ ๊ตฌํํฉ๋๋ค. ํฑ๋ํ ์ค์ค๋ ์ดํฐ, ๋ก์ฐํจ์ค ํํฐ, ๊ทธ๋ฆฌ๊ณ ADSR ์๋ฒจ๋กํ๋ฅผ ์ฌ์ฉํด ํ๋ถํ ์ ๋์ฌ์ด์ ์๋ฆฌ๋ฅผ ๋ง๋ค์ด๋์ต๋๋ค. C ๋ฉ์ด์ ์ฝ๋๋ฅผ ์ฐ์ฃผํ๋ฉด ๋ง์น 80๋ ๋ ์ ์คํ์ ๋ฃ๋ ๊ฒ ๊ฐ์ ๋๋์ด ๋ค ๊ฑฐ์์!
์์ฐ! ์ด์ ์ฐ๋ฆฌ๋ ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํด ๋ค์ํ ์ ๊ธฐ๋ฅผ ์๋ฎฌ๋ ์ด์ ํ๊ณ , ์ค์ ์์ ์ ๋ง๋ค์ด๋ณด์์ต๋๋ค. ์ํ์, ๋๋ผ ๋จธ์ , ๊ธฐํ, ์ ๋์ฌ์ด์ ๋ฑ ๋ค์ํ ์ ๊ธฐ๋ค์ด ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ์์์ ์๋ช ์ ์ป์์ฃ .
์ด๊ฒ์ ๋จ์ง ์์์ผ ๋ฟ์ ๋๋ค. ์น ์ค๋์ค API์ ๊ฐ๋ฅ์ฑ์ ๋ฌด๊ถ๋ฌด์งํฉ๋๋ค. ์ฌ๋ฌ๋ถ์ ์์๋ ฅ๊ณผ ์ฐฝ์๋ ฅ์ ๋ํด ๋์ฑ ๋ณต์กํ๊ณ ์๋ฆ๋ค์ด ์์ ์ ๋ง๋ค์ด๋ผ ์ ์์ ๊ฑฐ์์. ์ด์ฉ๋ฉด ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ์ ์์ ํ ๋์งํธ ์ค๋์ค ์ํฌ์คํ ์ด์ (DAW)์ ๋ง๋ค ์๋ ์๊ฒ ์ฃ !
๋ค์ ์น์ ์์๋ ์ด๋ฌํ ์์ ์ ์์๋ค์ ์๊ฐํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค. ์๋ฆฌ๋ฅผ ๋ฃ๋ ๊ฒ๋ ์ข์ง๋ง, ๋ณผ ์ ์๋ค๋ฉด ์ด๋จ๊น์? ์์ ์ ์ธ๊ณ๋ฅผ ๋์ผ๋ก ๋ณด๋ ๊ฒฝํ์ ํจ๊ป ํด๋ณด์์ฃ ! ๐ต๐
๐๏ธ ์ค๋์ค ์๊ฐํ: ์๋ฆฌ๋ฅผ ๋์ผ๋ก ๋ณด๋ค
์์ ์ ๋ฃ๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ ์๋ฆ๋ต์ง๋ง, ๊ทธ๊ฒ์ ์๊ฐ์ ์ผ๋ก ํํํ ์ ์๋ค๋ฉด ์ด๋จ๊น์? ์น ์ค๋์ค API์ HTML5 Canvas๋ฅผ ๊ฒฐํฉํ๋ฉด, ์ฐ๋ฆฌ๋ ์๋ฆฌ๋ฅผ ๋์ผ๋ก ๋ณผ ์ ์๋ ๋ฉ์ง ์๊ฐํ๋ฅผ ๋ง๋ค์ด๋ผ ์ ์์ต๋๋ค. ์ด์ ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ ์์ ์คํ๋์ค์์ ํ ๊ฑธ์ ๋ ๋์๊ฐ ๋ฉ์ง ์๊ฐ ํจ๊ณผ๋ฅผ ๊ฐ์ถ ๊ณต์ฐ์ฅ์ผ๋ก ๋ณ์ ํ ์ฐจ๋ก์ ๋๋ค! ๐ญ๐จ
๐ ์ฃผํ์ ๋ถ์๊ธฐ
๋จผ์ , ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํํ์ ์ค๋์ค ์๊ฐํ์ธ ์ฃผํ์ ๋ถ์๊ธฐ๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์ด๋ ์์ ์ ๊ฐ ์ฃผํ์ ๋์ญ์ ๊ฐ๋๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๋๊ตฌ์ ๋๋ค.
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// ์ค๋์ค ์์ค๋ฅผ ๋ถ์๊ธฐ์ ์ฐ๊ฒฐ
source.connect(analyser);
analyser.connect(audioContext.destination);
function draw() {
const canvas = document.getElementById('visualizer');
const canvasCtx = canvas.getContext('2d');
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
canvasCtx.fillStyle = 'rgb(0, 0, 0)';
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
const barWidth = (WIDTH / bufferLength) * 2.5;
let barHeight;
let x = 0;
for(let i = 0; i < bufferLength; i++) {
barHeight = dataArray[i] / 2;
canvasCtx.fillStyle = `rgb(${barHeight + 100},50,50)`;
canvasCtx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
draw();
์ด ์ฝ๋๋ ์์ ์ ์ฃผํ์ ์คํํธ๋ผ์ ์ค์๊ฐ์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๋ง๋ ๊ทธ๋ํ๋ฅผ ๊ทธ๋ฆฝ๋๋ค. ์ ์๋ถํฐ ๊ณ ์๊น์ง, ๊ฐ ์ฃผํ์ ๋์ญ์ ๊ฐ๋๊ฐ ๋ง๋์ ๋์ด๋ก ํํ๋ฉ๋๋ค. ๋ง์น ์ดํ๋ผ์ด์ ๋ฅผ ๋ณด๋ ๊ฒ ๊ฐ์ฃ ?
๐ ํํ ์๊ฐํ
๋ค์์ผ๋ก, ์ค๋์ค์ ํํ์ ์ง์ ๊ทธ๋ ค๋ณด๋ ์๊ฐํ๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์ด๋ ์๋ฆฌ์ ์งํญ ๋ณํ๋ฅผ ์๊ฐ์ ๋ฐ๋ผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ ๋๋ค.
function drawWaveform() {
const canvas = document.getElementById('waveform');
const canvasCtx = canvas.getContext('2d');
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
requestAnimationFrame(drawWaveform);
analyser.getByteTimeDomainData(dataArray);
canvasCtx.fillStyle = 'rgb(200, 200, 200)';
canvasCtx.fillRect(0, 0, WIDTH, HEIGHT);
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = 'rgb(0, 0, 0)';
canvasCtx.beginPath();
const sliceWidth = WIDTH * 1.0 / bufferLength;
let x = 0;
for(let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * HEIGHT/2;
if(i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(canvas.width, canvas.height/2);
canvasCtx.stroke();
}
drawWaveform();
์ด ์ฝ๋๋ ์ค๋์ค์ ํํ์ ์ค์๊ฐ์ผ๋ก ๊ทธ๋ ค๋ ๋๋ค. ์๋ฆฌ์ ์งํญ ๋ณํ๊ฐ ์๊ฐ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ๋ณํ๋์ง๋ฅผ ์ง์ ๋ณผ ์ ์์ฃ . ๋ง์น ์ค์ค๋ก์ค์ฝํ๋ฅผ ๋ณด๋ ๊ฒ ๊ฐ์ ๋๋์ด ๋ค์ง ์๋์?
๐ 3D ์๊ฐํ
์ด์ ์ข ๋ ํ๋ คํ ๊ฒ์ ๋ง๋ค์ด๋ณผ๊น์? Three.js ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํด ์ค๋์ค๋ฅผ 3D๋ก ์๊ฐํํด๋ณด๊ฒ ์ต๋๋ค.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
analyser.getByteFrequencyData(dataArray);
// ์ฃผํ์ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ํ๋ธ ํฌ๊ธฐ ๋ณ๊ฒฝ
const baseScale = 1;
const scaleX = baseScale + (dataArray[0] / 128.0);
const scaleY = baseScale + (dataArray[Math.floor(bufferLength / 2)] / 128.0);
const scaleZ = baseScale + (dataArray[bufferLength - 1] / 128.0);
cube.scale.set(scaleX, scaleY, scaleZ);
// ํ๋ธ ํ์
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
์ด ์ฝ๋๋ 3D ๊ณต๊ฐ์ ํ๋ธ๋ฅผ ๊ทธ๋ฆฌ๊ณ , ์ค๋์ค์ ์ฃผํ์ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ํ๋ธ์ ํฌ๊ธฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ณํ์ํต๋๋ค. ์ ์, ์ค์, ๊ณ ์ ๊ฐ๊ฐ์ด ํ๋ธ์ x, y, z ์ถ ํฌ๊ธฐ์ ์ํฅ์ ์ค๋๋ค. ์์ ์ ๋ง์ถฐ ์ถค์ถ๋ ํ๋ธ๋ผ๊ณ ์๊ฐํ๋ฉด ๋๊ฒ ๋ค์!
๐จ ํํฐํด ์์คํ
๋ง์ง๋ง์ผ๋ก, ์ข ๋ ์ ๊ธฐ์ ์ด๊ณ ํ๋ฅด๋ ๋ฏํ ์๊ฐํ๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ํํฐํด ์์คํ ์ ์ด์ฉํด ์์ ์ ๋ฐ์ํ๋ ํ๋ คํ ํจ๊ณผ๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์.
const particles = [];
const particleCount = 1000;
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: Math.random() * 5 + 1,
color: `hsl(${Math.random() * 360}, 50%, 50%)`,
vx: Math.random() * 2 - 1,
vy: Math.random() * 2 - 1
});
}
function drawParticles() {
const canvas = document.getElementById('particles');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
analyser.getByteFrequencyData(dataArray);
const averageFrequency = dataArray.reduce((a, b) => a + b) / bufferLength;
particles.forEach(particle => {
particle.x += particle.vx * (averageFrequency / 128);
particle.y += particle.vy * (averageFrequency / 128);
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
});
requestAnimationFrame(drawParticles);
}
drawParticles();
์ด ์ฝ๋๋ ํ๋ฉด์ ์๋ง์ ์ปฌ๋ฌํํ ํํฐํด์ ๊ทธ๋ฆฌ๊ณ , ์์ ์ ํ๊ท ์ฃผํ์์ ๋ฐ๋ผ ํํฐํด์ ์์ง์ ์๋๋ฅผ ์กฐ์ ํฉ๋๋ค. ์์ ์ด ๊ณ ์กฐ๋ ์๋ก ํํฐํด๋ค์ ์์ง์์ด ํ๋ฐํด์ง๋ ๋ชจ์ต์ ๋ณผ ์ ์์ ๊ฑฐ์์. ๋ง์น ์์ ์ ๋ง์ถฐ ์ถค์ถ๋ ๋ณ๋ค์ ๊ตฐ๋ฌด ๊ฐ์ง ์๋์?
์์ฐ! ์ด์ ์ฐ๋ฆฌ๋ ๋จ์ํ ์์ ์ ๋ฃ๋ ๊ฒ์ ๋์ด์ ๊ทธ๊ฒ์ ๋์ผ๋ก ๋ณผ ์ ์๊ฒ ๋์์ต๋๋ค. ์ฃผํ์ ๋ถ์๊ธฐ, ํํ ์๊ฐํ, 3D ์๊ฐํ, ํํฐํด ์์คํ ๋ฑ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์๋ฆฌ๋ฅผ ์๊ฐํํด๋ณด์์ฃ . ์ด๊ฒ๋ค์ ๋จ์ง ์์์ผ ๋ฟ์ ๋๋ค. ์ฌ๋ฌ๋ถ์ ์ฐฝ์๋ ฅ์ ๋ํด ๋์ฑ ์๋ฆ๋ต๊ณ ๋ ํนํ ์๊ฐํ๋ฅผ ๋ง๋ค์ด๋ผ ์ ์์ ๊ฑฐ์์.
์ด๋ฌํ ์๊ฐํ ๊ธฐ์ ๋ค์ ๋จ์ํ ๋ฉ์ง ํจ๊ณผ๋ฅผ ๋์ด์ ์ค์ ๋ก ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์์ ์ ์ ์ํํธ์จ์ด์์ ์ค๋์ค ํธ์ง์ ๋๊ฑฐ๋, ์ฒญ๊ฐ์ฅ์ ์ธ๋ค์ด ์์ ์ '๋ณด๊ณ ' ์ฆ๊ธธ ์ ์๊ฒ ํ๋ ๋ฐ ํ์ฉ๋ ์ ์์ฃ . ๋ํ ๋ผ์ด๋ธ ๊ณต์ฐ์์ ์์ ๊ณผ ํจ๊ป ์๊ฐ์ ์์๋ฅผ ์ ๊ณตํ๋ ๋ฐ์๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
์, ์ด์ ์ฐ๋ฆฌ๋ ์น ์ค๋์ค API๋ฅผ ์ฌ์ฉํด ์๋ฆฌ๋ฅผ ๋ง๋ค๊ณ , ์กฐ์ํ๊ณ , ์๊ฐํํ๋ ๋ฐฉ๋ฒ์ ๋ชจ๋ ๋ฐฐ์ ์ต๋๋ค. ์ฌ๋ฌ๋ถ์ ๋ธ๋ผ์ฐ์ ๋ ์ด์ ์๋ฒฝํ ๋ฉํฐ๋ฏธ๋์ด ์คํ๋์ค๊ฐ ๋์์ต๋๋ค! ๐จ๐ต
๋ค์ ์น์ ์์๋ ์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ชจ๋ ๊ฒ์ ์ข ํฉํด์ ํ๋์ ์์ฑ๋ ์น ์ค๋์ค ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. ์์ ์ ์, ์ค๋์ค ์ฒ๋ฆฌ, ์๊ฐํ๊ฐ ๋ชจ๋ ํฌํจ๋ ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ํจ๊ป ๋ง๋ค์ด๋ณผ๊น์? ๐๐ถ
๐ผ ์ข ํฉ ํ๋ก์ ํธ: ์น ์ค๋์ค ์ ๋์ฌ์ด์
์, ์ด์ ์ฐ๋ฆฌ๊ฐ ๋ฐฐ์ด ๋ชจ๋ ๊ฒ์ ํ๋๋ก ๋ชจ์ ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ ๋๋ค. ์ฐ๋ฆฌ์ ์ต์ข ๋ชฉํ๋ ์น ๋ธ๋ผ์ฐ์ ์์ ๋์ํ๋ ์์ ํ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์ ๋์ฌ์ด์ ๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค. ์ด ์ ๋์ฌ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ฐ์ง ๊ฑฐ์์:
- ๋ค์ํ ํํ์ ์ค์ค๋ ์ดํฐ
- ADSR ์๋ฒจ๋กํ
- ํํฐ
- LFO (Low Frequency Oscillator)
- ์ดํํธ (๋ฆฌ๋ฒ๋ธ, ๋๋ ์ด)
- ํค๋ณด๋ ์ธํฐํ์ด์ค
- ์ค์๊ฐ ์ค๋์ค ์๊ฐํ
์ ๋ง ๋ฉ์ง์ง ์๋์? ๊ทธ๋ผ ํ๋์ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๐น ์ ๋์ฌ์ด์ ์ฝ์ด
๋จผ์ ์ ๋์ฌ์ด์ ์ ํต์ฌ ๊ธฐ๋ฅ์ ๋ด๋นํ ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
class Synth {
constructor(audioContext) {
this.audioContext = audioContext;
this.oscillator = this.audioContext.createOscillator();
this.gainNode = this.audioContext.createGain();
this.filter = this.audioContext.createBiquadFilter();
this.lfo = this.audioContext.createOscillator();
this.lfoGain = this.audioContext.createGain();
this.oscillator.type = 'sawtooth';
this.filter.type = 'lowpass';
this.filter.frequency.setValueAtTime(1000, this.audioContext.currentTime);
this.lfo.frequency.setValueAtTime(5, this.audioContext.currentTime);
this.lfoGain.gain.setValueAtTime(50, this.audioContext.currentTime);
this.lfo.connect(this.lfoGain);
this.lfoGain.connect(this.oscillator.frequency);
this.oscillator.connect(this.filter);
this.filter.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
this.oscillator.start();
this.lfo.start();
}
setFrequency(frequency) {
this.oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
}
setWaveform(waveform) {
this.oscillator.type = waveform;
}
setFilterFrequency(frequency) {
this.filter.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
}
setLFOFrequency(frequency) {
this.lfo.frequency.setValueAtTime(frequency, this.audioContext.currentTime);
}
setLFOGain(gain) {
this.lfoGain.gain.setValueAtTime(gain, this.audioContext.currentTime);
}
noteOn(frequency) {
this.setFrequency(frequency);
this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);
this.gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);
this.gainNode.gain.linearRampToValueAtTime(1, this.audioContext.currentTime + 0.01);
}
noteOff() {
this.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime);
this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, this.audioContext.currentTime);
this.gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 0.5);
}
}
์ด ํด๋์ค๋ ๊ธฐ๋ณธ์ ์ธ ์ ๋์ฌ์ด์ ์ ๊ธฐ๋ฅ์ ๋ชจ๋ ํฌํจํ๊ณ ์์ต๋๋ค. ์ค์ค๋ ์ดํฐ, ํํฐ, LFO, ๊ทธ๋ฆฌ๊ณ ๊ฒ์ธ ๋ ธ๋๋ฅผ ์ฌ์ฉํด ์๋ฆฌ๋ฅผ ์์ฑํ๊ณ ์กฐ์ํ ์ ์์ฃ .
๐๏ธ ์ฌ์ฉ์ ์ธํฐํ์ด์ค
์ด์ ์ฌ์ฉ์๊ฐ ์ ๋์ฌ์ด์ ๋ฅผ ์ ์ดํ ์ ์๋ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค. HTML๊ณผ CSS๋ฅผ ์ฌ์ฉํด ๊ฐ๋จํ UI๋ฅผ ๊ตฌ์ฑํ๊ณ , JavaScript๋ก ์ด๋ฅผ ์ ์ดํ ๊ฑฐ์์.
<!-- HTML -->
<div id="synth-container">
<div id="controls">
<select id="waveform">
<option value="sine">Sine</option>
<option value="square">Square</option>
<option value="sawtooth">Sawtooth</option>
<option value="triangle">Triangle</option>
</select>
<input type="range" id="filter" min="0" max="1000" value="1000">
<input type="range" id="lfo-freq" min="0" max="20" value="5">
<input type="range" id="lfo-gain" min="0" max="100" value="50">
</div>
<div id="keyboard"></div>
<canvas id="visualizer"></canvas>
</div>
/* CSS */
#synth-container {
width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f0f0f0;
border-radius: 10px;
}
#controls {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
#keyboard {
display: flex;
height: 200px;
}
.key {
flex-grow: 1;
background-color: white;
border: 1px solid black;
cursor: pointer;
}
.key.black {
background-color: black;
height: 60%;
width: 60%;
margin-left: -30%;
margin-right: -30%;
z-index: 1;
}
#visualizer {
width: 100%;
height: 100px;
background-color: black;
}
// JavaScript
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const synth = new Synth(audioContext);
const waveformSelect = document.getElementById('waveform');
const filterSlider = document.getElementById('filter');
const lfoFreqSlider = document.getElementById('lfo-freq');
const lfoGainSlider = document.getElementById('lfo-gain');
const keyboard = document.getElementById('keyboard');
waveformSelect.addEventListener('change', (e) => synth.setWaveform(e.target.value));
filterSlider.addEventListener('input', (e) => synth.setFilterFrequency(e.target.value));
lfoFreqSlider.addEventListener('input', (e) => synth.setLFOFrequency(e.target.value));
lfoGainSlider.addEventListener('input', (e) => synth.setLFOGain(e.target.value));
// ํค๋ณด๋ ์์ฑ
const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
for (let octave = 3; octave < 5; octave++) {
notes.forEach((note, index) => {
const key = document.createElement('div');
key.className = `key ${note.includes('#') ? 'black' : 'white'}`;
key.dataset.note = note + octave;
key.dataset.frequency = 440 * Math.pow(2, (octave - 4 + index / 12));
keyboard.appendChild(key);
key.addEventListener('mousedown', () => synth.noteOn(key.dataset.frequency));
key.addEventListener('mouseup', () => synth.noteOff());
key.addEventListener('mouseleave', () => synth.noteOff());
});
}
์ด ์ฝ๋๋ ์ ๋์ฌ์ด์ ์ ๊ธฐ๋ณธ์ ์ธ ์ปจํธ๋กค(ํํ ์ ํ, ํํฐ ์ฃผํ์, LFO ์ฃผํ์ ๋ฐ ๊ฒ์ธ)๊ณผ ๊ฐ๋จํ ํค๋ณด๋ ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ญ๋๋ค.
๐๏ธ ์ค๋์ค ์๊ฐํ
๋ง์ง๋ง์ผ๋ก, ์ฐ๋ฆฌ์ ์ ๋์ฌ์ด์ ์ ๋ฉ์ง ์๊ฐํ ํจ๊ณผ๋ฅผ ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค.
const visualizer = document.getElementById('visualizer');
const visualizerContext = visualizer.getContext('2d');
function drawVisualizer() {
const width = visualizer.width;
const height = visualizer.height;
const analyser = audioContext.createAnalyser();
synth.gainNode.connect(analyser);
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
visualizerContext.clearRect(0, 0, width, height);
function draw() {
requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);
visualizerContext.fillStyle = 'rgb(200, 200, 200)';
visualizerContext.fillRect(0, 0, width, height);
visualizerContext.lineWidth = 2;
visualizerContext.strokeStyle = 'rgb(0, 0, 0)';
visualizerContext.beginPath();
const sliceWidth = width * 1.0 / bufferLength;
let x = 0;
for(let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * height/2;
if(i === 0) {
visualizerContext.moveTo(x, y);
} else {
visualizerContext.lineTo(x, y);
}
x += sliceWidth;
}
visualizerContext.lineTo(width, height/2);
visualizerContext.stroke();
}
draw();
}
drawVisualizer();
์ด ์ฝ๋๋ ์ ๋์ฌ์ด์ ์ ์ถ๋ ฅ์ ์ค์๊ฐ์ผ๋ก ์๊ฐํํฉ๋๋ค. ํํ์ด ์บ๋ฒ์ค์ ๊ทธ๋ ค์ง๋ ๋ชจ์ต์ ๋ณผ ์ ์์ ๊ฑฐ์์.
์ถํํฉ๋๋ค! ๐ ์ฐ๋ฆฌ๋ ๋ฐฉ๊ธ ์์ ํ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์น ์ค๋์ค ์ ๋์ฌ์ด์ ๋ฅผ ๋ง๋ค์์ต๋๋ค. ์ด ์ ๋์ฌ์ด์ ๋ ๋ค์ํ ํํ์ ์์ฑํ๊ณ , ํํฐ์ LFO๋ฅผ ์ ์ฉํ๋ฉฐ, ํค๋ณด๋ ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ฐ์ฃผํ ์ ์๊ณ , ์ค์๊ฐ์ผ๋ก ์ค๋์ค๋ฅผ ์๊ฐํํฉ๋๋ค.
์ด ํ๋ก์ ํธ๋ ์น ์ค๋์ค API์ ๊ฐ๋ ฅํจ์ ์ ๋ณด์ฌ์ค๋๋ค. ๋ธ๋ผ์ฐ์ ๋ง์ผ๋ก๋ ์ ๋ฌธ์ ์ธ ์์ค์ ์ค๋์ค ์ฒ๋ฆฌ์ ์์ ์ ์์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ์ ์ ์์ฃ . ์ด์ ์ฌ๋ฌ๋ถ์ ์ด ๊ธฐ๋ณธ์ ์ธ ์ ๋์ฌ์ด์ ๋ฅผ ๋ฐํ์ผ๋ก ๋ ๋ง์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฑฐ๋, ์์ ํ ์๋ก์ด ํํ์ ์น ์ค๋์ค ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณผ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํด๋ณผ ์ ์์ ๊ฑฐ์์:
- ๋ ๋ณต์กํ ADSR ์๋ฒจ๋กํ
- ๋ค์ค ์ค์ค๋ ์ดํฐ
- ๋ ๋ค์ํ ์ดํํธ (์ฝ๋ฌ์ค, ํ๋์ , ๋์คํ ์ ๋ฑ)
- ์ํ์ ๊ธฐ๋ฅ
- MIDI ํค๋ณด๋ ์ง์
- ๋ ํ๋ คํ 3D ์๊ฐํ
์น ์ค๋์ค์ ์ธ๊ณ๋ ๋ฌดํํฉ๋๋ค. ์ฌ๋ฌ๋ถ์ ์์๋ ฅ์ด ๊ณง ํ๊ณ์ฃ . ์ด์ ์ฌ๋ฌ๋ถ๋ง์ ๋ ํนํ๊ณ ์ฐฝ์์ ์ธ ์น ์ค๋์ค ํ๋ก์ ํธ๋ฅผ ์์ํด๋ณด๋ ๊ฑด ์ด๋จ๊น์? ๐๐ต
์น ์ค๋์ค API๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ๋ธ๋ผ์ฐ์ ๋ฅผ ๊ฐ๋ ฅํ ์ค๋์ค ์ฒ๋ฆฌ ๋๊ตฌ๋ก ํ๋ฐ๊ฟ์์ผฐ์ต๋๋ค. ์ด๋ ๋จ์ํ ์์ ์ ๋ง๋๋ ๊ฒ์ ๋์ด์, ์๋ก์ด ํํ์ ์ธํฐ๋ํฐ๋ธ ์น ๊ฒฝํ์ ์ฐฝ์กฐํ ์ ์๋ ๊ฐ๋ฅ์ฑ์ ์ด์ด์ค๋๋ค. ๊ฒ์ ์ํฅ, ์จ๋ผ์ธ ์์ ๊ต์ก, ์น ๊ธฐ๋ฐ DAW(Digital Audio Workstation), ์ธํฐ๋ํฐ๋ธ ์ฌ์ด๋ ์ํธ ๋ฑ ๊ทธ ์์ฉ ๋ถ์ผ๋ ๋ฌด๊ถ๋ฌด์งํฉ๋๋ค.
์ฌ๋ฌ๋ถ์ ์น ์ค๋์ค ์ฌ์ ์ด ์ฌ๊ธฐ์ ๋๋์ง ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ๊ณ์ํด์ ์คํํ๊ณ , ํ์ตํ๊ณ , ์ฐฝ์กฐํ์ธ์. ์น์ ๋ฏธ๋๋ ๋ณด๋ค ํ๋ถํ๊ณ ์ธํฐ๋ํฐ๋ธํ ์ค๋์ค ๊ฒฝํ์ผ๋ก ๊ฐ๋ํ ํ ๋๊น์. ์ฌ๋ฌ๋ถ์ด ๊ทธ ๋ฏธ๋๋ฅผ ๋ง๋ค์ด๊ฐ ์ฃผ์ธ๊ณต์ ๋๋ค! ๐๐ถ
ย๊ด๋ จ ํค์๋
- ์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์ฌ๋ฅ๋ท์ ์ต์ AI ๊ธฐ์ ๊ณผ ๋ฒ๋ฅ ์ ๊ธฐ๋ฐํ์ฌ ์์ฌ์ ์ง์ ์ฌ์ฐ๊ถ์ ์ ๊ทน์ ์ผ๋ก ๋ณดํธํ๋ฉฐ,
๋ฌด๋จ ์ฌ์ฉ ๋ฐ ์นจํด ํ์์ ๋ํด ๋ฒ์ ๋์์ ํ ๊ถ๋ฆฌ๋ฅผ ๋ณด์ ํฉ๋๋ค.
ยฉ 2025 ์ฌ๋ฅ๋ท | All rights reserved.
๋๊ธ 0๊ฐ