from bge import logic
import aud
import random
import math

cont = bge.logic.getCurrentController()
driver = cont.owner

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 'init' in driver:
    
    ########### INCREMENT LAUGHING ENERGY ###########
    #this threshold indicates how many long the sound can be unchanged 
    static_sound_threshold = 10
    
    if driver['live_sound']:           
        
        def getAmplitude(streamName):
            import numpy 
            import analyse
            try:
                data = streamName.stream.read(streamName.chunk)
                samps = numpy.fromstring(data, dtype=numpy.int16)
                return analyse.loudness(samps)
            except: 
                return -22         
        
        driver['amplitude'] = getAmplitude(driver['micStream'])
        
        #keep track of the maximum and minimum amplitude that are coming from the microphone
        #these values can be used to scale the amplitude value to a range between 0 and 1
        if(driver['amplitude'] > driver['max_amp']):
            driver['max_amp'] = driver['amplitude']
        
        if(driver['amplitude'] < driver['min_amp']):
            driver['min_amp'] = driver['amplitude']
        
        #get the range of the amplitude
        amplitude_range = driver['max_amp'] - driver['min_amp']
        #division by 0 can not be allowed
        if amplitude_range == 0:
            amplitude_range = 1000
            
        #scale the amplitude to be in the range [0,1]
        scaled_amplitude = (driver['amplitude'] - driver['min_amp']) / amplitude_range
        
        #make sure the amplitude can not rise or fall faster than specified
        if scaled_amplitude > driver['previous_amplitude']:
            scaled_amplitude = min(scaled_amplitude, driver['previous_amplitude'] + driver['amp_rise'])
        else:
            scaled_amplitude = max(scaled_amplitude, driver['previous_amplitude'] - driver['amp_fall'])
        
        #update previous amplitude    
        driver['previous_amplitude'] = scaled_amplitude
        
        #set the current energy (tweeked value of amplitude to get better results)
        round = 100000
        power = 3
        driver['current_energy'] = math.ceil((  scaled_amplitude**power  )*round)/round
        
        #decrease max_amp and increase min_amp by a fixed amount to handle changes in volume
        amplitude_limits_fallof = 0.001
        driver['max_amp'] -= amplitude_limits_fallof
        driver['min_amp'] += amplitude_limits_fallof
        
    else:
        #increment the sound energy (only if the sound volume has changed)
        driver['current_energy'] = driver.channels['Driver'].scale.z
    
    #decrease laughing energy with a fixed amount. 
    #This is useful for decreasing the energy in case of silence.
    driver['laughing_energy'] = max(0, driver['laughing_energy'] - driver['fixed_decreasing_factor'])
    
    #decrease the laughing energy if it exceeds its maximum
    #if(driver['laughing_energy'] > driver['max_laughing_energy']):
    #    driver['laughing_energy'] -= driver['laughing_energy'] / 2
    driver['laughing_energy'] = min(driver['max_laughing_energy'], driver['laughing_energy'])
    
    #increment static_sound_count if the sound was the same in te last logic tic
    if driver['current_energy'] == driver['previous_energy']:
        driver['static_sound_count'] = min(static_sound_threshold, driver['static_sound_count']+1)
    else:
        driver['static_sound_count'] = 0
    
    #update previous_energy   
    driver['previous_energy'] = driver['current_energy']
    
    #only incremet sound energy if the static_sound_count hasn't exceeded a given threshold
    if driver['static_sound_count'] < static_sound_threshold:
        driver['laughing_energy'] += driver['current_energy'] * driver['energy_per_tic']     
    #else set the laughing energy to be zero (because the sound has ended)
    else:
        cont.activate('idle')
        driver['laughing_energy'] *= 0.9
                
    ########### ACTIVATE ANIMATIONS ##########
    random.shuffle(driver['animation_actions'])
    for act in driver['animation_actions']:          
            
        #activate an animation if it meets the requirements
        if driver['laughing_energy'] > act.energy and not act.active and act.energy > driver['active_animation'].energy * driver['animation_override_threshold'] and act.inactive_tics > driver['minimum_inactive_tics']:
            #deactivate the last action
            if not driver['last_played_action'] == None:
                driver['last_played_action'].active = False
                       
            driver.playAction(act.name, 1, act.length, layer=0, priority=0, blendin=10, play_mode=0, layer_weight=0.0, speed=random.uniform(driver['min_speed'], driver['max_speed']) )
            act.active = True
            #driver['laughing_energy'] -= act.energy/1.5
            driver['last_played_action'] = act
            driver['active_animation'] = act
            print("Animation: \"" + act.name + "\" playing...") 
            
        if act.active:            
            #deactivate the action if it has finished
            if(driver.getActionFrame(0) == act.length) :
                act.active = False
                driver.stopAction(0)
                print("Animation: \"" + act.name + "\" stopped playing...")
            else: #decrease the laughing_energy with the energy of the current animation
                driver['laughing_energy'] = max(0, driver['laughing_energy'] - act.energy * driver['energy_per_tic'] / 200)
            
            #set the number of inactive tics to 0 of the action is active...
            act.inactive_tics = 0   
        else:
            #increase the number of logic tics this action has been inactive
            act.inactive_tics += 1
        
        #set the active animation energy back to zero if the animation is not active anymore
        if act == driver['active_animation'] and not act.active:
            driver['active_animation'] = driver['null_action']
            
    ########### CONTROL FACIAL ANIMATION ##########
    z_scale = 0
    
    #jaw control
    jaw_energy = driver['jaw_smoother'].energy(driver['current_energy'])
    driver.channels['jaw_driver'].scale = [0,0,jaw_energy]
   
    #smile control
    smile_energy = driver['smile_smoother'].energy(driver['current_energy'])
    driver.channels['smile_driver'].scale = [0,0,smile_energy]
    
    #eye control
    #eye_energy = driver['eye_smoother'].energy(driver['current_energy'])
    #driver.channels['eye_driver'].scale = [0,0,1-eye_energy]
    if driver['no_blink_tics'] > driver['blink_interval']:
        driver.playAction("blink_eyes", 1, 10, layer=2, priority=0, blendin=0, play_mode=0, layer_weight=0.0 )
        driver['no_blink_tics'] = 0
        if driver.channels['eye_driver'].scale.z == 1.0:
            driver['blink_interval'] = random.uniform(driver['blink_interval_limits'][0], driver['blink_interval_limits'][1])
    else:
       driver['no_blink_tics'] += 1
        
    ########### CONTROL BREATHING ANIMATION ########## 
    #chest control
    chest_energy = driver['chest_smoother'].energy(driver['current_energy'])
    driver.channels['chest_driver'].scale = [0,0,chest_energy]
    
    #belly control
    belly_energy = driver['belly_smoother'].energy(driver['current_energy'])
    driver.channels['belly_driver'].scale = [0,0,belly_energy]
    
    #update the armature
    driver.update()
    
    
    ########### UPDATE AUDIO IF REQUESTED ########## 
    keyboard = bge.logic.keyboard
    JUST_ACTIVATED = bge.logic.KX_INPUT_JUST_ACTIVATED
    
    change_laugh = False
    if keyboard.events[bge.events.ONEKEY] == JUST_ACTIVATED:
        laugh_index = 0
        change_laugh = True
        driver['live_sound'] = False
    if keyboard.events[bge.events.TWOKEY] == JUST_ACTIVATED:
        laugh_index = 1
        change_laugh = True
        driver['live_sound'] = False
    if keyboard.events[bge.events.THREEKEY] == JUST_ACTIVATED:
        laugh_index = 2
        change_laugh = True
        driver['live_sound'] = False
    if keyboard.events[bge.events.FOURKEY] == JUST_ACTIVATED:
        laugh_index = 3
        change_laugh = True
        driver['live_sound'] = False
    if keyboard.events[bge.events.LKEY] == JUST_ACTIVATED:
        change_laugh = True
        driver['live_sound'] = True
        
    if change_laugh:
        #stop the previous sound
        if 'sound_handle' in driver:
            driver['sound_handle'].stop()
            driver.stopAction(1)
        
        #stop the microphone stream if it is active
        if 'micStream' in driver:
            del driver['micStream']
        
        if driver['live_sound']:
            driver['micStream'] = MicrophoneStream()
        else:           
            #load the sound
            driver['selected_sound'] = driver['sounds'][laugh_index]
            driver['sound_filepath'] = "C:/Users/Jordi/Documents/UU/THESIS/Characters/Male/"+driver['selected_sound']+".mp3"
            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...')
