#/*************************************************************************************
#*                                                                                    *
#*    Copyright (c) 2018 Adriano Gominho rui.gominho@gmail.com                        *
#*                                                                                    *
#*    This particular program is free software: you can redistribute it and/or        *
#*    modify it under the terms of the GNU General Public License as published by     *
#*    the Free Software Foundation, either version 3 of the License, or               *
#*    (at your option) any later version.                                             *
#*                                                                                    *
#*    This program is distributed in the hope that it will be useful, but WITHOUT     *
#*    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS   *
#*    FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more          *
#*    details.                                                                        *
#*                                                                                    *
#*    You should have received a copy of the GNU General Public License along with    *
#*    this program. If not, see <http://www.gnu.org/licenses/>.                       *
#*                                                                                    *
#*************************************************************************************/

import bpy
import os
import mathutils

# Definitions ------------------------------------------------------------------------------------------

eventsFileName = 'surrenderDrumMidiCapture-short.txt'              # File containing midi events - relative path from blender file!

channelFilter = 1                               # Channel to use for events (-1 for no filtering)

framesBeforeFirstEvent = 100

# End of definitions - no changes beyond this point ----------------------------------------------------

# Indexes of matrix elements
I_M_COMPONENT            = 0     
I_M_NOTE                 = 1     # The note this component will react to. This can be 0 for a) alternate components;
I_M_OBJECTTYPE           = 2
I_M_OBJECTNAME           = 3
I_M_BONEGROUP            = 4     # the bone group (for armatures) to be keyframed 
I_M_ABOUTTOHITPOSE       = 5     # the pose (for armatures) that should be set and keyframed. For other objects it holds the transformation to be keyframed (as per KEYFRAMETYPE)
I_M_HITPOSE              = 6     # the pose (for armatures) that should be set and keyframed. For other objects it holds the transformation to be keyframed (as per KEYFRAMETYPE)
I_M_RESTPOSE             = 7     # the pose (for armatures) that should be set and keyframed. For other objects it holds the transformation to be keyframed (as per KEYFRAMETYPE)
I_M_REBOUNDPOSE          = 8     # the pose (for armatures) that should be set and keyframed. For other objects it holds the transformation to be keyframed (as per KEYFRAMETYPE)
I_M_TIMERESTTOABOUTTOHIT = 9     # time in seconds between the initial rest and the AboutToHitPose
I_M_TIMEABOUTTOHIT       = 10    # time in seconds before the HIT where the ABoutToHitPose should be set
I_M_TIMETOREBOUND        = 11    # time in seconds after the HIT where the ReboundPose should be set
I_M_TIMERETURNTOREST     = 12    # time in seconds after the REBOUND where the final RestPose should be set
I_M_KEYFRAMETYPE         = 13    # type of keyframe to use (BUILTIN_KSI_* on blender-git/blender/release/scripts/startup/keyingsets_builtins.py)
I_M_LASTHITFRAME         = 14    # buffer to hold the frame of the lsat HIT
I_M_BUFFERREBOUNDFRAME   = 15    # buffer to hold the frame where the last REBOUND should be set
I_M_BUFFERRESTFRAME      = 16    # buffer to hold the frame where the last REST should be set
I_M_ALTERNATECOMPONENT   = 17    # indicates the alternate component to use for severe superimposing hits


OBJECTTYPE_ARMATURE = 0
OBJECTTYPE_RIGIDOBJECT = 1

# closedHat     12
# kick          13
# snare         14
# floortom      15
# midtom        16
# hitom         17
# openhat       18
# ridecymbal    19
# sizzlecymbal  20
# crashcymbal   21

