kimhyunwoo commited on
Commit
34a689f
ยท
verified ยท
1 Parent(s): bcaf481

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +173 -79
index.html CHANGED
@@ -1,11 +1,12 @@
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
4
- <title>FFT ๋ถ„์„ ํ›„ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์žฌ์ƒ ์˜ˆ์ œ</title>
5
  <style>
6
  body { font-family: sans-serif; text-align: center; margin-top: 50px; }
7
- button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
8
  #status { margin-top: 20px; color: gray; }
 
9
  </style>
10
  </head>
11
  <body>
@@ -14,17 +15,21 @@
14
  <p>๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋ฐ›์•„ FFT ๋ถ„์„ ํ›„ ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์œผ๋กœ ์†Œ๋ฆฌ๋ฅผ ์žฌ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.</p>
15
 
16
  <button id="startButton">๋งˆ์ดํฌ ์ž…๋ ฅ ์‹œ์ž‘ ๋ฐ ์žฌ์ƒ</button>
17
- <div id="status"></div>
 
18
 
19
  <script>
20
  let audioContext;
21
  let analyser;
22
  let source;
23
- let oscillatorNode; // ์‚ฌ์ธ/์ฝ”์‚ฌ์ธ ํŒŒํ˜•์„ ์ƒ์„ฑํ•  ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ
 
 
24
 
25
  // FFT ๋ถ„์„ํ•  ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ๊ฐœ์ˆ˜ (2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ)
26
  const fftSize = 2048;
27
- const bufferLength = fftSize / 2; // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์˜ ํฌ๊ธฐ
 
28
 
29
  // FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด
30
  let dataArray = new Uint8Array(bufferLength); // ์ฃผํŒŒ์ˆ˜ ํฌ๊ธฐ (0-255)
@@ -32,96 +37,185 @@
32
  // ์žฌ์ƒํ•  ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ์˜ ๊ฐœ์ˆ˜ (FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ช‡ ๊ฐœ์˜ ํŒŒํ˜•์œผ๋กœ ์žฌ๊ตฌ์„ฑํ• ์ง€)
33
  const numReconstructedOscillators = 50;
34
 
