質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.35%

classListのエラー

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 613

score 15

前提・実現したいこと

HTMLとJavascriptで音階当てゲームを作っています。
現在、実装しようとしている機能は、クイズ開始時に問題の音に対しての
解答をクリック、又はキーボードで操作しようとしています。

発生している問題・エラーメッセージ

Google Chromeで動作確認を行っているのですが、鍵盤のキーボードをクリックしても音が鳴りません。
Chromeの検証ツールで確認したところ、下記エラーが表示されましたが、対処方法が分かりません。
Uncaught TypeError: Cannot read property 'classList' of null
    at OscillatorNode.checkNote(164行目)
(追記)
Oscillator[noteNo].connect(gain[notoNo])(64行目)
notoNoをnoteNoに訂正。

該当のソースコード

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>音当てゲーム</title>
<script>
</script>
</head>
<body onload="init()">
<p>音当てゲーム</p>
<input type="button" id="test" value="テスト" onclick="testGame()">
<input type="button" id="start" value="スタート" onclick="initGame()">
<input type="button" id="stop" value="ストップ" onclick="stop2Game()">
【第<span id="qNo">1</span>問:<span id="memo">白鍵のみ</span><span id="message"></span>
<hr>
volume:<input type="range" id="volume" value="0.5" min="0" max="1"
step="0.1" onchange="setVolume()">
<span id="volumeText">[0.5]</span>

<input type="button" value="ドレミを表示" onclick="showKeyboard(0)">
<input type="button" value="キーボードを表示" onclick="showKeyboard(1)">
<input type="button" value="表示を消す" onclick="clearKeyboard()">
<div id="piano"></div>
</body>
</html>
<style>
#piano {margin-top: 10px;}
#piano div {
  position: relative;
  width: 80px;
  height: 400px;
  float: left;
}
#piano button {position: absolute;}
.white{
  background-color: white;
  top: 0px;
  width: 80px;
  height: 400px;
  border-radius: 20px;
  z-index: 0;
}
.black{
  background-color: black;
  top: 0px;
  left: 50px;
  width: 60px;
  height: 240px;
  border-radius: 20px;
  z-index: 1;
}
button div{
  position: absolute;
  bottom: 0px;
  left: 0px;
  width: 100px;
  font-size: 20px;
}
white div {color: black;}
black div {color: gray;}
.red{background-color: red;}
.strike{background-color: yellow;}
#memo {color: #0000CC;}
#message {color: #CC0000;}
</style>
var audioContext = new AudioContext(); //オーディオコンテキスト
var oscillator = new Array();
var gain = new Array();
var gainNode;                           //Gainノード
var qNo,answer,cnt,total;               //問題番号、解答、正解数
var status;                             //ステータス(wait/qustion/answer)
var startTime,timer1,timer2;
var minNoteNo = 72;

var doremi = ["ド","ド#","レ","レ#","ミ","ファ","ファ#","ソ","ソ#","ラ","ラ#","シ"];

var keyboard = ["Z","S","X","D","C","V","G","B","H","N","J","M"];

function init(){

for(var i=0;i<keyboard.length;i++){
  var noteNo;

  noteNo = i+minNoteNo;

var key = document.createElement("button");
key.id = "key_" + noteNo;
key.onclick = touchKey;
key.onclick = hitKey;
key.onmousedown = mouseKeyDown;
key.onmouseup = mouseKeyUp;
key.onmouseleave = mouseKeyUp;

if(doremi[noteNo%12].indexOf("#") > 0){
        //黒鍵
        key.classList.add("black");
      }else{
        //白鍵
        key.classList.add("white");
        var baseKey = document.createElement("div");
      }
  //ベースの鍵盤に追加
    baseKey.appendChild(key);
    //ベースの鍵盤をピアノに追加
    document.getElementById("piano").appendChild(baseKey);
    }

    //Gainノードの作成
    gainNode = audioContext.createGain();
    setVolume();
}

