๐ŸŽต ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์›น ์˜ค๋””์˜ค API: ๋ธŒ๋ผ์šฐ์ €์—์„œ ์Œ์•… ๋งŒ๋“ค๊ธฐ ๐ŸŽถ

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŽต ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์›น ์˜ค๋””์˜ค 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์˜ ๊ตฌ์„ฑ ์š”์†Œ

์ž, ์ด์ œ ์šฐ๋ฆฌ๋Š” ์›น ์˜ค๋””์˜ค 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 ํ‚ค๋ฅผ ๋ˆ„๋ฅธ ๊ฒƒ๊ณผ ๊ฐ™์€ ํšจ๊ณผ์ฃ !

์˜ค์‹ค๋ ˆ์ดํ„ฐ ํŒŒํ˜• ์‚ฌ์ธํŒŒ (Sine wave) ์‚ฌ๊ฐํŒŒ (Square wave) ์‚ผ๊ฐํŒŒ (Triangle wave)

๐ŸŽญ ๊ฒŒ์ธ ๋…ธ๋“œ: ๋ณผ๋ฅจ์˜ ๋งˆ๋ฒ•์‚ฌ

์˜ค์‹ค๋ ˆ์ดํ„ฐ๋งŒ์œผ๋กœ๋Š” ์†Œ๋ฆฌ์˜ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•˜๊ธฐ ์–ด๋ ค์›Œ์š”. ์ด๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ '๊ฒŒ์ธ ๋…ธ๋“œ(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๋ฅผ ์‚ฌ์šฉํ•ด ๋‹ค์–‘ํ•œ ์•…๊ธฐ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๊ณ , ์‹ค์ œ ์Œ์•…์„ ๋งŒ๋“ค์–ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์‹œํ€€์„œ, ๋“œ๋Ÿผ ๋จธ์‹ , ๊ธฐํƒ€, ์‹ ๋””์‚ฌ์ด์ € ๋“ฑ ๋‹ค์–‘ํ•œ ์•…๊ธฐ๋“ค์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ € ์•ˆ์—์„œ ์ƒ๋ช…์„ ์–ป์—ˆ์ฃ .

์ด๊ฒƒ์€ ๋‹จ์ง€ ์‹œ์ž‘์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ์›น ์˜ค๋””์˜ค 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 ์‹œ๊ฐํ™” ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ

์™€์šฐ! ์ด์ œ ์šฐ๋ฆฌ๋Š” ๋‹จ์ˆœํžˆ ์Œ์•…์„ ๋“ฃ๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ ๊ทธ๊ฒƒ์„ ๋ˆˆ์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ฃผํŒŒ์ˆ˜ ๋ถ„์„๊ธฐ, ํŒŒํ˜• ์‹œ๊ฐํ™”, 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();

์ด ์ฝ”๋“œ๋Š” ์‹ ๋””์‚ฌ์ด์ €์˜ ์ถœ๋ ฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ํŒŒํ˜•์ด ์บ”๋ฒ„์Šค์— ๊ทธ๋ ค์ง€๋Š” ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.

์›น ์˜ค๋””์˜ค ์‹ ๋””์‚ฌ์ด์ € ์›น ์˜ค๋””์˜ค ์‹ ๋””์‚ฌ์ด์ € Waveform Filter Frequency LFO Freq LFO Gain Audio Visualizer

์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ ์šฐ๋ฆฌ๋Š” ๋ฐฉ๊ธˆ ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ์›น ์˜ค๋””์˜ค ์‹ ๋””์‚ฌ์ด์ €๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์‹ ๋””์‚ฌ์ด์ €๋Š” ๋‹ค์–‘ํ•œ ํŒŒํ˜•์„ ์ƒ์„ฑํ•˜๊ณ , ํ•„ํ„ฐ์™€ LFO๋ฅผ ์ ์šฉํ•˜๋ฉฐ, ํ‚ค๋ณด๋“œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด ์—ฐ์ฃผํ•  ์ˆ˜ ์žˆ๊ณ , ์‹ค์‹œ๊ฐ„์œผ๋กœ ์˜ค๋””์˜ค๋ฅผ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

