music shaderを色々試しているが、webGLで開発するには、小回りが効かなくて何かと大変になってきた。
そこで、pythonを使ってGLSL shaderからwav fileを作るスクリプトを作ってみました。
pythonを絡めさせれば、細かい設定を調整するには楽になると思ってます。
##必要なもの
pyopengl,pyaudioをpipでインストールしてください。
うまくいかない場合は
http://www.lfd.uci.edu/~gohlke/pythonlibs/
ここからパッケージをdownloadすれば大抵はうまくいきます。
##スクリプトはこれ
ここの説明は大変なのでライブラリーと思って使ってください。
サンプリングレートは48000に決めうちしています。
違うのが良ければ適当に直してください。
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