テラByteの時代にキロByte

shader又はdemosceneに関係する事

shaderシンセ用の自作クラスを使ってみる。その3

キーボードを鍵盤に見立てて、音階を出せるようにしました。ADSRをツマミで調整できるようにしました。
ブログに書けるスクリプトは、このくらいかな。これ以上は、音色の調整の世界、個人の好みの問題になるので、書いてもスルーのみたい。
一回、ブログとかに露出すると、考えが整理されます。だいぶ、スッキリしました。あとは、地味に遊んでいきます。
glSynth.pyはここ

from glSynth import *
from PySide2.QtGui import QFont
from PySide2.QtWidgets import QFrame

class SynthDial(QFrame):
    def __init__(self, name, min, max, init=0):
        super().__init__()
        self.setFrameShape(QFrame.Panel)
        self.setLineWidth(2)
        self.layout = QVBoxLayout(self)
        self.value = 0
        self.min = min
        self.max = max
        font = QFont()
        font.setBold(True)
        font.setWeight(75)
        label = QLabel(name)
        label.setFont(font)
        self.layout.addWidget(label)
        self.dial = QDial()
        self.layout.addWidget(self.dial)
        self.valuelabel = QLabel()
        self.valuelabel.setFont(font)
        self.layout.addWidget(self.valuelabel)
        self.dial.setSliderPosition(init)
        self.valueChanged(init)
        self.dial.valueChanged.connect(self.valueChanged)

    def valueChanged(self, v): 
        self.value = (self.max - self.min) * v / 99 + self.min
        self.valuelabel.setText(format(self.value,'.1f'))

class MainWindow(GLSynth):
    def __init__(self, parent=None):
        super().__init__()
        self.note = 69
        self.setVolume(0.8)

    def shaderScript(self):
        return '''
uniform int note;
uniform vec4 env;

#define OSC(f) saw(f)

float saw(float f)
{
    return 1.-fract(f)*2.;
}

float square(float f)
{
  return sign(fract(f)-0.5);
}

float note2freq(int n)
{
    return 440.0*exp2((float(n)-69.0)/12.0);
}

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));
}

void main(){
    float freq = note2freq(note);
    gain = vec2( OSC(freq*iTime) * adsr(iTime, env, iGateTime));
}
'''

    def initializeWindow(self):
        self.attack =  SynthDial("attack",  0, 0.5, 0);  self.addWidget(self.attack);
        self.decay =   SynthDial("decay",   0,   1, 30); self.addWidget(self.decay);
        self.sustain = SynthDial("sustain", 0,   1, 50); self.addWidget(self.sustain);
        self.release = SynthDial("release", 0,   1, 30); self.addWidget(self.release);
    def setUniform(self):
        glUniform1i(glGetUniformLocation(self.program, "note"), self.note)
        glUniform4f(glGetUniformLocation(self.program, "env"), 
                                                            self.attack.value,
                                                            self.decay.value,
                                                            self.sustain.value,
                                                            self.release.value)
    
    def keyPressEvent(self, e):
        super().keyPressEvent(e)
        self.phase = 1<<32
        self.gate = 0
        note = {
            Qt.Key_A: 59,
            Qt.Key_S: 60,
            Qt.Key_E: 61,
            Qt.Key_D: 62,
            Qt.Key_R: 63,
            Qt.Key_F: 64,
            Qt.Key_G: 65,
            Qt.Key_Y: 66,
            Qt.Key_H: 67,
            Qt.Key_U: 68,
            Qt.Key_J: 69,
            Qt.Key_I: 70,
            Qt.Key_K: 71,
            Qt.Key_L: 72,
            }
        if e.key() in note.keys():
            self.note = note[e.key()]
            self.phase = 0
            self.gate = 1<<32
    
if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())