class Module(BaseModule):
    """
    "Filter audo-modulated by an extracted feature of its input sound"

    Description

    This module implements a variable filter, whose frequency is driven by the
    outcome of an analysis on its input signal. Available analysis continuous amplitude, 
    centroid, fundamental frequency and zero-crossing. The filter type can be lowpass,
    highpass, bandpass or bandstop.

    Sliders
    
        # Freq Range : 
            Lowest and highest filter frequencies, corresponding to the lowest and 
            highest values of the analyzer output.
        # Filter Q : 
            Q factor (inverse of bandwidth) of the filter.
        # Num of Filters : 
            Number of filters (with identical parameters) applied in serie.
        # Dry / Wet : 
            Mix between the original and the filtered signals
    
    Graph Only
    
        # Overall Amplitude : 
            The amplitude curve applied on the total duration of the performance

    Popups & Toggles
    
        # Analysis Type : 
            Choose between the four possible analyzers (amplitude, centroid,
            pitch or zero-crossing).
        # Filter Type : 
            Choose the type of the filter (lowpass, highpass, bandpass or bandstop).
        # Balance :
            Compression mode. Off, balanced with a fixed signal
            or balanced with the input source.
        # Polyphony Voices : 
            Number of voices played simultaneously (polyphony), 
            only available at initialization time
        # Polyphony Spread : 
            Pitch variation between voices (chorus), 
            only available at initialization time

    """
    def __init__(self):
        BaseModule.__init__(self)
        self.snd = self.addSampler("snd")

        self.ampFollow = Follower(self.snd, freq=20).stop()
        self.ampScale = Scale(self.ampFollow, outmin=self.frange[0], outmax=self.frange[1]).stop()

        self.centroidFollow = Centroid(self.snd, size=512).stop()
        self.centroidScale = Scale(self.centroidFollow, inmin=20, inmax=15000, outmin=self.frange[0], outmax=self.frange[1]).stop()

        self.pitchFollow = Yin(self.snd, tolerance=0.20, minfreq=40, maxfreq=2000, cutoff=1000, winsize=2048).stop()
        self.pitchScale = Scale(self.pitchFollow, inmin=40, inmax=2000, outmin=self.frange[0], outmax=self.frange[1]).stop()

        self.zcrossFollow = ZCross(self.snd, thresh=0.001).stop()
        self.zcrossScale = Scale(self.zcrossFollow, outmin=self.frange[0], outmax=self.frange[1]).stop()

        self.filterFreq = Clip(self.centroidScale, min=self.frange[0], max=self.frange[1])
        self.filterFreqPort = Port(self.filterFreq, 0.02, 0.02)
        self.filter = Biquadx(self.snd, freq=self.filterFreqPort, q=self.q, type=self.filttype_index, stages=int(self.stages.get()))

        self.proc = Interp(self.snd, self.filter, self.drywet, mul=self.env)

        self.osc = Sine(10000,mul=.1)
        self.balanced = Balance(self.proc, self.osc, freq=10)
        self.out = Interp(self.proc, self.balanced)

        self.lastAnalysis = ""
        self.currentAnalysis = ""
        self.analysisLaterCall = CallAfter(self.analysisTurnoff, time=0.1)

        #INIT
        self.balance(self.balance_index, self.balance_value)
        self.analysis(self.analysis_index, self.analysis_value)

    def balance(self, index, value):
       if index == 0:
           self.out.interp  = 0
       elif index == 1:
           self.out.interp  = 1
           self.balanced.input2 = self.osc
       elif index == 2:
           self.out.interp = 1
           self.balanced.input2 = self.snd

    def analysis(self, index, value):
        self.lastAnalysis = self.currentAnalysis
        self.currentAnalysis = value
        if index == 0:
            self.ampFollow.play()
            self.ampScale.play()
            self.filterFreq.setInput(self.ampScale, 0.05)
        elif index == 1:
            self.centroidFollow.play()
            self.centroidScale.play()
            self.filterFreq.setInput(self.centroidScale, 0.05)
        elif index == 2:
            self.pitchFollow.play()
            self.pitchScale.play()
            self.filterFreq.setInput(self.pitchScale, 0.05)
        elif index == 2:
            self.zcrossFollow.play()
            self.zcrossScale.play()
            self.filterFreq.setInput(self.zcrossScale, 0.05)
        self.analysisLaterCall.play()

    def analysisTurnoff(self):
        if self.lastAnalysis == self.currentAnalysis:
            return
        if self.lastAnalysis == "Amplitude":
            self.ampFollow.stop()
            self.ampScale.stop()
        elif self.lastAnalysis == "Centroid":
            self.centroidFollow.stop()
            self.centroidScale.stop()
        elif self.lastAnalysis == "Pitch":
            self.pitchFollow.stop()
            self.pitchScale.stop()
        elif self.lastAnalysis == "Zero-Crossing":
            self.zcrossFollow.stop()
            self.zcrossScale.stop()

    def filttype(self, index, value):
        self.filter.type = index

    def stages_up(self, value):
        self.filter.stages = value

Interface = [
    csampler(name="snd"),
    cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"),
    crange(name='frange', label='Freq Range', min=20.0, max=18000.0, init=[100.0, 5000.0], rel='log', unit='Hz', col='green'),
    cslider(name="q", label="Filter Q", min=0.5, max=5, init=1, unit="Q", col="green2"), 
    cslider(name="stages", label="# of Filters", min=1, max=4, init=2, unit="x", res="int", up=True), 
    cslider(name="drywet", label="Dry / Wet", min=0, max=1, init=1, rel="lin", unit="x", col="blue1"),
    cpopup(name="analysis", label="Analysis Type", init="Amplitude", value=["Amplitude", "Centroid", "Pitch", "Zero-Crossing"], col="orange"),
    cpopup(name="filttype", label="Filter Type", init="Lowpass", col="green3", value=["Lowpass","Highpass","Bandpass","Bandstop"]),
    cpopup(name="balance", label = "Balance", init= "Off", col="blue", value=["Off","Compress", "Source"]),
    cpoly()
]
