from math import *
from fractions import Fraction
from random import randint
import numpy as np
from tkinter import *
from tkinter import ttk

midiC2,midiC3,midiC4,midiC5,midiC6=36,48,60,72,84

freqSamp=44100

midiC2,midiC3,midiC4,midiC5,midiC6=36,48,60,72,84

midiET={36:65.41,38:73.42,40:82.41,41:87.31,
        43:98.00,45:110.00,47:123.47,
        48:130.81,50:146.83,52:164.81,53:174.61,
        53:196.00,57:220.00,59:246.94,
        60:261.63,62:293.66,64:329.63,65:349.23,
        67:392.00,69:440.00,71:493.88,
        72:523.25,74:587.33,76:659.26,77:698.46,
        79:783.99,81:880.00,83:987.77,
        84:1046.50}

tonalInt=[-12,-10,-8,-7,-5,-3,-1,0,2,4,5,7,9,11,12]

consonance,perfect,imperfect=[0,4,7,9],[0,7],[4,9]

speciesDur={1:44100,2:22050,3:11025}

up,down=1,-1
upIonian={0:2,1:1,2:2,3:1,4:1,5:2,6:1,7:2,8:1,9:2,10:1,11:1}
downIonian={0:1,1:1,2:2,3:1,4:2,5:1,6:1,7:2,8:1,9:2,10:1,11:2}

def cantusFirmus(centreC,numBars):
    
    def rollDie():
        
        return randint(1,6)-biasDie

    def voss(index):

        listDice[0]=rollDie()
        if index%2==0:listDice[1]=rollDie()
        if index%4==0:listDice[2]=rollDie()
        if index%8==0:listDice[3]=rollDie()
        if index%16==0:listDice[4]=rollDie()
        if index%32==0:listDice[5]=rollDie()
        if index%64==0:listDice[6]=rollDie()
        if index%128==0:listDice[7]=rollDie()
        
        return listDice

    def pinkNoise(listPink):
        
        pink=0
        for index in range(indexRange):
            pink=int(sum(voss(index)))

# filtering out non-tonal intervals

            while pink not in tonalInt:
                pink=int(sum(voss(index)))
                
            listPink.append(pink+centreC)
            
        listPink.append(centreC)

        return listPink

    numDice,biasDie,listDice,listPink=8,3.5,[0,0,0,0,0,0,0,0],[centreC]
    indexRange=numBars-2
    
    listPink=pinkNoise(listPink)
    
    midiCF=listPink
    
    return midiCF

def dynamicCF(midiCF):
    
    dynCF=[]
    
    for index in range(len(midiCF)-1):
        dir=midiCF[index+1]-midiCF[index]
        if dir>0:
            dyn='rise'
        elif dir<0:
            dyn='fall'
        else:
            dyn='same'
        
        dynCF=np.append(dynCF,dyn)
        
    return dynCF

def rules(indexCF,midiCF,dynCF,midiCpt,noteCpt):
    
    def motion():
        
        prevCpt=midiCpt[-1]        
        motCpt=noteCpt-prevCpt
        currDynCF=dynCF[indexCF]
        
        if currDynCF=='rise' and motCpt>0:
            return 'direct'
        elif currDynCF=='fall' and motCpt<0:
            return 'direct'
        elif currDynCF=='same' and motCpt==0:
            return 'direct'
        else:
            return 'indirect'
        
    currInt=(noteCpt-midiCF[indexCF])%12
    accept=False
        
    if motion()=='indirect':
        if currInt in consonant:
            midiCpt=np.append(midiCpt,noteCpt)
            accept=True
    elif currInt in imperfect:
        midiCpt=np.append(midiCpt,noteCpt)
        accept=True
            
    return (accept,midiCpt)
                
def midi2freq(midiList):
    
    freqList=[]
    
    for note in midiList:
        freqList.append(midiET[note])
            
    return freqList

def freqData(freqList,species):
    
    def padSaw(freqNote):
    
        numRpts=modSamp//floor(freqNote)
        sawData=np.linspace(1.0,-1.0,numRpts,False).astype(float)
        
        rpts=floor(freqNote)-1
        sawNote=sawData
        
        while rpts>0:
            sawNote=np.append(sawNote,sawData)
            rpts=rpts-1
        
        padding=np.zeros(modSamp-len(sawNote))
        sawNote=np.append((sawNote,padding))
        
        return sawNote
    
    modSamp=speciesDur[species]
    noteData=[]
    
    for note in freqList:
        noteData=np.append(notedata,padSaw(note))
        
    return noteData
        
def stereo(wavCF,wavCpt,stereoFile):
    
    stereoNotes=np.array((wavCF,wavCpt))
    stereoNotes=stereoNote.transpose()

    io.write(stereoFile+'.wav',freqSamp,stereoNotes)
    
    return None

def upDown(dirn,note):
    
    note12=note%12
    
    if dirn==up:
        return note+upIonian[note12]
    return note-downIonian[note12]

def addNote(cptList,note):

    cptList.append(note)
 
    return cptList

def delNote(cptList):
    
    del cptList[-1]
    
    return cptList

def display(CFList,cptList,species):
    
    plt.plot(CFList,'bo')
    
    xlst,lcpt=[],len(cptList)
    
    if species==1:
    
        plt.plot(cptList,'r.')
    
    elif species==2:
    
        idx=0
        while idx<lcpt/2:
            xlst.append(idx)
            idx=idx+0.5
        
        plt.plot(xlst,cptList,'r.')
    
    else:
    
        idx=0
        while idx<lcpt/4:
            xlst.append(idx)
            idx=idx+0.25
        
        plt.plot(xlst,cptList,'r.')
    
    plt.show()
 
