再)リアルタイム music shader (used transform feedback)
compute shaderで書いた奴を違うPCで試したら、使えなかった。どうなってるのだろう? なので、transform feedbackで書いた奴を載せておきます。きっと、こっちの方が安定してるかな。 そのうちにcompute shaderの方は調べてみます。
from PySide2.QtWidgets import (QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, 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) # +++++++++++++++++++++++++++++++++++++++ # +++++++++++++++++++++++++++++++++++++ # music shader shadertoy = ''' #define BPM 140. #define A (15./BPM) float adsr(float t, vec4 e, float s) { return max(0.0, min(1.0, t/max(0.0001, e.x)) - min((1.0 - s) ,max(0.0, t - e.x)*(1.0 - s)/max(0.0001, e.y)) - max(0.0, t - e.z)*s/max(0.0001, e.w)); } float square(float f) { return sign(fract(f)-0.5); } float noise(float t) { return square(2763.*t*sin(t*8000.)); //return fract(sin(t*45678.0)*1234.5)*2.0-1.0; } float kick(float t){ return cos(315. * t - 10. * exp( -50. * t )+0.3)*adsr(t,vec4(0.0, 0.3, 0.0, 0.0), 0.0); +0.2*square(50.*t)* adsr(t,vec4(0.0, 0.05, 0.0, 0.0), 0.0); } // http://www.tsurishi.info/chiptune-neiro-edit-drum/ float snare(float t) { return square(3063.*t*sin(t*8000.)) * adsr(t,vec4(0.005, 0.08, 0.0, 0.0), 0.0); } // http://www.tsurishi.info/chiptune-neiro-edit-hihat/ float closeHihat(float t) { return square(2763.*t*sin(t*8500.)) * adsr(t,vec4(0.0, 0.03, 0.0, 0.0), 0.0); } float openHihat(float t) { return square(2763.*t*sin(t*8300.)) * adsr(t,vec4(0.0, 0.05, 0.03, 0.03), 0.5); } // https://qiita.com/gaziya5/items/e58f8c1fce3f3f227ca7 float sequence(int s,float t) { float n =mod(t,A); for(int i=0;i<16;i++){ if((s>>(int(t/A)-i)%16&1)==1)break; n+=A; } return n; } // http://www.spotlight-jp.com/matsutake/mt/images/ArmenBreakTab.jpg vec2 mainSound( float time ) { int i = int(floor(time/(A*16.)))&3; int velocity = int[](0x3030,0x3030,0x3030,0x3030)[i]>>(int(floor(time/A))&15)&1; float vol = 0.2 *(1.0+1.5*float(velocity)); return vec2( 0.0 +0.4 * kick(sequence( int[](0x0c05,0x0c05,0x0c05,0x0c0c)[i],time)) +0.3 * snare(sequence( int[](0x9290,0x9290,0x4290,0x4292)[i],time)) +vol * closeHihat(sequence(int[](0x5555,0x5555,0x5555,0x5155)[i],time)) +vol * openHihat(sequence( int[](0x0000,0x0000,0x0000,0x0400)[i],time)) ); } ''' src = ''' #version 330 uniform float phase; uniform float bufferSize; uniform float sampleRate; out vec2 gain; ''' + shadertoy + ''' void main(){ float time = (bufferSize * phase + float(gl_VertexID)) / sampleRate; gain=mainSound(time); } ''' class MainWindow(QHLayoutWidget): def __init__(self, parent=None): super().__init__() self.sampleRate = 44100 self.channelCount = 2 self.bufferSize = 1024 self.phase = 0 self.initializeWindow() self.initializeAudio() self.initializeGL() self.timer = QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(10) def initializeWindow(self): audioLayout = QHLayoutWidget(); self.layout.addWidget(audioLayout) backwardButton = QPushButton(); audioLayout.addWidget(backwardButton) self.ctrlButton = QPushButton(); audioLayout.addWidget(self.ctrlButton) audioLayout.layout.setContentsMargins(0, 0, 0, 0) backwardButton.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward)) self.ctrlButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPause)) backwardButton.clicked.connect(self.backwardButton_clicked) self.ctrlButton.clicked.connect(self.ctrlButton_clicked) 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() self.audioOutput.setVolume(1) def initializeGL(self): self.gl = QGLWidget() self.gl.makeCurrent() self.samples = (ctypes.c_float*self.bufferSize*self.channelCount)() self.variable = { "sampleRate": "sampleRate", "gain": "gain", "bufferSize": "bufferSize", "phase": "phase" } self.program = glCreateProgram() shader = glCreateShader(GL_VERTEX_SHADER) glShaderSource(shader, src) 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)(self.variable["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) 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) glUniform1f(glGetUniformLocation(self.program, self.variable["sampleRate"]), self.sampleRate) glUniform1f(glGetUniformLocation(self.program, self.variable["bufferSize"]), self.bufferSize) def update(self): if self.audioOutput.bytesFree() > 0: glUniform1f(glGetUniformLocation(self.program, self.variable["phase"]), self.phase) 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 ctrlButton_clicked(self): if self.timer.isActive(): self.timer.stop() self.ctrlButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) else: self.timer.start(10) self.ctrlButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPause)) def backwardButton_clicked(self): self.phase = 0 def keyPressEvent(self, e): if e.key() == Qt.Key_Escape: self.close() if __name__ == '__main__': import sys app = QApplication(sys.argv) main_window = MainWindow() main_window.show() sys.exit(app.exec_())