from math import *
from fractions import Fraction
import numpy as np
import scipy.io.wavfile as io
import scipy.signal as sig
import scipy.fftpack as fft
import matplotlib.pyplot as plt
from tkinter import *
from tkinter import ttk
import tkinter.colorchooser as col

freqSamp=44100

allowFreqs=[22050,11025,7350,4410,3675,3150,2450,2205,
                1575,1470,1225,1050,882,735,630,525,490,
                450,441,350,315,294,245,225,210,180,175,
                150,147,126,105,98,90,75,70,63,60,50,45,42,35,30]

allowMods=[30,25,21,18,15,14,10,9,7,6,5,3,2,1]

# oscillators and mixer

def VCO(freqNote=441,mix=0x7F7F7F):
    
    def oscillators():
    
        def Saw():
            numRpts=freqSamp//freqNote
            sawData=np.linspace(1.0,-1.0,numRpts,False).astype(float)
        
            rpts=freqNote-1
            sawNote=sawData
        
            while rpts>0:
                sawNote=np.append(sawNote,sawData)
                rpts=rpts-1
        
            return sawNote

        def SquTri():
            halfRpts=freqSamp//(2*freqNote)
            firstHalf=np.linspace(1.0,1.0,halfRpts,False).astype(float)
            lastHalf=np.linspace(-1.0,-1.0,halfRpts,False).astype(float)
            squData=np.append(firstHalf,lastHalf)
        
            firstHalf=np.linspace(-1.0,1.0,halfRpts,False).astype(float)
            lastHalf=np.linspace(1.0,-1.0,halfRpts,False).astype(float)
            triData=np.append(firstHalf,lastHalf)
        
            rpts=freqNote-1
            squNote=squData
            triNote=triData
        
            while rpts>0:
                squNote=np.append(squNote,squData)
                triNote=np.append(triNote,triData)
                rpts=rpts-1
            
            return (squNote,triNote)
    
        sawNote=Saw()
        (squNote,triNote)=SquTri()
    
        return (sawNote,squNote,triNote)
    
    def mixer(oscs):
        
        (saw,squ,tri)=oscs
        blue=mix%256
        green=(mix//256)%256
        red=(mix//65536)
        norm=red+green+blue
        
        return (red*saw+green*squ+blue*tri)/norm
    
    return mixer(oscillators())

# filter

def VCF(soundData,cutoff=5000,order=4):
    
# initialising filter

    buffer=np.zeros(freqSamp)

    Nyquist=freqSamp/2
    Wn=cutoff/Nyquist

    (b,a)=sig.butter(order,Wn)
    zi=sig.lfilter_zi(b,a)

# applying filter

    (buffer,_)=sig.lfilter(b,a,soundData,zi=zi*soundData[0])
    
    return buffer

# amplitude envelope generator

def AEG(soundData,envelope=(10,10,0.5,10)):

    onems=freqSamp/1000

# ADSR envelope

    (att,dec,sus,rel)=envelope
    
    attack=floor(att * onems)
    decay=floor(dec * onems)
    sustain=sus
    release=floor(rel * onems)


    susDur=freqSamp-(attack+decay+release)

# 4 linspace for envelope

    A=np.linspace(0,1,attack,False).astype(float)
    D=np.linspace(1,sustain,decay,False).astype(float)
    S=np.linspace(sustain,sustain,susDur,False).astype(float)
    R=np.linspace(sustain,0,release,False).astype(float)

    AD=np.append(A,D)
    ADS=np.append(AD,S)
    ADSR=np.append(ADS,R)

    return ADSR*soundData

#   LFO modulation

def LFO (soundData,LFOrate=5,depth=0.5):
    
    sampInt=freqSamp//LFOrate
    
    sampSine=np.empty(sampInt).astype(float)

    twopi=2*np.pi
    sampSine[0]=0.0
    time=1

    while time<sampInt:
        sampSine[time]=np.sin(twopi*time/sampInt)
        time=time+1
    
# can be replaced by two calls to np.linspace

    sineWav=sampSine
    cnt=LFOrate-1

    while cnt>0:
        sineWav=np.append(sineWav,sampSine)
        cnt=cnt-1

    mod=sineWav*depth

# tremolo

    return soundData*(1+mod)

# amplifier

def VCA(soundData,volume=1):
    
    return soundData*volume

# display and output

def display(soundData,fileName='out'):
    
    plt.ion()
    plt.figure(1)
    plt.subplot(211)
    plt.plot(soundData[0:freqSamp])
    plt.title('time')
    plt.grid()
    plt.subplot(212)
    plt.semilogx()
    plt.title('freq')
    plt.grid()
    plt.plot(abs(fft.rfft(soundData))/32768)
    
    io.write(fileName+'.wav',freqSamp,soundData)
    
    return None