LoginSignup
8
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-04-25

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

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8