今回はWeb Audio APIで音楽を演奏してみることにします。既存のmp3ファイルを再生するのではなく、自分で音を作ります。
楽譜はここを参考にしました:【ドレミ付きあり無料楽譜】童謡_ねこふんじゃった 難易度別4楽譜 – ピアノ塾
Contents
基本部分 Web Audio APIで「ラ」を再生する
Web Audio APIでは音を生成するインターフェースとしてOscillatorNodeが用意されています。これで「ラ」の音を再生できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>テスト</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> </head> <body> <button onclick="play()">再生</button> <button onclick="stop()">停止</button> <script> window.AudioContext = window.AudioContext || window.webkitAudioContext; const ctx = new AudioContext(); const gainNode = ctx.createGain(); gainNode.gain.value = 0.5; // 音量の初期値を0.5にする let oscillator = null; function play(){ if(oscillator != null) // 二重に再生されないようにする return; oscillator = ctx.createOscillator(); oscillator.type = "square"; oscillator.frequency.value = 440; // ラは440hz oscillator.connect(gainNode).connect(ctx.destination); oscillator.start(); } function stop(){ oscillator?.stop(); oscillator = null; } </script> </body> </html> |
鍵盤を押下すると音がでるアプリをつくる
鍵盤のようなものを表示させて押下すると音がでるアプリをつくります。
HTML部分
まずHTML部分を示します。白鍵と黒鍵のボタンを生成します。黒鍵は存在しない部分がありますが、ここでは入れています(鍵盤の座標を決める処理が簡略化できるので)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>あなたも作曲家</title> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <link rel = "stylesheet" href = "./style.css" type = "text/css" media = "all"> </head> <body> <div id = "container"> <div>音量:<span id="vol-label">5</span></div> <input type="range" value="5" max="100" min="1" step="1" id="vol"><br> <div id = "field"> <button id = "c" class = "white"><div class ="floor-name">ド</div></button> <button id = "d" class = "white"><div class ="floor-name">レ</div></button> <button id = "e" class = "white"><div class ="floor-name">ミ</div></button> <button id = "f" class = "white"><div class ="floor-name">ファ</div></button> <button id = "g" class = "white"><div class ="floor-name">ソ</div></button> <button id = "a" class = "white"><div class ="floor-name">ラ</div></button> <button id = "b" class = "white"><div class ="floor-name">シ</div></button> <button id = "c2" class = "white"><div class ="floor-name">ド</div></button> <button id = "c#" class = "black"></button> <button id = "d#" class = "black"></button> <button id = "e#" class = "black"></button><!-- 存在しない黒鍵 --> <button id = "f#" class = "black"></button> <button id = "g#" class = "black"></button> <button id = "a#" class = "black"></button> <button id = "b#" class = "black"></button><!-- 存在しない黒鍵 --> </div> </div> <script src = "./index.js"></script> </body> </html> |
スタイルシートを示します。黒鍵と白鍵が重なっているので絶対配置を使います。
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#container { width: 360px; } #field { position: relative; height: 350px; } .white { height: 300px; width: 40px; position: absolute; background-color: #fff; } .black { height: 200px; width: 40px; position: absolute; background-color: #000; } /* 白鍵に表示する階名 */ .floor-name { margin-top: 200px; } |
グローバル変数と定数
JavaScript部分を示します。まずグローバル変数と定数を示します。
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const hzs = { 'c': 261, 'd': 294, 'e': 330, 'f': 349, 'g': 392, 'a': 440, 'b': 494, 'c2': 522, 'c#': 277, 'd#': 311, 'f#': 369, 'g#': 415, 'a#': 466, }; let audioCtx = null; let oscillator = null; let gainNode = null; let vol = 5; |
ページが読み込まれたときの処理
ページが読み込まれたら鍵盤の初期化とボリューム調整を可能する処理(後述)をおこないます。
1 2 3 4 |
window.onload = () => { initKeyboards(); // 後述 initVolume(); // 後述 } |
鍵盤を初期化する処理を示します。
ここでは適切な座標を指定して、イベントリスナーを追加しています。playFromHz関数とstop関数は後述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function initKeyboards(){ const keyboards = []; const whiteKeyboards = []; const arr1 = ['c', 'd', 'e', 'f', 'g', 'a', 'b', 'c2', ]; arr1.forEach(char => whiteKeyboards.push(document.getElementById(char))); for(let i = 0; i < whiteKeyboards.length; i++){ let left = 45 * i; whiteKeyboards[i].style.top = '0px'; whiteKeyboards[i].style.left = left + 'px'; } const blackKeyboards = []; const arr2 = ['c#', 'd#', 'e#', 'f#', 'g#', 'a#', 'b#', ]; arr2.forEach(char => blackKeyboards.push(document.getElementById(char))); for(let i = 0; i < blackKeyboards.length; i++){ let left = 45 * i + 20; blackKeyboards[i].style.top = '0px'; blackKeyboards[i].style.left = left + 'px'; if(blackKeyboards[i].id == 'e#' || blackKeyboards[i].id == 'b#') // 存在しない黒鍵は非表示 blackKeyboards[i].style.display = 'none'; } arr1.forEach(char => keyboards.push(document.getElementById(char))); arr2.forEach(char => keyboards.push(document.getElementById(char))); keyboards.forEach(keyboard => { const hz = hzs[keyboard.id]; if(hz == undefined) return; keyboard.addEventListener('mousedown', () => playFromHz(hz)); keyboard.addEventListener('mouseup', () => stop()); keyboard.addEventListener('touchstart', () => playFromHz(hz)); keyboard.addEventListener('touchend', () => stop()); }); document.addEventListener('mouseup', () => stop()); } |
initVolume関数はボリューム調整を可能にします。
1 2 3 4 5 6 7 8 9 10 |
function initVolume(){ document.getElementById('vol').addEventListener('input',() => { document.getElementById('vol-label').innerHTML=document.getElementById('vol').value; vol = document.getElementById('vol').value; if(gainNode!=null){ gainNode.gain.value=vol/100; } }); document.getElementById('vol').value = 5; } |
音の再生と停止
playFromHz関数は引数で指定された周波数で音を鳴らします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function playFromHz(hz){ if(audioCtx == null) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); if(gainNode == null){ gainNode = audioCtx.createGain(); gainNode.gain.value=vol/100; } if(oscillator == null){ oscillator = audioCtx.createOscillator(); oscillator.type = 'square'; oscillator.frequency.setValueAtTime(hz, audioCtx.currentTime); oscillator.connect(gainNode).connect(audioCtx.destination); oscillator.start(); } } |
stop関数は音が再生されている場合、停止させます。
1 2 3 4 5 6 |
function stop(){ if(oscillator != null){ oscillator.stop(); oscillator = null; } } |