和音をプログラムで扱うには その3
GLSLで和音の配列を扱うのは厄介なので、配列をpackする方法をとっています。
packって何だって思うと思います。GLSLにはpackUnorm4x8()という関数が有ります。これはvec4をビット演算を使いuintにして、packしたuintをunpackUnorm4x8()で又vec4にするといったものです。これの応用になります。和音のノート番号は127未満なので7bitで済みます。4和音を使う事を前提にしても、4x7=28で28bitで済みます。GLSLは32bit使えるから8bitで良いだろうと思われますが、uintを使うと数値に 60uとかuを付けないとならないのです。そこを嫌ってintで31bit以内としました。ここはセコイって言われても記述が1文字増えるのに抵抗があるのです。気にならないならuintの方がスマートだと思います。
ここは改造というか、仕様変更も有るかもなので、具体例は出さないで、そういう例もあるよって紹介だけにしておきます。
和音をプログラムで扱うには その2
3音和音(トライアド)をアルゴリズムで扱うと、馬鹿みたいに単純です。だけど音楽理論が頭に有ると邪魔をします。
4音和音(セブンス)も単純です。さて、説明にあぐねています。
言葉での説明を考えたけどFoxDotの方が良いかなで。
p1>>pluck(P[0,1,2,3,4,5,6]+(0,2,4))
これで、I ,IIm, IIIm, IV, V, VIm, VIIm(♭5)の和音が順次なります。
ここでメジャースケールの配列を見てみます。
>>> print(Scale.major) P[0, 2, 4, 5, 7, 9, 11]
では、
Iの和音の構成を見ます。0,4,7 これは長3度でメジャーになります。
IIの和音の構成を見ます。2,5,9 これは短3度でマイナーになります。
IIIの和音の構成を見ます。4,7,11 これは短3度でマイナーになります。
スケールを使ってインデックスで0,2,4に組み合わせると、勝手に和音になり、メジャー、マイナーを意識しないでよくなります。
4音和音(セブンス)でも同じ事。
p1>>pluck(P[0,1,2,3,4,5,6]+(0,2,4,6))
なので扱いはディグリーネームIVのトライアドとか、ディグリーネームVIのセブンスという感じになる訳です。
以前の図より、sus4なら
p1>>pluck(P[0,1,2,3,4,5,6]+(0,3,4))
解れば、単純なんですね。ただ覚えた音楽理論が有ると頭の整理に時間が要ります。 慣れなのですかね スケールが変わってもルールは変わりません。これが絶妙です。音楽理論の本質って、むしろこれだろうって思っています。
和音には転回と言うのが有ります。トライアドの場合、3音の内低い音をオクターブ上げて和音にするか、3音の内高い音をオクターブ下げて和音にするかのようですが、この辺りもコントロールしないとならないのですかね。
和音をプログラムで扱うには
ある時にスケールを使って和音を構成すれば簡単である事に気付いた。それ以来、その方法を使っている。FoxDotを始めて眺めた時に、スケールを使って和音を作っていた。これで確信に変わった気がした。それもあるのでFoxDotを選択したのもある。更にpythonがベースなんて最高だね。
今まで3音和音(トライアド)には使っていた方法ではあるが、他の4音和音(セブンス)とかsus4とか考えあぐねていたが良いモノを見つけた。
これだ。ちょっと小躍りしました。まだ、これをどう使うか固めてません。とりあえず、紹介まで。これから、説明を書きながら考えを纏めながら仕組みを考えるつもりです。こんがらがってる〽️ pic.twitter.com/y3a1ro3KbF
— ア (@yuruyurau) 2021年6月19日
FoxDotでのスケール
FoxDotのスケール周りの事を書いていきます。
FoxDot | Documentation | Roots And Scales
手始めにメジャースケールの配列を見てみます。
>>> print(Scale.major) P[0, 2, 4, 5, 7, 9, 11]
FoxDotのデフォルトも見てみます。
>>> print(Scale.default) P[0, 2, 4, 5, 7, 9, 11]
デフォルトでメジャースケールが設定されてます。 キーも見てみます。
>>> print(Root.default) 0
0が返ってきています。これは'C'です。1なら'C#'、2なら'D'となっていきます。
オクターブを取得方法は分りませんが、おそらく4だと思います。
FoxDotでドレミファソラシドを鳴らしてみます。
p1 >> pluck(P[:8])
これは[0,1,2,3,4,5,6,7,8]という配列でドレミファソラシドを鳴らします。ピアノの白鍵を順番に鳴らした状態です。
スケールの配列に順番に0,1,2.....というわけです。8はサイズ外ですが勝手にオクターブを上げてインデックスを0に戻してくれるようです。オクターブ下は、-1,-2...と書いて行き、やはり勝手にオクターブを合わせてくれます。スケールから外れた#とかは、0.5,1.5....と柔軟に対応してくれます。
これをGLSLにしてみます。
#define C 60 #define Major_scale int[](0,2,4,5,7,9,11) #define Pitch(n) C + Major_scale[n%7] + n/7*12
GLSLだとスケールを外れた音には対応できません。スケールを外れた音は、やって出来ない事もないけど、特殊ケースなので素直に諦めましょう。和音の中にはスケールを外れた音が有りますが、それは和音の方で対応出来ます。
shadertoyに載せてあるsound shader
shadertoyに載せてあるsound shaderの近い所から4つ貼っておきます。
一々、探してまでの手間を省きます。
イマイチなんだけど、これがもう少し良い感じになるように、このブログを作りました。
これらは、考え方がブレブレなので、微妙に違っています。何が正解なのですかね。
スケール
FoxDotを始めから思ったんだけど、live coding勢は、和音を重視してない気がする。リズムがメインで構成してる気がする。
なので、倣ってリズムから始めていけば良いんだろうけど、あえて、情報が薄い和音から纏めていく。これは試行錯誤している部分なので、先に纏めておいてFoxDotのスキルでブラッシュアップする作戦。その前にスケールから。
GLSLではピッチをノート番号で管理してる。
float note2freq(int n){ return 440.0*exp2((float(n)-69.0)/12.0); }
これは、ノート番号から周波数を出力する関数。
ノート番号から周波数に変換する関数があるので、これが絶対で、これから全てが構成される。
プログラムで音階を扱うならば、スケールを使って配列で処理をするのが一番簡単だ。 だけど、例外とかあったりして、それをどうするかで頭を悩ます。
話をメジャースケールで進めていきます。
GLSLでは配列で処理しています。
int[](0,2,4,5,7,9,11)
スケールを使うのでインデックスはデグリーになります。ここで問題が入ってきます。デグリーは、I,II,III,IV,V,VI,VIIなので1から始まります。配列は0から始まります。なので
int[](0,2,4,5,7,9,11)[n-1]
と扱っていました。FoxDotではオフセットを取らないで単純に0から始めています。ちょっと悩んだのですが、今度からGLSLもFoxDotに合わせて0からにしようと決めました。音楽をやっている人には抵抗があるとは思うけど、プログラムだから0ですよね。
FoxDotの仕様をGLSLに持ち込みます。