class GrainFilter:
    def __init__(self, input, freq, q, type):
        self.filter = Biquad(input, freq, q, self.clipTypeToFilter(type))
        self.output = Interp(input, self.filter, interp=self.clipTypeToInterp(type))

    def stop(self):
        self.filter.stop()
        self.output.stop()

    def play(self):
        self.filter.play()
        self.output.play()

    def setType(self, x):
        self.filter.type = self.clipTypeToFilter(x)
        self.output.interp = self.clipTypeToInterp(x)

    def clipTypeToFilter(self, x):
        x = x - 1
        if x < 0:
            return 0
        return x

    def clipTypeToInterp(self, x):
        if x > 0:
            return 1
        return 0

class GrainSnd:
    def __init__(self, order, count, table, freq, start, dur, pan, ftype, ffr, fq, mul, env, nchnls, table_dur):
        self.trig = Select(count, order)
        self.freq = SampHold(freq, self.trig, 1.0)
        self.start_rng = SampHold(start, self.trig, 1.0)
        self.start = TrigXnoise(self.trig, mul=self.start_rng[1]-self.start_rng[0], add=self.start_rng[0])
        self.dur_rng = SampHold(dur, self.trig, 1.0)
        self.dur = TrigXnoise(self.trig, mul=self.dur_rng[1]-self.dur_rng[0], add=self.dur_rng[0])
        self.pan = TrigRand(self.trig, pan[0], pan[1])
        self.ffr = TrigRand(self.trig, ffr[0], ffr[1])
        self.fq = TrigRand(self.trig, fq[0], fq[1])
        self.mul = TrigXnoise(self.trig, mul=mul[1]-mul[0], add=mul[0])
        self.amp = TrigEnv(self.trig, env, self.dur, mul=self.mul*0.25)
        self.pointer = TrigEnv(self.trig, LinTable(), self.dur, mul=self.dur*self.freq/table_dur, add=self.start)
        self.s1 = Pointer(table, self.pointer, mul=self.amp)
        self.filter = GrainFilter(self.s1, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class Module(BaseModule):
    """
    "Stochastic granular synthesis based on a soundfile"
    
    Description

    This module implements a stochastic granular synthesis where grains
    coe from a given soundfile. The user has control over the range of 
    every generation parameters and envelopes.

    Sliders

        # Pitch Offset : 
            Base transposition, in semitones, applied to every grain
        # Pitch Range : 
            Range, in semitone, over which grain transpositions are chosen randomly
        # Speed Range : 
            Range, in second, over which grain start times are chosen randomly
        # Duration Range : 
            Range, in second, over which grain durations are chosen randomly
        # Start Range : 
            Range, in seconds, over which grain starting poistions (in the file) 
            are chosen randomly
        # Intensity Range : 
            Range, in dB, over which grain amplitudes are chosen randomly
        # Filter Freq Range :
            Range over which filter cutoff or center frequencies are chosen randomly
        # Filter Q Range :
            Range over which filter Qsare chosen
        # Pan Range : 
            Range over which grain spatial positions are chosen randomly
        # Density :
            Density of active grains, expressed as percent of the total generated grains
        # Global Seed :
            Root of stochatic generators. If 0, a new value is chosen randomly each
            time the performance starts. Otherwise, the same root is used every 
            performance, making the generated sequences the same every time.

    Graph Only
    
        # Overall Amplitude : 
            The amplitude curve applied on the total duration of the performance
        # Grain Envelope :
            Amplitude envelope of each grain

    Popups & Toggles
    
        # Pitch Scaling :
            Controls the possible values (as chords or scales) of the pitch generation
        # Pitch Algorithm :
            Noise distribution used by the pitch generator
        # Speed Algorithm :
            Noise distribution used by the speed generator
        # Duration Algorithm :
            Noise distribution used by the duration generator
        # Intensity Algorithm :
            Noise distribution used by the intensity generator
        # Filter Type : 
            Type of the grain's filter (None, Lowpass, Highpass, Bandpass, Bandstop)
        # Max Num of Grains :
            Regardless the speed generation and the duration of each grain, there will
            never be more overlapped grains than this value. The more CPU power you have,
            higher this value can be.

    """
    def __init__(self):
        BaseModule.__init__(self)
        self.table = self.addFilein("snd")
        self.table_dur = self.table.getDur()
        self.setGlobalSeed(int(self.seed.get()))
        self.num = int(self.numofvoices_value)
        self.scaledict =    {'Major':[0,4,7], 'Minor':[0,3,7], 'Seventh':[0,4,7,10], 'Minor 7':[0,3,7,10], 'Major 7':[0,4,7,11], 
                            'Minor 7 b5':[0,3,6,10], 'Diminished':[0,3,6], 'Diminished 7':[0,3,6,9], 'Minor 9':[0,3,7,10,14], 
                            'Major 9':[0,4,7,11,14], 'Ninth':[0,4,7,10,14], 'Minor 11':[0,3,7,10,14,17], 'Major 11':[0,4,7,11,14,18], 
                            'Eleventh':[0,4,7,10,14,18], 'Major 13':[0,4,7,11,14,18,21], 'Thirteenth':[0,4,7,10,14,18,21], 
                            'Serial':[0,1,2,3,4,5,6,7,8,9,10,11], 'Whole-tone': [0,2,4,6,8,10]}

        self.speedgen = XnoiseDur(min=self.speed_rng[0], max=self.speed_rng[1])
        self.new = Change(self.speedgen)
        self.newpass = Percent(self.new, self.density)
        self.count = VoiceManager(self.newpass)

        self.pitfloat = TrigXnoise(self.newpass, mul=self.pitch[1]-self.pitch[0], add=self.pitch_off+self.pitch[0])
        self.freq = MToT(self.pitfloat)
        self.pitint = TrigXnoiseMidi(self.newpass, mrange=(0, 120), mul=0.007874015748031496)
        self.pitch_range = self.pitch[1]-self.pitch[0]
        self.scl = Snap(self.pitint*self.pitch_range+self.pitch[0]+self.pitch_off, choice=self.scaledict["Serial"], scale=2)
        self.frtostack = Sig(self.freq)

        self.mul_rng = DBToA(self.dbamp_rng)

        self.stack = [GrainSnd(i, self.count, self.table, self.frtostack, self.start_rng, self.dur_rng, self.pan_rng, 
                               self.ftype_index, self.ffreq_rng, self.fq_rng, self.mul_rng, self.grainenv, self.nchnls, self.table_dur) for i in range(self.num)]
        self.stack_mix = Mix([gr.out for gr in self.stack], voices=self.nchnls)
        self.out = Sig(self.stack_mix, mul=self.env)

        self.count.setTriggers([obj.amp["trig"] for obj in self.stack])

        self.speedalgo(self.speedalgo_index, self.speedalgo_value)
        self.mulalgo(self.mulalgo_index, self.mulalgo_value)
        self.duralgo(self.duralgo_index, self.duralgo_value)
        self.pitalgo(self.pitalgo_index, self.pitalgo_value)
        self.genmethod(self.genmethod_index, self.genmethod_value)

    def assignX1X2(self, index, *args):
        for arg in args:
            arg.dist = index
            if index in [4,5,6]:
                arg.x1 = 8
            elif index == 7:
                arg.x1 = 2
            elif index == 8:
                arg.x1 = 0.5
                arg.x2 = 3.2
            elif index == 9:
                arg.x1 = 0.5
                arg.x2 = 1
            elif index == 10:
                arg.x1 = 3
                arg.x2 = 2
            elif index in [11,12]:
                arg.x1 = 1
                arg.x2 = .25

    def speedalgo(self, index, value):
        self.assignX1X2(index, self.speedgen)

    def pitalgo(self, index, value):
        self.assignX1X2(index, self.pitfloat, self.pitint)

    def duralgo(self, index, value):
        self.assignX1X2(index, *[obj.dur for obj in self.stack])

    def mulalgo(self, index, value):
        self.assignX1X2(index, *[obj.mul for obj in self.stack])

    def genmethod(self, index, value):
        if value == "All-over":
            self.pitfloat.play()
            self.freq.play()
            self.frtostack.value = self.freq
            self.pitint.stop()
            self.scl.stop()
        else:
            self.scl.choice = self.scaledict[value]
            self.pitint.play()
            self.scl.play()
            self.frtostack.value = self.scl
            self.pitfloat.stop()
            self.freq.stop()

    def seed_up(self, value):
        self.setGlobalSeed(int(value))

    def ftype(self, index, value):
        [obj.filter.setType(index) for obj in self.stack]

Interface = [   cfilein(name="snd", label="Audio"),
                cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue1"),
                cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(.1,1),(.4,.8),(.7,.3),(1,0)], table=True, col="orange1"),
                cslider(name="pitch_off", label="Pitch Offset", min=-12, max=12, init=0, rel="lin", res="int", unit="midi", col="red1"),
                crange(name="pitch", label="Pitch Range", min=12, max=115, init=[48,72], rel="lin", unit="midi", col="red2"),
                crange(name="speed_rng", label="Speed Range", min=0.0005, max=5, init=[.05, .25], rel="log", unit="sec", col="orange1"),
                crange(name="dur_rng", label="Duration Range", min=0.001, max=10, init=[.25,2], rel="log", unit="sec", col="orange2"),
                crange(name="start_rng", label="Start Range", min=0, max=1, init=[.1,.5], rel="lin", unit="%", col="purple1"),
                crange(name="dbamp_rng", label="Intensity Range", min=-90, max=0, init=[-18,-6], rel="lin", unit="dB", col="blue2"),
                crange(name="pan_rng", label="Pan Range", min=0, max=1, init=[0,1], rel="lin", unit="x", col="orange1"),
                cslider(name="density", label="Density", min=0, max=100, init=100, rel="lin", unit="%", col="orange3"),
                crange(name="ffreq_rng", label="Filter Freq Range", min=50, max=18000, init=[500, 5000], rel="log", unit="Hz", col="green1"),
                crange(name="fq_rng", label="Filter Q Range", min=0.5, max=50, init=[1, 5], rel="log", unit="Q", col="green2"),
                cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True),
                cpopup(name="genmethod", label="Pitch Scaling", value=['All-over', 'Serial', 'Major', 'Minor', 'Seventh', 'Minor 7', 
                        'Major 7', 'Minor 7 b5', 'Diminished', 'Diminished 7', 'Ninth', 'Major 9', 'Minor 9', 'Eleventh', 'Major 11', 
                        'Minor 11', 'Thirteenth', 'Major 13', 'Whole-tone'], init="Major 11", col="red1"),
                cpopup(name="pitalgo", label="Pitch Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 
                        'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], 
                        init="Uniform", col="red2"),
                cpopup(name="speedalgo", label="Speed Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 
                        'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], 
                        init="Uniform", col="orange1"),
                cpopup(name="duralgo", label="Duration Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 
                        'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], 
                        init="Uniform", col="orange2"),
                cpopup(name="mulalgo", label="Intensity Algorithm", value=['Uniform', 'Linear min', 'Linear max', 'Triangular', 
                        'Expon min', 'Expon max', 'Bi-exponential', 'Cauchy', 'Weibull', 'Gaussian', 'Poisson', 'Walker', 'Loopseg'], 
                        init="Uniform", col="blue2"),
                cpopup(name="ftype", label="Filter Type", value=['None', 'Lowpass', 'Highpass', 'Bandpass', 'Bandstop'],  init="None", col="green1"),
                cpopup(name="numofvoices", label="Max Num of Grains", value=['5','10','15','20','25','30','40','50','60'], init='10', rate="i")
            ]

