shaderシンセ用のクラスを作る
やはり、毎回コピペで作るのは、いかがなもので、クラスを作りました。
しかし、世間でいうライブラリーとは言い難い。自分が使いやすいように作りました。
受け入れられるかは、置いておいて載せてみます。
とりあえず、ファイル名は glSynth.py
と言う事にしてください。これは単体でも動きます。
次回から、これを使った奴を書きます。
compute shaderで書いた奴とtransform feedbackで書いた奴を書きました。環境によっては使えない可能性が、あるので2つ書きました。
どちらかを glSynth.py
として使ってください。
<追記>
compute shaderの場合GPUによりけりの仕様と思われるが、1回の処理をvec2又はvec4の単位でするものが、あるようだ。なので、dummyのvec2を置いて対応しました。とりあえず、不具合はなさそうです。でも、tansform feedbackを使ったclassの方が無難な気はします。
transform feedbackで書いた奴
from PySide2.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QDial, QLabel, QStyle) from PySide2.QtCore import Qt, QTimer, QIODevice from PySide2.QtMultimedia import QAudioFormat, QAudioOutput from PySide2.QtOpenGL import QGLWidget from OpenGL.GL import * import ctypes import numpy import array # ++++++++++++++++++++++++++++++++++++ # https://qiita.com/gaziya5/items/e3cdb0251c01e76cbb05 class QHLayoutWidget(QWidget): def __init__(self): super().__init__() self.layout = QHBoxLayout() self.setLayout(self.layout) def addWidget(self,w): self.layout.addWidget(w) class QVLayoutWidget(QWidget): def __init__(self): super().__init__() self.layout = QVBoxLayout() self.setLayout(self.layout) def addWidget(self,w): self.layout.addWidget(w) # +++++++++++++++++++++++++++++++++++++ class GLSynth(QHLayoutWidget): def __init__(self, parent=None): super().__init__() self.sampleRate = 44100 self.channelCount = 2 self.bufferSize = 1024 self.phase = 0 self.shaderHead=''' #version 330 uniform float bufferSize; uniform float sampleRate; uniform float phase; uniform float gate; out vec2 gain; #define iTime ((bufferSize * phase + float(gl_VertexID)) / sampleRate) #define iGateTime (bufferSize * gate / sampleRate) ''' self.initializeWindow() self.initializeAudio() self.initializeGL() self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(10) def shaderScript(self): return ''' void main(){ gain = vec2( sin(440.0 * 6.2831 * iTime) * step(iTime, iGateTime) ); } ''' def setUniform(self): pass def initializeWindow(self): pass def initializeAudio(self): format = QAudioFormat() format.setSampleRate(self.sampleRate) format.setChannelCount(self.channelCount) format.setSampleSize(16) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.audioOutput = QAudioOutput(format, self) self.stream = self.audioOutput.start() def initializeGL(self): self.gl = QGLWidget() self.gl.makeCurrent() self.phase = 10000 self.gate = 0 self.program = glCreateProgram() shader = glCreateShader(GL_VERTEX_SHADER) glShaderSource(shader, self.shaderHead+self.shaderScript()) glCompileShader(shader) if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE: self.error = glGetShaderInfoLog(shader).decode() glAttachShader(self.program, shader) outs = ctypes.cast( (ctypes.c_char_p*1)("gain".encode('utf-8')), ctypes.POINTER(ctypes.POINTER(ctypes.c_char))) glTransformFeedbackVaryings(self.program, 1, outs, GL_INTERLEAVED_ATTRIBS) glLinkProgram(self.program) glUseProgram(self.program) glUniform1f(glGetUniformLocation(self.program, "sampleRate"), self.sampleRate) glUniform1f(glGetUniformLocation(self.program, "bufferSize"), self.bufferSize) glUniform1f(glGetUniformLocation(self.program, "phase"), self.phase) glUniform1f(glGetUniformLocation(self.program, "gate"), self.gate) self.samples = (ctypes.c_float*self.bufferSize*self.channelCount)() vbo = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo) glBufferData(GL_ARRAY_BUFFER, sizeof(self.samples), None, GL_STATIC_DRAW) glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo) def update(self): if self.audioOutput.bytesFree() > 0: glUseProgram(self.program) glUniform1f(glGetUniformLocation(self.program, "phase"), self.phase) glUniform1f(glGetUniformLocation(self.program, "gate"), self.gate) self.setUniform() self.phase += 1 glEnable(GL_RASTERIZER_DISCARD) glBeginTransformFeedback(GL_POINTS) glDrawArrays(GL_POINTS, 0, self.bufferSize) glEndTransformFeedback() glDisable(GL_RASTERIZER_DISCARD) glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(self.samples), ctypes.byref(self.samples)) d = numpy.frombuffer(self.samples, dtype=numpy.float32) d = numpy.maximum(d, -1) d = numpy.minimum(d, 1) self.stream.write(array.array('h', d*32767).tobytes()) def setVolume(self, v): self.audioOutput.setVolume(v) def keyPressEvent(self, e): if e.isAutoRepeat(): return if e.key() == Qt.Key_Escape: self.close() self.phase = 0 self.gate = 1<<32 def keyReleaseEvent(self, e): if e.isAutoRepeat(): return self.gate = self.phase if __name__ == '__main__': import sys app = QApplication(sys.argv) main_window = GLSynth() main_window.show() sys.exit(app.exec_())
compute shaderで書いた奴
from PySide2.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QDial, QLabel, QStyle) from PySide2.QtCore import Qt, QTimer, QIODevice from PySide2.QtMultimedia import QAudioFormat, QAudioOutput from PySide2.QtOpenGL import QGLWidget from OpenGL.GL import * import ctypes import numpy import array # ++++++++++++++++++++++++++++++++++++ # https://qiita.com/gaziya5/items/e3cdb0251c01e76cbb05 class QHLayoutWidget(QWidget): def __init__(self): super().__init__() self.layout = QHBoxLayout() self.setLayout(self.layout) def addWidget(self,w): self.layout.addWidget(w) class QVLayoutWidget(QWidget): def __init__(self): super().__init__() self.layout = QVBoxLayout() self.setLayout(self.layout) def addWidget(self,w): self.layout.addWidget(w) # +++++++++++++++++++++++++++++++++++++ class GLSynth(QHLayoutWidget): def __init__(self, parent=None): super().__init__() self.sampleRate = 44100 self.channelCount = 2 self.bufferSize = 1024 self.phase = 0 self.shaderHead=''' #version 430 struct Sound{ vec2 buf; vec2 dummy; }; layout(std430, binding=7) buffer particles{ Sound ins[]; }sound; layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in; uniform float bufferSize; uniform float sampleRate; uniform float phase; uniform float gate; #define gain sound.ins[gl_GlobalInvocationID.x].buf #define iTime ((bufferSize * phase + float(gl_GlobalInvocationID.x)) / sampleRate) #define iGateTime (bufferSize * gate / sampleRate) ''' self.initializeWindow() self.initializeAudio() self.initializeGL() self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(10) def shaderScript(self): return ''' void main(){ gain = vec2( sin(440.0 * 6.2831 * iTime) * step(iTime, iGateTime) ); } ''' def setUniform(self): pass def initializeWindow(self): pass def initializeAudio(self): format = QAudioFormat() format.setSampleRate(self.sampleRate) format.setChannelCount(self.channelCount) format.setSampleSize(16) format.setCodec("audio/pcm") format.setByteOrder(QAudioFormat.LittleEndian) format.setSampleType(QAudioFormat.SignedInt) self.audioOutput = QAudioOutput(format, self) self.stream = self.audioOutput.start() def initializeGL(self): self.gl = QGLWidget() self.gl.makeCurrent() self.phase = 10000 self.gate = 0 self.program = glCreateProgram() shader = glCreateShader(GL_COMPUTE_SHADER) glShaderSource(shader, self.shaderHead+self.shaderScript()) glCompileShader(shader) if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE: raise RuntimeError(glGetShaderInfoLog(shader).decode()) glAttachShader(self.program, shader) glLinkProgram(self.program) glUseProgram(self.program) glUniform1f(glGetUniformLocation(self.program, "sampleRate"), self.sampleRate) glUniform1f(glGetUniformLocation(self.program, "bufferSize"), self.bufferSize) glUniform1f(glGetUniformLocation(self.program, "phase"), self.phase) glUniform1f(glGetUniformLocation(self.program, "gate"), self.gate) self.samples = (ctypes.c_float*self.bufferSize*4)() vbo = glGenBuffers(1) glBindBuffer(GL_SHADER_STORAGE_BUFFER, vbo) glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(self.samples), None, GL_STATIC_DRAW ) glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 7, vbo) def update(self): if self.audioOutput.bytesFree() > 0: glUseProgram(self.program) glUniform1f(glGetUniformLocation(self.program, "phase"), self.phase) glUniform1f(glGetUniformLocation(self.program, "gate"), self.gate) self.setUniform() self.phase += 1 glDispatchCompute(self.bufferSize // 128, 1, 1) glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(self.samples), ctypes.byref(self.samples)) d = numpy.frombuffer(self.samples, dtype=numpy.float32) d = numpy.maximum(d, -1) d = numpy.minimum(d, 1) d = d.reshape((-1,2))[::2] d= d.flatten() self.stream.write(array.array('h', d*32767).tobytes()) def setVolume(self, v): self.audioOutput.setVolume(v) def keyPressEvent(self, e): if e.isAutoRepeat(): return if e.key() == Qt.Key_Escape: self.close() self.phase = 0 self.gate = 1<<32 def keyReleaseEvent(self, e): if e.isAutoRepeat(): return self.gate = self.phase if __name__ == '__main__': import sys app = QApplication(sys.argv) main_window = GLSynth() main_window.show() sys.exit(app.exec_())