LoginSignup
13
10

More than 5 years have passed since last update.

Music Shader シーケンス

Last updated at Posted at 2017-03-02

最近、shaderで音楽について模索してきました。なんとなく形になったので記事を書きたくなりました。
音楽をshaderで作る為に必要なことは、「音源」と「シーケンス」の2つです。
音源についてはディープな知識が必要でまだまだ足りないのですが、シーケンスについては使えるレベルになりました。
このシーケンスについて書いていきます。

サンプルは、ここです。
https://www.shadertoy.com/view/4sXcWM

シーケンスをする為にマクロを使う

シーケンスをするのには関数を作るよりマクロを使った方が記述量を含め考えてみると有利みたいです。配列を使う方法もありそうですが、直観的に記述するなら面倒な感じです。
とりあえず今回使うマクロ群です。

#define BPM 120.0
#define INIT(t) float tmp = t, nTime = t; int val, num; ivec3 chord;
#define D(v)if (tmp >= 0.0 ) {nTime = tmp;} tmp -= 240.0 / BPM / float(v);
#define N(v, n) if (tmp >= 0.0) {nTime = tmp; val = v; num = n;}  tmp -= 240.0 / BPM / float(v);
#define C(v, c) if (tmp >= 0.0) {nTime = tmp; val = v; chord = c;}  tmp -= 240.0 / BPM / float(v);
#define R(v) tmp -= 240.0 / BPM / float(v);
#define LOOP(b) if(tmp>0.0) tmp = mod(tmp, float(b) * 240.0 / BPM);
#define FOR(b, n) if(tmp>0.0) {float a = float(b) * 240.0 / BPM; for(int i = 0; i < n-1; i++){if (tmp> a) tmp -= a;}}

まず使用法の説明

説明と言っても音符の長さと高さをヅラヅラ書いていくだけです。

    INIT(time)
    R(2)
    R(4)
    N(4, 67)
    //--------    
    N(4, 72)
    R(8)
    N(8, 72)
    N(4, 72)
    N(4, 74)
    //--------
    N(2, 72)
    N(4, 67)
    N(4, 76)
    //--------

こんな感じです。
R(2)は2分休符。R(4)は4分休符。R(8)は8分休符。
N(4, 67)は4分音符でノート番号が67です。
//-------- これは小節の区切りが解りやすいように書いただけ。
ノート番号については
http://www.asahi-net.or.jp/~HB9T-KTD/music/Japan/Research/DTM/freq_map.html
こちらを参照してください。
この他に
D(4),C(4, ivec3(60, 64, 67)) を用意しました。
D(4)は4分音符だけど打楽器みたいにノート番号が必要ない時に使います。
C(4, ivec3(60, 64, 67))は4分音符で和音(これはドミソ)を出したい時に使います。
和音が3つじゃない時は対応してないけど臨機応変に使ってください。
これらは、第一引数がintになってます。付点4分音符とかに対応してません。とりあえず無視する方向でつくりました。使いたい時はマクロを書き直すか、N(4, 67)R(8)とかで誤魔化して使うかです。
これはマクロなので
R(2)R(4)N(4, 67)N(4, 72)R(8)N(8, 72)N(4, 72)N(4, 74)
みたいに一行でも書けます。
ノート番号から音階が解るなんて無理な話なのでマクロを用意します。

#define C3  60
#define Cs3 61
#define D3  62
#define Ds3 63
#define E3  64
#define F3  65
#define Fs3 66
#define G3  67
#define Gs3 68
#define A3  69
#define As3 70
#define B3  71
#define UP + 12
#define DN - 12

すると

    INIT(time)
    R(2)
    R(4)
    N(4, G3)
    //--------    
    N(4,C3 UP)
    R(8)
    N(8,C3 UP)
    N(4,C3 UP)
    N(4,E3 UP)
    //--------
    N(2,C3 UP)
    N(4,G3)
    N(4,E3 UP)
    //--------

こういう表記になります。
UPが嫌なら

#define C4  72
#define Cs4 73
#define D4  74

としてもかまいません。
shaderの記述量を減らしたいならマクロを使わずに直接ノート番号でも良いと思います。
和音の扱いは

#define CHO(a,b,c) ivec3(a,b,c)

を書いておいて

C(4, CHO(C3, E3, G3))

でいいと思います。

LOOP(),FOR() の説明

とりあえず、バスドラのリズムパターンを書いてみます。

    INIT(time)
    LOOP(2)
        FOR(0.5,2)
            D(4)
            R(4)
        // -------
        D(4)
        R(4)
        D(8)
        D(8)
        R(4)

