

# Kjetil S. Matheussen, eksamen mus2840 2003


# This is a script read by the pyext module inside pd.
# It does several things, but its main task is to read
# the file "generated.txt" and do the commands described
# in that file. The generated.txt file must have lines
# thats written in the following format:
# <time> <commandname> <variables>
# 'Time' can be any positive number or 0, and 'commandname'
# can be either r, pan, set_sound_array, set_pause, unset_pause,
# set_usefiddle, unset_usefiddle, microphone_on, microphone_off
# or p. For most of these commands, the script sends messages
# to a k_bank~ object loaded in pd. The k_bank~ external is written
# in C and handles recording and playing of sound.


try:
    import pyext
except:
    print "no pyext module"
    pass

import time,string,traceback


numbanks=20
numsounds=20
interval=0.05
pre_recordtime=2
num_fiddles=5
min_fiddlevol=0.2

def scale(x,x1,x2,y1,y2):
    return (y1+((x-x1)*(y2-y1))/(x2-x1))


class sound:
    def __init__(self,pyextclass,argnum):
        self.pyextclass=pyextclass
        self.argnum=argnum
        self.te=self.pyextclass.te;
        
    def record_start(self):
        self.pyextclass.send(0,"record",self.argnum)
    def record_stop(self):
        self.pyextclass.send(0,"stop",self.argnum)
    def record_progress2(self,time,start_time,end_time):
        self.pyextclass._send("recorderbar",scale(time,start_time,end_time,60,1000))
        if time>=end_time:
            self.record_stop()
        else:
            self.te.addEvent(time+interval,self.record_progress2,[start_time,end_time])
    def record_progress1(self,time,pre_time,start_time,end_time):
        self.pyextclass._send("recorderbar",scale(time,pre_time,start_time,0,60))
        if time>=start_time:
            self.record_start()
            self.record_progress2(time,start_time,end_time)
        else:
            self.te.addEvent(time+interval,self.record_progress1,[pre_time,start_time,end_time])

    def record(self,time,text,length):
        #print "record"
        self.pyextclass._send("recordertext",text);
        self.pyextclass._send("recorderbar",0);
        self.record_progress1(time,time,time+pre_recordtime,time+pre_recordtime+length)
    
    def set_sound_array(self,time,arrayname):
        self.pyextclass.send(0,"set",arrayname,self.argnum)


class bank:
    def __init__(self,pyextclass,argnum):
        self.pyextclass=pyextclass
        self.argnum=argnum
        self.te=self.pyextclass.te;
        self.usefiddle=0

    # The scheduler crashes for some reason when putting the pyexctlass as an argument to addEvent. This is a workaround.
    def timesend(self,time,arg,val):
        self.pyextclass._send(arg,val)

    def set_pause(self,time):
        self.pyextclass.send(self.argnum,"set_pause");
    def unset_pause(self,time):
        self.pyextclass.send(self.argnum,"unset_pause");
    def set_usefiddle(self,time):
        print "usefiddle"
        self.usefiddle=1
    def unset_usefiddle(self,time):
        print "usefiddleoff"
        self.usefiddle=0
        
    def play(self,time,soundnum,src=1,start=0,loopstart=0,loopend=1,repeat=1,playlen=0,backwards=0,pingpong=0):
        self.pyextclass.send(self.argnum,"play",soundnum,src,start,loopstart,loopend,repeat,playlen,backwards,pingpong)
    def set_src(self,time,val):
        if self.usefiddle==1:
            newsrc=val*scale(self.pyextclass.fiddles[self.argnum-10][0],200,1200,0.5,8.0)
            if newsrc<0.5: newsrc=0.8
            #print self.pyextclass.fiddles[self.argnum-9][0]
            #self.pyextclass.send(self.argnum,"set_src",scale(self.pyextclass.fiddles[self.argnum-10][0],200,1200,0.4,10.0));
            self.pyextclass.send(self.argnum,"set_src",newsrc)
            #self.pyextclass.send(self.argnum,"set_src",val)
        else:
            self.pyextclass.send(self.argnum,"set_src",val)
    def set_pan(self,time,val):
        if self.usefiddle==1:
            newpan=scale(self.pyextclass.fiddles[self.argnum-10][1],0,1,-1,1)
            self.pyextclass.send(self.argnum,"set_pan",newpan)
            #print self.pyextclass.fiddles[self.argnum-10][1]
            #self.pyextclass.send(self.argnum,"set_pan",val)
        else:
            self.pyextclass.send(self.argnum,"set_pan",val)

    def do_fiddle(self):
        if self.usefiddle==-1:
            newsrc=scale(self.pyextclass.fiddles[self.argnum-10][0],200,1200,0.5,8.0)
            if newsrc<0.5: newsrc=0.5
            self.pyextclass.send(self.argnum,"set_src",newsrc)
            newpan=scale(self.pyextclass.fiddles[self.argnum-10][1],0,1,-1,1)
            self.pyextclass.send(self.argnum,"set_pan",newpan)
     


