概要
ブラウザで某 RPG のレベルアップ音を奏でてみました。
Tone.js とは?
ブラウザでインタラクティブな音楽を作成するための Web Audio フレームワークです。JavaScript にはWeb Audio APIという音の再生、生成、加工をするための API があるのですが、中々扱いづらいです。Tone.js は Web Audio API を扱いやすくするインターフェースを提供するライブラリです。
作成するもの
ブラウザで表示されるクリックしたらレベルアップ音(テレレテッテッテ-)が流れるボタンを作成します(simple)
用意するもの
インストール!
npm install tone
前提知識
やはり音楽、楽典の知識からは逃れられません...
ただ、全力で説明してしまうとそれだけで何記事が出来てしまいそうですので、簡単に最低限だけ記載いたします。
-
音符
音符とは音の高さ、長さなどを表す記号です。
音の高さと鳴らす長さ、それらを時間に配置することによってメロディが作られます。 -
音名
簡単に言うとドレミファソラシド
です。
ただ、この馴染みのある読み方はイタリア語でして、Tone.js では英語表記を使います。
英語表記はCDEFGAB
になります。
ギターとかやってる方はコードの表記で馴染みがあるかもしれませんね。
英語表記をイタリア語表記と照らすと下記のようになります。
ちなみに日本語でいうといろはにほへと
です。
C(ド)
D(レ)
E(ミ)
F(ファ)
G(ソ)
A(ラ)
B(シ)
-
休符
音を鳴らさないという音符です。
音高はなく、基本的に長さを表現しています。
レベルアップ音のテレレテッテッテ-
でいうと小さいツ
の部分になります。 -
音価
音が鳴る長さのことをいいます。 -
BPM
Beat Per Minite。
例えば BPM が 180 だとしたら、1 分間に 180 回テンポを刻むということです。心拍数も BPM で表すことが多いので、自分の心音を感じてみるのが BPM を体感しやすいかもしれませんね。 -
シンセサイザー
音色を合成して奏でる電子楽器です。
小室哲也さんが鳴らしまくってるあの楽器です。 -
シーケンサー
音のデータの順番を記録しておいて、再生することで自動演奏を行う装置のことです。
以上、簡易でちょっと分かりづらかったかもですが、興味がある方は是非もっと深く調べてみてくださいませ...
実装
ブラウザ表示部分
抜粋すると以下になります。
<button type="button">▲Play</button>
<script src="/js/bundle.js"></script>
メロディを奏でてみた
メロディの**テレレテッテッテ-を音名にするとファファファファミ♭ソファ-**となります。(耳コピなので間違ってるかもです)
これをコードで表現していきます。
import Tone from "tone";
//BPM
Tone.Transport.bpm.value = 240;
//シンセサイザーインスタンス
const melody_synth = new Tone.Synth().toMaster();
// 楽譜データ
const melody_score = [
//note(音名) nullは休符
[{ note: "F5" }, { note: "F5" }],
[{ note: "F5" }, { note: "F5" }],
[null, { note: "D#5" }],
[null, { note: "G5" }],
[null, { note: "F5" }]
];
//シーケンサーインスタンス
const melody_sequence = new Tone.Sequence(
(time, { note }) => {
melody_synth.triggerAttackRelease(note);
},
melody_score,
"4n"
).start();
//演奏のリピートをfalseに
melody_sequence.loop = false;
//シンセサイザーインスタンス
const bass_synth = new Tone.Synth().toMaster();
// 楽譜データ
const bass_score = [
//note(音名) nullは休符
[{ note: "C5" }, { note: "B4" }],
[{ note: "A#4" }, { note: "A4" }],
[null, { note: "G4" }],
[null, { note: "A#4" }],
[null, { note: "A4" }]
];
//シーケンサーインスタンス
const bass_sequence = new Tone.Sequence(
(time, { note }) => {
bass_synth.triggerAttackRelease(note);
},
bass_score,
"4n"
).start();
//演奏のリピートをfalseに
bass_sequence.loop = false;
document.querySelector("[type=button]").addEventListener(
"click",
e => {
//登録された演奏をスタートする
Tone.Transport.start();
},
false
);
音を出す処理の流れとしては、
1.シンセサイザーを作成
2.メロディデータを作成
3.シーケンサーにシンセサイザーとそのシンセサイザーに演奏させたいメロディデータを渡す
4.トランスポート(音楽リソースのタイミングを調整するオブジェクト)に音を鳴らすよう命じる
となっています。
実行してみるとおなじみのあのメロディが確認できます。
ベースを鳴らしてみた
メロディだけでは寂しいので次はベースを打ち込んでいきます。
方法は先ほどのメロディで行なった処理をベースに置き換えて同時に音を鳴らすようにトランスポートに命じるだけです。
ベースの音程はドシシ♭ラソシ♭ラですね。
import Tone from "tone";
//BPM
Tone.Transport.bpm.value = 240;
//シンセサイザーインスタンス
const melody_synth = new Tone.Synth().toMaster();
// 楽譜データ
const melody_score = [
//note(音名) nullは休符
[{ note: "F5" }, { note: "F5" }],
[{ note: "F5" }, { note: "F5" }],
[null, { note: "D#5" }],
[null, { note: "G5" }],
[null, { note: "F5" }]
];
//シーケンサーインスタンス
const melody_sequence = new Tone.Sequence(
(time, { note }) => {
melody_synth.triggerAttackRelease(note);
},
melody_score,
"4n"
).start();
//演奏のリピートをfalseに
melody_sequence.loop = false;
//シンセサイザーインスタンス
const bass_synth = new Tone.Synth().toMaster();
// 楽譜データ
const bass_score = [
//note(音名) nullは休符
[{ note: "C5" }, { note: "B4" }],
[{ note: "A#4" }, { note: "A4" }],
[null, { note: "G4" }],
[null, { note: "A#4" }],
[null, { note: "A4" }]
];
//シーケンサーインスタンス
const bass_sequence = new Tone.Sequence(
(time, { note }) => {
bass_synth.triggerAttackRelease(note);
},
bass_score,
"4n"
).start();
//演奏のリピートをfalseに
bass_sequence.loop = false;
document.querySelector("[type=button]").addEventListener(
"click",
e => {
//登録された演奏をスタートする
Tone.Transport.start();
},
false
);
これでメロディとベースが鳴るようになりました。
音を加工してみた
せっかくですので音色を加工してみます。
オシレーターでファミコンっぽい音になる矩形波を選択。
そしてリバーブ(エコーのようなエフェクト)を追加してみます。
import Tone from "tone";
//BPM
Tone.Transport.bpm.value = 240;
const option = {
//オシレーターのオプション
oscillator: {
//矩形波を選択
type: "square"
}
};
//シンセサイザーインスタンス
const melody_synth = new Tone.Synth(option).toMaster();
//リバーブエフェクトインスタンス
const reverb = new Tone.JCReverb(0.1).toMaster();
//リバーブエフェクトを適用
melody_synth.connect(reverb);
// 楽譜データ
const melody_score = [
//note(音名) nullは休符
[{ note: "F5" }, { note: "F5" }],
[{ note: "F5" }, { note: "F5" }],
[null, { note: "D#5" }],
[null, { note: "G5" }],
[null, { note: "F5" }]
];
//シーケンサーインスタンス
const melody_sequence = new Tone.Sequence(
(time, { note }) => {
melody_synth.triggerAttackRelease(note);
},
melody_score,
"4n"
).start();
//演奏のリピートをfalseに
melody_sequence.loop = false;
//シンセサイザーインスタンス
const bass_synth = new Tone.Synth().toMaster();
// 楽譜データ
const bass_score = [
//note(音名) nullは休符
[{ note: "C5" }, { note: "B4" }],
[{ note: "A#4" }, { note: "A4" }],
[null, { note: "G4" }],
[null, { note: "A#4" }],
[null, { note: "A4" }]
];
//シーケンサーインスタンス
const bass_sequence = new Tone.Sequence(
(time, { note }) => {
bass_synth.triggerAttackRelease(note);
},
bass_score,
"4n"
).start();
//演奏のリピートをfalseに
bass_sequence.loop = false;
document.querySelector("[type=button]").addEventListener(
"click",
e => {
//登録された演奏をスタートする
Tone.Transport.start();
},
false
);
実行してみるとちゃんと音色が変わることが確認できます。
所感
当然ですが普通に音楽を制作する場合には DAW のような専門性のあるソフトを使った方が全然効率がいいですね。
ですが、動的に音を作成できるので組み合わせで面白そうなこと色々出来るんじゃないかなーと思いました(機械学習とか)
まださらっとしか触れていないので、今度ガッツリやってみようかなと思います。
FORK Advent Calendar 2019
9日目 Google Translation API v3 を Node で使ってみた @karaage7 さんお疲れ様でした!
12日目 @momoken さんよろしくおねがいします。