function touchKey(event){
  var noteNo = Number(event.target.id.split("_")[1]) + 72;
  event.target.classList.add("yellow");
  //音階をセット
  sound(noteNo);
}

function sound(noteNo){
  document.getElementById("test").disabled = true;

var f = 440 * Math.pow(2,(noteNo-69) / 12);

  oscillator[noteNo] = audioContext.createOscillator();
  gain[noteNo] = audioContext.createGain();

  //接続
  oscillator[noteNo].connect(gain[noteNo]);
  gain[noteNo].connect(audioContext.destination);

  //周波数、ボリューム(無音)をセット
  oscillator[noteNo].frequency.value = f;
  gain[noteNo].gain.value = 0;

  //無音で鳴らす
  oscillator[noteNo].start(0);
  }

  function mouseKeyDown(event){
    //鍵盤のノートナンバー(4文字目以降の文字列)を取得
    var noteNo = event.target.id.substr(4);
    //音を鳴らす
    playKey(noteNo);
  }

  function mouseKeyUp(event){
    //鍵盤のノートナンバー(4文字目以降の文字列)を取得
    var noteNo = event.target.id.substr(4);
    //音を止める
    stopKey(noteNo);
  }

  document.onkeydown = function(event){
    //キーに割り当てられているショートカットをキャンセル
    event.preventDefault();
    //押されたキーボードと対応する音を鳴らす
    for(var i=0;i<keyboard.length;i++){
      if(event.key == keyboard[i].toLowerCase()) playKey(i+minNoteNo);
    }
  }

  document.onkeyup = function(event){
    //放されたキーボードと対応する音を止める
    for(var i=0;i<keyboard.length;i++){
      if(event.key == keyboard[i].toLowerCase()) stopKey(i+minNoteNo);
    }
  }

  function playKey(noteNo){
    //音を鳴らす(ボリュームを上げる)
    gain.value = document.getElementById("volume").value;
    //押されている鍵盤にstrikeクラスを追加
    document.getElementById("key_" + noteNo).classList.add("strike");
  }

  function stopKey(noteNo){
    //無音にする
    gain.value = 0;
    //放された鍵盤からstrikeクラスを削除
    document.getElementById("key_" + noteNo).classList.remove("strike");
  }

function hitKey(event){
  if(status == "question"){
    //解答
    cnt++;
    status = "answer";
    //音階の生成
    var noteNo = Number(event.target.id.split("_")[1]) + 72;
    event.target.classList.add("red");
    //音階をセット
    sound(noteNo);
  }
}

function sound(noteNo){
  //oscillatorノードの作成
  var oscillatorNode = audioContext.createOscillator();
  var f = 440 * Math.pow(2,(noteNo-69) / 12);
  oscillatorNode.frequency.setValueAtTime(f,audioContext.currentTime);
  //ノードの接続
  oscillatorNode.connect(gainNode);
  gainNode.connect(audioContext.destination);
  //0.2秒間再生
  oscillatorNode.start(audioContext.currentTime);
  oscillatorNode.stop(audioContext.currentTime + 0.2);
  if(status == "answer")oscillatorNode.onended = checkNote;
}

function setVolume(){
  //ボリュームをセット
  var volume = document.getElementById("volume").value;
  gainNode.gain.setValueAtTime(volume,audioContext.currentTime);
  document.getElementById("volumeText").innerHTML = "[" + volume + "]";
}

function checkNote(){
  //解答チェック
  var message,key;
  for(var noteNo=0;noteNo<doremi.length;noteNo++){
    key = document.getElementById("key_" + noteNo);
    if(key.classList.contains("red")){
      if(key.id.split("_")[1] == answer){
        message = "正解![" + doremi[answer] + "]";
        if(cnt == 1)total++;
        //5秒後に次の問題を表示
        timer2 = setTimeout(function(){
          qNo++;
          //次の問題/終了
          if(qNo < 11){
            startGame();
          }else{
            stopGame();
          }
        },5000);
      }else{
        message = "不正解!!";
        status = "question";
      }
        key.classList.remove("red");
        document.getElementById("message").innerText = message;
    }
  }
}

