Spaces:
Running
Running
Update index.html
Browse files- index.html +173 -79
index.html
CHANGED
@@ -1,11 +1,12 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
-
<title>FFT ๋ถ์ ํ ์ฌ์ธํ/์ฝ์ฌ์ธํ ์ฌ์
|
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 |
-
<
|
|
|
18 |
|
19 |
<script>
|
20 |
let audioContext;
|
21 |
let analyser;
|
22 |
let source;
|
23 |
-
let
|
|
|
|
|
24 |
|
25 |
// FFT ๋ถ์ํ ์ฃผํ์ ์ฑ๋ถ์ ๊ฐ์ (2์ ๊ฑฐ๋ญ์ ๊ณฑ)
|
26 |
const fftSize = 2048;
|
27 |
-
|
|
|
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')
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
-
|
74 |
-
|
75 |
|
76 |
-
|
77 |
-
|
78 |
-
const oscillator = audioContext.createOscillator();
|
79 |
|
80 |
-
|
81 |
-
|
82 |
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
|
|
|
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
92 |
|
93 |
-
|
94 |
-
|
95 |
-
oscillatorGain.gain.value = dataArray[frequencyIndex] / 255 * 0.1; // ํฌ๊ธฐ์ ๋น๋กํ์ฌ ๋ณผ๋ฅจ ์กฐ์
|
96 |
|
97 |
-
|
98 |
-
|
99 |
-
oscillatorGain.connect(gainNode);
|
100 |
|
101 |
-
|
102 |
-
|
|
|
|
|
103 |
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
|
|
|
|
|
|
|
|
108 |
|
109 |
-
|
110 |
-
|
|
|
|
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
|
|
|
|
|
|
115 |
|
116 |
-
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
|
|
|
|
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 |
|