navigator.mediaDevices.getUserMedia({ audio: true }).then(
(stream)=>{
}
)
.catch(
(error)=>{
}
);
이 요청에서 엑세스에 성공하면 then 내의 콜백이 실행되고, 실패하면 error catch 절이 실행된다. 엑세스가 허용되었을 때 이 stream 을 활용해서 FFT 분석을 진행할 수 있다.
const audioContext = new window.AudioContext();
const audioSource = audioContext.createMediaStreamSource(stream);
위와 같이 오디오 소스를 생성했으면, 해당 오디오 소스를 통해서 외부 노드에 connect할 수 있다. 대표적으로 존재하는 노드가 Analyser 라는 노드가 있는데, 이 노드는 오디오 스트림을 읽어 푸리에 변환 등의 작업을 제공한다.
주파수 처리를 하기 위해서는 푸리에 변환이 반드시 필요하다. 푸리에 변환은 수학자 푸리에가 만들어낸 계산 공식으로 보통은 DFT 방법이 사용된다. 하지만 DFT 방법은 연산 시간이 매우 오래 걸리기에 일반적인 실시간 오디오 프로세스에는 적합하지 않다. 따라서 FFT 라는 고속 푸리에 변환 기법이 사용되는데 이 푸리에 변환은 일반적인 DFT 와는 연산 방법이 많이 다르고 결과도 규칙적으로 나오기에 조건 사항이 제법 존재한다.하지만 여기서는 FFT 특징에 대해서는 건너뛰고 호출해서 사용하는 방법만 언급한다.
const audioAnalyser = audioContext.createAnalyser();
아무튼 스트림 소스를 생성한 뒤 Analyser 객체를 생성하여 연결해주면 된다. 그러면 내부적인 푸리에 변환은 모두 처리되어 결과를 계산할 수 있다.
이 audioAnalyser 객체에 대해서 FFT Size를 지정할 수 있는데 그 방법은 아래와 같다.
audioAnalyser.fftSize = 2048;
FFT 사이즈는 반드시 2의 거듭제곱이어야 하는데, 이는 고속 푸리에 변환의 특징이다.
일반 DFT 는 원하는 범위로 계산할 수 있지만, FFT는 그렇지 않다. 고속으로 계산할 수 있는 대신 2의 거듭제곱으로 계산해야하고 그 결과의 반쪽만 사용할 수 있다는 특이점이 존재한다. (계산 결과는 복소수 값으로 절반 영역은 허수 영역이기 때문에 실제 사용하는 영역은 아니다.)
audioSource.connect(audioAnalyser);
AudioSource 의 connect 메서드를 통해 AudioAnalyser 에 연결하면 기본적인 세팅은 모두 끝난다.
const fftBufferLength = audioAnalyser.frequencyBinCount;
이제 실제 푸리에 변환된 바이트 버퍼를 가져와야하는데 웹 브라우저에서 계산하는 FFT 결과는 FFT 사이즈의 절반 값이 실제 데이터 버퍼에 담기기 때문에 (FFT의 특징상 전체 버퍼의 절반이 실제 유효한 데이터이다.) audioAnalyser.frequencyBinCount 로 값을 가져와보면 FFT Size의 절반 값임을 알 수 있다.
const buffer = new Uint8Array(fftBufferLength);
audioAnalyser.getByteFrequencyData(buffer);
즉 Uint8Array 의 바이트 공간을 만들 때 위 frequencyBinCount 에서 반환된 FFT Size 의 절반 값으로 할당한 뒤 해당 공간에 Analyser의 getByteFrequencyData 메서드를 통해 실제 유효한 마이크 스트림 버퍼 데이터를 담아주면 된다.
이 작업은 동기 작업이기 때문에 setInterval 등을 통해 반복해주며 값을 가져와준 뒤 원하는 작업을 수행하면 된다.
이제 해당 데이터 버퍼의 각 index 가 고유한 범위의 hz 주파수 값을 의미하며, 값이 진폭 세기를 의미한다. 다만 수치 계산 변환은 필요하다.