今回使うツール
DemoEditor
https://github.com/notargs/DemoEditor
- 作りました
- ホットリロード機能付きGLSLEditor
- 動的に音楽/映像をGLSLで編集できる
- ビルドにpremake5が必要
- 好きなエディタでshaders直下のファイルを弄る(自分はVisual Studio Codeを使用)
- 今回のサンプルも入ってます
シェーダーで曲を作る
- 4kb introなどで有効な手法
- プロシージャルなので当然mp3よりは圧倒的に軽い
- Math用の標準ライブラリをincludeする必要が無くなるため、その分ちょっと軽くできる
頑張って曲を作りました
- サンプルを流す
わかったこと
- DAW使って普通に作曲したほうが圧倒的にラク
- 手探りでやってる部分が多いのでまだまだ改善できそう
音を鳴らす
- 空気の振動が耳に伝わって音が聞こえる
- スピーカーが振動して空気を振動させる
- スピーカーの振動周期をGLSLで定義する
音階の計算
441\pi(s/ 12)^2
float calcHertz(float scale)
{
return 441.0 * pow(2.0, scale / 12.0) * PI;
}
『ラ』の音を基準とした音階を計算する
秘伝のタレその1
#define A 0
#define B 2
#define C 3
#define D 5
#define E 7
#define F 8
#define G 10
- 音階を数値で入力するのは人間には早すぎる
- 英・米式表記は『ラ』を『A』として、ABCDEFGと呼ぶ
ついでにCalcHealtzも拡張
float calcHertz(float octave, float note)
{
return calcHertz(octave * 12 + note);
}
- オクターブを受け取る
これが
Sin1(0, 8)
Sin1(0, 3)
Sin1(0, 10)
こう書ける
Sin1(0, F)
Sin1(0, C)
Sin1(0, G)
- 多少慣れはいるが圧倒的に読みやすい!!
ちなみに♯(半音上げ)や♭(半音下げ)はこう書く
Sin1(0, F+1)
Sin1(0, F-1)
- わかりやすい
秘伝のタレその2
#define Sin1(u, v) ret += clamp(sin(time * calcHertz(u, v)) * (1 - localTime + sin(time * 80.0) * 0.1), -0.3, 0.3);
#define Rect1(u, v) ret += rect(time * calcHertz(u, v)) * (1 - localTime);
- 自分でも読めない
- でもコードはかなりスッキリする
- もっと綺麗な書き方がありそう
これが
ret += clamp(sin(time * calcHertz(0, F)) * (1 - localTime + sin(time * 80.0) * 0.1), -0.3, 0.3);
ret += clamp(sin(time * calcHertz(0, C)) * (1 - localTime + sin(time * 80.0) * 0.1), -0.3, 0.3);
ret += clamp(sin(time * calcHertz(0, G)) * (1 - localTime + sin(time * 80.0) * 0.1), -0.3, 0.3);
こう書ける
Sin1(0, F)
Sin1(0, C)
Sin1(0, G)
- 無いとシンドイ
ループを作る
float bassDrum(float time)
{
float localTime = mod(time * 4, 2);
float ret;
ret += rect(max(0, 1 - localTime * 2) * localTime * 300) * max(0, 1 - localTime * 4);
return ret;
}
- 音源ごとに1小節程度のループを作り、それを組み合わせていく
ループ内で時間を定義
float loopTime = mod(time, 8);
float localTime = mod(time * 2, 1.0);
- 時間を二つ定義
- ノートの大まかな定義を行うための「loopTime」
- ノートの中での減衰などを行うための「localTime」
- ここも雑な実装なのでもっと何かありそう
ループ内でのメロディを定義
- 素直につらつらとif文を並べる
- 音楽やってる人なら割と読みやすい感じになっている
float poly(float time)
{
float loopTime = mod(time * 8, 8);
float localTime = mod(time * 8, 1.0);
float ret = 0;
if (loopTime < 1)
{
Sin1(1, G)
}
else if (loopTime < 2)
{
Sin1(2, D)
}
else if (loopTime < 3)
{
Sin1(2, F)
}
else if (loopTime < 4)
{
Sin1(2, G)
}
else if (loopTime < 5)
{
Sin1(3, C)
}
else if (loopTime < 6)
{
Sin1(2, G)
}
else if (loopTime < 7)
{
Sin1(2, E)
}
else if (loopTime < 8)
{
Sin1(2, C)
}
return ret;
}
和音を鳴らす
- さっきからチラチラ出てたコード
if (loopTime < 2)
{
Sin1(0, F)
Sin1(0, C)
Sin1(0, G)
}
else if (loopTime < 4)
{
Sin1(0, E)
Sin1(0, C)
Sin1(0, G)
}
else if (loopTime < 6)
{
Sin1(0, D)
Sin1(0, C)
Sin1(0, G)
}
else
{
Sin1(0, E)
Sin1(0, C)
Sin1(0, G)
}
Mix
- タイムラインを用いてループを繋げていく
- コードだとややこしく見えるが、よくあるDAWのループ編集と同じことをやっている
- vec2を掛けることでボリューム調整、パン調整ができる
vec2 mainSound(float time)
{
vec2 sound = vec2(0.0);
if (time < 16)
{
sound += strings(time) * vec2(0.55, 0.6);
}
else if (time < 32)
{
sound += strings(time) * vec2(0.55, 0.6);
sound += base(time) * vec2(0.4, 0.38);
}
else if (time < 48)
{
sound += strings(time) * vec2(0.55, 0.6);
sound += base(time) * vec2(0.4, 0.38);
sound += poly(time) * vec2(0.2, 0.3);
}
else if (time < 64)
{
sound += strings(time) * vec2(0.55, 0.6);
sound += base(time) * vec2(0.4, 0.38);
sound += poly(time) * vec2(0.2, 0.3);
sound += bassDrum(time) * 0.4;
}
else if (time < 80)
{
sound += strings(time) * vec2(0.55, 0.6);
sound += base(time) * vec2(0.4, 0.38);
}
else if (time < 96)
{
sound += strings(time) * vec2(0.55, 0.6);
}
sound += noiseSound(time);
sound = clamp(sound, -vec2(1), vec2(1));
return vec2(sound);
}