FM test tatmos の音色の解説とFM音色の作り方のコツ

[FM test tatmos](https://www.shadertoy.com/view/4dscW7)

まずはじめに、このshadertoyには２つのglslが書かれていて、


![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/0b47552b-37fb-f51d-0c56-316eefe26945.png)

このSound側の説明になります。

(ImageはFMの合成結果をビジュアライズするためのものになっているのでほぼ一緒）

ソース全体はこんな感じ。

``` fmtesttatmos.glsl
#define PI 3.1415926535
#define A 0.0
#define B 2.0
#define C 3.0
#define D 5.0
#define E 7.0
#define F 8.0
#define G 10.0

//================================================
// Utils
//================================================
float rand(vec2 co)
{
	return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}
float calcHertz(float scale)
{
    return 441.0 * pow(2.0, scale / 12.0) * PI;
}

float calcHertz(float octave, float note)
{
    return calcHertz(octave * 12.0 + note);
}
float rect(float time)
{
    return sign(fract(time / PI / 2.0) - 0.5);
}
float rect2(float time)
{
    return sign(fract(time / PI / 2.0) - 0.25);
}
float rect3(float time)
{
    return sign(fract(time / PI / 2.0) - 0.125);
}

float eg(float time,float gate)
{
    return  exp(-gate*(time));
}

//================================================
// Sound
//================================================
#define Sin1(u, v) sound += clamp(sin(time * calcHertz(u, v)) * (1.0 - localTime2 + sin(time * 80.0) * 0.1), -0.3, 0.3);
#define Rect1(u, v, l) sound += rect(time * calcHertz(u, v)) * l;
#define Rect2(u, v, l) sound += rect2(time * calcHertz(u, v)) * l;
#define Rect3(u, v, l) sound += rect3(time * calcHertz(u, v)) * l;
#define BD_Rect1(u, v, l) sound += rect(time * calcHertz(u, v +  eg( localTime2,2.0) )) * l;

vec2 mainSound( float time )
{
    float localTime = mod(time*0.5 , 8.0);
    float localTime2 = mod(localTime , 1.0);
    
    vec2 sound = vec2(0.0);
    
    
    if(rand(vec2 ( localTime2))*2.0 > 0.2)
    {
        //	BD
        localTime2 = mod(localTime *16.0, 1.0);	//	L8
     	BD_Rect1(0.0, C, eg( localTime2,8.0));
    }
    
    if (localTime < 2.0)
    {
        localTime2 = mod(localTime *2.0, 1.0);	//	L8
        //	piano
        float op0 = sin(1.0* calcHertz(1.0, C)*time) * eg( localTime2,1.0);
        float op1 = sin(1.14*op0)* eg( localTime2,2.0);
        float op2 = sin(1.5*calcHertz(1.0, C)*time)* eg(localTime2,5.0);
        float op3 = sin(2.14*op2) * eg( localTime2,2.0);
        
        sound += vec2(  
        op1* 0.5 
        + op3* 0.5 
        );
    } else 
    if (localTime < 4.0)
    {
        localTime2 = mod(localTime *2.0, 1.0);	//	L8
        //	bell
        float op0 = sin(8.0* calcHertz(1.0, C )*time) * eg( localTime2,3.0);
        float op1 = sin(6.5*op0)*  eg( localTime2,1.0);
        float op2 = sin(3.14*calcHertz(1.0, C )*time)* eg( localTime2,1.0);
        float op3 = sin(6.5*op2) *  eg( localTime2,1.0);
        
        sound += vec2(  
        op1* 0.5 
        + op3* 0.5 
        );
       
    } else  
    if (localTime < 6.0)
    {
        localTime2 = mod(localTime *1.0, 1.0);	// L4
        //	duty 0.5
     	Rect1(0.0, C, eg( localTime2,1.0));
     	Rect1(1.0, E, eg( localTime2,1.0));
     	Rect1(1.0, G, eg( localTime2,1.0));
     	Rect1(1.0, B, eg( localTime2,1.0));     
        sound*=0.25;
    } else 
    if (localTime < 7.0)
    {  
        localTime2 = mod(localTime *3.0, 1.0);	//	L6
        //	duty 0.25
     	Rect2(0.0, C, eg( localTime2,1.0));
     	Rect2(1.0, E, eg( localTime2,1.0));
     	Rect2(1.0, F, eg( localTime2,1.0));
     	Rect2(1.0, A, eg( localTime2,1.0));      
        sound*=0.25;     
    }
    else 
    if (localTime < 8.0)
    {
        localTime2 = mod(localTime *6.0, 1.0);	//	L12
        //	duty 0.125
     	Rect3(0.0, G, eg( localTime2,2.0));
     	Rect3(2.0, E, eg( localTime2,2.0));
     	Rect3(1.0, F, eg( localTime2,3.0));
     	Rect3(1.0, B, eg( localTime2,3.0));      
        sound*=0.25;     
    }
    
    return vec2(sound);
               
}
```

## FM音源のピアノ音色

この部分

``` piano.glsl
        //  piano
        float op0 = sin(1.0* calcHertz(1.0, C)*time) * eg( localTime2,1.0);
        float op1 = sin(1.14*op0)* eg( localTime2,2.0);
        float op2 = sin(1.5*calcHertz(1.0, C)*time)* eg(localTime2,5.0);
        float op3 = sin(2.14*op2) * eg( localTime2,2.0);

        sound += vec2(  
        op1* 0.5 
        + op3* 0.5 
        );
```

図解するとこんな感じ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/96808/ed539f55-ece5-abaf-c3e7-99b6faf2a42a.png)

右側で、打撃音的な音がちょっとだけ鳴る。
左側で、弦が振動している音

押した鍵盤から、基準の周波数がて、それの倍率が、1とか２とかだと柔らかい音。
1,14とか2.14とかだと金属的な音に変化する。


## FM音源の音色作りは難しい？

難しいけど、コツをつかめば、予測できる音色を手に入れることができる。

### 倍音決め

まず。楽器の音は、倍音が重なった音。

減衰しない音で、まず基本の周波数を決めます。

倍音の重なり方の違いで、バイオリンだったり、ブラスだったり、クラリネット、ハーモニカ、といった音になる。
フルートはすごくシンプルで、サイン波形にすごく近い。

キリの良い周波数、　1,2,3,4,とかは楽音としてつかえて、
.14とかの端数のあるような音は複雑な周波数ではないノイズに近い音に変化する。

キリが良いもので、1,2,4,6,8はやわらかい音
1,3,5,7,とかは硬い音になる傾向がある。

### 部分音に分ける

あと、音色を部分音に分割して考える。

ピアノだと、打鍵時のアタックの「カッ」という音と「ポーン」というリリースの音に分けられる。

音色の出だし部分は何かとノイズになりやすい。ここのニュアンスが音色を決めていることが多い。

伸ばしているところだけだと、ぶーとかぽーとかのブザー音と大差がない。

エンベロープで、アタックの時だけ鳴ったり、波形が歪むようなところで、らしさを作ることができる。

フルートが尺八になったり、パンフルートのような音になったり。

### 持続音のゆれの表現

ビブラートのような、ゆるりとした音のゆれ、ピッチのゆれだったり、音量変化のゆれとかを再現すると
フルートがサイレンになったりする。
ほんの少しデチューンをかけたような音はうねりが入って、
リッチに聞こえる。
こういうのを重ねて音の厚みを表現することができる。

分厚いブラス、分厚いストリングス　vs 細いブラス、　細いストリングス

### デチューンをかけまくると　壊れた音になる

デチューンをかけるのもたくさんやると、たとえば、
ピアノ　-> ホンキートンク　-> おもちゃのピアノ

調律が狂ったピアノになっていきます。

ピアノの弦もよくみると一つの音に３弦くらいある音域もあったりするので、これらがばらけることで、音が「がちゃーん」という感じに変わります。

### エンベロープのリリースで残響効果

リリースがあると、空間表現になる。　リリースがない音は、音を止めた音、無響室で鳴る音のような感じになる。
ここも音色らしさを作れる。

逆に短くすれば、ミュートギターみたいな音にもできる。

### エンベロープのアタックをゆるめると・・・

アタックをゆっくり始めると、やわらかい演奏表現もできる。

らしい音量変化もといった演奏表現も音色の一部として考えることもできる。

ストリングスなどで、対比することもできる。

### フィードバックって何者？

よくFM音源などでは一つのオペレータで、フィードバックができるものがあります。
あれも、よくできていて、　オシロスコープなどで、フィードバック量を変化させつつ波形をみるとわかるのですが、
サイン波形とノコギリ波形のウェーブシェーピング（モーフィング）的な変化がおきます。
ノコギリ波形をサイン波形から作る場合、ものすごい数のオペレータを直列で繋ぐ必要がありますが、
このフィードバックを使うと簡単にノコギリ波形っぽい音になります。

フルート　-> ブラスや、弦楽器などに変化していきます。

# などなど、FM音源すごい

これ考えた人すごい。
上を意識しつつFM音源　OPMやOPNなどのパラメータを眺めてみるのも良いかも。

DX7などの音色やアルゴリズムに感動します。