728x90

40음계 피아노

포함 기능:

  • 화면에 40개 피아노 건반 표시
  • 마우스 클릭 연주
  • 키보드 연주 지원
  • 8비트 칩튠(square wave) 사운드
  • 건반 눌림 애니메이션
  • 여러 옥타브 지원

사용 키:

  • 1~0
  • Q~P
  • A~;
  • Z~/

 

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>40음계 8비트 피아노</title>
  <style>
    body {
      margin: 0;
      background: #0f172a;
      font-family: Arial, sans-serif;
      color: white;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      overflow-x: auto;
    }

    h1 {
      margin-bottom: 10px;
      color: #38bdf8;
    }

    .info {
      margin-bottom: 20px;
      color: #cbd5e1;
    }

    .piano {
      display: flex;
      position: relative;
      user-select: none;
    }

    .key {
      width: 48px;
      height: 220px;
      border: 2px solid #111827;
      background: white;
      color: black;
      display: flex;
      align-items: flex-end;
      justify-content: center;
      padding-bottom: 12px;
      box-sizing: border-box;
      cursor: pointer;
      font-weight: bold;
      transition: 0.05s;
    }

    .key:active,
    .key.active {
      background: #93c5fd;
      transform: translateY(2px);
    }

    .footer {
      margin-top: 20px;
      color: #94a3b8;
    }
  </style>
</head>
<body>
  <h1>🎹 40음계 8비트 피아노</h1>

  <div class="info">
    키보드 또는 마우스로 연주 가능
  </div>

  <div class="piano" id="piano"></div>

  <div class="footer">
    1~0 / Q~P / A~; / Z~/ 키 사용
  </div>

  <script>
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    const audioCtx = new AudioContext();

    const keyboardMap = [
      '1','2','3','4','5','6','7','8','9','0',
      'q','w','e','r','t','y','u','i','o','p',
      'a','s','d','f','g','h','j','k','l',';',
      'z','x','c','v','b','n','m',',','.','/'
    ];

    const piano = document.getElementById('piano');

    const baseFreq = 130.81; // C3

    const notes = {};

    keyboardMap.forEach((key, index) => {
      const freq = baseFreq * Math.pow(2, index / 12);
      notes[key] = freq;

      const keyEl = document.createElement('div');
      keyEl.className = 'key';
      keyEl.dataset.key = key;
      keyEl.innerHTML = `<div>${key.toUpperCase()}</div>`;

      keyEl.addEventListener('mousedown', async () => {
        await audioCtx.resume();
        playNote(freq, keyEl);
      });

      piano.appendChild(keyEl);
    });

    function playNote(freq, keyEl) {
      const now = audioCtx.currentTime;

      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();

      osc.type = 'square';
      osc.frequency.setValueAtTime(freq, now);

      gain.gain.setValueAtTime(0.25, now);
      gain.gain.exponentialRampToValueAtTime(0.001, now + 1);

      osc.connect(gain);
      gain.connect(audioCtx.destination);

      osc.start(now);
      osc.stop(now + 1);

      keyEl.classList.add('active');

      setTimeout(() => {
        keyEl.classList.remove('active');
      }, 120);
    }

    document.addEventListener('keydown', async (e) => {
      await audioCtx.resume();

      const key = e.key.toLowerCase();

      if (notes[key]) {
        const keyEl = document.querySelector(`[data-key='${key}']`);
        playNote(notes[key], keyEl);
      }
    });
  </script>
</body>
</html>
728x90

+ Recent posts