Try & Error for Sound Program

FoxDotを始めました。色々と試してみます。

Sound Shaderの解説 3

infoをオーソドックスに使う方法

前回の解説を書き終えた時点で、自分のsound shaderの解説より先に、単純なシーケンスの仕方を書いた方が良いなって思いました。
なので、一度デモのsound shaderの説明を離れて、今まで書いた事だけでシーケンスをする方法を書いてみようと思います。多分、音楽が出来る人にとっては、そっちの方が良いと思います。
私は音楽が良く解っていないので音楽理論を捏ね回し素人レベルで、それっぽい奴を作るコードを書いてきました。解る人にはウザいかな。
とはいえ、これも何年かの成果なので書きます。今回はひとまず、前回までのコードを使ってshader musicを作る説明にします。

ノート番号

www.asahi-net.or.jp

ピッチ名にはノート番号が対応されています。shaderでノート番号から周波数を出す関数があります。なのでノート番号を使ったシーケンスが可能になります。

float note2freq(int note){
    return 440.*exp2(float(note-69+67)/12.);
}

リンクのページを見ると、0~127とノート番号が振られています。これはshaderで扱うにはちょうど良い数です。7bitです。4和音を想定した時、4X7=28 intで使用できる31bitに収まります。しかもbitが余るので、3和音と4和音の区別をする事ができます。

bit演算を使いintで和音を扱う方法

ビットを7bit事にずらしてintに格納します。そうすると前回のやり方だけで和音を扱えるようになります。

int encodeChord(int pitch1, int pitch2, int pitch3){
    return pitch1 +
        (pitch2 << 7) +
        (pitch3 << 14);
}

int encodeChord(int pitch1, int pitch2, int pitch3, int pitch4){
    return pitch1 +
        (pitch2 << 7) +
        (pitch3 << 14) +
        (pitch4 << 21) +
        (1<<28) ; // 4和音を判別するフラグ
}
ivec4 decodeChord(int chord){
    return ivec4(
        chord & 127,
        chord >> 7 & 127,
        chord >> 14 & 127,
        chord >> 21 & 127
    );
}

int chordLength(int chord){
    return 3 + (chord >> 28 & 1);
}

使用例

これだけ解れば音楽の出来る人なら問題無く使えると思います。まあ書くのは面倒くさいですけど。これでsound shaderで音楽が出来るんじゃないですかね。音符に対しての情報が足りないのなら、INFと同じ仕組みを増やせば良いだけです。

// 音源関係
#define PI acos(-1.)
#define PIH (PI*.5)
#define TAU (PI*2.)
#define osc_sin(x)sin((x)*TAU)
#define osc_saw(x)(1.-fract(x)*2.)
#define osc_sqr(x)sign(0.5-fract(x))
#define osc_tri(x)(asin(sin((x)*PI))/PIH)
 
float noize(float t,  float a, float b){
    float g=fract(cos(t*exp2(a))*exp2(b))+6.0;
    return exp(-0.08*g*g) *40.0-1.;
}

float kick(float t)
{
    return clamp(1.5*asin(cos(320.0*t-30.0*exp(-40.0*t))),-1.,1.)*exp(-4.*t);
}

float snare(float t){
    float n=noize(t,10.,10.)*exp(-t*10.), f=t*160.,a=.3*osc_tri(1.2*f)*exp(-t*3.);
    float b=.05*osc_sin(10.*f+a)*exp(-t*3.);
    float c=osc_sin(f+b)*exp(-t*15.), z;
    return n+c;
}

float hihat(float t){
    return noize(t,10.,18.)*exp(-t*15.);
}

float piano(float t,float f){
    float g=0.,a,b=0.;
    f*=t;
    for(float j=1.;j<5.;j++){
        b+=a=exp(-j*.5);
        g+=a*osc_sin(f*j)*exp(-t*1.8/(2.-j*.1));
    }
    g*=1.+1.2*exp(-5.*t);
    return g/b;
}

float lead(float t,float f){
    f *= t;
    return clamp((
        8.0*osc_saw(f)
        + 2.0*osc_sin(f*2.0)
        + osc_tri(f*3.0)
    )/3.0,-1.,1.)*exp(-3.*t);
}

float base(float t,float f){
    f *= t;
    return clamp((
        8.0*osc_sqr(f)
        + 3.0*osc_saw(f*2.0)
        + osc_sin(f*3.0)
    )/2.0,-1.,1.)*exp(-4.*t);
}