LOOP(2) は以降の2小節を永遠に繰り返す関数です。この末尾に関数を追加しても関数が呼ばれる事がありません。逆に関数が足りない場合は休符が挿入された状態になります。
FOR(0.5,2) は以降0.5小節を2回繰り返す関数です。FOR()は入れ子に使えるので便利です。
この表記の弱点はイテレータの最後の境界が、わからないので意図的にTABなどの段落変化をつけてやらないとシーケンスの流れを見失う事です。
リズム・パターンは、ここを参照しました。
http://www.rokushokitan.com/archives/2761/comment-page-1

マクロ群について

#define INIT(t) float tmp = t, nTime = t; int val, num; ivec3 chord;

初期化部分ですが、入力は時間になってます。それを仮にworld timeとします。関数の中でfloat nTime; int val, num; ivec3 chord; を定義します。そして、これがマクロ群を抜けた後、音源からゲイン(音の大きさ)を作る為の入力値になります。キーボードで例えるならキーを押した瞬間nTimeが0になります。そこから時間が進行して次にキーが押されたら又0になります。この時間を仮にlocal timeとします。そのタイミングによってval,num,chordが変化していきます。valは音価(8分音符とか4分休符とかの数字の部分)。numはノート番号。chordは和音用のノート番号が入ったivec3。
ここで、楽器の音の作り方を簡単に説明します。楽器のゲイン(音の大きさ)というのは爆発的に大きくなり頂点まで達したら後は段々と小さくなります。このゲイン(音の大きさ)の推移の曲線をエンベロープと呼びます。このエンベロープの関数とオシレータと呼ばれる波を作る関数の乗算で楽器の音は作られます。
この時、必要な入力は音の始まりを0とした時間です。その為に面倒な手順を踏んでlocal timeを取り出しました。これをエンベロープの関数に入力します。
オシレーターは、発振器です。sin関数と同じように-1.0~1.0の間を時間経過と共に揺れる関数です。入力は、world timeでもlocal timeでも構わない気はしますが、なんとなくworld timeを使ってます。
次にノート番号です。ノート番号から周波数を割り出すには
440.0 * exp2((float(note_number)-69.0) / 12.0); // exp2(x)はpow(2.0, x)と同じ。
これを使ってオシレーターの音程を変えます。

次は音価(val)です。これは今回は使用していませんが、キーを離すタイミングに影響します。ADSRというエンベロープを使う時のゲートタイムに関係してきます。この辺りは、まだ試してないので詳しい事はわかりません。

次は和音(chord)です。ノート番号が入ったivec3です。和音制御用に一応書いておいただけです。

これで、あるworld timeの時にlocal timeと周波数が得られ音源を使ってゲイン(音の大きさ)が導き出す流れです。

音源の関数の使い方

まだ初心者なので用語があやふやなのでズバリ、スクリプトを書かせてもらいます、

#define PI2 3.1415

float noteFreq(in float t, in int n)
{
    return PI2 * fract(440.0 * exp2((float(n)-69.0) / 12.0) * t);    
}

float env(float t, float r) { //エンベロープ
  return exp(-t*r);
}

float melody(float time)
{
    INIT(time)
    N(4,C3 UP)
    R(8)
    N(8,C3 UP)
    N(4,C3 UP)
    N(4,E3 UP)

    float f = noteFreq(time, num);
    return sin(f) * env(nTime,2.0);
}

こんな感じで使います。
これのsin(f)がオシレーターで、env(nTime,2.0)がエンベロープという事です。
この部分が、ディープな知識が必要をされているところです。
この部分は今から探していきますが、とりあえずシーケンスが出来るので音楽にはなってます。

おわりに

まだ、音楽プログラムを始めて日が浅いのに記事を書いているものですから、怪しいところがあると思います。
shaderで音のシーケンスという部分について、触れているものが無かったので書かしてもらいました。
もう少し良い方法があるかもしれません。とりあえずの叩き台にしてください。

メロディーラインは、イギリス民謡なら難しい事ないだろうという理由で
http://www.mu-tech.co.jp/music_files/score/forest_camp.html
を使わせてもらいました。

webGL2.0のtransform feedbackでmusic shaderが使いたいなら
http://jsdo.it/gaziya/2Ikw
を参考にしてください。

付記:
BPM : Beats Per Minute 音楽で演奏のテンポを示す単位。
4分の4拍子の曲の一小節の時間は、240.0 / bpm 一拍の時間は、 60.0 / bpm

shader music について大幅の見直しをしました

GLSLで音楽(はじめに)
GLSLで音楽(まずは、ドラムだ)
GLSLで音楽(メロディーいってみます)
GLSLで音楽(和音を使ってみる)
GLSLで音楽(今までの応用の一つ)

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10