[프론트엔드] 웹에서 음정 계산하기

SeonDal·2024년 5월 21일
0

🕊️ 기록기룩 

목록 보기
7/7
post-custom-banner

0. 사용한 기술

  • 동작환경: AudioContext API를 지원하는 웹 브라우저 (Chrome 42, Safari 14.1 이상)
  • 개발환경: python 3.9, React 18
  1. AudioContext API
    • 기기에 내장된 마이크와 연결하여 음성 데이터 수집
  2. FFT(Fast Fourier Transform, 고속 푸리에 변환)
    • 사용자의 음성 데이터를 주파수 영역으로 변환하여 음정 측정 후 옥타브와 음계 계산
  3. FrontEnd 개발
    • Typescript와 React기반 모바일 웹 서비스
    • TailwindCSS: 전반적인 UI 스타일링
    • Styled-Components을 이용한 실시간 음정 시각화
    • Recoil: 측정 결과값을 전역으로 상태관리

2. Context API를 이용한 음성 수집

const audioCtx = new window.AudioContext();

let analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;

기기의 내장 마이크를 통해 사용자의 음성 데이터를 수집한다.
이때 음성 데이터는 Float32Array 형태로 들어오게된다.


3. Correlate Function로 주파수 변환

수집한 음성 데이터를 주파수로 변환한다

1) RMS(Root Mean Square) 계산

주어진 버퍼의 각 값에 대해 제곱하여 더한 후 전체 크기로 나누고 제곱근을 취한다

        for (let i = 0; i < SIZE; i++) {
          let val = buf[i];
          rms += val * val;
        }
        rms = Math.sqrt(rms / SIZE);

2) 신호 강도 체크

RMS 값이 일정 임계치(여기서는 0.1)보다 작으면 -1을 반환한다.

        if (rms < 0.1) return -1;

3) 데이터 정제 및 불필요한 부분 제거

버퍼의 양 끝에서 특정 임계치(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;

4) 자동 상관 함수 계산

버퍼를 이용하여 자동 상관 함수를 계산합니다.

        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];

5) 최대값 위치 찾기

상관 함수에서 최대값과 해당 위치를 찾는다.

        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;

6) 주파수 추정

최대값을 기반으로 주파수를 추정한다

          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);

4. 음정 계산하기

1) MIDI Note 음정값 구하기

MidiNote=round(12×log2(440/주파수)+69)MidiNote=round(12×log_2(440/주파수)+69)

소수로 주어진 주파수는 위 공식을 통해 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);
};

2) 옥타브와 음계 도출

음정으로부터 음계와 옥타브를 도출한다.
C부터 B까지 이루어진 12개의 음계로 옥타브 하나를 이루기 때문에
구한 음정값을 12로 나눈 몫이 옥타브, 나먼지가 음계가 된다.


5. 이분탐색을 이용한 음역대 측정

성별 선택 → 음성 수집 → 음정계산 → 이분탐색 → 최고/최저 음정 추출

에서 마지막에 해당하는 과정이다.

음역대를 알기 위해서는 사용자가 낸 음정의 최고값과 최저값을 구해야하는데,
이때 이분탐색 알고리즘을 사용한다.

  • 사용자가 선택한 성별은 이분탐색 알고리즘의 초기값으로 사용한다.
  • 이분탐색 과정에서 위에 언급한 음성 수집 + 음정 계산 과정을 반복한다


profile
김선달 개발블로그
post-custom-banner

0개의 댓글