matrix = (
#Component              Note        ObjectType,                  Object               Bone Group      AboutToHitPose                  HitPose                        RestPose                     ReboundPose                     TimeRestToAboutToHit  TimeAboutToHit      TimeToRebound      TimeReturnToRest  KeyFrameType            LastHitFrame,   BufferReboundFrame, BufferRestFrame,    AlternateComponent
['DrummerHead',         0,          OBJECTTYPE_ARMATURE,         'Drummer',           'HeadAndNeck',  'HeadBangNeutral',              'HeadBangForward',            'HeadBangNeutral',           'HeadBangNeutral',               0.000,                0.200,              0.200,             1.000,            'BUILTIN_KSI_LocRot',   0,              0,                  0,                  ''],
['DrummerUpperBody',    0,          OBJECTTYPE_ARMATURE,         'Drummer',           'UpperBody',    'Sitting-relaxed',              'Sitting-relaxed',            'Sitting-relaxed',           'Sitting-relaxed',               0.000,                0.000,              0.000,             0.000,            'BUILTIN_KSI_LocRot',   0,              0,                  0,                  ''],
['DrummerLowerBody',    0,          OBJECTTYPE_ARMATURE,         'Drummer',           'LowerBody',    'Sitting-relaxed',              'Sitting-relaxed',            'Sitting-relaxed',           'Sitting-relaxed',               0.000,                0.000,              0.000,             0.000,            'BUILTIN_KSI_LocRot',   0,              0,                  0,                  ''],
['KickDrumFoot',        13,         OBJECTTYPE_ARMATURE,         'Drummer',           'RightLeg',     'RightLeg-Rest',                'RightLeg-Hit-KickDrum',      'RightLeg-Rest',             'RightLeg-Rest',                 0.100,                0.100,              0.100,             0.100,            'BUILTIN_KSI_LocRot',   0,              0,                  0,                  ''],
['KickDrumPedal',       13,         OBJECTTYPE_RIGIDOBJECT,      'KickDrumPedal',     '',             [0.380, -0.102, -0.276],        [0.429, -0.269, -0.285],      [0.380, -0.102, -0.276],     [0.380, -0.102, -0.276],         0.100,                0.100,              0.100,             0.100,            'Rotation',             0,              0,                  0,                  ''],
['LeftArmSnare',        14,         OBJECTTYPE_ARMATURE,         'Drummer',           'LeftArm',      'LeftArm-ATH-Snare',            'LeftArm-H-Snare',            'Sitting-relaxed',           'LeftArm-Rebound-Snare',         0.400,                0.100,              0.100,             0.300,            'Rotation',             0,              0,                  0,                  'RightArmSnare'],
['RightArmSnare',       0,          OBJECTTYPE_ARMATURE,         'Drummer',           'RightArm',     'RightArm-ATH-Snare',           'RightArm-H-Snare',           'RightArm-ATH-Snare',        'RightArm-ATH-Snare',            0.400,                0.100,              0.100,             0.300,            'Rotation',             0,              0,                  0,                  ''],
['RightArmHiHat',       12,         OBJECTTYPE_ARMATURE,         'Drummer',           'RightArm',     'RightArm-ATH-HiHat',           'RightArm-H-HiHat',           'Sitting-relaxed',           'RightArm-ATH-HiHat',            0.400,                0.100,              0.100,             0.300,            'Rotation',             0,              0,                  0,                  ''],
['RightArmHiHat',       18,         OBJECTTYPE_ARMATURE,         'Drummer',           'RightArm',     'RightArm-ATH-HiHat',           'RightArm-H-HiHat',           'Sitting-relaxed',           'RightArm-ATH-HiHat',            0.400,                0.100,              0.100,             0.300,            'Rotation',             0,              0,                  0,                  ''],
['LeftFootHiHatClosed', 12,         OBJECTTYPE_ARMATURE,         'Drummer',           'LeftLeg',      'LeftLeg-Rest',                 'LeftLeg-H-HiHat',            'LeftLeg-Rest',              'LeftLeg-Rest',                  0.400,                0.200,              0.200,             1.000,            'Rotation',             0,              0,                  0,                  ''],
['HiHatPedal',          12,         OBJECTTYPE_RIGIDOBJECT,      'HiHatPedal',        '',             [0.423, 0.508, 0.0328],         [0.413, 0.215, 0.0293],       [0.423, 0.508, 0.0328],      [0.423, 0.508, 0.0328],          0.400,                0.200,              0.200,             1.000,            'Rotation',             0,              0,                  0,                  '']
)

# Indexes of event details on event line
I_EV_TICKS = 0
I_EV_SECONDS = 1
I_EV_SOURCE = 2
I_EV_CHANNEL = 3
I_EV_TYPE = 4
I_EV_DATA1 = 5
I_EV_DATA2 = 6

# Function that returns the index of the pose marker with the supplied name on the supplied armature (-1 if not found)
def PoseMarkerID(armature, poseMarkerName):
    index = -1
    for pose in armature.pose_library.pose_markers:
        index += 1
        if pose.name == poseMarkerName:
            break
    else:
        index = -1  
    return index

# Function that creates KeyFrames for the active object
def createKeyFrame(frameNumber, keyframeType):
    bpy.data.scenes['Scene'].frame_current = frameNumber
    bpy.ops.anim.keyframe_insert(type = keyframeType)    

