GLSL shader からpython3を使ってwav fileを作る

music shaderを色々試しているが、webGLで開発するには、小回りが効かなくて何かと大変になってきた。
そこで、pythonを使ってGLSL shaderからwav fileを作るスクリプトを作ってみました。
pythonを絡めさせれば、細かい設定を調整するには楽になると思ってます。

##必要なもの
pyopengl,pyaudioをpipでインストールしてください。
うまくいかない場合は
http://www.lfd.uci.edu/~gohlke/pythonlibs/
ここからパッケージをdownloadすれば大抵はうまくいきます。

##スクリプトはこれ
ここの説明は大変なのでライブラリーと思って使ってください。
サンプリングレートは48000に決めうちしています。
違うのが良ければ適当に直してください。

```py
from OpenGL.GL import *
from OpenGL.WGL import *
from ctypes import *
import numpy
import pyaudio
import wave
import array

def shader2data(duration, src): # 出力時間、shader music ソース
    sampleRate = 48000
    numSamples  = duration*sampleRate
    numSamplesC = numSamples*2
    samples = (c_float * numSamplesC)()
    hWnd = windll.user32.CreateWindowExA(0,0xC018,0,0,0,0,0,0,0,0,0,0)
    hDC = windll.user32.GetDC(hWnd)
    pfd = PIXELFORMATDESCRIPTOR(0,1,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0)
    SetPixelFormat(hDC,ChoosePixelFormat(hDC, pfd), pfd)
    hGLrc = wglCreateContext(hDC)
    wglMakeCurrent(hDC, hGLrc)
    program = glCreateProgram()
    shader = glCreateShader(GL_VERTEX_SHADER)
    glShaderSource(shader, src)
    glCompileShader(shader)
    if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE:
      raise RuntimeError(glGetShaderInfoLog(shader).decode())
    glAttachShader(program, shader)
    outs =  cast((c_char_p*1)(b"gain"), POINTER(POINTER(c_char)))
    glTransformFeedbackVaryings(program, 1, outs, GL_INTERLEAVED_ATTRIBS)
    glLinkProgram(program)
    glUseProgram(program)
    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, sizeof(samples), None, GL_STATIC_DRAW)
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo)
    glUniform1f(glGetUniformLocation(program, "sampleRate"), sampleRate)
    glEnable(GL_RASTERIZER_DISCARD)
    glBeginTransformFeedback(GL_POINTS)
    glDrawArrays(GL_POINTS, 0, numSamples)
    glEndTransformFeedback()
    glDisable(GL_RASTERIZER_DISCARD)
    glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(samples), byref(samples))
    wglMakeCurrent(0, 0);
    wglDeleteContext(hGLrc);
    windll.user32.ReleaseDC(hWnd, hDC);
    windll.user32.PostQuitMessage(0);
    return numpy.frombuffer(samples, dtype=numpy.float32)

def writeWav(filename, data):    
    sampleRate = 48000
    w = wave.Wave_write(filename)
    w.setnchannels(2)
    w.setsampwidth(2)
    w.setframerate(sampleRate)
    w.writeframes(array.array('h', data*32767).tobytes())
    w.close()

def readWav(filename): 
    w = wave.open(filename, 'rb')
    print("params  :", w.getparams())
    print("duration:", w.getnframes() / w.getframerate(), "sec")
    p = pyaudio.PyAudio()
    stream = p.open(format=p.get_format_from_width(w.getsampwidth()),
                channels=w.getnchannels(),
                rate=w.getframerate(),
                output=True)
    stream.write(w.readframes(w.getnframes()))
    stream.close()
    p.terminate()

def playSound(data):    
    sampleRate = 48000
    p = pyaudio.PyAudio()
    stream = p.open(rate=sampleRate, channels=2, format=pyaudio.paFloat32, output=True)
    stream.write(array.array('f', data).tobytes())
    stream.close()
    p.terminate()
```

##shaderの書き方の簡単なルール
shadertoyのmusic shaderのメイン関数である　mainSound()と同じ関数名と引数名を使ってます。
なので、この部分にshadertoyのmusic shaderをコピペで使えます。
その他のuniform,out,main()等は変えないでください。

```
src = """
#version 300 es

out vec2 gain; 
uniform float sampleRate;    

vec2 mainSound( float time )
{
  return vec2(sin(6.2831*440.0*time)); ;
}

void main() {
  float time = float(gl_VertexID) / sampleRate;
  gain =mainSound(time); 
}
"""
```

これがshaderのソースになります。
これと、出力時間を引数にしてshader2data(duration, src)にいれるとサンプルデータが出力されます。
このサンプルデータとファイル名をwriteWav(filename, data)に入力するとwav fileができます。
readWav(filename)でサウンドが聞けます。
ファイルを作らないで、そのまま音を出したい時はplaySound(data)です。


```  
data = shader2data(2,src)
filename = "output.wav"
writeWav(filename, data)
readWav(filename)
playSound(data)
```

こんな感じで使います。

##最後に
たぶん需要はない気はしますが、せっかく作ったし忘備録も兼ねて記事を書きました。
ここにipython fileを置いておきました。
https://gist.github.com/gaziya/e5d40b0df1a65510af81b124c4ecf4cc