#ドラムのmusic shader
ドラムのシーケンスから始めていきます。
とりあえずサンプルのshaderを書きました。バスドラムの音源は自力で見つけるこどが出来ずに、ここから、勝手にお借りしてます。ごめんなさい。
#define A 0.125
float noise(float time)
{
return (fract(sin(time*99.0)*50000.0)*2.0-1.0);
}
float kick(float time) {
return sin(6.283 * 50.0 * time - 10.0 * exp( -70.0 * time ))*exp(-time*1.6);
}
float snare(float time) {
return noise(time)*max(0.0,1.0-min(0.85,time*4.25)-(time-0.25)*0.3);
}
float hihat(float time) {
return noise(time)*exp(-time*150.0);
}
float sequence(int s,float time)
{
float n =mod(time,A);
for(int i=0;i<16;i++){
if((s>>(int(time/A)-i)%16&1)==1)break;
n+=A;
}
return n;
}
vec2 mainSound( float time )
{
return vec2(
0.5 * kick(sequence(0x0581,time))+
0.3 * snare(sequence(0x1010,time))+
0.2 * hihat(sequence(0x5555,time))
);
}
この音はshader toyで聞けます。
先日書いた記事のhtmlサンプルの中のvertex shaderの中の
vec2 mainSound(float time)
{
return vec2( sin(6.2831*440.0*time)*exp(-3.0*time) );
}
これを上のサンプルと書き換えても使えます。この辺りはshadertoyのsound shaderを意識してつくってます。以後、サンプルはshadertoyのsound shaderで音がでるように書いていきます。
#シーケンス
バスドラム、ハイハット、スネアドラム音源の説明は後日にして、シーケンスについて説明していきます。音源の関数は、なんとなくわかると思いますが、
sequence(0x5555,time)
この数字ですね。これは、いわゆるデータです。ノートオンするタイミングが書いてあります。
0x5555これを2進数に変換すると101010101010101 こうなります。
なんとなくhihatのリズムが見えてきたと思います。
#sequence()関数の簡単な説明
webGL2.0からshaderでビット演算が使えるようになりました。ビット演算の右シフトとANDを使って、各桁の数字を取り出すスキルがあります。
0x5555 >> 2 & 1
こんな感じです。使い方としては
int bitShift(int data, int order)
{
return data >> order & 1;
}
########
bitShift(0x5555, 2); // ==> output 1;
0x5555を2進数(0b101010101010101)にして下位から3つ目の数字は 1 だってこと。
これを利用してシーケンスをしていきます。
8ビートは、1小節に8分音符のハイハットを8回って意味でいいのかな?この辺の認識に自信が無いですが、そういう事で話を進めます。間違えていたら教えてください。
世間のドラム打ち込みをみると、16分音符を最小リズムにしてるようなので、それにならってみます。一小節に16個のon-offが必要です。GLSLのintは32bitなので十分にたります。
BMPを使って表す一小節の秒数は 240/BPM。それを更に16で割り、16分音符の秒数は 15/BPM。
今回のサンプルでは、BPM = 120 にしたので、16分音符の秒数は 0.125秒。
こんな感じをベースにして、sequence()関数を作りました。
他の拍子とかにも応用できると思います。
#データ制作について
0x0581とかは、人様のドラム打ち込みの画像を見ながら、onには1をoffには0と並べて2進数を作り、webの16進数変換アプリを使わせてもらいました。色々試したが、これが一番楽でした。注意点として、ビットシフトを使っているので、右から左にbitを並べていってください。
#追記
自力で変換するのは大変なのでpythonでツールを作りました。
import sys
from PyQt5 import QtCore,QtWidgets,QtGui
class Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.setGeometry(500, 200, 500, 350)
self.setWindowTitle('Binary Convert')
font = QtGui.QFont()
font.setBold(True)
w = 50
self.CheakBoices=[]
for i in range(16):
if i%4 == 0: w += 20
label = QtWidgets.QLabel(format(i,'x'),self)
label.setFont(font)
label.move(w+5, 20)
cb = QtWidgets.QCheckBox(self)
cb.move(w, 40)
self.CheakBoices.append(cb)
w += 20
binaryLabel = QtWidgets.QLabel("Binary",self)
hexLabel = QtWidgets.QLabel("Hex",self)
decimalLabel = QtWidgets.QLabel("Decimal",self)
binaryLabel.move(50, 90)
hexLabel.move(50, 130)
decimalLabel.move(50, 170)
binaryLabel.setFont(font)
hexLabel.setFont(font)
decimalLabel.setFont(font)
self.binaryEdit = QtWidgets.QLineEdit(self)
self.hexEdit = QtWidgets.QLineEdit(self)
self.decimalEdit = QtWidgets.QLineEdit(self)
self.binaryEdit.move(120, 90)
self.hexEdit.move(120, 130)
self.decimalEdit.move(120, 170)
self.binaryEdit.setFont(font)
self.hexEdit.setFont(font)
self.decimalEdit.setFont(font)
self.binaryEdit.setReadOnly(True)
self.hexEdit.setReadOnly(True)
self.decimalEdit.setReadOnly(True)
convertButton = QtWidgets.QPushButton("Convert",self)
convertButton.setFont(font)
convertButton.move(360, 160)
convertButton.clicked.connect(self.convert)
numberLabel = QtWidgets.QLabel("Number",self)
numberLabel.move(50, 260)
numberLabel.setFont(font)
self.numberEdit = QtWidgets.QLineEdit("0",self)
self.numberEdit.move(120, 260)
self.numberEdit.setFont(font)
numberButton = QtWidgets.QPushButton("Check",self)
numberButton.setFont(font)
numberButton.move(360, 260)
convertButton.clicked.connect(self.convert)
numberButton.clicked.connect(self.cheak)
def convert(self):
n=0
for i in range(16):
if self.CheakBoices[i].isChecked():
n += 1<<i
self.binaryEdit.setText(format(n, '#016b'))
self.hexEdit.setText(format(n, '#04x'))
self.decimalEdit.setText(str(n))
def cheak(self):
for i in range(16):
self.CheakBoices[i].setCheckState([QtCore.Qt.Unchecked, QtCore.Qt.Checked][int(self.numberEdit.text(),0)>>i&1])
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
チェックボックスにリズムパターンを入れて"convert"ボタンでint
が出てきます。
"number"にint
を入れて"cheack"ボタンでチェックボックスにリズムパターンがでます。
#追記
データを別アプリで作るのも、無理があると気付きshaderの中で作る奴にしました。2019/12/09
Snake Knot
#define BPM 140.
#define A (15./BPM)
float adsr(float t, float a, float d, float s, float r, float gt)
{
return max(0.0,
min(1.0, t/max(1e-4, a))
- min((1.0 - s) ,max(0.0, t - a)*(1.0 - s)/max(1e-4, d))
- max(0.0, t - gt)*s/max(1e-4, r));
}
float noise(float t)
{
return fract(sin(t*45678.0)*1234.5)*2.0-1.0;
}
float square(float f)
{
return sign(fract(f)-0.5);
}
float kick(float t){
return sin(315.0*t-10.0*exp(-50.0*t))*adsr(t,0.0, 0.3, 0.0, 0.0, 0.0);
+0.2*square(50.0*t)* adsr(t,0.0, 0.05, 0.0, 0.0, 0.0);
}
float snare(float t)
{
return noise(t)*adsr(t,0.01, 0.1, 0.0, 0.0, 0.0);
}
float closeHihat(float t)
{
return noise(t)*adsr(t,0.0, 0.03, 0.0, 0.0, 0.0);
}
float openHihat(float t)
{
return noise(t)*adsr(t,0.0, 0.05, 0.5, 0.03, 0.03);
}
float sequence(int s,float t)
{
float n =mod(t,A);
for(int i=0;i<16;i++){
if((s>>(int(t/A)-i)%16&1)==1)break;
n+=A;
}
return n;
}
#define Rhythm2Int(v,a)v=0;for(int i=0;i<16;i++)v+=a[i]<<i;
vec2 mainSound( float time )
{
int[4] r_kick; // int[](0x0c05,0x0c05,0x0405,0x0c0c)
int[4] r_snare; // int[](0x9290,0x9290,0x4290,0x4292)
int[4] r_closeHihat; // int[](0x5555,0x5555,0x5555,0x5155)
int[4] r_openHihat; // int[](0x0000,0x0000,0x0000,0x0400)
int[4] r_velocity; // int[](0x3030,0x3030,0x3030,0x3030)
Rhythm2Int( r_kick[0], int[]( 1,0,1,0, 0,0,0,0, 0,0,1,1, 0,0,0,0 ))
Rhythm2Int( r_snare[0], int[]( 0,0,0,0, 1,0,0,1, 0,1,0,0, 1,0,0,1 ))
Rhythm2Int( r_closeHihat[0], int[]( 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0 ))
Rhythm2Int( r_openHihat[0], int[]( 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 ))
Rhythm2Int( r_velocity[0], int[]( 0,0,0,0, 1,1,0,0, 0,0,0,0, 1,1,0,0 ))
Rhythm2Int( r_kick[1], int[]( 1,0,1,0, 0,0,0,0, 0,0,1,1, 0,0,0,0 ))
Rhythm2Int( r_snare[1], int[]( 0,0,0,0, 1,0,0,1, 0,1,0,0, 1,0,0,1 ))
Rhythm2Int( r_closeHihat[1], int[]( 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0 ))
Rhythm2Int( r_openHihat[1], int[]( 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 ))
Rhythm2Int( r_velocity[1], int[]( 0,0,0,0, 1,1,0,0, 0,0,0,0, 1,1,0,0 ))
Rhythm2Int( r_kick[2], int[]( 1,0,1,0, 0,0,0,0, 0,0,1,0, 0,0,0,0 ))
Rhythm2Int( r_snare[2], int[]( 0,0,0,0, 1,0,0,1, 0,1,0,0, 0,0,1,0 ))
Rhythm2Int( r_closeHihat[2], int[]( 1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0 ))
Rhythm2Int( r_openHihat[2], int[]( 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 ))
Rhythm2Int( r_velocity[2], int[]( 0,0,0,0, 1,1,0,0, 0,0,0,0, 1,1,0,0 ))
Rhythm2Int( r_kick[3], int[]( 0,0,1,1, 0,0,0,0, 0,0,1,1, 0,0,0,0 ))
Rhythm2Int( r_snare[3], int[]( 0,1,0,0, 1,0,0,1, 0,1,0,0, 0,0,1,0 ))
Rhythm2Int( r_closeHihat[3], int[]( 1,0,1,0, 1,0,1,0, 1,0,0,0, 1,0,1,0 ))
Rhythm2Int( r_openHihat[3], int[]( 0,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,0 ))
Rhythm2Int( r_velocity[3], int[]( 0,0,0,0, 1,1,0,0, 0,0,0,0, 1,1,0,0 ))
int i = int(floor(time/(A*16.)))&3;
int velocity = r_velocity[i]>>(int(floor(time/A))&15)&1;
float vol = 0.2 *(1.0+0.5*float(velocity));
return vec2(
0.0
+0.4 * kick(sequence( r_kick[i], time))
+0.3 * snare(sequence( r_snare[i], time))
+vol * closeHihat(sequence( r_closeHihat[i], time))
+vol * openHihat(sequence( r_openHihat[i], time))
);
}
Amen Breakの打ち込みをしてみたのですが、誰か添削してくれませんかね?
— gaz (@gaziya5) 2018年12月11日
もしくは、これをドラム用の雛形にしてもいいです。https://t.co/X9YKC6U1bl
#shaderMusic音源
いっそのこと、shader musicの音源を共有ってのはどうですか?#shaderMusic音源
— gaz (@gaziya5) 2018年12月8日
なんてのを企てました。参加おねがいします。
#GLSLで音楽の記事
GLSLで音楽(はじめに)
GLSLで音楽(メロディーいってみます)
GLSLで音楽(和音を使ってみる)
GLSLで音楽(今までの応用の一つ)