# This is a scheduler. self.events is a sorted stack
class timeevents:
    def __init__(self):
        self.events=[]
    def reset(self):
        self.events=[]
        
    def addEvent(self,time,func,args):
        if len(self.events)==0:
            self.events=[[time,func,args]]
        elif self.events[-1][0]<time:
            self.events.append([time,func,args])
        else:
            for i in range(len(self.events)):
                if self.events[i][0]>=time:
                    self.events.insert(i,[time,func,args])
                    break

    def sweep_src(self,time,bank,src_start,src_end,start_time,end_time):
        if time>end_time: return
        bank.set_src(time,scale(time,start_time,end_time,src_start,src_end))
        self.addEvent(time+interval,self.sweep_src,[bank,src_start,src_end,start_time,end_time])
            
    def addSrcSweepEvent(self,time,bank,start,end,length):
        if length<=0:
            print "Error adding src sweep "+str(bank.argnum)+" "+str(length)
            pass
        else:
            self.addEvent(time,self.sweep_src,[bank,start,end,time,time+length])

    def sweep_pan(self,time,bank,pan_start,pan_end,start_time,end_time):
        if time>end_time: return
        bank.set_pan(time,scale(time,start_time,end_time,pan_start,pan_end))
        self.addEvent(time+interval,self.sweep_pan,[bank,pan_start,pan_end,start_time,end_time])
            
    def addPanSweepEvent(self,time,bank,start,end,length):
        if length<=0:
            print "Error adding pan sweep"
            pass
        else:
            self.addEvent(time,self.sweep_pan,[bank,start,end,time,time+length])

    # Could not be recursive because Python sometimes ran out of stack.
    def check(self,time):
        while len(self.events)>0 and time>=self.events[0][0]:
            event=self.events.pop(0)
            apply(event[1],[event[0]]+event[2])
            
    def printEvents(self):
        print self.events
        

class test:
    def __init__(self,ai):
        self.ai=ai
    def myprint(self,time,var):
        print var+self.ai+" "+str(time)

if __name__=="__main__":
    t2=test("ai")
    t=timeevents()
    t.addEvent(4,t2.myprint,["gakk"])
    t.printEvents()
    t.check(4)
    t.printEvents()
    t.addPanSweepEvent(5,bank(0,0),0.2,2.3,3)
    t.check(5)
    t.check(6)
    t.check(7)
    t.check(8)
    t.check(9)
    t.check(10)
    t.check(11)
                