์ด ํ”„๋กœ์ ํŠธ๋Š” ์›น ์˜ค๋””์˜ค API์˜ ๊ฐ•๋ ฅํ•จ์„ ์ž˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋งŒ์œผ๋กœ๋„ ์ „๋ฌธ์ ์ธ ์ˆ˜์ค€์˜ ์˜ค๋””์˜ค ์ฒ˜๋ฆฌ์™€ ์Œ์•… ์ œ์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์ฃ . ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์ด ๊ธฐ๋ณธ์ ์ธ ์‹ ๋””์‚ฌ์ด์ €๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ํ˜•ํƒœ์˜ ์›น ์˜ค๋””์˜ค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”:

  • ๋” ๋ณต์žกํ•œ ADSR ์—”๋ฒจ๋กœํ”„
  • ๋‹ค์ค‘ ์˜ค์‹ค๋ ˆ์ดํ„ฐ
  • ๋” ๋‹ค์–‘ํ•œ ์ดํŽ™ํŠธ (์ฝ”๋Ÿฌ์Šค, ํ”Œ๋žœ์ €, ๋””์Šคํ† ์…˜ ๋“ฑ)
  • ์‹œํ€€์„œ ๊ธฐ๋Šฅ
  • MIDI ํ‚ค๋ณด๋“œ ์ง€์›
  • ๋” ํ™”๋ คํ•œ 3D ์‹œ๊ฐํ™”

์›น ์˜ค๋””์˜ค์˜ ์„ธ๊ณ„๋Š” ๋ฌดํ•œํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ์ƒ๋ ฅ์ด ๊ณง ํ•œ๊ณ„์ฃ . ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„๋งŒ์˜ ๋…ํŠนํ•˜๊ณ  ์ฐฝ์˜์ ์ธ ์›น ์˜ค๋””์˜ค ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋–จ๊นŒ์š”? ๐Ÿš€๐ŸŽต

์›น ์˜ค๋””์˜ค API๋ฅผ ํ†ตํ•ด ์šฐ๋ฆฌ๋Š” ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ฐ•๋ ฅํ•œ ์˜ค๋””์˜ค ์ฒ˜๋ฆฌ ๋„๊ตฌ๋กœ ํƒˆ๋ฐ”๊ฟˆ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ ์Œ์•…์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋„˜์–ด์„œ, ์ƒˆ๋กœ์šด ํ˜•ํƒœ์˜ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์›น ๊ฒฝํ—˜์„ ์ฐฝ์กฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€๋Šฅ์„ฑ์„ ์—ด์–ด์ค๋‹ˆ๋‹ค. ๊ฒŒ์ž„ ์Œํ–ฅ, ์˜จ๋ผ์ธ ์Œ์•… ๊ต์œก, ์›น ๊ธฐ๋ฐ˜ DAW(Digital Audio Workstation), ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‚ฌ์šด๋“œ ์•„ํŠธ ๋“ฑ ๊ทธ ์‘์šฉ ๋ถ„์•ผ๋Š” ๋ฌด๊ถ๋ฌด์ง„ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์˜ ์›น ์˜ค๋””์˜ค ์—ฌ์ •์ด ์—ฌ๊ธฐ์„œ ๋๋‚˜์ง€ ์•Š๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๊ณ„์†ํ•ด์„œ ์‹คํ—˜ํ•˜๊ณ , ํ•™์Šตํ•˜๊ณ , ์ฐฝ์กฐํ•˜์„ธ์š”. ์›น์˜ ๋ฏธ๋ž˜๋Š” ๋ณด๋‹ค ํ’๋ถ€ํ•˜๊ณ  ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•œ ์˜ค๋””์˜ค ๊ฒฝํ—˜์œผ๋กœ ๊ฐ€๋“ํ•  ํ…Œ๋‹ˆ๊นŒ์š”. ์—ฌ๋Ÿฌ๋ถ„์ด ๊ทธ ๋ฏธ๋ž˜๋ฅผ ๋งŒ๋“ค์–ด๊ฐˆ ์ฃผ์ธ๊ณต์ž…๋‹ˆ๋‹ค! ๐ŸŒŸ๐ŸŽถ

ย