개요

노래연습 해보기(클릭)

음치 탈출을 위해 노래 연습을 하고 싶어도 내가 부르는 음이 맞는지 틀린지조차 알 수가 없는게 음치라서 도무지 소용이 없었다. 그러다가 우연히 퍼펙트싱어라는 프로그램을 보고 저렇게 눈으로 음 높이를 볼 수 있다면 도움이 되지 않을까 하는 생각에 잠깐 짬을 내서 만들어보기로 했다.

사용 기술 스택

사실 딱히 뭐 쓴 게 없어서...

  • TypeScript

  • esbuild

요 정도?

필요한 기능과 구현

화면에 악보 그리기

우선 화면에 오선지와 음높이를 표시해주는 기능이 필요했다. 어차피 복잡한 그래픽 처리가 필요한 것은 아니라서 평소 게임 만들 때 하듯이 Canvas 를 하나 만들고 오선지를 그려주었다. 그리고 두 가지 입력값을 처리할 수 있도록 기능을 추가했다.

미리 정의된 악보를 표시하는 기능

음 높이와 해당 음의 start / end 시간을 가진 Note 라는 구조체를 정의하고 해당 구조체의 배열을 받아서 시간에 맞추어 화면에 뿌려주는 형식으로 구현했다.

실시간으로 입력되는 음 높이를 표시하는 기능

실시간으로 입력되는 음 높이를 받아서 큐 형태의 배열에 넣고 해당 배열을 화면 중앙에 맞추어 그려주는 루틴을 제작.

내 목소리를 입력받아 음 높이를 알아내기

음 높이 알아내기

일반적으로 이런 주파수 관련 기능에는 푸리에 변환이 사용된다. 수포자로 살아온 오랜 세월 덕에 푸리에변환 자체를 이해하는 건 어려웠지만 기본적인 이산 푸리에 변환과 관련한 코드들은 이너넷에 많이 있었기 때문에 해당 소스들을 보고 적절히 구현할 수 있었다.

마이크 입력 받기

UserMedia 관련 API 를 통해 음성 입력 소스를 요청하고 이를 WebAudio 의 Analyser 에 연결하면 간단히 입력 스트림을 얻어올 수 있다. 이렇게 얻은 데이터를 위에서 구현한 푸리에변환 소스에 던져주면 현재 입력값의 음 높이가 나오게 된다.

악보 입력받아 파싱하기

내 목소리만 보여줘도 사실 무슨 소용이 있겠는가... 맞는지 틀린지를 알아야 쓸모가 있지.. 그래서 악보를 화면에 보여줘야 한다. 악보 데이터를 어떻게 만들어야 할지 좀 고민하다가 QBasic 하던 시절에 음악 연주하려고 썼던 mmf 라는 형식이 떠올랐다. (아마 게이머들에게는 마비노기의 악보 문법으로 익숙할 그것)
해당 형식을 파싱할 수 있는 파서를 만들고(여기는 이 프로젝트에서 유일하게 유닛테스트를 추가했다. 파서는 유닛테스트 없이 개발하기 너무 힘드니깐) 문법 자체도 가사, 이음표 등을 지원할 수 있도록 약간 개조했다. 이렇게 해서 악보를 입력받으면 위에 말했던 Note 구조체의 배열을 반환하는 파싱 함수 개발 끗

악보 연주하기

입력받은 악보를 눈으로만 보면서 맞추자니 너무 쉽지 않았기 때문에 악보의 음을 연주해주는 기능도 추가했다. Note 구조체가 이미 음 높이와 길이를 가지고 있으니 Oscillator 를 사용해서 필요한 주파수의 소리를 만들어주기만 하면 된다.

결과

별로 소용없었다.... 프로그램이 잘못된거 아닌가 해서 아내가 하는걸 봣는데 아내는 칼같이 잘도 맞췄다. 나는... 노래를 할 수 없는 몸인것 같다.