import bpy
import aud
#import anim_action as aa
from bge import logic
from collections import deque

cont = bge.logic.getCurrentController()
driver = cont.owner
#set to true if you want to use live audio
#set to false if you want to use a specified audio file
driver['live_sound'] = True
driver['sounds'] = ["laugh1","laugh2","laugh3","laugh4"]
#driver['selected_sound'] = driver['sounds'][0]
#driver['sound_filepath'] = "C:/Users/Jordi/Documents/UU/THESIS/Characters/Male/"+driver['selected_sound']+".mp3"

class Action:
    
    def __init__(self, name="", length=0, energy=0):
        self.name = name
        self.length = length
        self.energy = energy
        self.active = False
        self.inactive_tics = 10000

#this class is used to smooth out the sound curve by 
#returning the average value of the past n sound energy values
class SmoothingQueue:
    
    def __init__(self, size=5):
        self.queue = deque([0] * size)
        self.size = size
        
    def update(self, current_energy):
        self.queue.append(current_energy)
        self.queue.popleft()
        
    def getAveragedValue(self):
        averaged_value = 0    
        for value in self.queue:
            averaged_value += value
        return averaged_value / self.size

#this class is used to smooth out the sound curve by 
#setting a maximum rise and fall parameter 
class Smoother:
    
    def __init__(self, rise=0.5, fall=0.2, minE=0.0, maxE=1.0, minE2 = -1, maxE2 = -1):
        
        self.rise = rise
        self.fall = fall
        
        self.minE = minE
        self.maxE = maxE
        self.range = max(0.001, maxE - minE) #prevent division by 0       
        
        self.stick = False
        if minE2 >= 0 and maxE2 > minE2:            
            self.useSticky = True
            self.minE2 = minE2
            self.maxE2 = maxE2
            self.range2 = max(0.001, maxE2 - minE2)
        else:
            self.useSticky = False
        
        self.e = 0
    
    #input:  actual sound energy
    #output: energy subject to a rise and fall parameters & scaled to [min, max]    
    def energy(self, sound_energy):
        
        #allow the energy to rise and fall only by a set amount
        if sound_energy > self.e:
            self.e = min(sound_energy, self.e + self.rise)
        else:
            self.e = max(sound_energy, self.e - self.fall)
        
        scaled = self.e
        if not self.stick:
            #scale energy to range [minE, maxE]
            if self.e < self.minE:
                scaled = 0.0
            elif self.e > self.maxE:
                scaled = 1.0
            else:
                scaled = (self.e - self.minE) / self.range
            
            #turn on sticky  mode if the scaled value gets close to 1    
            if scaled > 0.95 and self.useSticky:
                self.stick = True
        else:
            #scale energy to range [minE2, maxE2]
            if self.e < self.minE2:
                scaled = 0.0
            elif self.e > self.maxE2:
                scaled = 1.0
            else:
                scaled = (self.e - self.minE2) / self.range2
            
            #turn off sticky  mode if the scaled value gets close to 0 
            if scaled < 0.05:
                self.stick = False
                
        return scaled

#this class handles the microphone stream    
class MicrophoneStream:
	def __init__(self):
		import numpy
		import pyaudio
		import sys
		
		self.chunk = 512
		FORMAT = pyaudio.paInt16
		CHANNELS = 1
		RATE = 44100
		
		self.p = pyaudio.PyAudio()
		
		self.stream = self.p.open(format = FORMAT,
                channels = CHANNELS,
                rate = RATE,
                input = True,
                frames_per_buffer = self.chunk)

		print("Microphone input stream opened.")

	def __del__(self):
		self.stream.stop_stream()
		self.stream.close()
		self.p.terminate()
		
		print("Microphone input stream closed.")

if not 'init' in driver:   
    
    #live sound parameters
    
    #initialize the microphone stream
    driver['micStream'] = MicrophoneStream()
    
    #to keep track of the previous amplitude
    driver['previous_amplitude'] = 0.0
    #paramters to control the maximum rise and fall of the amplitude
    driver['amp_rise'] = 0.5
    driver['amp_fall'] = 0.2
    
    #create animation actions to be used in the simulation
    driver['animation_actions'] = [Action("lean_front1",22,energy=5),
                                   Action("lean_front2",42,energy=10),
                                   Action("lean_back1",22,energy=5),
                                   Action("lean_back2",36,energy=10),
                                   Action("clap_hands",46,energy=15),
                                   Action("hands_on_belly",73,energy=15),
                                   Action("point1",69,energy=20),
                                   Action("point2",78,energy=20), 
                                   Action("slap_leg",80,energy=25),                                  
                                   Action("stop_it",79,energy=30),
                                   Action("rofl",117,energy=40)]
    #last played action
    driver['last_played_action'] = None
    #the currently active animation (a null action with energy = 0
    driver['null_action'] = Action(energy=0)
    driver['active_animation'] = driver['null_action']
    
    #max and min animation speed
    driver['max_speed'] = 1.4
    driver['min_speed'] = 0.9
    
    #if the laughing energy + this threshold value exceed the current animation energy,
    #a new animation may start
    driver['animation_override_threshold'] = 2.0
    
    #the time in logic tics an action has to be inactive before playing again
    driver['minimum_inactive_tics'] = 10.0 * bge.logic.getLogicTicRate() #seconds * ticrate
    
    #set the fps to know how much to increment the sound energy
    driver['logicticrate'] = bge.logic.getLogicTicRate()
    
    #a factor used to scale some values up or down
    driver['energy_per_tic'] = 100 / driver['logicticrate']
    #the amount of energy that is taken of the current laughing energy every logic tic
    driver['fixed_decreasing_factor'] = driver['energy_per_tic'] / 10
    #the maximum laughing energy to prevent the energy from reaching very high numbers
    driver['max_laughing_energy'] = 60
    
    #the smoothers that return a smoother vesion of the sound curve
    driver['jaw_smoother'] = Smoother(rise = 0.03, fall = 0.01, minE = 0.0, maxE = 0.8)
    driver['smile_smoother'] = Smoother(rise = 0.03, fall = 0.001, minE = 0.0, maxE = 0.3)
    #driver['eye_smoother'] = Smoother(rise = 1.0, fall = 1.0, minE = 0.9, maxE = 1.0, minE2 = 0.0, maxE2 = 0.1)
    driver['chest_smoother'] = Smoother(rise = 0.2, fall = 0.1, minE = 0.0, maxE = 1.0)
    driver['belly_smoother'] = Smoother(rise = 0.1, fall = 0.05, minE = 0.0, maxE = 1.0)
    
    #eye blinking parameters
    #let there be 2 to 3.5 seconds between each blink
    driver['blink_interval_limits'] = (2 * driver['logicticrate'], 3.5 * driver['logicticrate'])
    driver['blink_interval'] = 2 * driver['logicticrate']
    driver['no_blink_tics'] = 0
    
    
    #if not driver['live_sound']:
    #create audio device to play the laughing sound
    logic.device = aud.device()
    '''
    #load the sound
    soundpath = logic.expandPath(driver['sound_filepath'])
    driver['sound'] = aud.Factory.file(soundpath)
    #play the sound and save the handle for later use
    driver['sound_handle'] = logic.device.play(driver['sound'])
    print('Sound playing...')
    
    #activate the driver
    #cont.activate("driver")
    driver.playAction(driver['selected_sound'], 1, 100000, layer=1, priority=0, blendin=0, play_mode=0, layer_weight=0.0)
    print('Driver activated...')
    '''
    #the driver is now initiated 
    driver['init'] = 1
    
else:
    True
    #replay the sound