Tone.jsリズム編の続きです。前回までドラムパッドからリズム音を鳴らしていましたが、今回からはTone.Loop()
メソッドを使ってドラムのフレーズを打ち込んでみます。まずはドラムの基本、エイトビートから。それではいきましょう!
【目次】
- 前回のおさらい(ドラムパッド)
- イベント系のメソッド(Tone.Sequence()、Totne.Part()、Tone.Loop())
- Tone.Loop()による8拍子
- バスドラを増やしてエイトビートに!
- バージョン重要!
- 最後に:今後の課題
※参考:【Tone.js】ドラムパッドにエンベロープ(ADSR)とエフェクトを設定する - クモのようにコツコツと]
※参考:Tone.jsを習得するためにやった事まとめ
qiita.com
前回のおさらい(ドラムパッド)
前回いくつか作ったうちの一つ。エンベロープ(ADSR)を設定したドラムパッド。これを元にする。
See the Pen tone.js-Drum Pad-2 by イイダリョウ (@i_ryo) on CodePen.
//DOM const kick = document.querySelector('#kick'); const snare = document.querySelector('#snare'); const hihat = document.querySelector('#hihat'); // エンベロープ(キック) let optsMembrane = { pitchDecay : 0.001, envelope : { attack : 0.001 , decay : 1.5 , sustain : 0.01 , release : 0.01 }, volume: 25 } // エンベロープ(スネア) let optsNoiseSnare = { envelope : { attack : 0.001 , decay : 0.7 , sustain : 0 } } // エンベロープ(ハイハット) let optsNoiseHihat = { type : "brown", envelope : { attack : 0.001 , decay : 0.03 , sustain : 0 } } //シンセ生成 const membrane = new Tone.MembraneSynth(optsMembrane).toMaster(); const noise1 = new Tone.NoiseSynth(optsNoiseSnare).toMaster(); const noise2 = new Tone.NoiseSynth(optsNoiseHihat).toMaster(); kick.addEventListener('click', () => { membrane.triggerAttackRelease('C0','2n'); }, false); snare.addEventListener('click', () => { noise1.triggerAttackRelease('8n'); }, false); hihat.addEventListener('click', () => { noise2.triggerAttackRelease('32n'); }, false);
詳細は前回の記事を参照。
※参考:【Tone.js】ドラムパッドにエンベロープ(ADSR)とエフェクトを設定する - クモのようにコツコツと]
イベント系のメソッド(Tone.Sequence()、Totne.Part()、Tone.Loop())
メロディ系のイベントTone.Sequence()
まず検証したのは過去の自分がメロディ打ち込みで使ったTone.Sequence()
メソッド
※参考:Sequence
Tone.Sequence()
メソッドを使って作ったものこちら。
See the Pen Mario BGM-fix by イイダリョウ (@i_ryo) on CodePen.
詳細は下記の記事を参照。
※参考:【Tone.js】メロディ再生、シンセ音源加工、エフェクター接続(マリオを打ち込んだ!) - クモのようにコツコツと
メロディ系のイベントTotne.Part()
もう一つ、Totne.Part()
メソッド。こちらの方が細かい設定ができる。
※参考:Part
Totne.Part()
メソッドを使って作ったものこちら。
See the Pen Mario TheEnd 03 by イイダリョウ (@i_ryo) on CodePen.
詳細は下記の記事を参照。
※参考:【Tone.js】Tone.Part()でメロディのタイミング、音の長さ、音量を調節する(マリオのゲームオーバー音) - クモのようにコツコツと
メロディ系はうまく行かない(音程設定がネック)
しかし、これらの書き方ではうまく行かなかった。
上記の書き方は「音程」と「タイミング」を指定していたが、スネアとハイハットに使っているNoiseシンセは音程の概念がないようだった。
例えばAMシンセは第1引数が音程、音の長さだが
var synth = new Tone.AMSynth().toMaster(); synth.triggerAttackRelease("C4", "4n");
※参考:AMSynth
ノイズシンセには音の長さの引数しかない。
var noiseSynth = new Tone.NoiseSynth().toMaster(); noiseSynth.triggerAttackRelease("8n");
※参考:NoiseSynth
そのため、「音程」部分を指定することができない。
ループ系のイベントTone.Loop()
以前も紹介した下記の作品「EDM」ではドラムの打ち込みを実現している!
See the Pen EDM by Matt Farmer (@mrfarmer777) on CodePen.
コードを見るとTone.Loop()
メソッドを使っているようだった。
Tone.Loop creates a looped callback at the specified interval. The callback can be started, stopped and scheduled along the Transport’s timeline.
Tone.Loopは、指定された間隔でループコールバックを作成します。 コールバックは、トランスポートのタイムラインに沿って開始、停止、スケジュールできます。
むむ、これはリズムの打ち込みというよりループを作る用途と思って気にしてなかったメソッドだ。
こんなコード
var loop = new Tone.Loop(function(time){ //triggered every eighth note. console.log(time); }, "8n").start(0); Tone.Transport.start();
確かに「音程」を指定してない。これならリズムの打ち込にも使えそう?
※参考:Loop
Tone.Loop()による8拍子
先程の例を参考にTone.Loop()
の8拍子を反映してみた!
完成品
ドン|タン|ドン|タン
See the Pen tone.js-DrumBeat_1 by イイダリョウ (@i_ryo) on CodePen.
ハイハットは8拍子で、キックは1、5拍、スネアは3、7拍に鳴る。
HTMLコード
<h1>エイトビート</h1> <ul id="pad"> <li id="beat">Beat</li> </ul>
まずパッドは一つに減らした。id名は#beat
。
JSコード(全体)
//DOM const beat = document.querySelector('#beat'); // エンベロープ(キック) let optsMembrane = { pitchDecay: 0.001, envelope: { attack: 0.001 , decay: 0.75 , sustain: 0.01 , release: 0.01 }, volume: 25 } // エンベロープ(スネア) let optsNoiseSnare = { envelope: { attack: 0.001 , decay: 0.5 , sustain: 0 } } // エンベロープ(ハイハット) let optsNoiseHihat = { type: "brown", envelope: { attack: 0.001 , decay: 0.03 , sustain: 0 } } //シンセ生成 const membrane = new Tone.MembraneSynth(optsMembrane).toMaster(); const noise1 = new Tone.NoiseSynth(optsNoiseSnare).toMaster(); const noise2 = new Tone.NoiseSynth(optsNoiseHihat).toMaster(); // シンセ実行 const kick = () => { membrane.triggerAttackRelease('C0','2n'); }; const snare = () => { noise1.triggerAttackRelease('8n'); }; const hihat = () => { noise2.triggerAttackRelease('32n'); }; // ループ設定 let count = 1; var loop = new Tone.Loop((time) => { if(count === 1 || count === 5){ kick(); hihat(); } else if(count === 3 || count === 7){ snare(); hihat(); } else { hihat(); } if(count === 8){ count = 1; } else { count++; } }, '8n').start(0); Tone.Transport.bpm.value = 90; beat.addEventListener('click', () => { Tone.Transport.toggle(); }, false);
DOM取得
まずDOM取得は一つだけにした。
//DOM const beat = document.querySelector('#beat');
(なお、その下のエンベロープ設定も鳴らしながら少し調整したが、些末な違いなので省略)
シンセ実行
前回はパッドのクリックイベントでシンセを実行してたが、今回は何度も鳴らすので関数にした。
// シンセ実行 const kick = () => { membrane.triggerAttackRelease('C0','2n'); }; const snare = () => { noise1.triggerAttackRelease('8n'); }; const hihat = () => { noise2.triggerAttackRelease('32n'); };
kick()
関数でmembrane
を実行snare()
関数でnoise1
を実行hihat()
関数でnoise2
を実行
ループ設定(大枠)
ここからループ設定
// ループ設定 let count = 1; var loop = new Tone.Loop((time) => { // 8拍子の設定 }, '8n').start(0);
- 変数
count
にカウントの初期値1
- 変数
loop
でTone.Loop()
インスタンスを生成 Tone.Loop()
の第1引数は無名関数で引数はtime
(処理は後述)Tone.Loop()
の第2引数は8n
で8拍子start()
メソッドを実行。引数は0
で0から開始
ループの中で音を鳴らす
Tone.Loop()
の中の処理を見ていく。if文になっている。ここが音を鳴らす処理!
if(count === 1 || count === 5){ kick(); hihat(); } else if(count === 3 || count === 7){ snare(); hihat(); } else { hihat(); }
- もし
count
が1
または5
ならばkick()
とhihat()
を実行 - さもなくば
count
が3
または7
ならばsnare()
とhihat()
を実行 - それ以外は
hihat()
を実行
ループ内のカウント設定
下にもう一つif文がある。
if(count === 8){ count = 1; } else { count++; }
- もし
count
が8
ならばcount
を1
にする - それ以外は
count
の値を1つ加算する
これはカウントが8
になったら1
に戻す、それ以外は1つずつ加算する、という設定。
テンポ(BPM)
ここからはTransport
(イベント実行のタイミング)の設定。
※参考:Transport
Transport
のテンポ(BPM)を設定。
Tone.Transport.bpm.value = 90;
これは前回のマリオメロディの時と同じ書き方。今回は少しゆったり目で1分に90拍にしている。
※参考:【Tone.js】メロディ再生、シンセ音源加工、エフェクター接続(マリオを打ち込んだ!) - クモのようにコツコツと
再生設定(toggle())
Transport
の再生設定。
beat.addEventListener('click', () => { Tone.Transport.toggle(); }, false);
beat
をクリックした時、Transport
が再生状態なら停止、停止状態なら再生にする。
マリオメロディの時はstart()
とstop()
を使ったがtoggle()
だと交互に実行してくれるようだ!
※参考:Transport
バスドラを増やしてエイトビートに!
エイトビートその1(ドン|タン|ドド|タン)
少しアレンジ。キック音を増やしてエイビートっぽくする。
ドン|タン|ドド|タン
See the Pen tone.js-DrumBeat_2 by イイダリョウ (@i_ryo) on CodePen.
var loop = new Tone.Loop((time) => { if(count === 1 || count === 5 || count === 6){ kick(); hihat(); } // 後略
- もしカウントが
1
、5
、6
だったらキックとハイハットを鳴らす
最初のif文に6
を追加することで「ドン|タン|ドド|タン」という音になる。
エイトビートその2(ドン|タド|ドン|タン)
キック音をズラしてみる。
ドン|タド|ドン|タン
See the Pen tone.js-DrumBeat_3 by イイダリョウ (@i_ryo) on CodePen.
var loop = new Tone.Loop((time) => { if(count === 1 || count === 4 || count === 5){ kick(); hihat(); } // 後略
- もしカウントが
1
、4
、5
だったらキックとハイハットを鳴らす
最初のif文に4
を追加することで「ドン|タド|ドン|タン」という音になる。
どちらもドラムのエイトビートで最初に練習するフレーズw
バージョン重要!
ちなみに、上記のコードは当初、なクリックイベントの中にtriggerAttackRelease()
を書かないとなぜか鳴らなかった。
beat.addEventListener('click', () => { noise2.triggerAttackRelease('32n'); Tone.Transport.toggle(); }, false);
一番小さい音であるハイハット音を書いてもボタンを押した時にいちいち「チッ」となるw
「EDM」の作品はそんなことしなくてもちゃんと処理が実行される。違いを細かく見てもどうにもわからず…。
最終的にリンクしているTone.jsのバージョンが違うことがわかった!
「EDM」はバージョン「13.8.10」をリンクしているが
自分の作品はバージョン「13.1.25」と古いものだった。 そしてこのバージョンにしてみたらちゃんと動いた!!
「cdnjs」を見ると最新バージョンは「14.6.14」だったが、このバージョンだと動かない。。 ※参考:tone - cdnjs.com - The best FOSS CDN for web related libraries to speed up your websites!
「GitHub」のリリースを見ると「13.8.25」が最新とある。ちなみに「13.8.25」でもちゃんと動く。
※参考:Releases · Tonejs/Tone.js · GitHub
不具合などを常に修正していると思うので、今後もバージョンは気にした方が良さそう。ただし、新しすぎるのもよくないのかな?GitHubのリリースに合わせるのがいいのかもしれない。
最後に:今後の課題
ということでようやくエイトビートを鳴らすことができました!ただ、今のところまだ課題がいくつか。
- if文だと直感的でないので配列やループなどを使ってもっとわかりやすく書きたい
- カウントが整数じゃないと認識しない(1.5とか指定しても鳴らなかった)
- 最終的に「○拍目のコンマいくつ」などの細かいタイミングを指定したい
- 一音ごとに強拍、弱拍などのアクセントを変えたい(引数を使うったり)
いやー、自分はドラムをやっていたので、やはりリズムの打ち込みは楽しいですね。もっと複雑なビートを作りたいでっす!
それではまた!
※参考:Tone.jsを習得するためにやった事まとめ
qiita.com