35
- document.getElementById('startButton').addEventListener('click', async () => {
36
- try {
37
- // ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ (๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์ ‘๋‘์‚ฌ ๊ณ ๋ ค)
38
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
39
-
40
- // ๋งˆ์ดํฌ ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
41
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
42
-
43
- // ๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆผ ์†Œ์Šค ๋…ธ๋“œ ์ƒ์„ฑ
44
- source = audioContext.createMediaStreamSource(stream);
45
-
46
- // ๋ถ„์„๊ธฐ ๋…ธ๋“œ ์ƒ์„ฑ
47
- analyser = audioContext.createAnalyser();
48
- analyser.fftSize = fftSize; // FFT ์‚ฌ์ด์ฆˆ ์„ค์ •
49
-
50
- // ๋งˆ์ดํฌ ์†Œ์Šค๋ฅผ ๋ถ„์„๊ธฐ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ
51
- source.connect(analyser);
52
-
53
- // ------------- FFT ๋ถ„์„ ๋ฐ ํŒŒํ˜• ์žฌ๊ตฌ์„ฑ ๋กœ์ง -------------
54
-
55
- // ์‹ค์‹œ๊ฐ„์œผ๋กœ FFT ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜
56
- const processAudio = () => {
57
- // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (ํฌ๊ธฐ)
58
- analyser.getByteFrequencyData(dataArray);
59
-
60
- // ์ด์ „ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์—ฐ๊ฒฐ ํ•ด์ œ ๋ฐ ์‚ญ์ œ
61
- if (oscillatorNode) {
62
- oscillatorNode.disconnect();
63
- oscillatorNode = null;
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- // ์ƒˆ๋กœ์šด ๊ฒŒ์ธ ๋…ธ๋“œ ์ƒ์„ฑ (๋ณผ๋ฅจ ์กฐ์ ˆ)
67
- const gainNode = audioContext.createGain();
68
- gainNode.gain.value = 0.5; // ์žฌ์ƒ ๋ณผ๋ฅจ (์กฐ์ ˆ ๊ฐ€๋Šฅ)
69
 
70
- // ๊ฒŒ์ธ ๋…ธ๋“œ๋ฅผ ์ตœ์ข… ์ถœ๋ ฅ์— ์—ฐ๊ฒฐ
71
- gainNode.connect(audioContext.destination);
 
 
 
 
 
72
 
73
- // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๋ฅผ ๋‹ด์„ ๋ฐฐ์—ด
74
- const oscillators = [];
75
 
76
- // FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์ƒ์„ฑ (๋‹จ์ˆœํ™”๋œ ์˜ˆ์ œ)
77
- for (let i = 0; i < numReconstructedOscillators; i++) {
78
- const oscillator = audioContext.createOscillator();
79
 
80
- // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์—์„œ ํ•ด๋‹นํ•˜๋Š” ์ฃผํŒŒ์ˆ˜ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ
81
- const frequencyIndex = Math.min(Math.floor(i * (bufferLength / numReconstructedOscillators)), bufferLength - 1);
82
 
83
- // ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฃผํŒŒ์ˆ˜ ์„ค์ • (๋‹จ์ˆœํ™”)
84
- // ์‹ค์ œ๋กœ๋Š” analyser.getByteFrequencyData๋Š” ํฌ๊ธฐ๋งŒ ์ œ๊ณตํ•˜๋ฉฐ, ์ฃผํŒŒ์ˆ˜๋Š” ์ธ๋ฑ์Šค์™€ ์ƒ˜ํ”Œ ๋ ˆ์ดํŠธ๋กœ ๊ณ„์‚ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
85
- // ์—ฌ๊ธฐ์„œ์˜ frequency๋Š” ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฃผํŒŒ์ˆ˜ ์ธ๋ฑ์Šค์™€ ๋น„๋ก€ํ•˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
86
- const frequency = audioContext.sampleRate / fftSize * frequencyIndex; // ์‹ค์ œ ์ฃผํŒŒ์ˆ˜ ๊ณ„์‚ฐ
 
 
87
 
88
- // ์ฃผํŒŒ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ๊ฑฐ๋‚˜ ๋†’์œผ๋ฉด ์ œ์™ธ (์กฐ์ ˆ ๊ฐ€๋Šฅ)
89
- if (frequency > 50 && frequency < 15000) {
90
- oscillator.type = 'sine'; // ๋˜๋Š” 'cosine'
91
- oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
 
92
 
93
- // ๊ฐ ์˜ค์‹ค๋ ˆ์ดํ„ฐ์˜ ๋ณผ๋ฅจ์„ ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜์—ฌ ์„ค์ • (๋‹จ์ˆœํ™”)
94
- const oscillatorGain = audioContext.createGain();
95
- oscillatorGain.gain.value = dataArray[frequencyIndex] / 255 * 0.1; // ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜์—ฌ ๋ณผ๋ฅจ ์กฐ์ ˆ
96
 
97
- // ์˜ค์‹ค๋ ˆ์ดํ„ฐ๋ฅผ ๊ฒŒ์ธ ๋…ธ๋“œ์— ์—ฐ๊ฒฐํ•˜๊ณ  ๊ฒŒ์ธ ๋…ธ๋“œ๋ฅผ ๋ฉ”์ธ ๊ฒŒ์ธ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ
98
- oscillator.connect(oscillatorGain);
99
- oscillatorGain.connect(gainNode);
100
 
101
- // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์‹œ์ž‘
102
- oscillator.start();
 
 
103
 
104
- // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
105
- oscillators.push(oscillator);
106
- }
107
- }
 
 
 
 
108
 
109
- // ์ƒ์„ฑ๋œ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๋ฅผ ์ €์žฅ
110
- oscillatorNode = oscillators;
 
 
111
 
112
- // ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ๋‹ค์‹œ ๋ถ„์„ ๋ฐ ์žฌ์ƒ ์š”์ฒญ
113
- requestAnimationFrame(processAudio);
114
- };
 
 
 
115
 
116
- // ๋ถ„์„ ๋ฐ ์žฌ์ƒ ์‹œ์ž‘
117
- processAudio();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- document.getElementById('status').textContent = '๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ ์žฌ์ƒ ์ค‘...';
120
- } catch (err) {
121
- console.error('๋งˆ์ดํฌ ์ ‘๊ทผ ์˜ค๋ฅ˜:', err);
122
- document.getElementById('status').textContent = `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${err.message}`;
 
 
123
  }
124
- });
 
 
 
 
 
 
125
 
126
  </script>
127
 
 
1
  <!DOCTYPE html>
2
  <html>
3
  <head>
4
+ <title>FFT ๋ถ„์„ ํ›„ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์žฌ์ƒ (์ˆ˜์ •)</title>
5
  <style>
6
  body { font-family: sans-serif; text-align: center; margin-top: 50px; }
7
+ button { padding: 10px 20px; font-size: 16px; cursor: pointer; margin: 5px; }
8
  #status { margin-top: 20px; color: gray; }
9
+ .error { color: red; font-weight: bold; }
10
  </style>
11
  </head>
12
  <body>
 
15
  <p>๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋ฐ›์•„ FFT ๋ถ„์„ ํ›„ ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์œผ๋กœ ์†Œ๋ฆฌ๋ฅผ ์žฌ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.</p>
16
 
17
  <button id="startButton">๋งˆ์ดํฌ ์ž…๋ ฅ ์‹œ์ž‘ ๋ฐ ์žฌ์ƒ</button>
18
+ <button id="stopButton" disabled>์ค‘์ง€</button>
19
+ <div id="status">๋Œ€๊ธฐ ์ค‘...</div>
20
 
21
  <script>
22
  let audioContext;
23
  let analyser;
24
  let source;
25
+ let gainNode; // ์ „์ฒด ๋ณผ๋ฅจ ์กฐ์ ˆ ๋…ธ๋“œ
26
+ let animationFrameId = null; // requestAnimationFrame ID
27
+ let activeOscillators = []; // ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๋“ค์„ ์ €์žฅํ•˜๋Š” ๋ฐฐ์—ด
28
 
29
  // FFT ๋ถ„์„ํ•  ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ๊ฐœ์ˆ˜ (2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ)
30
  const fftSize = 2048;
31
+ // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์˜ ํฌ๊ธฐ (fftSize / 2)
32
+ const bufferLength = analyser ? analyser.frequencyBinCount : fftSize / 2;
33
 
34
  // FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด
35
  let dataArray = new Uint8Array(bufferLength); // ์ฃผํŒŒ์ˆ˜ ํฌ๊ธฐ (0-255)
 
37
  // ์žฌ์ƒํ•  ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ์˜ ๊ฐœ์ˆ˜ (FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ช‡ ๊ฐœ์˜ ํŒŒํ˜•์œผ๋กœ ์žฌ๊ตฌ์„ฑํ• ์ง€)
38
  const numReconstructedOscillators = 50;
39
 
40
+ const startButton = document.getElementById('startButton');
41
+ const stopButton = document.getElementById('stopButton');
42
+ const statusDiv = document.getElementById('status');
43
+
44
+ // ---------- ์˜ค๋””์˜ค ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ----------
45
+ const processAudio = () => {
46
+ if (!analyser) return; // analyser๊ฐ€ ์—†์œผ๋ฉด ์ข…๋ฃŒ
47
+
48
+ // 1. FFT ๋ถ„์„: ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
49
+ analyser.getByteFrequencyData(dataArray);
50
+
51
+ // 2. ์ด์ „ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ •๋ฆฌ: ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ๋ชจ๋“  ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ค‘์ง€ ๋ฐ ์—ฐ๊ฒฐ ํ•ด์ œ
52
+ activeOscillators.forEach(osc => {
53
+ try {
54
+ osc.stop(); // ์ฆ‰์‹œ ์ค‘์ง€
55
+ osc.disconnect(); // ๋ชจ๋“  ์—ฐ๊ฒฐ ํ•ด์ œ
56
+ } catch (e) {
57
+ // ์ด๋ฏธ ์ค‘์ง€๋˜์—ˆ๊ฑฐ๋‚˜ ์—ฐ๊ฒฐ ํ•ด์ œ๋œ ๊ฒฝ์šฐ ์˜ค๋ฅ˜ ๋ฌด์‹œ
58
+ // console.warn("Error stopping/disconnecting oscillator:", e);
59
+ }
60
+ });
61
+ activeOscillators = []; // ๋ฐฐ์—ด ๋น„์šฐ๊ธฐ
62
+
63
+ // 3. ์ƒˆ๋กœ์šด ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ƒ์„ฑ ๋ฐ ์—ฐ๊ฒฐ
64
+ // ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„ ๋ถ„์„ํ•˜์—ฌ ์ผ์ • ๊ฐœ์ˆ˜์˜ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ƒ์„ฑ
65
+ for (let i = 0; i < numReconstructedOscillators; i++) {
66
+ const oscillator = audioContext.createOscillator();
67
+
68
+ // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์—์„œ ํ•ด๋‹นํ•˜๋Š” ์ฃผํŒŒ์ˆ˜ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ
69
+ // bufferLength๊ฐ€ numReconstructedOscillators๋ณด๋‹ค ํด ๋•Œ ์ธ๋ฑ์Šค๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๋„๋ก ๊ณ„์‚ฐ
70
+ const frequencyIndex = Math.min(Math.floor(i * (bufferLength / numReconstructedOscillators)), bufferLength - 1);
71
+
72
+ // ํ•ด๋‹น ์ธ๋ฑ์Šค์˜ ์ฃผํŒŒ์ˆ˜ ํฌ๊ธฐ ๊ฐ€์ ธ์˜ค๊ธฐ
73
+ const magnitude = dataArray[frequencyIndex];
74
+
75
+ // ํฌ๊ธฐ๊ฐ€ ์ผ์ • ๊ฐ’ ์ด์ƒ์ธ ๊ฒฝ์šฐ์—๋งŒ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ƒ์„ฑ (๋…ธ์ด์ฆˆ ๊ฐ์†Œ ํšจ๊ณผ)
76
+ if (magnitude > 10) { // ์ž„๊ณ„๊ฐ’ (์กฐ์ ˆ ๊ฐ€๋Šฅ)
77
+ // ์‹ค์ œ ์ฃผํŒŒ์ˆ˜ ๊ณ„์‚ฐ (Hz)
78
+ const frequency = (frequencyIndex * audioContext.sampleRate) / fftSize;
79
+
80
+ // ์žฌ์ƒํ•  ์ฃผํŒŒ์ˆ˜ ๋ฒ”์œ„ ์ œํ•œ (์„ ํƒ ์‚ฌํ•ญ)
81
+ if (frequency > 20 && frequency < audioContext.sampleRate / 2) { // ๊ฐ€์ฒญ ์ฃผํŒŒ์ˆ˜ ๋ฒ”์œ„ ๋‚ด
82
+ oscillator.type = 'sine'; // ํŒŒํ˜• ํƒ€์ž…: 'sine', 'square', 'sawtooth', 'triangle' ๋˜๋Š” 'custom'
83
+ try {
84
+ // frequency ๊ฐ’์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ (0๋ณด๋‹ค ์ปค์•ผ ํ•จ)
85
+ if (frequency > 0) {
86
+ oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
87
+ } else {
88
+ continue; // ์œ ํšจํ•˜์ง€ ์•Š์€ ์ฃผํŒŒ์ˆ˜๋Š” ๊ฑด๋„ˆ๋œ€
89
+ }
90
+ } catch (e) {
91
+ console.error("Error setting frequency:", e, "Frequency:", frequency);
92
+ continue; // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ํ•ด๋‹น ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๊ฑด๋„ˆ๋œ€
93
+ }
94
+
95
+
96
+ // ๊ฐ ์˜ค์‹ค๋ ˆ์ดํ„ฐ์˜ ๋ณผ๋ฅจ์„ ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜์—ฌ ์„ค์ •
97
+ // magnitude(0~255)๋ฅผ 0~1 ๋ฒ”์œ„๋กœ ์ •๊ทœํ™”ํ•˜๊ณ , ์ „์ฒด ๋ณผ๋ฅจ ๊ณ ๋ ค
98
+ const oscGainNode = audioContext.createGain();
99
+ // magnitude๊ฐ€ ํด์ˆ˜๋ก ์†Œ๋ฆฌ๊ฐ€ ์ปค์ง€๋„๋ก ์„ค์ • (๋น„์„ ํ˜•์ ์œผ๋กœ ์กฐ์ ˆ ๊ฐ€๋Šฅ)
100
+ oscGainNode.gain.setValueAtTime((magnitude / 255) ** 2 * 0.5, audioContext.currentTime); // ๋ณผ๋ฅจ ์กฐ์ ˆ (0.5๋Š” ์ถ”๊ฐ€ ๊ฐ์‡ )
101
+
102
+ // ์˜ค์‹ค๋ ˆ์ดํ„ฐ -> ๊ฐœ๋ณ„ ๊ฒŒ์ธ ๋…ธ๋“œ -> ์ „์ฒด ๊ฒŒ์ธ ๋…ธ๋“œ -> ์ถœ๋ ฅ
103
+ oscillator.connect(oscGainNode);
104
+ oscGainNode.connect(gainNode);
105
+
106
+ // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์‹œ์ž‘ (ํ˜„์žฌ ์‹œ๊ฐ„๋ถ€ํ„ฐ)
107
+ oscillator.start(audioContext.currentTime);
108
+
109
+ // ํ™œ์„ฑํ™”๋œ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
110
+ activeOscillators.push(oscillator);
111
+ }
112
+ }
113
+ }
114
 
115
+ // 4. ๋‹ค์Œ ํ”„๋ ˆ์ž„ ์š”์ฒญ
116
+ animationFrameId = requestAnimationFrame(processAudio);
117
+ };
118
 
119
+ // ---------- ์‹œ์ž‘ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ ----------
120
+ startButton.addEventListener('click', async () => {
121
+ try {
122
+ if (audioContext) { // ์ด๋ฏธ ์‹คํ–‰ ์ค‘์ด๋ฉด ๋ฐ˜ํ™˜
123
+ console.log("AudioContext already active.");
124
+ return;
125
+ }
126
 
127
+ statusDiv.textContent = '๋งˆ์ดํฌ ์ ‘๊ทผ ์ค‘...';
128
+ statusDiv.classList.remove('error');
129
 
130
+ // 1. ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
131
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
 
132
 
133
+ // 2. ๋งˆ์ดํฌ ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
134
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
135
 
136
+ // 3. ์˜ค๋””์˜ค ๋…ธ๋“œ ์ƒ์„ฑ ๋ฐ ์—ฐ๊ฒฐ
137
+ source = audioContext.createMediaStreamSource(stream); // ๋งˆ์ดํฌ ์†Œ์Šค
138
+ analyser = audioContext.createAnalyser(); // ๋ถ„์„๊ธฐ
139
+ analyser.fftSize = fftSize; // FFT ์‚ฌ์ด์ฆˆ ์„ค์ •
140
+ gainNode = audioContext.createGain(); // ์ „์ฒด ๋ณผ๋ฅจ ์กฐ์ ˆ ๋…ธ๋“œ
141
+ gainNode.gain.setValueAtTime(0.5, audioContext.currentTime); // ์ดˆ๊ธฐ ๋ณผ๋ฅจ ์„ค์ • (0.5)
142
 
143
+ // ์—ฐ๊ฒฐ: ๋งˆ์ดํฌ ์†Œ์Šค -> ๋ถ„์„๊ธฐ -> ์ „์ฒด ๊ฒŒ์ธ -> ์ถœ๋ ฅ
144
+ source.connect(analyser);
145
+ // ์ฃผ์˜: ๋ถ„์„๊ธฐ ๋…ธ๋“œ์—์„œ ์ง์ ‘ ์ถœ๋ ฅ์œผ๋กœ ์†Œ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก gainNode์—๋งŒ ์—ฐ๊ฒฐ
146
+ // source.connect(gainNode); // ํ•„์š”์— ๋”ฐ๋ผ ์›๋ณธ ์†Œ๋ฆฌ๋„ ์ถœ๋ ฅํ•˜๋ ค๋ฉด ์—ฐ๊ฒฐ
147
+ gainNode.connect(audioContext.destination); // ์ตœ์ข… ์Šคํ”ผ์ปค ์ถœ๋ ฅ
148
 
149
+ // dataArray ํฌ๊ธฐ ์žฌ์„ค์ • (analyser ์ƒ์„ฑ ํ›„)
150
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
 
151
 
152
+ // 4. ์˜ค๋””์˜ค ์ฒ˜๋ฆฌ ์‹œ์ž‘
153
+ processAudio();
 
154
 
155
+ // UI ์—…๋ฐ์ดํŠธ
156
+ startButton.disabled = true;
157
+ stopButton.disabled = false;
158
+ statusDiv.textContent = '๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ ์žฌ์ƒ ์ค‘...';
159
 
160
+ } catch (err) {
161
+ console.error('์˜ค๋””์˜ค ์‹œ์ž‘ ์˜ค๋ฅ˜:', err);
162
+ statusDiv.textContent = `์˜ค๋ฅ˜: ${err.message}`;
163
+ statusDiv.classList.add('error');
164
+ // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์ž์› ์ •๋ฆฌ
165
+ stopAudio();
166
+ }
167
+ });
168
 
169
+ // ---------- ์ค‘์ง€ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ ----------
170
+ stopButton.addEventListener('click', () => {
171
+ stopAudio();
172
+ });
173
 
174
+ // ---------- ์˜ค๋””์˜ค ์ค‘์ง€ ๋ฐ ์ž์› ์ •๋ฆฌ ํ•จ์ˆ˜ ----------
175
+ const stopAudio = () => {
176
+ if (animationFrameId) {
177
+ cancelAnimationFrame(animationFrameId); // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ”„๋ ˆ์ž„ ์ค‘์ง€
178
+ animationFrameId = null;
179
+ }
180
 
181
+ // ํ™œ์„ฑํ™”๋œ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ค‘์ง€ ๋ฐ ์—ฐ๊ฒฐ ํ•ด์ œ
182
+ activeOscillators.forEach(osc => {
183
+ try {
184
+ osc.stop();
185
+ osc.disconnect();
186
+ } catch (e) { /* ์ด๋ฏธ ์ค‘์ง€/ํ•ด์ œ๋œ ๊ฒฝ์šฐ ๋ฌด์‹œ */ }
187
+ });
188
+ activeOscillators = [];
189
+
190
+ // ์˜ค๋””์˜ค ๋…ธ๋“œ ์—ฐ๊ฒฐ ํ•ด์ œ
191
+ if (source) {
192
+ source.disconnect();
193
+ source.mediaStream.getTracks().forEach(track => track.stop()); // ๋งˆ์ดํฌ ์ŠคํŠธ๋ฆผ ํŠธ๋ž™ ์ค‘์ง€
194
+ source = null;
195
+ }
196
+ if (analyser) {
197
+ analyser.disconnect();
198
+ analyser = null;
199
+ }
200
+ if (gainNode) {
201
+ gainNode.disconnect();
202
+ gainNode = null;
203
+ }
204
 
205
+ // ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ๋‹ซ๊ธฐ (์ž์› ํ•ด์ œ)
206
+ if (audioContext && audioContext.state !== 'closed') {
207
+ audioContext.close().then(() => {
208
+ audioContext = null;
209
+ console.log("AudioContext closed.");
210
+ });
211
  }
212
+
213
+ // UI ์—…๋ฐ์ดํŠธ
214
+ startButton.disabled = false;
215
+ stopButton.disabled = true;
216
+ statusDiv.textContent = '์ค‘์ง€๋จ.';
217
+ dataArray = new Uint8Array(fftSize / 2); // dataArray ์ดˆ๊ธฐํ™”
218
+ };
219
 
220
  </script>
221