본문 바로가기
웹 프로그래밍/기초

웹 프로그래밍 기초 4일 - 피아노 건반 구현🎹

by JJong | 쫑 2024. 5. 26.

'키보드로 피아노 건반 연주를 할 수 있으면 좋겠다'는 생각에서 시작한 아이디어 + 외부 혹은 내부 파일을 가져다가 써보는 작업을 해보고 싶었음

 

완성본 : https://jshman.github.io/web/simulator/inArmy/piano/index.html

github source : https://github.com/Jshman/web/tree/main/simulator/inArmy/piano

 


추가하면 좋을 패치

  1. 지금이 몇 octave인지 알 수 없어 불편함.
  2. 낮은 음, 높은 음 건반을 한 세트 씩 더 만들어 두면 옥타브를 동시에 play하는 데에 지장이 크지 않을 듯함.
    >> 그러면 키로 매핑 하는데에 한계가 있음
  3. input 을 받아서 input 받은 순서대로 연주
  4. 음의 길이(박자) 추가

당장 떠오르는 것은 이정도.. 누군가에게 내놓기엔 부끄러운 수준이다.


🎹 도레미파솔라시 정하기;  qwerty 키보드 기준 🎹

 

  1. 도레미파솔라시?
    g h j k l ; ' 를 각각 도레미파솔라시로 설정.
  2. 그럼 반올림( # )은?  → 좌측 위 키 사용.
    따라서 y u i o p [ ] 이 각각 도# 레# 미#(파) 파# 솔# 라# 시# (* 미#은 파 이므로 '파' 소리가 재생될 생각)
  3. 그럼 반내림( b )는?   우측 아래 키 사용
    따라서 v b n m , . / 가 각각 도b(시) 레b 미b 파b(미) 솔b 라b 시b (* 도b는 시 위치라서 '시'가 재생될 생각)
    굳이 만들 필요 없다고 판단함.
  4. 옥타브는? → '+', '-' 를 통해 옥타브 음 출력 가능하게 설정
    높은 도 : +g  입력 (+로 설정 후, g 키입력)

🎵 audio 파일 준비 🎶

같은 폴더에 위치
audio 파일 안

다운로드는 여기에서 했습니다.


HTML로 틀 구성🧱

1. 피아노 건반 구현하기

도레미파솔라시 - 도#레# 파#솔#라#시# : 총 13개의 건반이 필요.

<body>
    <div class="piano">
        <button class="key white" data-note="C"></button>
        <button class="key black" data-note="Cs"></button>
        <button class="key white" data-note="D"></button>
        <button class="key black" data-note="Ds"></button>
        <button class="key white" data-note="E"></button>
        <button class="key white" data-note="F"></button>
        <button class="key black" data-note="Fs"></button>
        <button class="key white" data-note="G"></button>
        <button class="key black" data-note="Gs"></button>
        <button class="key white" data-note="A"></button>
        <button class="key black" data-note="As"></button>
        <button class="key white" data-note="B"></button>
    </div>

 

    <audio id="audioC" data-note="C"></audio>
    <audio id="audioCSharp" data-note="Cs"></audio>
    <audio id="audioD" data-note="D"></audio>
    <audio id="audioDSharp" data-note="Ds"></audio>
    <audio id="audioE" data-note="E"></audio>
    <audio id="audioF" data-note="F"></audio>
    <audio id="audioFSharp" data-note="Fs"></audio>
    <audio id="audioG" data-note="G"></audio>
    <audio id="audioGSharp" data-note="Gs"></audio>
    <audio id="audioA" data-note="A"></audio>
    <audio id="audioASharp" data-note="As"></audio>
    <audio id="audioB" data-note="B"></audio>
    <!-- 필요한 만큼의 옥타브별로 추가 -->
    
    <script src="script.js"></script>
</body>

HTML5 부터는 audio라고, 내부에서 음원을 재생할 수 있게 해준다고 한다.

 

#(sharp) 기호는 제대로 인식이 안 되는 경우가 많아, Sharp의 이니셜인 S를 가져다 붙였다.

 

추가로 data-note 또한 HTML5에서 제공하는 사용자 정의 데이터 속성 중 하나로, data-로 시작하는 속성은 개발자가 HTML 요소에 추가적인 정보를 저장할 수 있도록 해준다. 부가적인 설명요소를 주석이 아닌 형태로 함께 저장할 수 있다는 점이 특징이다.

data-note의 의미와 기능

  • 의미: data-note는 해당 HTML 요소가 어떤 음을 나타내는지를 나타내기 위해 사용됩니다. 예를 들어, data-note="C"는 이 특정 HTML 요소(여기서는 <button class="key white" data-note="C">)가 "C" 음을 나타낸다는 것을 의미합니다.
  • 기능: JavaScript에서 이 데이터를 읽어서 사용할 수 있습니다. 예를 들어, 키보드 입력에 따라 특정 음을 재생하거나, 버튼 클릭에 따라 특정 음을 재생할 때 이 값을 사용합니다.

✨CSS로 꾸미기✨

1. 건반 크기 조절하기

아무것도 꾸미지 않은 상태라면, 이렇게 정렬되어 있기만 한다.

우선, 건반의 크기를 모두 키워보자.

.key {
    width: 40px;
    height: 200px;
}

key white, key black에 모두 적용가능한 클래스다. 자세히는 여기를 통해 알아보자.

 

2. 건반에 색 입히기

건반이 너무 밋밋하므로, 색을 입혀보겠다.

색을 입히는 것은 { background : #rgb } 로 할 수 있다.

흰 건반은 #FFFFFF로, 검은 건반은 #000000으로 설정하면 된다.

 

.black {
    background: #000000;
}

.white {
    background: #FFFFFF;
}

 

3. 건반 사이 간격 조절하기

실제 건반은 사이에 간격이 다닥다닥 붙어 있으므로, 마찬가지로 건반 사이 간격을 붙여보겠다.

그럴싸해졌다.

 

4. 검은 건반 크기 조절하기

검은 건반은 흰 건반 사이에, 작게 있기 때문에 우선 크기 조절을 해볼 생각이다.

크기와 어느정도의 위치를 잡아주었다.

5. 검은 건반을 위로 올리기

key black, key white 는 piano class 아래의 클래스이다. 피아노 자체의 display를 flex로 설정해서 검은 건반의 위치를 위로 옮겼다. 자세히 알고 싶다면, 'CSS Flex를 익혀보자' 또는 '모질라'에서 공부해보자.

 

6. 흰 건반은 붙이고 검은 건반을 그 위에 두기

이제 진짜 피아노 건반처럼 만들 차례다. 흰 건반은 서로 붙이고, 검은 건반은 제자리에 있게 만들 계획이다.

이것은 z-index로 설정할 수 있다. 흰 건반을 1로, 검은 건반을 2로 설정해두면 자연스레 건반의 위치가 잡힐 것이다.

z-index에 할당된 숫자의 크기가 작은 순서대로 만들어진다. 그래서 z-index가 2인 검은 건반이 z-index가 1인 흰 건반 보다 위에 그려진 것이다. 

  • z-index로 생성에 우선순위롤 둬, 겹침이 가능하도록 설정했다.
  • 그리고 margin을 이용해서 건반 사이가 겹치도록 했다.

기본적으로 먼저 선언된 태그를 가장 위에 올린다고 한다. 'Z-index 속성 이해하기'에서 잘 알 수 있었다.

 

7. 화면 가운데로 가져오기

지금까지는 피아노 건반이 웹페이지 가장 왼쪽 상단에 박혀서 보기가 힘들었다.

피아노 건반의 전체적인 크기를 조절하기 전에, 화면 중앙으로 옮기겠다.

첫 시도에 이렇게 냈다. 알고보니 align-items 속성이 세로 기준으로 화면 가운데 정렬이었던 것이다.

그리고 .piano안에 들어 있으니, 피아노를 감싸고 있는 <piano> 안에서 중앙 정렬을 실시한 것이다. 의도대로 하기 위해선 <body> 에서 중앙정렬을 해야 한다.

* piano 의 margin을 50px 정도로 한 뒤에 확인하면

이처럼 piano의 중앙에서 정렬됨음 쉽게 파악할 수 있다.

그래서 body로 빼서 정렬을 진행했더니 의도대로 정렬되었다.

body {
    display: flex;
    justify-content: center;     /*가로 기준 화면 가운데 정렬*/
    align-items: center;         /*세로 기준 화면 가운데 정렬*/
}
.piano {
    display:flex;
}

 

하지만 아직 웹페이지의 중앙에 오지 않는다. 이거는 단순히 body의 높이를 늘여서 맞추기로 했다.

이렇게 대강 중앙으로 맞췄다.

8. 건반 크기 키우기

건반 크기가 페이지 크기에 비해 너무 작다고 느껴져서 크기를 키우기로 했다. 사이즈를 여러 시도 끝에 1.2배씩 키웠다.


🎡Javascript 기능 구현🎡

0. 데이터에 대한 초기 상태

const keys = document.querySelectorAll('.key');
const keyMap = {
    'g': 'C', 'h': 'D', 'j': 'E', 'k': 'F', 'l': 'G', ';': 'A', "'": 'B',
    'y': 'Cs', 'u': 'Ds', 'i':'E' ,'o': 'Fs', 'p': 'Gs', '[': 'As', ']': 'Bs'
};
let octave = 0;

document.querySelectorAll()

이 메서드는 CSS 선택자(selector)를 사용하여 문서(document)에서 일치하는 모든 요소를 선택합니다.

여기서는 '.key'라는 CSS 클래스 선택자를 사용하고 있습니다.

선택된 요소들은 NodeList 객체로 반환됩니다. NodeList는 배열과 유사한 객체로, 선택된 요소들을 순회할 수 있습니다.

 

1. 키 입력 시에 소리 내기

document.addEventListener('keydown', (event) => {
    if (event.key === '-' || event.key === '+') {
        octave = event.key === '-' ? octave - 1 : octave + 1;
    } else {
        const note = keyMap[event.key];
        if (note) {
            playNoteWithOctave(note, octave);
        }
    }
});

function playNote(note) {
    const audio = new Audio(`audio/${note}${octave}.mp3`);
    audio.play();
}

2. 건반 클릭 시에 소리 내기

keys.forEach(key => {
    key.addEventListener('click', () => {
        const note = key.dataset.note;
        playNoteWithOctave(note, octave);
    });
});

lambda 식을 이용했다. keys에는 key에 대한 객체가 들어있다고 했다. 각 key에 대해 click 이벤트 리스너를 달아주고, 실행할 함수를 입력해주면 된다.


Behind

처음에 # 때문에 반올림 음이 출력이 안 되는 줄 모르고, 아래처럼 코드를 바꿨었다.

const blacks = document.querySelectorAll('.black');
const whites = document.querySelectorAll('.white');

...

blacks.forEach(black => {
    black.addEventListener('click', () => {
        const note = black.dataset.note;
        playNoteWithOctave(note, octave);
    });
});

whites.forEach(white => {
    white.addEventListener('click', () => {
        const note = white.dataset.note;
        playNoteWithOctave(note, octave);
    });
});

 

댓글