Edited at

GLSL作曲講座

More than 1 year has passed since last update.


今回使うツール


DemoEditor


https://github.com/notargs/DemoEditor


  • 作りました

  • ホットリロード機能付きGLSLEditor

  • 動的に音楽/映像をGLSLで編集できる

  • ビルドにpremake5が必要

  • 好きなエディタでshaders直下のファイルを弄る(自分はVisual Studio Codeを使用)

  • 今回のサンプルも入ってます



シェーダーで曲を作る


  • 4kb introなどで有効な手法

  • プロシージャルなので当然mp3よりは圧倒的に軽い

  • Math用の標準ライブラリをincludeする必要が無くなるため、その分ちょっと軽くできる



頑張って曲を作りました


  • サンプルを流す



わかったこと



  • DAW使って普通に作曲したほうが圧倒的にラク :innocent:

  • 手探りでやってる部分が多いのでまだまだ改善できそう



音を鳴らす

Temp.gif

- 空気の振動が耳に伝わって音が聞こえる

- スピーカーが振動して空気を振動させる

- スピーカーの振動周期を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);


  • 自分でも読めない :weary:

  • でもコードはかなりスッキリする

  • もっと綺麗な書き方がありそう



これが

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);
}



みんなもGLSLで作曲しよう!!!



質疑応答