// シーケンスのマクロ

#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 C4  72
#define Cs4 73
#define D4  74
#define Ds4 75
#define E4  76
#define F4  77
#define Fs4 78
#define G4  79
#define Gs4 80
#define A4  81
#define As4 82
#define B4  83

float OFFSET;
int TMP,POS,IDX;
#define EXTRACT(m,s)\
    {\
        TIME=mod(TIME,barTime);\
        float a,b;\
        IDX=-1;\
        for(int i=0;i<m;i++){\
            a=float(int[]s[i%int[]s.length()]);\
            b=barTime/float(m);\
            if(a>0.)OFFSET=0.;\
            if(TIME<b)break;\
            IDX+=int(a);\
            TIME-=b;OFFSET+=b/max(1.,a);\
        }\
        if(a>0.)IDX+=int(TIME/b*a)+1;TIME=mod(TIME,b/max(1.,a));TIME+=OFFSET;\
    }
#define Rhythm(m,s)TMP++;if(POS==TMP)EXTRACT(m,s)
#define Info(a)if(POS==TMP)INF=int[]a[IDX%int[]a.length()];

#define Reset OFFSET=100.;TIME=t;TMP=-1;POS=int(t/barTime);
#define Loop(n)TIME= mod(TIME,barTime*float(n));POS%=n;

float note2freq(int note){
    return 440.*exp2(float(note-69)/12.);
}

int encodeChord(int pitch1, int pitch2, int pitch3){
    return pitch1 +
        (pitch2 << 7) +
        (pitch3 << 14);
}

int encodeChord(int pitch1, int pitch2, int pitch3, int pitch4){
    return pitch1 +
        (pitch2 << 7) +
        (pitch3 << 14) +
        (pitch4 << 21) +
        (1<<28) ; // 4和音を判別するフラグ
}

ivec4 decodeChord(int chord){
    return ivec4(
        chord & 127,
        chord >> 7 & 127,
        chord >> 14 & 127,
        chord >> 21 & 127
    );
}

int chordLength(int chord){
    return 3 + (chord >> 28 & 1);
}

vec2 mainSound(int s,float t){
    float bpm=120.;
    float barTime = 240./bpm;
    float TIME;
    int INF;
    float gain =0.;

    
    Reset;
    Loop(1)
    Rhythm(2,(1,1))
    gain += .5*kick(TIME);
 
    Reset;
    Loop(4)
    for(int i=0;i<3;i++){
        Rhythm(4,(0,1,0,1))
    }
    Rhythm(4,(0,1,0,2)) 
    gain +=  .6*snare(TIME);
      
    Reset;
    Loop(1)
    Rhythm(8,(1))
    Info((1,1,2,1))
    gain +=  .7*hihat(TIME)*float(INF)*.5;
    
    Reset;
    Loop(4)
    Rhythm(1,(1)) Info((encodeChord(C3,E3,G3)))
    Rhythm(1,(1)) Info((encodeChord(F3,A4,C4)))
    Rhythm(1,(1)) Info((encodeChord(A3,C4,E4)))
    Rhythm(2,(1)) Info((encodeChord(G3,B4,D4,F4), encodeChord(D3,F3,A3)))
    
    ivec4 chord = decodeChord(INF);
    int size = chordLength(INF);
    
    for(int i=0;i<size;i++){
        gain += .6*piano(TIME,note2freq(chord[i]))/float(size);
    }
    
    Reset;
    Loop(1)
    Rhythm(8,(1))
    Info((chord[0]-24,chord[0]-12))
    gain+= .2*base(TIME,note2freq(INF));
      
    Reset;
    Loop(1)
    Rhythm(4,(1,1,1,0))
    Info((C3,E3,G3))
    gain+= .2*lead(TIME,note2freq(INF));
    
    return .5*clamp(1.5*vec2(gain),-1.,1.);
}

www.shadertoy.com

終わりに

長いちゃんとした曲に仕上げるならLoop()を外してズラズラと書けば良いだけです。
これを書き終わり、自分の偏った音楽理論の使い方を説明するのも、何か違うかなって思えてきました。一旦、これで区切りを付けて、別記事で自分のやり方とか書いてみようと思います。音源については、説明って程のことも余りないので、また別記事で思いつく事があったら書いてみます。
何か質問がありましたら、遠慮なくどうぞ。