テラByteの時代にキロByte

shader又はdemosceneに関係する事

pygameでsound shaderを使い音を鳴らす

普段使っているsound shaderをpygameに組み込んでみた。
transform feedbackを使っているのでグラフィック部分は要らないのでOpenGLのcontextは短く書ける。
compute shaderでやる方法もあるので、そっちも試したい。
とりあえず、コードだけ。

from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
import pygame
from OpenGL.GL import *
from OpenGL.WGL import *
from ctypes import *
import pyaudio
import numpy

user32 = windll.user32

width,height = 340,240
pygame.init()
screen_display = pygame.display
display = screen_display.set_mode((width,height))

# Get OpenGL context
hWnd = user32.CreateWindowExA(0,0xC018,0,0,0,0,0,0,0,0,0,0)
hDC = 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)

# audio
chunk = 4096
rate = 48000
channels = 2

samples = (c_float * chunk * channels)()
chunkData=numpy.frombuffer(samples, dtype=numpy.float32)

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)

program = glCreateProgram()
shader = glCreateShader(GL_VERTEX_SHADER)
glsl='''
#version 300 es
precision highp float;
out vec2 gain; 
uniform float iFrameCount;

const float iChunk = {0:.1f};
const float iSampleRate = {1:.1f};
'''.format(chunk ,rate)+'''
vec2 mainSound( int samp, float time );
void main(){
    float time = (iChunk * iFrameCount + float(gl_VertexID)) / iSampleRate;
    gain = clamp(mainSound(int(iSampleRate),time), -1.0, 1.0);
}
'''

glsl += '''
vec2 mainSound(int samp,float time){
    return 0.5*vec2(sin(3.14*440.*time));
}
'''

glShaderSource(shader, glsl)
glCompileShader(shader)
if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE:
    raise RuntimeError(glGetShaderInfoLog(shader).decode())
glAttachShader(program, shader)
glDeleteShader(shader)
outs = cast((c_char_p*1)(b"gain"), POINTER(POINTER(c_char)))
glTransformFeedbackVaryings(program, 1, outs, GL_INTERLEAVED_ATTRIBS)
glLinkProgram(program)
glUseProgram(program)


p = pyaudio.PyAudio()
stream = p.open(
    format = pyaudio.paFloat32,
    channels = channels,
    rate = rate,
    frames_per_buffer = chunk,
    output=True,
    input=False
    )
stream.start_stream()

tick = 0
running = True
while running:
    glUniform1f(glGetUniformLocation(program, "iFrameCount"), tick)
    glEnable(GL_RASTERIZER_DISCARD)
    glBeginTransformFeedback(GL_POINTS)
    glDrawArrays(GL_POINTS, 0, chunk)
    glEndTransformFeedback()
    glDisable(GL_RASTERIZER_DISCARD)
    glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(samples), byref(samples))
    data=numpy.frombuffer(samples, dtype=numpy.float32)
    stream.write(data.tobytes())
    tick += 1
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

stream.stop_stream()
stream.close()

# OpenGL finsh
wglMakeCurrent(0, 0)
wglDeleteContext(hGLrc)
user32.ReleaseDC(hWnd, hDC)
user32.PostQuitMessage(0)
user32.DestroyWindow(hWnd)

pygame.quit()