04-simple-midi-synth.py - Create a MIDI synthesizer as a custom class.
============================================================================================================================================


A more elaborate MIDI synthesizer built in a custom python class. This
makes easier to use it multiple times. In this example, two objects are
created, one for the played pitch and another playing one octave lower.

.. code-block:: python

    from pyo import *
    from random import random
    
    
    class Synth:
        def __init__(self, transpo=1, mul=1):
            # Transposition factor.
            self.transpo = Sig(transpo)
            # Receive midi notes, convert pitch to Hz and manage 10 voices of polyphony.
            self.note = Notein(poly=10, scale=1, first=0, last=127)
    
            # Handle pitch and velocity (Notein outputs normalized amplitude (0 -> 1)).
            self.pit = self.note["pitch"] * self.transpo
            self.amp = MidiAdsr(self.note["velocity"], attack=0.001, decay=0.1, sustain=0.7, release=1, mul=0.1,)
    
            # Anti-aliased stereo square waves, mixed from 10 streams to 1 stream
            # to avoid channel alternation on new notes.
            self.osc1 = LFO(self.pit, sharp=0.5, type=2, mul=self.amp).mix(1)
            self.osc2 = LFO(self.pit * 0.997, sharp=0.5, type=2, mul=self.amp).mix(1)
    
            # Stereo mix.
            self.mix = Mix([self.osc1, self.osc2], voices=2)
    
            # High frequencies damping.
            self.damp = ButLP(self.mix, freq=5000)
    
            # Moving notches, using two out-of-phase sine wave oscillators.
            self.lfo = Sine(0.2, phase=[random(), random()]).range(250, 4000)
            self.notch = ButBR(self.damp, self.lfo, mul=mul)
    
        def out(self):
            "Sends the synth's signal to the audio output and return the object itself."
            self.notch.out()
            return self
    
        def sig(self):
            "Returns the synth's signal for future processing."
            return self.notch
    
    
    s = Server()
    s.setMidiInputDevice(99)  # Open all input devices.
    s.boot()
    
    # Create the midi synth.
    a1 = Synth()
    
    # Send the synth's signal into a reverb processor.
    rev = STRev(a1.sig(), inpos=[0.1, 0.9], revtime=2, cutoff=4000, bal=0.15).out()
    
    # It's very easy to double the synth sound!
    # One octave lower and directly sent to the audio output.
    a2 = Synth(transpo=0.5, mul=0.7).out()
    
    s.gui(locals())