function testGame(){
  //開始
  var
  cnt = 0;
  for(var i=0;i<doremi.length;i++){
    key = document.getElementById("key_" + noteNo);
  document.getElementById("test").disabled = true;
}
}

function initGame(){
  //初期化
  total = 0;
  qNo = 1;
  //開始
  document.getElementById("start").disabled = true;
  startGame();
}

function startGame(){
  //開始
  cnt = 0;
  status = "wait";
  document.getElementById("qNo").innerText = qNo;
  document.getElementById("memo").innerText = "白鍵のみ";
  if(qNo > 5){
    document.getElementById("memo").innerText = "白鍵+黒鍵";
  }
  startTime = Date.now();
  timer1 = setInterval(countTime,100);
}

function stop2Game(){
  //終了
  status = "wait";
  var message = "終了 (正解数:" + total + "/" + qNo + ")"
  document.getElementById("message").innerText = message;
  document.getElementById("start").disabled = false;
}

function stopGame(){
  //終了
  status = "wait";
  var message = "終了 (正解数:" + total + "/10)"
  document.getElementById("message").innerText = message;
  document.getElementById("start").disabled = false;
}

function countTime(){
  //カウントダウン
  var time = 3 - Math.floor((Date.now() - startTime)/1000);
  document.getElementById("message").innerHTML = "<b>" + time + "</b>";
  //問題
  if(time == 0){
    status = "question";
    clearInterval(timer1);
    document.getElementById("message").innerText = "";
    answer = Math.floor(Math.random() * 12);
    //白鍵のみ調整
    if((qNo < 6)&&(doremi[answer].indexOf("#") > 0)) answer++;
    sound(answer + 72);
  }
}

function showKeyboard(n){
  var noteNo,keyText;
  for(var i=0;i<keyboard.length;i++){
    noteNo = i+minNoteNo;
    if(n == 0){
    keyText = "<div id='text"+noteNo+"'>"+doremi[noteNo%12]+"</div>";
  }else{
    keyText = "<div id='text"+noteNo+"'>"+keyboard[i]+"</div>";
  }
    document.getElementById("key_" + noteNo).innerHTML = keyText;
  }
}
function clearKeyboard(){

  for(var i=0;i<keyboard.length;i++){
    document.getElementById("key_" + (i+minNoteNo)).innerHTML = "";
  }
}

試したこと

エラーの該当箇所「 if(key.classList.contains("red")){」
JavaScript(158行目)から変数のスペルミス等確認していますがよくわかりません。
また、Oscillatorノードの周波数が規定値を超えている注意が出ていたため、
計算ミス等も探しましたが分かりませんでした。

補足情報(FW/ツールのバージョンなど)

Google Chrome最新バージョン:75.0.3770.142(Official Build) (64 ビット)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • kei344

    2019/07/28 20:39

    回答が付いた質問の編集は慎重に行ってください。質問文のコードについて回答にて指摘があった場合は「追記」し、元のコードを編集する場合も「直したこと」がわかるようにしてください。後から見た人から見て、現在回答されておられる方の回答が「無いコードに対しての指摘」になり、意味がわからなくなります。

    キャンセル

回答 1

+1

noteNonotoNo担っている箇所が
複数箇所あります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/28 20:14

    ご回答ありがとうございます。
    修正しましたが、エラーは解決しませんでした。

    キャンセル

  • 2019/07/28 20:16

    ほかにも間違っている箇所があります。
    確認が甘いので、もう一度よく見直してみてください。

    キャンセル

  • 2019/07/28 20:37

    確認したところ、Oscillatorノードの接続部分にありましたので
    修正しました。
    エラーは変わらず出ているのですが、いかがでしょうか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.35%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る