sound / index.html
kimhyunwoo's picture
Update index.html
bcaf481 verified
raw
history blame
5.32 kB
<!DOCTYPE html>
<html>
<head>
<title>FFT ๋ถ„์„ ํ›„ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์žฌ์ƒ ์˜ˆ์ œ</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 50px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
#status { margin-top: 20px; color: gray; }
</style>
</head>
<body>
<h1>FFT ๋ถ„์„ ํ›„ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์žฌ์ƒ</h1>
<p>๋งˆ์ดํฌ ์ž…๋ ฅ์„ ๋ฐ›์•„ FFT ๋ถ„์„ ํ›„ ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์œผ๋กœ ์†Œ๋ฆฌ๋ฅผ ์žฌ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.</p>
<button id="startButton">๋งˆ์ดํฌ ์ž…๋ ฅ ์‹œ์ž‘ ๋ฐ ์žฌ์ƒ</button>
<div id="status"></div>
<script>
let audioContext;
let analyser;
let source;
let oscillatorNode; // ์‚ฌ์ธ/์ฝ”์‚ฌ์ธ ํŒŒํ˜•์„ ์ƒ์„ฑํ•  ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ
// FFT ๋ถ„์„ํ•  ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ๊ฐœ์ˆ˜ (2์˜ ๊ฑฐ๋“ญ์ œ๊ณฑ)
const fftSize = 2048;
const bufferLength = fftSize / 2; // ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์˜ ํฌ๊ธฐ
// FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด
let dataArray = new Uint8Array(bufferLength); // ์ฃผํŒŒ์ˆ˜ ํฌ๊ธฐ (0-255)
// ์žฌ์ƒํ•  ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ์˜ ๊ฐœ์ˆ˜ (FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ช‡ ๊ฐœ์˜ ํŒŒํ˜•์œผ๋กœ ์žฌ๊ตฌ์„ฑํ• ์ง€)
const numReconstructedOscillators = 50;
document.getElementById('startButton').addEventListener('click', async () => {
try {
// ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ (๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์ ‘๋‘์‚ฌ ๊ณ ๋ ค)
audioContext = new (window.AudioContext || window.webkitAudioContext)();
// ๋งˆ์ดํฌ ์ž…๋ ฅ ์ŠคํŠธ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// ๋ฏธ๋””์–ด ์ŠคํŠธ๋ฆผ ์†Œ์Šค ๋…ธ๋“œ ์ƒ์„ฑ
source = audioContext.createMediaStreamSource(stream);
// ๋ถ„์„๊ธฐ ๋…ธ๋“œ ์ƒ์„ฑ
analyser = audioContext.createAnalyser();
analyser.fftSize = fftSize; // FFT ์‚ฌ์ด์ฆˆ ์„ค์ •
// ๋งˆ์ดํฌ ์†Œ์Šค๋ฅผ ๋ถ„์„๊ธฐ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ
source.connect(analyser);
// ------------- FFT ๋ถ„์„ ๋ฐ ํŒŒํ˜• ์žฌ๊ตฌ์„ฑ ๋กœ์ง -------------
// ์‹ค์‹œ๊ฐ„์œผ๋กœ FFT ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜
const processAudio = () => {
// ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ (ํฌ๊ธฐ)
analyser.getByteFrequencyData(dataArray);
// ์ด์ „ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์—ฐ๊ฒฐ ํ•ด์ œ ๋ฐ ์‚ญ์ œ
if (oscillatorNode) {
oscillatorNode.disconnect();
oscillatorNode = null;
}
// ์ƒˆ๋กœ์šด ๊ฒŒ์ธ ๋…ธ๋“œ ์ƒ์„ฑ (๋ณผ๋ฅจ ์กฐ์ ˆ)
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.5; // ์žฌ์ƒ ๋ณผ๋ฅจ (์กฐ์ ˆ ๊ฐ€๋Šฅ)
// ๊ฒŒ์ธ ๋…ธ๋“œ๋ฅผ ์ตœ์ข… ์ถœ๋ ฅ์— ์—ฐ๊ฒฐ
gainNode.connect(audioContext.destination);
// ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๋ฅผ ๋‹ด์„ ๋ฐฐ์—ด
const oscillators = [];
// FFT ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์ธํŒŒ/์ฝ”์‚ฌ์ธํŒŒ ์ƒ์„ฑ (๋‹จ์ˆœํ™”๋œ ์˜ˆ์ œ)
for (let i = 0; i < numReconstructedOscillators; i++) {
const oscillator = audioContext.createOscillator();
// ์ฃผํŒŒ์ˆ˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์—์„œ ํ•ด๋‹นํ•˜๋Š” ์ฃผํŒŒ์ˆ˜ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ
const frequencyIndex = Math.min(Math.floor(i * (bufferLength / numReconstructedOscillators)), bufferLength - 1);
// ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฃผํŒŒ์ˆ˜ ์„ค์ • (๋‹จ์ˆœํ™”)
// ์‹ค์ œ๋กœ๋Š” analyser.getByteFrequencyData๋Š” ํฌ๊ธฐ๋งŒ ์ œ๊ณตํ•˜๋ฉฐ, ์ฃผํŒŒ์ˆ˜๋Š” ์ธ๋ฑ์Šค์™€ ์ƒ˜ํ”Œ ๋ ˆ์ดํŠธ๋กœ ๊ณ„์‚ฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
// ์—ฌ๊ธฐ์„œ์˜ frequency๋Š” ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฃผํŒŒ์ˆ˜ ์ธ๋ฑ์Šค์™€ ๋น„๋ก€ํ•˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
const frequency = audioContext.sampleRate / fftSize * frequencyIndex; // ์‹ค์ œ ์ฃผํŒŒ์ˆ˜ ๊ณ„์‚ฐ
// ์ฃผํŒŒ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ๊ฑฐ๋‚˜ ๋†’์œผ๋ฉด ์ œ์™ธ (์กฐ์ ˆ ๊ฐ€๋Šฅ)
if (frequency > 50 && frequency < 15000) {
oscillator.type = 'sine'; // ๋˜๋Š” 'cosine'
oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime);
// ๊ฐ ์˜ค์‹ค๋ ˆ์ดํ„ฐ์˜ ๋ณผ๋ฅจ์„ ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜ ์„ฑ๋ถ„์˜ ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜์—ฌ ์„ค์ • (๋‹จ์ˆœํ™”)
const oscillatorGain = audioContext.createGain();
oscillatorGain.gain.value = dataArray[frequencyIndex] / 255 * 0.1; // ํฌ๊ธฐ์— ๋น„๋ก€ํ•˜์—ฌ ๋ณผ๋ฅจ ์กฐ์ ˆ
// ์˜ค์‹ค๋ ˆ์ดํ„ฐ๋ฅผ ๊ฒŒ์ธ ๋…ธ๋“œ์— ์—ฐ๊ฒฐํ•˜๊ณ  ๊ฒŒ์ธ ๋…ธ๋“œ๋ฅผ ๋ฉ”์ธ ๊ฒŒ์ธ ๋…ธ๋“œ์— ์—ฐ๊ฒฐ
oscillator.connect(oscillatorGain);
oscillatorGain.connect(gainNode);
// ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์‹œ์ž‘
oscillator.start();
// ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
oscillators.push(oscillator);
}
}
// ์ƒ์„ฑ๋œ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ๋…ธ๋“œ๋ฅผ ์ €์žฅ
oscillatorNode = oscillators;
// ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ๋‹ค์‹œ ๋ถ„์„ ๋ฐ ์žฌ์ƒ ์š”์ฒญ
requestAnimationFrame(processAudio);
};
// ๋ถ„์„ ๋ฐ ์žฌ์ƒ ์‹œ์ž‘
processAudio();
document.getElementById('status').textContent = '๋งˆ์ดํฌ ์ž…๋ ฅ ๋ฐ ์žฌ์ƒ ์ค‘...';
} catch (err) {
console.error('๋งˆ์ดํฌ ์ ‘๊ทผ ์˜ค๋ฅ˜:', err);
document.getElementById('status').textContent = `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${err.message}`;
}
});
</script>
</body>
</html>