JSFiddle - React, Tailwind, and code Playground

HTML

press z key

JavaScript

'use strict';

// Web Audio APIを利用するためのインスタンス生成
var audioContext = new AudioContext();
// キーごとにMIDIノートナンバーを割り振る
var keymap = {
  // Zキー = C4
  90: 60,
  // Sキー = C#4
  83: 61,
  // Xキー = D4
  88: 62,
  // Dキー = D#4
  68: 63,
  // Cキー = E4
  67: 64,
  // Vキー = F4
  86: 65,
  // Gキー = F#4
  71: 66,
  // Bキー = G4
  66: 67,
  // Hキー = G#4
  72: 68,
  // Nキー = A4
  78: 69,
  // Jキー = A#4
  74: 70,
  // Mキー = B4
  77: 71,
  // ,キー = C5
  188: 72
};

// キーダウンした際の処理
document.onkeydown = function(keyDownEvent) {
  // キー押しっぱなしの状態で発火した場合は、動作を終了する
  if (keyDownEvent.repeat === true) {
    return;
  }

  // オシレーターを作成
  var osciillatorNode = audioContext.createOscillator();
  // エンベロープジェネレーターを作成
  var envelopeGen = audioContext.createGain();

  // MIDIノートナンバーを周波数に変換
  var freq = 440.0 * Math.pow(2.0, (keymap[keyDownEvent.keyCode] - 69.0) / 12.0);

  // オシレーターの周波数を決定
  osciillatorNode.frequency.value = freq;

  // オシレーターをエンベロープジェネレーターに接続
  osciillatorNode.connect(envelopeGen);
  // エンベロープジェネレーターを最終出力にx接続
  envelopeGen.connect(audioContext.destination);
  // 現在の時間を取得
  var t1 = audioContext.currentTime;

  envelopeGen.gain.cancelScheduledValues(t1);
  envelopeGen.gain.setValueAtTime(1, t1);
  // オシレーター動作
  osciillatorNode.start();

  // キーを離した際に音が止まるよう、イベントを登録する
  document.addEventListener('keyup', checkKeyUp);

  // キーを離したかどうかチェック
  function checkKeyUp(keyUpEvent) {
    var release = 0.01;

    // 離したキーが、押下したキーで無い場合は処理を行わない
    if (keyUpEvent.keyCode !== keyDownEvent.keyCode) {
      return;
    }
    var keyUpTime = audioContext.currentTime;

    // キーをリリースした後にクリック音がなるのを防ぐため、現在の音量をセットする
    envelopeGen.gain.setValueAtTime(envelopeGen.gain.value, keyUpTime);

    envelopeGen.gain.linearRampToValueAtTime(0, keyUpTime + release);

    // オシレーターを停止する
    osciillatorNode.stop(keyUpTime + release);
    // 自身のイベントを削除
    removeEventListener('keyup', checkKeyUp);
  }
};