이야기 정리

타입스크립트 - 피아노 만들기 본문

Project/TypeScript

타입스크립트 - 피아노 만들기

jinhistory 2023. 3. 13. 15:19

사이트 보기 : https://palying-piano.netlify.app/

깃허브 보기 : https://github.com/beren-105/Mini-Project/tree/main/8_Piano

 

개발동기

피아노 소리는 좋아하지만, 나는 피아노를 칠 줄 알기는 커녕 악보를 읽을 줄도 모른다. 악보를 읽으려면 도레미파솔라시도를 하나하나 세야하니 피아노를 치는 것은 꿈도 못 꿀 일이다. 나와 같은 사람들이 편하게 피아노를 칠 수 있도록 피아노와 악보에 모두 계이름이 적혀 있는 사이트가 있었으면 했다.

또, 피아도도 칠 줄 모르지만 건반이 도레미파솔라시도 한줄이면 재미가 없었기에 높은 음 한줄과 흑건도 사용이 가능했으면 좋겠다는 마음으로 제작했다.

 

필요한 기능
  • 키보드를 누르거나, 건반을 클릭 했을 시 소리가 나야한다.
  • 키보드를 누르거나, 건반을 클릭 했을 시 건반이 움직이는 모션이 있어야한다.
  • 자유롭게 연주할 수 있는 자유연주 모드와 악보를 보고 따라치는 악보연주 모드를 각각 탭으로 구현한다.
  • 악보연주 모드일 시, 맞게 연주하면 점수가 올라가고 틀릴 시 점수가 낮아진다.
  • 악보연주 모드일 시, 맞은 계이름은 빨간색으로 표시한다.
  • 악보연주 모드일 시, 리셋 버튼을 클릭하면 점수와 표시가 원점으로 돌아간다.

 

파일 경로

작은 프로젝트에서도 파일을 나누는 연습을 하기 위해 여러개의 스크립트를 만들었다.

 

pianoItems -> 피아노 건반, 악보 등의 데이터

score -> 악보 연주 모드화면을 구성하고, 점수를 화면에 띄운다.

sheet -> 악보 연주 모드의 기능(맞게 연주했는지, 점수는 몇점인지, 리셋 버튼을 클릭 헸을 시 등등)을 클래스로 세팅

script -> 피아노 건반 세팅과 클릭 이벤트 등 피아노 기능에 있어 중요한 부분들

 

총 4개의 자바스크립트 파일로 나누어 작성했다.

 

 

발생한 문제(1) : 키를 누르고 있으면 소리가 중첩해서 들린다.


피아노를 칠 때 건반을 한번 누르면 한번만 소리가 나야한다. 하지만 keydown 이벤트를 사용해도 키를 계속 누르고 있으면 소리가 여러번 반복해서 들렸다.

 

해결 : .repeat 사용

repeat는 keybordEvent의 속성 중 하나로, 지정된 키가 반복되어 누르고 있을 경우 boolean값을 리턴한다.

그 외의 해결 방법으로는 let으로 새로운 변수를 만들어 눌렀을 시 false로 바꾸거나, once를 옵션으로 활용하는 방법이 있었다. 그 중에서 repeat를 활용한 이유는 1. 변수를 하나 더 만들지 않아도 가능했고, 2. 함수를 지속적으로 사용할 수 있었기 때문이다.

키 각각에 이벤트를 준 것이 아닌, document에 이벤트를 주었다. 그리고 해당 이벤트 내에서 forEach를 활용해 만들었다. 그렇기에 함수가 1회용이면 곤란했다. 함수를 계속 사용할 수 있는 방법으로는 repeat가 가장 적절했기에 이 방법을 채택했다.

const keys = document.querySelectorAll('#key');

keys.forEach((key) => {
    if (key instanceof HTMLButtonElement) {
        const keyboard = key.getAttribute('data-keyboard');

        if (e.code === keyboard && !e.repeat) {
            const score = new Score(e.code);
            score.littleStar()

            const keyData = whiteKeys.find(whiteKey => whiteKey.keyboard === e.code);
            const audio = new Audio(keyData?.src);
            audio.play();
            key.firstElementChild?.classList.add('key-active');
        }
    }
})

 

 

발생한 문제(2) : 타입스크립트에서 import 시 에러가 발생


Uncaught ReferenceError: exports is not defined

타입스크립트에서 파일을 import하자 위와 같은 에러 메시지가 출력됐다.

브라우저에서는 commonjs 모듈을 지원하지 않기 때문에 웹팩 등이 도구를 사용해 별도로 빌드를 해줘야하기 때문에 발생한 문제다.

 

해결

먼저 스크립트에 type module을 추가했다.

<script type="module" src="./script/script.js"></script>

그리고 tsconfig에서 module을 수정하자 에러가 사라졌다.

"module": "ES2015"

 

 

발생한 문제(3) : CORS 에러 발생


2번 문제를 해결하자마자 바로 3번 문제가 발생했다! 이 에러에 대해 제대로 이해하기 위해서는 몇가지 지식이 필요했다.

 

Origin이란?
  • protolcol, host, port까지를 합친 URL을 말한다.

 

Same-Origin Policy
  • 동일한 origin에서만 리소스를 자유롭게 가져올 수 있는 정책이다.
  • 이러한 정책이 필요한 이유는 해커가 사용자의 개인정보를 가로챌 수 있는 위험이 있기 때문에 이를 방지하기 위해서이다.
  • 여기서 조금 신기한 점은 이 출처 비교를 서버가 아닌 브라우저에서 한다. 때문에 크롬에서 발생한 에러가 다른 브라우저에서는 발생하지 않을 수 있다.

 

Cross-Origin Resource Sharing(CORS)란?

교차 출처 리소스 공유 정책으로, 다른 Origin의 리소스 공유에 대한 허용과 비허용 정책이다. 다른 출처간의 상호작용이 필요한 경우가 개발을 하다보면 발생하기 때문에 일부 허용해주는 것이다.

이 정책을 어겼기 때문에 오류가 발생했다. 정확히 어떤 부분을 어겼을까?

module의 경우 바로 로컬의 리소스를 요철할 시 origin이 null로 설정되는 것이다.

 

해결

그렇다면 해결방법은 간단하다. 파일을 서버에 올려 origin을 같게 만든다. 

http-server를 인스톨하거나, live server를 사용하면 된다.

 

 

발생한 문제(4) : map 사용 시 콤마가 찍히는 현상


문제가 발생한 모습

리액트에서는 단 한번도 이런 적이 없었는데 타입스크립트에서 map을 이용해 span 태그를 반복적으로 생성하자 위와같이 사이사이 콤마가 찍혔다.

 

해결 : .join('') 사용

map은 문자열로 값을 리턴할 때 기본적으로 콤마로 묶어 리턴 시키기 때문에 발생한 문제였다. .join('')을 사용해 문자열 사이 콤마를 제거해 문제를 해결했다.

 

 

느낀점


현재 코드의 가장 큰 문제점은 코드의 재활용이 어렵다는 점이다.

악곡을 추가하려면 코드를 새로 만들거나, 기존 코드를 수정하는 방법 밖에 없다. 추후 코드의 재활용 가능성을 높여 여러개의 악곡을 정보만 넣으면 추가할 수 있도록 수정하고 싶다.

 

 

 

참조

https://stackoverflow.com/questions/5353254/javascript-onkeydown-event-fire-only-once

https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat

https://stackoverflow.com/questions/54670544/how-to-fix-referenceerror-exports-is-not-defined-in-a-pure-typescript-project

https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-CORS-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95-%F0%9F%91%8F

 

Comments