else:        
    class eksamen(pyext._class):

        _inlets=3
        _outlets=1

        def __init__(self,*args):
            self.fiddles=map(lambda x:[0,0],range(num_fiddles))
            print self.fiddles
            self.te=timeevents()
            self.banks=map(lambda x:bank(self,x),range(numbanks))
            self.sounds=map(lambda x:sound(self,x),range(numsounds))
            #self.sounds[0].record(0,"Hello little bluebird, i know how you feel",3)
            self.isrunning=0
            #self.te.addEvent(3,self.banks[0].play,[10,2,5,6,7,8,9]);
            self.env=0
            self.envthreshold=0
            
        def send(self,num,*args):
            self._send("b"+str(num),args)

        def list_1(self,l):
            try:
                if l[2]>min_fiddlevol:
                    self.fiddles[int(l[0])-1]=[l[1],l[2]]
                for bank in self.banks:
                    bank.do_fiddle()
                if self.isrunning==1:
                    self.te.check(time.time()-self.starttime)
                    if l[0]==1:
                        self._send("agurk",l[1])
            except:
                traceback.print_exc()

        def start_1(self):
            try:
                #self.parseScript("/home/kjetil/mus2840/hello.txt")
                self.te.reset()
                self.parseScript("/home/kjetil/mus2840/generated.txt")
                self.starttime=time.time()
                self.isrunning=1
                #self.te.printEvents()
            except:
                traceback.print_exc()

        def checkthresh(self):
            if self.envthreshold<self.env:
                self._send("isthresh",1.0)
            else:
                self._send("isthresh",0.0)
                            
        def float_2(self,f):
            self.env=f
            self.checkthresh()
            
        def float_3(self,f):
            self.envthreshold=f
            self.checkthresh()
            
        def parseScript(self,filename):
            try:
                file=open(filename,"r")
            except:
                print "File "+filename+" not found."
                return

            def lrstrip(dasstring):
                dasstring=string.lstrip(dasstring)
                dasstring=string.rstrip(dasstring)
                return dasstring

            linenum=0
            while 1:
                line=""
                while line=="" or line=="\n" or line[0:1]=="#":
                    line=file.readline()
                    if line=="":
                        file.close()
                        return
                    line=lrstrip(line)
                ls=line.split()
                time=float(ls[0])
                command=lrstrip(ls[1])
                linenum+=1
                #print linenum
                if command=="r":
                    self.te.addEvent(time-pre_recordtime,
                                     self.sounds[int(ls[2])].record,
                                     [line[line.find('"')+1:-1],float(ls[3])])
                elif command=="src":
                    if len(ls)==6:
                        self.te.addSrcSweepEvent(time,self.banks[int(ls[2])],float(ls[3]),float(ls[4]),float(ls[5]))
                    else:
                        self.te.addEvent(time,self.banks[int(ls[2])].set_src,[float(ls[3])])
                elif command=="pan":
                    if len(ls)==6:
                        self.te.addPanSweepEvent(time,self.banks[int(ls[2])],float(ls[3]),float(ls[4]),float(ls[5]))
                    else:
                        self.te.addEvent(time,self.banks[int(ls[2])].set_pan,[float(ls[3])])

                elif command=="set_sound_array":
                    self.te.addEvent(time,
                                     self.sounds[int(ls[2])].set_sound_array,
                                     [lrstrip(ls[3])])
                                     
                elif command=="set_pause":
                    self.te.addEvent(time,
                                     self.banks[int(ls[2])].set_pause,[])
                elif command=="unset_pause":
                    self.te.addEvent(time,
                                     self.banks[int(ls[2])].unset_pause,[])
                elif command=="set_usefiddle":
                    self.te.addEvent(time,
                                     self.banks[int(ls[2])].set_usefiddle,[])
                elif command=="unset_usefiddle":
                    self.te.addEvent(time,
                                     self.banks[int(ls[2])].unset_usefiddle,[])
                elif command=="microphone_on":
                    self.te.addEvent(time,
                                     self.banks[0].timesend,
                                     ["mikrofon_direkte",1.0])
                elif command=="microphone_off":
                    self.te.addEvent(time,
                                     self.banks[0].timesend,
                                     ["mikrofon_direkte",0.0]
                                     )
                    
                elif command=="p":
                    src=1
                    start=0
                    loopstart=0
                    loopend=1
                    repeat=1
                    playlen=0
                    backwards=0
                    pingpong=0
                    
                    for arg in ls[4:]:
                        dassplit=arg.split("=")
                        if len(dassplit)==1:
                            dassplit=dassplit+["1"]
                        if dassplit[0]=="src": src=float(dassplit[1])
                        if dassplit[0]=="start": start=float(dassplit[1])
                        if dassplit[0]=="loopstart": loopstart=float(dassplit[1])
                        if dassplit[0]=="loopend": loopend=float(dassplit[1])
                        if dassplit[0]=="repeat": repeat=float(dassplit[1])
                        if dassplit[0]=="playlen": playlen=float(dassplit[1])
                        if dassplit[0]=="backwards": backwards=float(dassplit[1])
                        if dassplit[0]=="pingpong": pingpong=float(dassplit[1])
                        
                    self.te.addEvent(time,
                                     self.banks[int(ls[2])].play,
                                     [int(ls[3]),src,start,loopstart,loopend,repeat,playlen,backwards,pingpong])
                    
                else:
                    print "Unknown command "+command

                                     

        





    
