Tone.jsの続きです。前回はTone.Part()
を使ってシャッフル、16ビートなどのリズムを作りました。今回はこれまで作ったリズムを合体させて、BPMとリズムの切り替えができるビート・プレイヤーを作りました!演奏練習用、BPM(曲の速度)調査など実用的なプレイヤーになったと思います。それではいきましょう!
【目次】
- 前回までのおさらい
- ビート・プレイヤー(完成品)
- BPMとは
- HTMLコード全体
- JSコード(全体)
- JSコード(DOM、シンセ設定)
- JSコード(リズム設定値)
- JSコード(リズム設定)
- JSコード(ビート設定)
- JSコード(ボタン設定)
- 音楽の三要素(メロディ、ハーモニー、リズム)
- React/Next版ビートプレイヤー作成!
- 最後に
※参考:前回記事
【Tone.js】Tone.Part()でいろいろなリズムを鳴らす ※12〜32拍子&シャッフル編 - クモのようにコツコツと
※参考:Tone.jsを習得するためにやった事まとめ
qiita.com
前回までのおさらい
ボタンを押すとリズムがなる仕組みで1〜32拍子までいろいろ作った。
See the Pen tone.js-DrumBeat_Swing 16beat by イイダリョウ (@i_ryo) on CodePen.
詳細は前回までの記事を参照
※参考:前々回(1〜8拍子)
【Tone.js】Tone.Part()でいろいろなリズムを鳴らす ※1〜8拍子編 - クモのようにコツコツと
※参考:前回(12〜32拍子)
【Tone.js】Tone.Part()でいろいろなリズムを鳴らす ※12〜32拍子&シャッフル編 - クモのようにコツコツと
今回はこれらを合体させてリズムを切り替えて鳴らしたい。ついでにBPM(速度)も変更する。
ビート・プレイヤー(完成品)
ここ数日、これを作るのに時間を費やしていた。ついに完成!!
See the Pen tone.js-BeatPlayer by イイダリョウ (@i_ryo) on CodePen.
音が鳴らない場合はこちら(スマホ画面でもちょうどいい幅、高さになった♪)
※参考:https://codepen.io/i_ryo/full/dyGpbxL
仕様はこんな感じ
- 再生ボタン「▶︎」を押すと音がなる(もう一回押すと止まる)
- 初期設定は120BPMの8拍子(エイトビート)
- 「BPM」のスライダーを動かすと速度が変わる(30〜250BPM)
- 「リズム」のラジオボタンを押すとリズムが変わる
こんな使い方ができます!
- 拍子によって変わるリズムの違いを知る
- 流れている音楽に合わせてBPMを調べる
- ビートを鳴らしながら楽器演奏の練習を行う
BPMとは
BPMとは「Beats Per Minute」の略。1分あたりのビート数(4拍子ベース)。
※参考:ビーピーエム:BPMとは | 偏ったDTM用語辞典 - DTM / MIDI 用語の意味・解説 | g200kg Music & Software
ジャンルによって使われるBPMが異なる。
※参考:【効果絶大!? - BPM(テンポ)設定集 -】|さがはじめ/Polyamory house|note
ちなみにBPM116は「絶対テンポ」らしい。集中力が増すとか。0.5秒に1回くらいの速さか。
※参考:記事の作成スピードが爆速的にあがるテンポ「BPM116」を聴いてみて! | 暮らしの自動化メディアASHETPIA【アシェトピア】
今回作ったビート・プレーヤーのBPMは30〜240の範囲にした(初期値は120)。
- 最小値:30は1拍=2秒
- 初期値:120は1拍=0.5秒(絶対テンポに近い!)
- 最大値:240は1拍=0.25秒。
HTMLコード全体
以下、書いたコードを解説。まずHTMLから。
<h1>Beat Player</h1> <ul id="pad"> <li id="beat">▶︎</li> </ul> <div class="setting"> <section class="bpm"> <h2>BPM: <span class="val"></span></h2> <input type="range" name="range" min="30" max="240" value="120" class="range"> </section> <section class="rhythm"> <h2>リズム: <span class="val"></span></h2> <label><input type="radio" name="beat" class="beat1" value="1拍子">1</label> <label><input type="radio" name="beat" class="beat2" value="2拍子">2</label> <label><input type="radio" name="beat" class="beat3" value="3拍子">3</label> <label><input type="radio" name="beat" class="beat4" value="4拍子">4</label> <label><input type="radio" name="beat" class="beat5" value="5拍子">5</label> <label><input type="radio" name="beat" class="beat6" value="6拍子">6</label> <label><input type="radio" name="beat" class="beat7" value="7拍子">7</label> <label><input type="radio" name="beat" class="Beat8" value="8拍子" checked="checked">8</label> <label><input type="radio" name="beat" class="beat12" value="12拍子">12</label> <label><input type="radio" name="beat" class="beatShuffle" value="シャッフル">Shuffle</label> <label><input type="radio" name="beat" class="beat16" value="16拍子">16</label> <label><input type="radio" name="beat" class="beat24" value="24拍子">24</label> <label><input type="radio" name="beat" class="beatSwing16" value="ハネた16">Swing16</label> <label><input type="radio" name="beat" class="beat32" value="32拍子">32</label> </section> </div>
#beat
が再生・停止ボタン. bpm
の.range
がBPM変更のスライダー.range
の設定、最小値min
は30
、最大値max
は240
、初期値value
は120
. rhythm
のinputタグ(name
属性がbeat
)がリズム変更のラジオボタンvalue
属性8拍子
のinputタグにchecked
が入って初期に選択されている
スライダーのinputタグ(type="range")の設定値を属性に書いている。
※参考:<input type="range"> - HTML: HyperText Markup Language | MDN
(CSSは割愛します。気になる方はCodePen参照)
※参考:https://codepen.io/i_ryo/pen/dyGpbxL
JSコード(全体)
次はJSコードの全体。
// DOM const beat = document.querySelector('#beat'); const BPMVal = document.querySelector('.bpm .val'); const BPMRange = document.querySelector('.bpm .range'); const rythmVal = document.querySelector('.rhythm .val'); const rythmRadio = document.querySelectorAll('.rhythm input[name="beat"]'); // エンベロープ(キック) let membraneKickOpts = { pitchDecay: 0.001, envelope: { attack: 0.001 , decay: 0.75 , sustain: 0.01 , release: 0.01 }, volume: 25 } // エンベロープ(スネア) let noiseSnareOpts = { envelope: { attack: 0.001 , decay: 0.5 , sustain: 0.01 } } // エンベロープ(ハイハット) let noiseHihatOpts = { type: "brown", envelope: { attack: 0.001 , decay: 0.03 , sustain: 0 } } // シンセ生成 const membraneKick = new Tone.MembraneSynth(membraneKickOpts).toMaster(); const noiseSnare = new Tone.NoiseSynth(noiseSnareOpts).toMaster(); const noiseHihat = new Tone.NoiseSynth(noiseHihatOpts).toMaster(); // シンセ実行 const kickSynth = () => { membraneKick.triggerAttackRelease('C0','2n'); }; const snareSynth = () => { noiseSnare.triggerAttackRelease('8n'); }; const hihatSynth = () => { noiseHihat.triggerAttackRelease('32n'); }; // リズム設定値 let rhythmData = [ { value: '1拍子', beatNumber: 1, kickRhythm: [0], snareRhythm: [] }, { value: '2拍子', beatNumber: 2, kickRhythm: [0], snareRhythm: [1] }, { value: '3拍子', beatNumber: 3, kickRhythm: [0], snareRhythm: [1, 2] }, { value: '4拍子', beatNumber: 4, kickRhythm: [0, 2], snareRhythm: [1, 3] }, { value: '5拍子', beatNumber: 5, kickRhythm: [0], snareRhythm: [3] }, { value: '6拍子', beatNumber: 6, kickRhythm: [0], snareRhythm: [3] }, { value: '7拍子', beatNumber: 7, kickRhythm: [0, 4], snareRhythm: [2, 6] }, { value: '8拍子', beatNumber: 8, kickRhythm: [0, 4], snareRhythm: [2, 6] }, { value: '12拍子', beatNumber: 12, kickRhythm: [0, 6], snareRhythm: [3, 9] }, { value: 'シャッフル', beatNumber: 12, kickRhythm: [0, 6], snareRhythm: [3, 9], shaffle: true }, { value: '16拍子', beatNumber: 16, kickRhythm: [0, 8], snareRhythm: [4, 12] }, { value: '24拍子', beatNumber: 24, kickRhythm: [0, 12], snareRhythm: [6, 18] }, { value: 'ハネた16', beatNumber: 24, kickRhythm: [0, 12], snareRhythm: [6, 18], shaffle: true }, { value: '32拍子', beatNumber: 32, kickRhythm: [0, 16], snareRhythm: [8, 24] }, ]; // リズム取得 let getRhythmData = (i) => { let data = rhythmData[i]; let beatLen = 4 / data.beatNumber; return { data: data, beatVal: data.value, beatNum: data.beatNumber, beatLen: beatLen, kickRhythm: data.kickRhythm, snareRhythm: data.snareRhythm, shaffle: data.shaffle } }; // キック、スネアリズム設定 const setRhythm = (beatNmb, beatLen, Array) => { let rhythm = []; for(i = 0; i < Array.length ; i++) { rhythm.push('0:' + beatLen * Array[i] + ':0'); } return rhythm; } // ハイハットリズム設定 const setHihatRhythm = (beatNmb, beatLen, shaffle) => { let rhythm = []; for(i = 0; i < beatNmb ; i++) { if (shaffle && i % 3 == 1) { // 鳴らさない } else { rhythm.push('0:' + beatLen * i + ':0'); } } return rhythm; }; // ビートリズム設定 let setBeatRhythm = (i) => { let data = getRhythmData(i); rythmVal.innerHTML = data.beatVal; const beatNum = data.beatNum; const beatLen = data.beatLen; const kickRhythm = data.kickRhythm; const snareRhythm = data.snareRhythm; const shaffle = data.shaffle; return { kick: setRhythm(beatNum, beatLen, kickRhythm), snare: setRhythm(beatNum, beatLen, snareRhythm), hihat: setHihatRhythm(beatNum, beatLen, shaffle) } }; // ビート再生設定 const playBeat = (kickRtm, snareRtm, hihatRtm) => { let kickPart = new Tone.Part(kickSynth, kickRtm).start(); let snarePart = new Tone.Part(snareSynth, snareRtm).start() let hihatPart = new Tone.Part(hihatSynth, hihatRtm).start(); kickPart.loop = true; snarePart.loop = true; hihatPart.loop = true; } // ビート初期値 let defRhythm = setBeatRhythm(7); playBeat(defRhythm.kick, defRhythm.snare, defRhythm.hihat); // 再生判定 let play = false; // 再生ボタン beat.addEventListener('click', () => { if(play){ play = false; beat.innerHTML = "▶︎"; Tone.Transport.stop(); } else { play = true; beat.innerHTML = "■"; Tone.Transport.start(); } }, false); // BPM設定 let BPMSet = () => { let BPMImput = BPMRange.value; BPMVal.innerHTML = BPMImput; Tone.Transport.bpm.value = BPMImput; } BPMSet(); // BPM変更 BPMRange.addEventListener('input', BPMSet, false); // リズム変更 function selectRythm() { for(let i = 0; i < rhythmData.length; i++){ rythmRadio[i].addEventListener('input', ()=> { let beat = setBeatRhythm(i); const kick = beat.kick; const snare = beat.snare; const hihat = beat.hihat; if( rythmRadio[i].checked && play ) { Tone.Transport.cancel(); Tone.Transport.start(); playBeat(kick, snare, hihat); } else { Tone.Transport.cancel(); playBeat(kick, snare, hihat); } }, false); } } selectRythm();
コードがちょっと長くなったが、なるべく機能ごとのブロックを分けるリファクタリングをした。変数名、関数名も規則性を保つために前回とちょっと変えている。
上記のコードが何をしているのか。以下、ブロックごとに解説する。
JSコード(DOM、シンセ設定)
まずはDOMやシンセ音の設定から。
DOM取得
冒頭で処理に使うDOMを取得する。
// DOM const beat = document.querySelector('#beat'); const BPMVal = document.querySelector('.bpm .val'); const BPMRange = document.querySelector('.bpm .range'); const rythmVal = document.querySelector('.rhythm .val'); const rythmRadio = document.querySelectorAll('.rhythm input[name="beat"]');
- 変数
beat
でquerySelector()
メソッドを実行し、#beat
のDOMを取得 - 変数
BPMVal
でquerySelector()
メソッドを実行し、.bpm .val
のDOMを取得 - 変数
BPMRange
でquerySelector()
メソッドを実行し、.bpm .range
のDOMを取得 - 変数
rythmVal
でquerySelector()
メソッドを実行し、.rhythm .val
のDOMを取得 - 変数
rythmRadio
でquerySelectorAll()
メソッドを実行し、.rhythm input[name="beat"]
のDOMを取得
最後の変数rythmRadio
(ラジオボタン)は複数あるため、querySelectorAll()
で配列に入れる。
※参考:Document.querySelectorAll() - Web API | MDN
エンベロープ
次にキック、スネア、ハイハットのエンベロープ(ADSR)を設定。
ADSRは「Attack」「Decay」「Sustain」「Release」の略。
※参考:【Tone.js】ドラムパッドにエンベロープ(ADSR)とエフェクトを設定する - クモのようにコツコツと
キックのADSR
// エンベロープ(キック) let membraneKickOpts = { pitchDecay: 0.001, envelope: { attack: 0.001 , decay: 0.75 , sustain: 0.01 , release: 0.01 }, volume: 25 }
- 変数
membraneKickOpts
に連想配列でキックのエンベロープを設定
キックはmembrane
シンセのオプション設定。
※参考:MembraneSynth
スネアのADSR
// エンベロープ(スネア) let noiseSnareOpts = { envelope: { attack: 0.001 , decay: 0.5 , sustain: 0.01 } }
- 変数
noiseSnareOpts
に連想配列でスネアのエンベロープを設定
ハイハットのエンベロープ
// エンベロープ(ハイハット) let noiseHihatOpts = { type: "brown", envelope: { attack: 0.001 , decay: 0.03 , sustain: 0 } }
- 変数
noiseHihatOpts
に連想配列でハイハットのエンベロープを設定
スネアとハイハットはどちらもnoise
シンセのオプション設定だがエンベロープ値は異なる。
※参考:NoiseSynth
シンセ生成
シンセ音を生成する。
// シンセ生成 const membraneKick = new Tone.MembraneSynth(membraneKickOpts).toMaster(); const noiseSnare = new Tone.NoiseSynth(noiseSnareOpts).toMaster(); const noiseHihat = new Tone.NoiseSynth(noiseHihatOpts).toMaster();
- 変数
membraneKick
でMembraneSynth()
を生成(引数はmembraneKickOpts
) - 変数
noiseSnare
でNoiseSynth()
を生成(引数はnoiseSnareOpts
) - 変数
noiseHihat
でNoiseSynth()
を生成(引数はnoiseHihatOpts
)
シンセの引数に先ほどのオプション設定(エンベロープ値)を入れている。
シンセ実行
先ほど生成したシンセを実行する。
// シンセ実行 const kickSynth = () => { membraneKick.triggerAttackRelease('C0','2n'); }; const snareSynth = () => { noiseSnare.triggerAttackRelease('8n'); }; const hihatSynth = () => { noiseHihat.triggerAttackRelease('32n'); };
- 変数
kickSynth
でmembraneKick
のtriggerAttackRelease()
メソッド実行(引数は左からC0
,'2n
) - 変数
snareSynth
でnoiseSnare
のtriggerAttackRelease()
メソッド実行(引数は8n
) - 変数
hihatSynth
でnoiseHihat
のtriggerAttackRelease()
メソッド実行(引数は32n
)
noiseシンセは音程がないため、引数は音の長さのみ。
JSコード(リズム設定値)
ここからは各リズムの設定値を設定する。
リズム設定値の書式
リズムの設定値を入れる。
// リズム設定値 let rhythmData = [ { value: '●拍子', beatNumber: ●, kickRhythm: [●, ●], snareRhythm: [●, ●] }, ];
- 変数
rhythmData
の値は配列 - 配列の値は連想配列
- 連想配列の
value
キーは文字列 beatNumber
キーは数値kickRhythm
キーは配列snareRhythm
キーも配列
リズムの設定値は連想配列でオブジェクトになる。
ハイハットは全ての拍子で鳴らすのでbeatNumber
を拍子数分for文でループする。
キックとスネアは配列に入れる。
1〜8拍子の設定値
以下、リズムの設定値。前々回の1〜8拍子の設定値。
※参考:前々回(1〜8拍子)
【Tone.js】Tone.Part()でいろいろなリズムを鳴らす ※1〜8拍子編 - クモのようにコツコツと
1拍子
{ value: '1拍子', beatNumber: 1, kickRhythm: [0], snareRhythm: [] },
1拍子はキックのみ。
2拍子
{ value: '2拍子', beatNumber: 2, kickRhythm: [0], snareRhythm: [1] },
2拍子からスネアが加わる。
3拍子
{ value: '3拍子', beatNumber: 3, kickRhythm: [0], snareRhythm: [1, 2] },
ここから4拍子ベースで3/4拍子。ワルツのリズム。
4拍子
{ value: '4拍子', beatNumber: 4, kickRhythm: [0, 2], snareRhythm: [1, 3] },
4拍子は2拍子の倍でマーチのリズム。
5拍子
{ value: '5拍子', beatNumber: 5, kickRhythm: [0], snareRhythm: [3] },
ここから8拍子ベースで5/8拍子。ハチロク-1のリズム。
6拍子
{ value: '6拍子', beatNumber: 6, kickRhythm: [0], snareRhythm: [3] },
6/8拍子=ハチロクのリズム。
7拍子
{ value: '7拍子', beatNumber: 7, kickRhythm: [0, 4], snareRhythm: [2, 6] },
7/8拍子。エイトビート-1のリズム。
8拍子
{ value: '8拍子', beatNumber: 8, kickRhythm: [0, 4], snareRhythm: [2, 6] },
8拍子=エイトビート
12〜32拍子の設定値
ここからは前回の12〜32拍子の設定値。
※参考:前回(12〜32拍子)
【Tone.js】Tone.Part()でいろいろなリズムを鳴らす ※12〜32拍子&シャッフル編 - クモのようにコツコツと
12拍子
{ value: '12拍子', beatNumber: 12, kickRhythm: [0, 6], snareRhythm: [3, 9] },
ここから16分音符ベースで12/16拍子。ハチロクの倍のリズム。
シャッフル
{ value: 'シャッフル', beatNumber: 12, kickRhythm: [0, 6], snareRhythm: [3, 9], shaffle: true },
shaffle
キーの値をtrue
、を追加
12拍子からハイハットの一部を抜いたシャッフルリズム。
16拍子
{ value: '16拍子', beatNumber: 16, kickRhythm: [0, 8], snareRhythm: [4, 12] },
16拍子=16ビート。
24拍子
{ value: '24拍子', beatNumber: 24, kickRhythm: [0, 12], snareRhythm: [6, 18] },
ここから32分音符ベースで24/32拍子。
ハネた16ビート
{ value: 'ハネた16', beatNumber: 24, kickRhythm: [0, 12], snareRhythm: [6, 18], shaffle: true },
shaffle
キーの値をtrue
、を追加
24拍子からハイハットの1部を抜くのでこれもshaffle
をtrue
に。
32拍子
{ value: '32拍子', beatNumber: 32, kickRhythm: [0, 16], snareRhythm: [8, 24] },
43拍子=32ビート。ハイハットがヘリコプターw
JSコード(リズム設定)
リズム取得
先ほど設定したリズムを取得する処理
// リズム取得 let getRhythmData = (i) => { let data = rhythmData[i]; let beatLen = 4 / data.beatNumber; return { data: data, beatVal: data.value, beatNum: data.beatNumber, beatLen: beatLen, kickRhythm: data.kickRhythm, snareRhythm: data.snareRhythm, shaffle: data.shaffle }
- 変数
getRhythmData
は無名関数(引数はi
) - 変数
data
は配列rhythmData[]
のi
番目 - 変数
beatLen
はdata
オブジェクトのbeatNumber
プロパティを4で割る return
を連想配列で複数の値を返すdata
キーの値はdata
beatVal
キーの値はdata
オブジェクトのvalue
プロパティbeatNum
キーの値はdata
オブジェクトのbeatNumber
プロパティbeatLen
キーの値はbeatLen
kickRhythm
キーの値はdata
オブジェクトのkickRhythm
プロパティsnareRhythm
キーの値はdata
オブジェクトのsnareRhythm
プロパティshaffle
キーの値はdata
オブジェクトのshaffle
プロパティ
変数data
で配列rhythmData[]
のi
番目を取得する。
BPMは四分音符ベースのため、変数beatLen
は拍子数を4で割る式。これで1拍の長さが出る(あとでハイハットのループで使う)。
return
で複数の値(処理結果)を返したい時は配列や連想配列に入れる。
※参考:【JavaScript入門】returnの使い方と戻り値・falseのまとめ! | 侍エンジニアブログ
キック、スネアリズム設定
キックとスネアのリズム設定は共通のため、一つの関数にまとめた。
// キック、スネアリズム設定 const setRhythm = (beatNmb, beatLen, Array) => { let rhythm = []; for(i = 0; i < Array.length ; i++) { rhythm.push('0:' + beatLen * Array[i] + ':0'); } return rhythm; }
- 変数
setRhythm
の値は無名関数(引数は左からbeatNmb
,beatLen
,Array
) - 変数
rhythm
の値は空の配列 - for文の条件:
i
の初期値は0
、i
はArray
の数(length
)まで繰り返す、i
を1つずつ加算する - 配列
rhythm
にpush()
メソッドで追加(引数は文字列0:
とbeatLen
をArray
のi
番目を割るった数と文字列:0
) return
で配列rhythm
を返す
Array
は配列でキックやスネアを鳴らす数値になる。これをfor文でループしてTone.jsのリズム設定「0:●:0」という文字列にする。
ハイハットリズム設定
ハイハットのリズム設定はキック、スネアとは異なるので別の関数にした
// ハイハットリズム設定 const setHihatRhythm = (beatNmb, beatLen, shaffle) => { let rhythm = []; for(i = 0; i < beatNmb ; i++) { if (shaffle && i % 3 == 1) { // 鳴らさない } else { rhythm.push('0:' + beatLen * i + ':0'); } } return rhythm; };
- 変数
setHihatRhythm
は無名関数(引数は左からbeatNmb
,beatLen
,shaffle
) - 変数
rhythm
は空の配列 - for文の条件:
i
の初期値は0
、i
はbeatNmb
の数まで繰り返す、i
を1つずつ加算する - if文の条件もし
shaffle
の値がtrue
、かつi
を3
で割った余りが1
なら何も鳴らさない - さもなくば配列
rhythm
にpush()
メソッドで追加(引数は文字列0:
とbeatLen
をArray
のi
番目を割るった数と文字列:0
) return
で配列rhythm
を返す
beatNmb
にlength
がないのは配列ではなくただの数値だから。ハイハットはすべての拍子を鳴らすので拍子数でOK。
if文の最初の条件でshaffle
の値は真偽値だがshaffle == true
を書かなくても単体でtrue
を意味する(!
を付けるとfalse
を意味する)。
※参考:【JavaScript入門】boolean型の使い方(文字列変換/判定/反転) | 侍エンジニアブログ
これの条件でshaffle
の時は3拍セットの真ん中の1拍のハイハットを鳴らさない。
JSコード(ビート設定)
ビートリズム設定
先ほど設定したキック、スネア、ハイハットのリズムを組み合わせてビートのリズムを設定する。
// ビートリズム設定 let setBeatRhythm = (i) => { let data = getRhythmData(i); rythmVal.innerHTML = data.beatVal; const beatNum = data.beatNum; const beatLen = data.beatLen; const kickRhythm = data.kickRhythm; const snareRhythm = data.snareRhythm; const shaffle = data.shaffle; return { kick: setRhythm(beatNum, beatLen, kickRhythm), snare: setRhythm(beatNum, beatLen, snareRhythm), hihat: setHihatRhythm(beatNum, beatLen, shaffle) } };
- 変数
setBeatRhythm
は無名関数(引数はi
) - 変数
data
はgetRhythmData()
関数(引数はi
) rythmVal
の中身(innerHTML
プロパティ)をdata
オブジェクトのbeatVa
プロパティに- 変数
beatNum
をdata
オブジェクトのbeatNum
プロパティに - 変数
beatLen
をdata
オブジェクトのbeatLen
プロパティに - 変数
kickRhythm
をdata
オブジェクトのkickRhythm
プロパティに - 変数
snareRhythm
をdata
オブジェクトのsnareRhythm
プロパティに - 変数
shaffle
をdata
オブジェクトのshaffle
プロパティに return
は連想配列で複数の値を返すkick
キーの値はsetRhythm()
関数(引数は左からbeatNum
,beatLen
,kickRhythm
)snare
キーの値はsetRhythm()
関数(引数は左からbeatNum
,beatLen
,snareRhythm
)hihat
キーの値はsetHihatRhythm()
関数(引数は左からbeatNum
,beatLen
,shaffle
)
getRhythmData
関数のi
番目を読み込んでキック、スネア、ハイハットのリズムを設定している。
このi
番目はgetRhythmData
関数では配列rhythmData
のi
番目を意味している。
キックとスネアはsetRhythm()
関数でハイハットはsetHihatRhythm()
関数。
ビート再生設定
先ほど設定したビートリズムを再生する設定
// ビート再生設定 const playBeat = (kickRtm, snareRtm, hihatRtm) => { let kickPart = new Tone.Part(kickSynth, kickRtm).start(); let snarePart = new Tone.Part(snareSynth, snareRtm).start() let hihatPart = new Tone.Part(hihatSynth, hihatRtm).start(); kickPart.loop = true; snarePart.loop = true; hihatPart.loop = true; }
- 変数
playBeat
は無名関数(引数は左からkickRtm
,snareRtm
,hihatRtm
) - 変数
kickPart
でTone.Part()
メソッドを生成(引数は左からkickSynth
,kickRtm
)、start()
メソッドを実行 - 変数
snarePart
でTone.Part()
メソッドを生成(引数は左からsnareSynth
,snareRtm
)、start()
メソッドを実行 - 変数
hihatPart
でTone.Part()
メソッドを生成(引数は左からhihatSynth
,hihatRtm
)、start()
メソッドを実行 kickPart
のloop
プロパティをtrue
にsnarePart
のloop
プロパティをtrue
にhihatPart
のloop
プロパティをtrue
に
Tone.Part()
が前回まで使っていた細かいタイミングで音を再生するメソッド。引数でキック、スネア、ハイハットのシンセ音とリズムを読み込んでいる。
※参考:Part
そしてキック、スネア、ハイハットいずれもloop
をtrue
にしてループ再生している。
次にビートの初期値を設定する。
// ビート初期値 let defRhythm = setBeatRhythm(7); playBeat(defRhythm.kick, defRhythm.snare, defRhythm.hihat);
- 変数
defRhythm
でsetBeatRhythm()
関数を実行(引数は7
) playBeat()
関数を実行(引数は左からdefRhythm.kick
,defRhythm.snare
,defRhythm.hihat
)
defRhythm
でsetBeatRhythm()
関数の引数7
は0からカウント開始なので配列rhythmData
の8番目=8拍子になる。
playBeat()
関数の引数にはdefRhythm
のキック、スネア、ハイハット設定値を入れる。
これで初期値がエイトビートになる。
JSコード(ボタン設定)
ここからは画面上のボタン操作の設定。
再生設定
まず画面の一番上にある再生ボタンの設定から。
// 再生判定 let play = false;
- 変数
play
の値はfalse
この真偽値(true
かfalse
か)は再生状態の判定に使う。初期値はfalse
なので音は鳴らない。
次に再生・停止を切り替える設定
// 再生ボタン beat.addEventListener('click', () => { if(play){ play = false; beat.innerHTML = "▶︎"; Tone.Transport.stop(); } else { play = true; beat.innerHTML = "■"; Tone.Transport.start(); } }, false);
beat
のクリックイベントを設定- もし
play
がtrure
なら
play
をfalse
に
beat
の中身(innerHTML
)を▶︎
に
Tone.Transport
でstop()
メソッド実行 - さもなくば
play
をtrue
に
beat
の中身(innerHTML
)を■
に
Tone.Transport
でstart()
メソッド実行
if文の条件、play
の真偽値は先ほどのshaffle
と同じでplay == true
ではなくplay
だけでOK。
処理はplay
の真偽値、beat
の文字「▶︎」と「■」、Tone.Transport
のstop()
とstart()
を切り替えている。
※参考:Transport
BPM変更設定
次はBPMのスライダー設定
// BPM設定 let BPMSet = () => { let BPMImput = BPMRange.value; BPMVal.innerHTML = BPMImput; Tone.Transport.bpm.value = BPMImput; }
- 変数
BPMSet
は無名関数(引数はなし) - 変数
BPMImput
はBPMRange
のvalue
属性 BPMVal
の中身(innerHTML
をBPMImput
にTone.Transport
のbpm.value
をBPMImput
に
BPMRange
のスライダーを動かすとBPMVal
の文字変更し再生のbpm.value
も変更する。
BPMSet();
BPMSet()
関数を実行
BPMSet()
関数を実行するとBPMRange
のvalue
属性の初期値120
が適用される。
BPMRange
スライダーのイベント設定
// BPM変更 BPMRange.addEventListener('input', BPMSet, false);
BPMRange
のinput
値が変更されたらBPMSet()
関数を実行
これでスライダーを動かすとBPMSet()
関数が実行される。
リズム変更設定
最後はリズム変更の設定
// リズム変更 function selectRythm() { for(let i = 0; i < rhythmData.length; i++){ rythmRadio[i].addEventListener('input', ()=> { let beat = setBeatRhythm(i); const kick = beat.kick; const snare = beat.snare; const hihat = beat.hihat; if( rythmRadio[i].checked && play ) { Tone.Transport.cancel(); Tone.Transport.start(); playBeat(kick, snare, hihat); } else { Tone.Transport.cancel(); playBeat(kick, snare, hihat); } }, false); } }
- for文の条件:変数
i
の初期値0
、i
をrhythmData
の数length
繰り返す、i
に1つずつ加算 - 配列
rythmRadio[]
のi
番目の'input'値が変更されたらイベントを実行 - 変数
beat
はsetBeatRhythm()
のi
番目 - 変数
kick
はbeat
のkick
プロパティ - 変数
snare
はbeat
のsnare
プロパティ - 変数
hihat
はbeat
のhihat
プロパティ - if文の条件:もし配列
rythmRadio[]
のi
番目がchecked
になっていて、かつplay
がtrue
なら
Tone.Transport
のcancel()
メソッドを実行
Tone.Transport
のstart()
メソッドを実行
playBeat()
関数を実行(引数は左からkick
,snare
,hihat
) - さもなくば
Tone.Transport
のcancel()
メソッドを実行
playBeat()
関数を実行(引数は左からkick
,snare
,hihat
)
変数beat
でsetBeatRhythm()
のi
番目を取得する。
もしrythmRadio[]
がチェック状態でかつ、プレイヤーが再生状態だったら、Tone.Transport
をいったんキャンセル*1した後にスタート。
playBeat()
関数にrythmRadio[]
のi
番目のキック、スネア、ハイハットを読み込んで実行。これでリズムが切り替わる。
プレイヤーが停止状態であればTone.Transport
はキャンセルのみでplayBeat()
関数のリズム切り替えのみ行う。
selectRythm();
selectRythm()
関数を実行
selectRythm()
関数を実行することでrythmRadio[]
で最初にチェックが入っている8拍子が適用される。
音楽の三要素(メロディ、ハーモニー、リズム)
音楽の三要素(メロディ、ハーモニー、リズム)をTone.jsで音楽の三要素が体験できる!
※参考:音楽 - Wikipedia
メロディ
マリオメロディを打ち込んだ。
See the Pen Mario BGM-fix by イイダリョウ (@i_ryo) on CodePen.
音が鳴らない場合はこちら
※参考:https://codepen.io/i_ryo/full/VOzedR
ハーモニー
コードを切り替えて鍵盤で引ける楽器を作った。
See the Pen tone.js-12cord- 20 by イイダリョウ (@i_ryo) on CodePen.
音が鳴らない場合はこちら
※参考:https://codepen.io/i_ryo/full/xxbPQNv
リズム
そして今回作ったビート・プレイヤー。
See the Pen tone.js-BeatPlayer by イイダリョウ (@i_ryo) on CodePen.
音が鳴らない場合はこちら
※参考:https://codepen.io/i_ryo/full/dyGpbxL
マリオメロディを鳴らしながらコードやビートを調べたり♪
これまでTone.jsでやってきたことはこちらを参照。
※参考:Tone.jsを習得するためにやった事まとめ
qiita.com
React/Next版ビートプレイヤー作成!
※2021/09/17追記
本投稿のCodrPen版を元にReact/Next版のビートプレイヤーを作成しました!
※参考:【React & Tone.js】ビートプレイヤーを作った(ビートとBPMを変更可能) - クモのようにコツコツと
作ったものこちら
※参考:ビートプレイヤー
使い方
ソースコード
※参考:GitHub - ryo-i/beat-player: Tone.jsを使って作ったビートプレイヤー
最後に
今回はTone.jsとしては新しいことはやってなかったんですが、下記の体験ができたのが良かったです。
input
タグのrange
属性(スライダー)とJS処理を紐付ける方法がわかったreturn
で連想配列を使うと複数の値を返すことができて便利- 真偽値
true
、false
を使うとif文の条件がシンプルになる(==
不要で!
の有無だけ)
特に長いコードを機能ごとにリファクタリングするときにreturn
の使い方は重要になるな、と感じました。
ビート・プレイヤー、実際に音楽を流しながらBPMを調べてみましたが、結構実用的で満足感がありました♪
リズム編はここで一区切り。次は音楽の三要素を組み合わせて音楽全体を作っていく段階!ミキシングなどの音響系の理解も必要になってきます。
それではまた!
※参考:Tone.jsを習得するためにやった事まとめ
qiita.com
*1:このキャンセルを行わない場合、スタートで音が二重に鳴ってしまう