GRAIN_SAWTOOTH_WAVEFORM = SawTable(20)
GRAIN_SQUARE_WAVEFORM = SquareTable(30)
GRAIN_SINC_WAVEFORM = SincTable(freq=3.14159*16, windowed=True)
GRAIN_PULSAR_WAVEFORM = SincTable(freq=3.14159*4, windowed=True)
GRAIN_ENVELOPE_WAVEFORM = HannTable()
GRAIN_SINE_WAVEFORM = HarmTable([1,0,0,.1,0,.3,0,0,.2,0,0,.1,0,0,.15,0,.1,0,0,.2,0,.1,0,0,.1,0,0,.07,0,.05])

class Grain:
    def __init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv):
        self.trig = Select(count, order)
        self.freq = SampHold(freq, self.trig, 1.0)
        self.dur = TrigXnoise(self.trig, mul=dur[1]-dur[0], add=dur[0])
        self.det = TrigRand(self.trig, det[0], det[1])
        self.bri = TrigRand(self.trig, bri[0], bri[1])
        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*.25)

    def stop(self):
        for obj in self.__dict__.values():
            obj.stop()
        return self

    def play(self):
        for obj in self.__dict__.values():
            obj.play()
        return self

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 GrainFM(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.ind = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*20)
        self.s1 = FM(carrier=self.freq, ratio=self.det*.4+1, index=self.ind, 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 GrainSL(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.feed = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*.3)
        self.s1 = SineLoop(self.freq, feedback=self.feed, mul=self.amp)
        self.detune = self.det * .3 + 1
        self.s2 = SineLoop(self.freq*self.detune, feedback=self.feed, mul=self.amp)
        self.filter = GrainFilter(self.s1+self.s2, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class GrainBlit(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100)
        self.s1 = Osc(GRAIN_SINC_WAVEFORM, self.freq, mul=self.amp)
        self.detune = self.det * .3 + 1
        self.s2 = Osc(GRAIN_SINC_WAVEFORM, self.freq*self.detune, mul=self.amp)
        self.filt = Tone(self.s1+self.s2, self.cutoff, mul=4)
        self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class GrainCrossFM(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.ind = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*4)
        self.s1 = CrossFM(carrier=self.freq, ratio=self.det*.4+1, ind1=4, ind2=self.ind, 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 GrainSaw(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100)
        self.s1 = Osc(GRAIN_SAWTOOTH_WAVEFORM, self.freq, mul=self.amp)
        self.detune = self.det * .3 + 1
        self.s2 = Osc(GRAIN_SAWTOOTH_WAVEFORM, self.freq*self.detune, mul=self.amp)
        self.filt = Tone(self.s1+self.s2, self.cutoff, mul=.7)
        self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class GrainSquare(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100)
        self.s1 = Osc(GRAIN_SQUARE_WAVEFORM, self.freq, mul=self.amp)
        self.detune = self.det * .3 + 1
        self.s2 = Osc(GRAIN_SQUARE_WAVEFORM, self.freq*self.detune, mul=self.amp)
        self.filt = Tone(self.s1+self.s2, self.cutoff, mul=.7)
        self.filter = GrainFilter(self.filt, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class GrainPulsar(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.frac = TrigEnv(self.trig, brienv, self.dur, mul=self.bri)
        self.scl_frac = Scale(self.frac, 0, 1, 1, 0.05)
        self.s1 = Pulsar(GRAIN_PULSAR_WAVEFORM, env=GRAIN_ENVELOPE_WAVEFORM, freq=self.freq, frac=self.scl_frac, mul=self.amp)
        self.detune = self.det * .3 + 1
        self.s2 = Pulsar(GRAIN_PULSAR_WAVEFORM, env=GRAIN_ENVELOPE_WAVEFORM, freq=self.freq*self.detune, frac=self.scl_frac, mul=self.amp)
        self.filter = GrainFilter(self.s1+self.s2, freq=self.ffr, q=self.fq, type=ftype)
        self.out = SPan(self.filter.output, outs=nchnls, pan=self.pan)

class GrainAddSynth(Grain):
    def __init__(self, order, count, freq, det, bri, dur, pan, ftype, ffr, fq, mul, env, brienv, nchnls):
        Grain.__init__(self, order, count, freq, det, bri, dur, pan, ffr, fq, mul, env, brienv)
        self.cutoff = TrigEnv(self.trig, brienv, self.dur, mul=self.bri*10000, add=100)
        self.s1 = OscBank(GRAIN_SINE_WAVEFORM, freq=self.freq, spread=self.det*.25, slope=1, num=8, fjit=True, mul=self.amp)
        self.s2 = Tone(self.s1, self.cutoff, mul=4)
        self.filter = GrainFilter(self.s2, 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 with different instrument tone qualities"
    
    Description

    This module implements a stochastic granular synthesis. Different synthesis
    engine are available and 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 pitches 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
        # Brightness Range : 
            Range over which grain brightness factors (high frequency power) 
            are chosen randomly
        # Detune Range : 
            Range over which grain detune factors (frequency deviation between
            voices) are chosen randomly
        # Intensity Range : 
            Range, in dB, over which grain amplitudes are chosen randomly
        # Pan Range : 
            Range over which grain spatial positions 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
        # 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
        # Brightness Envelope :
            Brightness (high frequency power) envelope of each grain

    Popups & Toggles
    
        # Synth Type : 
            Choose between the different synthesis engines
        # 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.setGlobalSeed(int(self.seed.get()))
        self.num = int(self.numofvoices_value)
        self.current_synth = self.synth_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.stack_dict = {"FM": GrainFM, "Looped Sine": GrainSL, "Impulse train": GrainBlit, "AddSynth": GrainAddSynth,
                           "CrossFM": GrainCrossFM, "Sawtooth": GrainSaw, "Square": GrainSquare, "Pulsar": GrainPulsar}

        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 = MToF(self.pitfloat)
        self.pitint = TrigXnoiseMidi(self.newpass, 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=1)
        self.frtostack = Sig(self.freq)

        self.mul_rng = DBToA(self.dbamp_rng)
        self.det_rng = Sig(self.detune_rng, mul=0.01)

        for key in self.stack_dict.keys():
            stack = [self.stack_dict[key](i, self.count, self.frtostack, self.det_rng, self.bright_rng, self.dur_rng, 
                     self.pan_rng, self.ftype_index, self.ffreq_rng, self.fq_rng, self.mul_rng, self.grainenv, self.brightenv, self.nchnls).stop() for i in range(self.num)]
            stack_mix = Mix([gr.out for gr in stack], voices=self.nchnls).stop()
            self.stack_dict[key] = [stack, stack_mix]

        self.count.setTriggers([obj.amp["trig"] for obj in self.stack_dict[self.current_synth][0]])

        [obj.play() for obj in self.stack_dict[self.current_synth][0]]
        self.stack_dict[self.current_synth][1].play()
        self.out = Sig(self.stack_dict[self.current_synth][1], mul=self.env)

        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 synth(self, index, value):
        [obj.stop() for obj in self.stack_dict[self.current_synth][0]]
        [obj.play() for obj in self.stack_dict[value][0]]
        self.stack_dict[value][1].play()
        self.out.value = self.stack_dict[value][1]
        self.current_synth = value
        self.count.setTriggers([obj.amp["trig"] for obj in self.stack_dict[self.current_synth][0]])

    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):
        for key in self.stack_dict.keys():
            self.assignX1X2(index, *[obj.dur for obj in self.stack_dict[key][0]])

    def mulalgo(self, index, value):
        for key in self.stack_dict.keys():
            self.assignX1X2(index, *[obj.mul for obj in self.stack_dict[key][0]])

    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):
        for key in self.stack_dict.keys():
            [obj.filter.setType(index) for obj in self.stack_dict[key][0]]

Interface = [   cgraph(name="env", label="Overall Amplitude", func=[(0,1),(1,1)], col="blue"),
                cgraph(name="grainenv", label="Grain Envelope", func=[(0,0),(.01,1),(.2,.4),(.5,.3),(1,0)], table=True, col="orange"),
                cgraph(name="brightenv", label="Brightness Envelope", func=[(0,1),(.2,.2),(1,0)], table=True, col="orange4"),
                cslider(name="pitch_off", label="Pitch Offset", min=-12, max=12, init=0, rel="lin", res="int", unit="midi", col="red"),
                crange(name="pitch", label="Pitch Range", min=12, max=115, init=[48,84], rel="lin", unit="midi", col="red4"),
                crange(name="speed_rng", label="Speed Range", min=.005, max=5, init=[.05, .25], rel="log", unit="sec", col="orange"),
                crange(name="dur_rng", label="Duration Range", min=.005, max=20, init=[.5,5], rel="log", unit="sec", col="orange2"),
                crange(name="bright_rng", label="Brightness Range", min=0, max=1, init=[0,.1], rel="lin", unit="x", col="blue2"),
                crange(name="detune_rng", label="Detune Range", min=0.01, max=100, init=[0.01, 0.05], rel="log", unit="x", col="blue3"),
                crange(name="dbamp_rng", label="Intensity Range", min=-90, max=0, init=[-18,-6], rel="lin", unit="dB", col="purple4"),
                crange(name="pan_rng", label="Pan Range", min=0, max=1, init=[0,1], rel="lin", unit="x", col="purple1"),
                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="density", label="Density", min=0, max=100, init=100, rel="lin", unit="%", col="orange"),
                cslider(name="seed", label="Global seed", min=0, max=5000, init=0, rel="lin", res="int", unit="x", up=True),
                cpopup(name="synth", label="Synth Type", value=['FM', 'Looped Sine', 'Impulse train', 'CrossFM', 'Sawtooth', 
                        'Square', 'Pulsar', 'AddSynth'], init="FM", col="blue1"),
                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="red"),
                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="red4"),
                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="orange"),
                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="purple4"),
                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")
            ]
