クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Tone.js】Tone.Loop()でエイトビートを鳴らす

Tone.jsリズム編の続きです。前回までドラムパッドからリズム音を鳴らしていましたが、今回からはTone.Loop()メソッドを使ってドラムのフレーズを打ち込んでみます。まずはドラムの基本、エイトビートから。それではいきましょう!

【目次】

※参考:【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
  • 変数loopTone.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();
  }
  • もしcount1または5ならばkick()hihat()を実行
  • さもなくばcount3または7ならばsnare()hihat()を実行
  • それ以外はhihat()を実行

ループ内のカウント設定

下にもう一つif文がある。

  if(count === 8){
    count = 1;
  } else {
    count++;
  }
  • もしcount8ならばcount1にする
  • それ以外は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();
  } // 後略
  • もしカウントが156だったらキックとハイハットを鳴らす

最初の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();
  }  // 後略
  • もしカウントが145だったらキックとハイハットを鳴らす

最初のif文に4を追加することで「ドン|タド|ドン|タン」という音になる。

どちらもドラムのエイトビートで最初に練習するフレーズw

バージョン重要!

ちなみに、上記のコードは当初、なクリックイベントの中にtriggerAttackRelease()を書かないとなぜか鳴らなかった。

beat.addEventListener('click', () => {
  noise2.triggerAttackRelease('32n');
  Tone.Transport.toggle();
}, false);

一番小さい音であるハイハット音を書いてもボタンを押した時にいちいち「チッ」となるw

「EDM」の作品はそんなことしなくてもちゃんと処理が実行される。違いを細かく見てもどうにもわからず…。

最終的にリンクしているTone.jsのバージョンが違うことがわかった!

「EDM」はバージョン「13.8.10」をリンクしているが f:id:idr_zz:20200512183832j:plain

自分の作品はバージョン「13.1.25」と古いものだった。 f:id:idr_zz:20200512183836j:plain そしてこのバージョンにしてみたらちゃんと動いた!!

「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のリリースに合わせるのがいいのかもしれない。

最後に:今後の課題

f:id:idr_zz:20200512184640j:plain

ということでようやくエイトビートを鳴らすことができました!ただ、今のところまだ課題がいくつか。

  • if文だと直感的でないので配列やループなどを使ってもっとわかりやすく書きたい
  • カウントが整数じゃないと認識しない(1.5とか指定しても鳴らなかった)
  • 最終的に「○拍目のコンマいくつ」などの細かいタイミングを指定したい
  • 一音ごとに強拍、弱拍などのアクセントを変えたい(引数を使うったり)

いやー、自分はドラムをやっていたので、やはりリズムの打ち込みは楽しいですね。もっと複雑なビートを作りたいでっす!

それではまた!


※参考:Tone.jsを習得するためにやった事まとめ
qiita.com