# Function that returns the event details
def getEventDetails(eventLine):
    return eventLine.split(',')

# Returns the minimum and maximum number of seconds on events (disregards channelFilter)
def getRangeSeconds(events):
    rangeSeconds = [-1, 0]
    for eventLine in events:
        eventDetails = getEventDetails(eventLine)
        if rangeSeconds[0] == -1:
            rangeSeconds[0] = float(eventDetails[I_EV_SECONDS])
        else:
            rangeSeconds[0] = min(rangeSeconds[0], float(eventDetails[I_EV_SECONDS]))
            
        rangeSeconds[1] = max(rangeSeconds[1], float(eventDetails[I_EV_SECONDS]))
        
    return rangeSeconds

# Returns a list of all ocurring notes
def getNotes(events):
    notes = []
    for i, eventLine in enumerate(events):
        eventDetails = getEventDetails(eventLine)
        if eventDetails[I_EV_TYPE] == 'Note on' and (channelFilter == -1 or int(eventDetails[I_EV_CHANNEL]) == channelFilter):
            if  not int(eventDetails[I_EV_DATA1]) in notes:
                notes.append(int(eventDetails[I_EV_DATA1]))
            
    return notes

# Removes all animation data from every object referenced in matrix
def removeAnimationFromMatrixObjects(matrix):
    
    # Get a list of object names referenced in matrix
    objectNames = []
    for matrixLine in matrix:
        objectName = matrixLine[I_M_OBJECTNAME]
        if not objectName in objectNames:
            objectNames.append(objectName)
        
    for objectName in objectNames:
        object = bpy.data.objects[objectName]
        object.animation_data_clear()

    bpy.ops.screen.frame_jump(end = False) # forces the timeline to be refreshed


# Returns the frame that corresponds to event at seconds
def getFrameForSecond(second, firstEventSecond):
    # print("Debug getFrameForSecond: second = ", end ="")
    # print(second, end = "")
    # print(", firstEventSecond = ", end = "")
    # print(firstEventSecond, end = "")
    # print(", bps = " + str(bpy.data.scenes['Scene'].render.fps) + ", returning " + str(1 + framesBeforeFirstEvent + int((second - firstEventSecond) * bpy.data.scenes['Scene'].render.fps)))
    return 1 + framesBeforeFirstEvent + int((second - firstEventSecond) * bpy.data.scenes['Scene'].render.fps)
        
DEF_SUPERPOSITION_NONE  = 0
DEF_SUPERPOSITION_RTR   = 1
DEF_SUPERPOSITION_ATH   = 2
DEF_SUPERPOSITION_HIT   = 3       
# returns the superposition type between these frames and the previous (buffered) event         
def getSuperpositionType(buffer_rest, buffer_rebound, rest0, ath, hit):  
    
    if buffer_rest == 0 or buffer_rebound == 0:
        return DEF_SUPERPOSITION_NONE
    elif rest0 > buffer_rest:
        return DEF_SUPERPOSITION_NONE
    elif rest0 < buffer_rest and ath >= buffer_rest:
        return DEF_SUPERPOSITION_RTR
    elif ath < buffer_rest and hit >= buffer_rebound:
        return DEF_SUPERPOSITION_ATH
    elif hit <= buffer_rebound:
        return DEF_SUPERPOSITION_HIT
    else:
        print()
        print("Reached superposition final branch, buffer_rest=" + str(buffer_rest) + ", buffer_rebound=" + str(buffer_rebound) + ", rest0=" + str(rest0) + ", ath=" + str(ath) + ", hit=" + str(hit))
        return DEF_SUPERPOSITION_NONE

def finalizeBufferedEvents(matrixEntry):

    buffer_rest         = matrixEntry[I_M_BUFFERRESTFRAME] 
    buffer_rebound      = matrixEntry[I_M_BUFFERREBOUNDFRAME] 

    # Get a reference to the object
    object = bpy.data.objects[matrixEntry[I_M_OBJECTNAME]]

    print("Finalizing buffered events for " + matrixEntry[I_M_COMPONENT])

    if buffer_rest > 0:
        # set buffer_rest
        setKeyframes(object, matrixEntry, I_M_RESTPOSE, buffer_rest)

    if buffer_rebound > 0:        
        # set buffer_rebound
        setKeyframes(object, matrixEntry, I_M_REBOUNDPOSE, buffer_rebound)


