ブラウザで音を鳴らせるJSライブラリ「tone.js」の続きになります。
前回は一音鍵盤を作りましたが、一音だと少々飽きてくるので1オクターブに拡張しました!
鍵盤を増やしながらコードが冗長になっていくことに気づき、このままではフルキーボードの頃にはすごいことになるのでメンテしやすい書き方を検討しました。では、どうぞ。
※目次:
- 前回のおさらい!一音鍵盤
- まずは3音に増やしてみる!
- 1オクターブに拡張!
- querySelectorAll()でliタグを配列に入れる
- for文で鍵盤と配列を紐づける。
- liタグ内のID名を取得する
- triggerAttackRelease()メソッドにID名を入れる
- 黒鍵増やしてもJSは手付かずでOK!
- 最後に
※参考:前回はこちら
はじめの一音!tone.js事始める - クモのようにコツコツと
前回のおさらい!一音鍵盤
まずは前回の成果物である一音鍵盤を振り返る。こんな楽器です。え、ええ。楽器ですってこれは…。
HTMLには一音鍵盤。うむ。潔き。
<div id="piano"> <div id="do">ド</div> </div>
JSはこんなです。
//DOM var btn = document.getElementById("do"); //イベントリスナ btn.addEventListener("click", function () { //シンセ生成 var synth = new Tone.Synth().toMaster(); //ドが8分音符の長さ鳴る synth.triggerAttackRelease("C4", "8n"); }, false);
- まずDOMでHTMLの鍵盤
#do
を取得。 - イベントリスナで
#do
のクリックイベントを作成。 - 変数
synth
にtone.jsに用意されたシンセSynth()
をインスタンス化し、シンセ生成。音色はtoMaster()
。 #do
をクリックするとtriggerAttackRelease()
で音がなる。ド(C4
)を8分音符(8n
)の長さ。
ではこの鍵盤を増やしていきましょう。
まずは3音に増やしてみる!
まずHTMLの鍵盤を3つに増やす。今度はドミソのCメジャーコードを弾き放題の楽器を作る!
ちなみにID名は、tone.jsの音階がドレミではなくCDEのため、c4
などに変えました。
<div id="piano"> <div id="c4" class="w_key">ド</div> <div id="e4" class="w_key">ミ</div> <div id="g4" class="w_key">ソ</div> </div>
次にJSでDOMに三つの鍵盤を読み出す。
//DOM var btnDo = document.getElementById("c4"); var btnMi = document.getElementById("e4"); var btnSo = document.getElementById("g4");
前回はシンセの生成をイベントリスナの中で行ってましたが、この生成は最初にやればいいのでブロック文の外に出しました。
//シンセ生成 var synth = new Tone.Synth().toMaster(); //イベントリスナ(ド) btnDo.addEventListener("click", function () { //ドが8分音符の長さ鳴る synth.triggerAttackRelease("C4", "8n"); }, false);
さあ、残り二つの楽器も作ってみよう。「ド」ベースで音階部分を打ち替え。
//イベントリスナ(ミ) btnMi.addEventListener("click", function () { //ミが8分音符の長さ鳴る synth.triggerAttackRelease("E4", "8n"); }, false); //イベントリスナ(ソ) btnSo.addEventListener("click", function () { //ソが8分音符の長さ鳴る synth.triggerAttackRelease("G4", "8n"); }, false);
できたものがこれ!
おお!鳴ってます。Cメジャー弾き放題です。明るい響き。ドとミでラッパのマークの正露丸のメロディが作れることを発見。
1オクターブに拡張!
乗ってきたのでどんどんいきませう。1オクターブに拡張!ドレミ各音階と次のオクターブのド(C5
)まで。
<ul id="piano"> <li id="C4" class="w_key">ド</li> <li id="D4" class="w_key">レ</li> <li id="E4" class="w_key">ミ</li> <li id="F4" class="w_key">ファ</li> <li id="G4" class="w_key">ソ</li> <li id="A4" class="w_key">ラ</li> <li id="B4" class="w_key">シ</li> <li id="C5" class="w_key">ド</li> </ul>
DOMも拡張!鍵盤のイベントリスナも拡張!
//DOM var btnC4 = document.getElementById("C4"); var btnD4 = document.getElementById("D4"); var btnE4 = document.getElementById("E4"); var btnF4 = document.getElementById("F4"); var btnG4 = document.getElementById("G4"); var btnA4 = document.getElementById("A4"); var btnB4 = document.getElementById("B4"); var btnC5 = document.getElementById("C5"); //シンセ生成 var synth = new Tone.Synth().toMaster(); //ド(下)が8分音符の長さ鳴る btnC4.addEventListener("click", function () { synth.triggerAttackRelease("C4", "8n"); }, false); //レが8分音符の長さ鳴る btnD4.addEventListener("click", function () { synth.triggerAttackRelease("D4", "8n"); }, false); //ミが8分音符の長さ鳴る btnE4.addEventListener("click", function () { synth.triggerAttackRelease("E4", "8n"); }, false); //ファが8分音符の長さ鳴る btnF4.addEventListener("click", function () { synth.triggerAttackRelease("F4", "8n"); }, false); //ソが8分音符の長さ鳴る btnG4.addEventListener("click", function () { synth.triggerAttackRelease("G4", "8n"); }, false); //ラが8分音符の長さ鳴る btnA4.addEventListener("click", function () { synth.triggerAttackRelease("A4", "8n"); }, false); //シが8分音符の長さ鳴る btnB4.addEventListener("click", function () { synth.triggerAttackRelease("B4", "8n"); }, false); //ド(上)が8分音符の長さ鳴る btnC5.addEventListener("click", function () { synth.triggerAttackRelease("C5", "8n"); }, false);
でけた。
おお!1オクターブ、なってます!正直さっきのCメジャーはもどかしさがあったので開放感。
でも、見た目、まだ物足りなさがありますよね?そう。一オクターブは本来12音階あり、「黒鍵」にある半音がまだ抜けています。さあ、ここからさらに黒鍵も足してピアノらしい鍵盤を完成させましょう〜。
っと思ってコードを見ていて気がついたのでが……これってすっごく…
じょ、冗長やないかぁ〜〜〜〜〜い!!!!(号泣)
確かにオクターブの音階を鳴らすことは実現できているのだが、コードを見るとドレミファ…の音階名以外のところが同じような内容で何回も書かれている。今後のメンテナンスを考えると使い勝手が悪そうです。この調子で数オクターブのグランドピアノを再現したらどんどんコードが肥大化するな。うーむ、なんとかしたい。。
querySelectorAll()でliタグを配列に入れる
もっとこう共通部分が汎用的にまとまってメンテナンスや拡張や変更がしやすい書き方に改変できないかなぁ。
//DOM var btnC4 = document.getElementById("C4"); //ド(下)が8分音符の長さ鳴る synth.triggerAttackRelease("C4", "8n");
DOMとtone.jsのメソッドを比較。ID名C4
を文字列として取得してtriggerAttackRelease()
の第一引数に代入できれば解決するような気がするなぁ。
まず、肥大化したDOMを書き換えてみる。getElementById()
はIDしか取得できないが、JSには他にもCSSやjQueryみたいな書式でセレクタを取得できる「クエリセレクタ」というDOMがありました。クエリセレクタはquerySelector()
とquerySelectorAll()
の二種類あります。
querySelector()
は単一のセレクタを取得したいときに使う。querySelectorAll()
は同じ種類のセレクタが複数あるときに全部取得して配列[]
に入れる。
今回はli
が8つなのでquerySelectorAll()
ですね。よし、これでリストli
を取得してみよう。
//DOM var Key = document.querySelectorAll('#piano li');
これで#piano
の中のli
を全部取得できたはず!
for文で鍵盤と配列を紐づける。
次にこの配列Key
をfor
ループで数えて実行したい。
//鍵盤数分ループ for (var i = 0; i < Key.length; i++) { Key[i].addEventListener("click", function () { //何か処理をする }, false); }
- 条件1:変数
i
に0
で開始する。 - 条件2:
length
で配列Key
の件数を取得。i
がKey
の件数になるまで繰り返す。 - 条件 3:
++
で一回実行するごとにi
に+1ずつ加算する。 - 配列
Key
のi
番目のキーをクリックしたときに処理を実行する。
for文に入れることで鍵盤をクリックするとその鍵盤が配列Key
の何番目かを見に行き、その数をi
に代入してくれるわけです。
liタグ内のID名を取得する
次はli
の中のID名を取得したい。ちゃんと文字列として取得できるか、まずはアラートalert()
で確かめる。
えーっと、IDの取得はgetElementById()
かな?変数idName
作ってthis
(=クリックした要素)に繋げてみる。どうだ!
//鍵盤数分ループ for (var i = 0; i < Key.length; i++) { Key[i].addEventListener("click", function () { //ID名取得 var idName = this.getElementById(); //アラート表示 alert(idName); }, false); }
結果は…シーーーン。へんじがない。ただのしかばねのようだ。
そりゃそうだよね。get
できそうな名前であはあるが、this
はもともとKey
であり、Key
はもともとdocument.querySelectorAll('#piano li')
。
document.querySelectorAll('#piano li').getElementById()
とDOMを二つ繋げてるようなもんだからな。
ID名の取得の他の方法をついて調べたところid
プロパティを繋げるといいようです!シンプル!この書き方はclass名や他の属性もいろいろ取得できそうだな。覚えておきましょう。
さっそくthis.id
に書き換えてみる。
//鍵盤数分ループ for (var i = 0; i < Key.length; i++) { Key[i].addEventListener("click", function () { //ID名取得 var idName = this.id; //アラート表示 alert(idName); }, false); }
どうかな?
おお!!「C4」「D4」などID名のアラートが現れた!
triggerAttackRelease()メソッドにID名を入れる
このID名idName
をtone.jsのtriggerAttackRelease()
メソッドの第一引数に入れることができれば音が鳴りそうな気がするぞ!なんだかいけそうな気がする〜(ドキドキ)。
//鍵盤数分ループ for (var i = 0; i < Key.length; i++) { Key[i].addEventListener("click", function () { //ID名取得 var idName = this.id; //音階を代入 synth.triggerAttackRelease(idName, "8n"); }, false); }
さあどうだ!!?
やた!鳴った!!!
先ほどの冗長コードと挙動は何も変わらないのですが、コード量は大幅に減ったのではなかろうか。音階名をいちいち打たなくていいのが素敵すぎです。
黒鍵増やしてもJSは手付かずでOK!
さあ、こうなると理論上はさらに黒鍵を増やしてもJSは何も手を加えないまま音が鳴ってくれるはずです。
さっそくHTMLに黒鍵を増やしてみた。
<ul id="piano"> <li id="C4" class="w_key">ド</li> <li id="C#4" class="b_key"></li> <li id="D4" class="w_key">レ</li> <li id="D#4" class="b_key"></li> <li id="E4" class="w_key">ミ</li> <li id="F4" class="w_key">ファ</li> <li id="F#4" class="b_key"></li> <li id="G4" class="w_key">ソ</li> <li id="G#4" class="b_key"></li> <li id="A4" class="w_key">ラ</li> <li id="A#4" class="b_key"></li> <li id="B4" class="w_key">シ</li> <li id="C5" class="w_key">ド</li> </ul>
- tone.jsの黒鍵は
C#4
など、音階とオクターブ数の間にシャープ#
を入れる書き方だったので、それをそのままID名に入れました。 - CSSで鍵盤色や位置を変えるため、黒鍵には
.b_key
というclass名をつけました。
これでJSは手付かずのまま黒鍵の音は鳴ってくれるはず。どうだ!
おお!やた!鳴った鳴った〜黒鍵が鳴った!
最終的なJSコードはこんなです。
//DOM var Key = document.querySelectorAll('#piano li'); //シンセ生成 var synth = new Tone.Synth().toMaster(); //鍵盤数分ループ for (var i = 0; i < Key.length; i++) { Key[i].addEventListener("click", function () { //ID名取得 var idName = this.id; //音階を代入 synth.triggerAttackRelease(idName, "8n"); }, false); }
ね?コンパクトになったでしょ! やろうと思えばここからさらにグランドピアノ並みに鍵盤を増やしたとしてもJSは何も変える必要がないってことです。これは嬉しい!!
最後に
ということで、鍵盤を1オクターブに増やしつつ、メンテしやすいコードに書き直しました。満足できた。
完成してから音が同時に発声できない(和音を鳴らせない)ことに気づいた。スマホでもダメでした。今後の課題です。
さて次回は、このままグランドピアノばりに鍵盤を増やしてもいいのだが、シンセの音色を変えたり、メロディを奏でるのも興味あるのでそっちに進もうかな、と考え中。
※参考:Tone.jsを習得するためにやった事まとめ qiita.com