Help us understand the problem. What is going on with this article?

GLSLで音楽(まずは、ドラムだ)

More than 1 year has passed since last update.

ドラムの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))
    );
}

なんてのを企てました。参加おねがいします。

GLSLで音楽の記事

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away