def setKeyframesForSuperposition(eventDetails, matrixEntry): #, buffer_rest, buffer_rebound, rest0, ath, hit, rebound, rest):

    # Get the key frames for this event
    initialRestFrame    = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) - matrixEntry[I_M_TIMEABOUTTOHIT] - matrixEntry[I_M_TIMERESTTOABOUTTOHIT], rangeSeconds[0])
    aboutToHitFrame     = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) - matrixEntry[I_M_TIMEABOUTTOHIT], rangeSeconds[0])
    hitFrame            = getFrameForSecond(float(eventDetails[I_EV_SECONDS]), rangeSeconds[0])
    reboundFrame        = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) + matrixEntry[I_M_TIMETOREBOUND], rangeSeconds[0])
    finalRestFrame      = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) + matrixEntry[I_M_TIMETOREBOUND] + matrixEntry[I_M_TIMERETURNTOREST], rangeSeconds[0])

    # Get the buffered frames from the last event
    lastHit             = matrixEntry[I_M_LASTHITFRAME] 
    buffer_rest         = matrixEntry[I_M_BUFFERRESTFRAME] 
    buffer_rebound      = matrixEntry[I_M_BUFFERREBOUNDFRAME] 
                
    # Get the superposition type between this event and the previous (buffered) event
    superpositionType = getSuperpositionType(buffer_rest, buffer_rebound, initialRestFrame, aboutToHitFrame, hitFrame)
    
    # If there is severe superposition and there's an alternate component that could handle this event
    if superpositionType == DEF_SUPERPOSITION_HIT and len(matrixEntry[I_M_ALTERNATECOMPONENT]):
        alternateMatrixEntry, found = getMatrixEntry(matrixEntry[I_M_ALTERNATECOMPONENT])
        if found:
            # determine what would be the superposition with the alternate component

            alternateLastHit        = alternateMatrixEntry[I_M_LASTHITFRAME] 
            alternateBuffer_rest    = alternateMatrixEntry[I_M_BUFFERRESTFRAME] 
            alternateBuffer_rebound = alternateMatrixEntry[I_M_BUFFERREBOUNDFRAME] 
            
            alternateInitialRestFrame = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) - alternateMatrixEntry[I_M_TIMEABOUTTOHIT] - alternateMatrixEntry[I_M_TIMERESTTOABOUTTOHIT], rangeSeconds[0])
            alternateAboutToHitFrame  = getFrameForSecond(float(eventDetails[I_EV_SECONDS]) - alternateMatrixEntry[I_M_TIMEABOUTTOHIT], rangeSeconds[0])
            alternateHitFrame         = getFrameForSecond(float(eventDetails[I_EV_SECONDS]), rangeSeconds[0])

            alternateSuperpositionType = getSuperpositionType(alternateBuffer_rest, alternateBuffer_rebound, alternateInitialRestFrame, alternateAboutToHitFrame, alternateHitFrame)
            if (alternateSuperpositionType < superpositionType):
                # Lets use the alternate component for this event
                
                print("Using component" + alternateMatrixEntry[I_M_COMPONENT] + " as alternate for " + matrixEntry[I_M_COMPONENT])
                
                # switch the matrix entry to use
                matrixEntry = alternateMatrixEntry
                
                superpositionType = alternateSuperpositionType
                
                # recalculate relevant frames
                initialRestFrame = alternateInitialRestFrame
                aboutToHitFrame = alternateAboutToHitFrame
                hitFrame = alternateHitFrame
                
                lastHit         = alternateLastHit
                buffer_rest     = alternateBuffer_rest
                buffer_rebound  = alternateBuffer_rebound
            
    # Get a reference to the object
    object = bpy.data.objects[matrixEntry[I_M_OBJECTNAME]]
    
    print("Triggering component" + matrixEntry[I_M_COMPONENT] + " with superpositionType = " + str(superpositionType) + " hitFrame = " + str(hitFrame))
    
    print("initialRestFrame = " + str(initialRestFrame) + ", aboutToHitFrame = " + str(aboutToHitFrame) + ", hitFrame = " + str(hitFrame) + ", reboundFrame = " + str(reboundFrame) + ", finalRestFrame = " + str(finalRestFrame) + ", buffer_rest = " + str(buffer_rest) + ", buffer_rebound = " + str(buffer_rebound))

    # Set keyframes according to the superposition type and buffer rebound and rest
    if superpositionType == DEF_SUPERPOSITION_NONE:
        
        # set buffer_rest
        if (buffer_rest) > 0:
            setKeyframes(object, matrixEntry, I_M_RESTPOSE, buffer_rest)
        
        # set buffer_rebound
        if (buffer_rebound) > 0:
            setKeyframes(object, matrixEntry, I_M_REBOUNDPOSE, buffer_rebound)
        
        # set rest0
        setKeyframes(object, matrixEntry, I_M_RESTPOSE, initialRestFrame)
        
        # set ath
        setKeyframes(object, matrixEntry, I_M_ABOUTTOHITPOSE, aboutToHitFrame)
        
        # set hit
        setKeyframes(object, matrixEntry, I_M_HITPOSE, hitFrame)
        
        # buffer last hit
        matrixEntry[I_M_LASTHITFRAME] = hitFrame
        
        # buffer rebound
        matrixEntry[I_M_BUFFERREBOUNDFRAME] = reboundFrame

        # buffer rest
        matrixEntry[I_M_BUFFERRESTFRAME] = finalRestFrame
        
    elif superpositionType == DEF_SUPERPOSITION_RTR:
        
        # discard buffer_rest

        # set buffer_rebound
        setKeyframes(object, matrixEntry, I_M_REBOUNDPOSE, buffer_rebound)
        
        # discard rest0
        
        # set ath
        setKeyframes(object, matrixEntry, I_M_ABOUTTOHITPOSE, aboutToHitFrame)
        
        # set hit
        setKeyframes(object, matrixEntry, I_M_HITPOSE, hitFrame)

        # buffer last hit
        matrixEntry[I_M_LASTHITFRAME] = hitFrame

        # buffer rebound
        matrixEntry[I_M_BUFFERREBOUNDFRAME] = reboundFrame

        # buffer rest
        matrixEntry[I_M_BUFFERRESTFRAME] = finalRestFrame
        
    elif superpositionType == DEF_SUPERPOSITION_ATH:
        
        # discard buffer_rest
        
        # discard rest0
        
        # discard ath
        
        # set buffer_rebound
        setKeyframes(object, matrixEntry, I_M_REBOUNDPOSE, buffer_rebound)

        # set hit
        setKeyframes(object, matrixEntry, I_M_HITPOSE, hitFrame)
        
        # buffer last hit
        matrixEntry[I_M_LASTHITFRAME] = hitFrame

        # buffer rebound
        matrixEntry[I_M_BUFFERREBOUNDFRAME] = reboundFrame

        # buffer rest
        matrixEntry[I_M_BUFFERRESTFRAME] = finalRestFrame

    elif superpositionType == DEF_SUPERPOSITION_HIT:
        
        # discard buffer_rest
        
        # discard rest0
        
        # discard ath

        # set buffer_rebound
        setKeyframes(object, matrixEntry, I_M_REBOUNDPOSE, lastHit + int((hitFrame - lastHit) / 2) )

        # set hit
        setKeyframes(object, matrixEntry, I_M_HITPOSE, hitFrame)

        # buffer last hit
        matrixEntry[I_M_LASTHITFRAME] = hitFrame
        
        # buffer rebound
        matrixEntry[I_M_BUFFERREBOUNDFRAME] = reboundFrame

        # buffer rest
        matrixEntry[I_M_BUFFERRESTFRAME] = finalRestFrame

