05-control-change.py - Adding control changes to our MIDI synthesizer class.
============================================================================================================================================


This example adds three continuous MIDI controllers to the previous one
(04-simple-midi-synth.py). The first one applied the pitch bend to the
`transpo` argument, which multiply the pitches from the Notein object.
The second is a continuous controller used to change the cutoff frequency
(`hfdamp` argument) of the lowpass filter. The last one sets the speed
(`lfofreq` argument) of moving notche oscillations in the spectrum.

.. code-block:: python

    from pyo import *
    from random import random
    
    
    class Synth:
        # Added some arguments that will be controlled with MIDI controllers.
        def __init__(self, transpo=1, hfdamp=5000, lfofreq=0.2, 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, use argument `hfdamp` to allow MIDI control.
            self.damp = ButLP(self.mix, freq=hfdamp)
    
            # Moving notches, use argument `lfofreq` to allow MIDI control.
            self.lfo = Sine(lfofreq, 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()
    
    ### Setup the MIDI controllers.
    
    # Bendin can output MIDI note values or transposition factors (scale=1).
    # A value of 2 to `brange` argument means that the bending range will be
    # 2 semitones above and below the current note.
    transpo = Bendin(brange=2, scale=1)
    
    # High frequency damping mapped to controller number 1.
    hfdamp = Midictl(ctlnumber=1, minscale=500, maxscale=10000, init=5000)
    
    # Frequency of the LFO applied to the speed of the moving notches.
    lfofreq = Midictl(ctlnumber=2, minscale=0.1, maxscale=8, init=0.2)
    
    
    # Create the midi synth.
    a1 = Synth(transpo, hfdamp, lfofreq)
    
    # 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()
    
    s.gui(locals())

