const audioCtx = new window.AudioContext();
let analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
기기의 내장 마이크를 통해 사용자의 음성 데이터를 수집한다.
이때 음성 데이터는 Float32Array 형태로 들어오게된다.
수집한 음성 데이터를 주파수로 변환한다
주어진 버퍼의 각 값에 대해 제곱하여 더한 후 전체 크기로 나누고 제곱근을 취한다
for (let i = 0; i < SIZE; i++) {
let val = buf[i];
rms += val * val;
}
rms = Math.sqrt(rms / SIZE);
RMS 값이 일정 임계치(여기서는 0.1)보다 작으면 -1을 반환한다.
if (rms < 0.1) return -1;
버퍼의 양 끝에서 특정 임계치(0.2로 설정)보다 작은 값의 인덱스를 찾아 해당 부분을 잘라낸다.
let r1 = 0,
r2 = SIZE - 1,
thres = 0.2;
for (let i = 0; i < SIZE / 2; i++)
if (Math.abs(buf[i]) < thres) {
r1 = i;
break;
}
for (let i = 1; i < SIZE / 2; i++)
if (Math.abs(buf[SIZE - i]) < thres) {
r2 = SIZE - i;
break;
}
buf = buf.slice(r1, r2);
SIZE = buf.length;
버퍼를 이용하여 자동 상관 함수를 계산합니다.
var c = new Array(SIZE).fill(0);
for (let i = 0; i < SIZE; i++)
for (var j = 0; j < SIZE - i; j++) c[i] = c[i] + buf[j] * buf[j + i];
상관 함수에서 최대값과 해당 위치를 찾는다.
var d = 0;
while (c[d] > c[d + 1]) d++;
var maxval = -1,
maxpos = -1;
for (let i = d; i < SIZE; i++) {
if (c[i] > maxval) {
maxval = c[i];
maxpos = i;
}
}
var T0 = maxpos;
최대값을 기반으로 주파수를 추정한다
var x1 = c[T0 - 1],
x2 = c[T0],
x3 = c[T0 + 1];
var a = (x1 + x3 - 2 * x2) / 2;
var b = (x3 - x1) / 2;
if (a) T0 = T0 - b / (2 * a);
소수로 주어진 주파수는 위 공식을 통해 MIDI Note 값을 구할 수 있다.
본 프로젝트에서는 MIDI 음정값을 공통으로 사용한다 (+1이나 -1등의 연산)
export const getNoteFromFrequency = (frequency: number) => {
return Math.round(12 * Math.log2(frequency / 440) + 69);
};
export const getFrequencyFromNote = (note: number) => {
return 440 * Math.pow(2, (note - 69) / 12);
};
음정으로부터 음계와 옥타브를 도출한다.
C부터 B까지 이루어진 12개의 음계로 옥타브 하나를 이루기 때문에
구한 음정값을 12로 나눈 몫이 옥타브, 나먼지가 음계가 된다.
성별 선택 → 음성 수집 → 음정계산 → 이분탐색 → 최고/최저 음정 추출
에서 마지막에 해당하는 과정이다.
음역대를 알기 위해서는 사용자가 낸 음정의 최고값과 최저값을 구해야하는데,
이때 이분탐색 알고리즘을 사용한다.