def setKeyframes(object, matrixEntry, indexMatrixPose, frame):

    # Select the object
    object.select = True

    bpy.context.scene.objects.active = object
    
    poseName = matrixEntry[indexMatrixPose]

    if matrixEntry[I_M_OBJECTTYPE] == OBJECTTYPE_ARMATURE:
        bpy.ops.object.mode_set(mode='POSE')

        # Deselect all bones
        #bpy.ops.pose.select_all(action='DESELECT')
        
        # Select the bone group
        boneGroup = object.pose.bone_groups[matrixEntry[I_M_BONEGROUP]]
        for pbone, bone in zip(object.pose.bones, object.data.bones):
            if pbone.bone_group == boneGroup:
                bone.select = True
            else:
                bone.select = False
        
        # Get index of pose
        poseIndex = PoseMarkerID(object, poseName)
        
        # Test and apply the pose
        if poseIndex >= 0:
            bpy.ops.poselib.apply_pose(pose_index = poseIndex)
            #print("Applying pose " + str(poseIndex))

            # Insert Keyframes
            print("Inserting keyframes for object " + object.name + ", select bone group = '" + boneGroup.name + "', pose = '" + poseName + "', frame = " + str(frame))
            bpy.data.scenes['Scene'].frame_current = frame
            bpy.ops.anim.keyframe_insert_menu(type = matrixEntry[I_M_KEYFRAMETYPE])

            # Select Object Mode
            bpy.ops.object.mode_set(mode='OBJECT')
        else:
            print("Could not find pose " + matrixEntry[indexMatrixPose])
                    
    elif matrixEntry[I_M_OBJECTTYPE] == OBJECTTYPE_RIGIDOBJECT: 
        if matrixEntry[I_M_KEYFRAMETYPE] == 'Rotation':
            object.rotation_euler = mathutils.Euler((matrixEntry[indexMatrixPose]), 'XYZ')

            # Insert Keyframes
            print("Inserting keyframes for object ", object.name + ", frame = " + str(frame))
            #print("Inserting keyframes for object ", object.name + ", pose = '" + poseName + "', frame = " + str(frame))
            bpy.data.scenes['Scene'].frame_current = frame
            bpy.ops.anim.keyframe_insert_menu(type = matrixEntry[I_M_KEYFRAMETYPE])

    # Deselect the object
    object.select = False

def getMatrixEntry(componentName):

    # for each matching matrix entry
    for matrixEntry in matrix:
        if matrixEntry[I_M_COMPONENT] == componentName:
            return matrixEntry, True

    return matrixEntry, False


# Start of main code block ---------------------------------------------------------------------

# Get path to events file based on current blender directory
eventsFileName = os.path.join(bpy.path.abspath("//"), eventsFileName)

print("Processing events file " + eventsFileName)

try:
    # Open events file
    eventsFile = open(eventsFileName, 'r')

    # Read events
    events = eventsFile.readlines();

    # Close events file
    eventsFile.close()
except:
    raise RuntimeError("Could not open events file with name '" + eventsFileName + "'")    

print("There are " + str(len(events)) + " events")
rangeSeconds = getRangeSeconds(events)
print("First event occurs at second " + str(rangeSeconds[0]))
print("Last event occurs at second " + str(rangeSeconds[1]))

# Get all the notes present on events
notes = getNotes(events)

orphanNotes = [];

# Check all notes have matrix entries
for note in notes:
    for matrixEntry in matrix:
        if note == matrixEntry[I_M_NOTE]:
            break
    else:
        orphanNotes.append(note)

if len(orphanNotes) > 0:
    print("The following notes do not have a corresponding component: ", end = "")
    print(orphanNotes)
    

# Removes all animation data from matrix objects    
removeAnimationFromMatrixObjects(matrix)

# Set starting poses and states
startFrame =  1 + framesBeforeFirstEvent
for eventLine in events:
    eventDetails = getEventDetails(eventLine)
    if eventDetails[I_EV_TYPE] == 'Note on' and (channelFilter == -1 or int(eventDetails[I_EV_CHANNEL]) == channelFilter):

        # for each matching matrix entry
        for matrixEntry in matrix:
            if int(eventDetails[I_EV_DATA1]) == matrixEntry[I_M_NOTE]:
                startFrame = min(startFrame, getFrameForSecond((float(eventDetails[I_EV_SECONDS]) - matrixEntry[I_M_TIMEABOUTTOHIT])* 2, rangeSeconds[0]))
        
bpy.ops.object.mode_set(mode='OBJECT')

# deselect all objects
bpy.ops.object.select_all(action='DESELECT')

# Set keyframes for object, rest pose    
print("Setting initial rest poses at frame " + str(startFrame))
for matrixEntry in matrix:
    # Get and select the object
    object = bpy.data.objects[matrixEntry[I_M_OBJECTNAME]]
    setKeyframes(object, matrixEntry, I_M_RESTPOSE, startFrame)
        
# main loop
for i, eventLine in enumerate(events):
    eventDetails = getEventDetails(eventLine)
    if eventDetails[I_EV_TYPE] == 'Note on' and (channelFilter == -1 or int(eventDetails[I_EV_CHANNEL]) == channelFilter):
        
        print()
        print("Processing event " + str(i) +" with note " + str(eventDetails[I_EV_DATA1]) + " : ", end="")
        triggeredComponent = False
        
        # for each matching matrix entry
        for matrixEntry in matrix:
            if int(eventDetails[I_EV_DATA1]) == matrixEntry[I_M_NOTE]:
                setKeyframesForSuperposition(eventDetails, matrixEntry)
                triggeredComponent = True
            
        if not triggeredComponent:
            print("No component was triggered")
        
# Purge all buffered values
for matrixEntry in matrix:
    finalizeBufferedEvents(matrixEntry)

print("Finished processing events file " + eventsFileName)
