Created
January 21, 2024 00:53
-
-
Save AdmTal/0dc97f4ef9b2eeaa28d26e50ffe27bb9 to your computer and use it in GitHub Desktop.
Animate Blender 3d piano
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import bpy | |
import pretty_midi | |
from collections import defaultdict | |
# Parameters | |
BUFFER = 12 | |
fps = 24 | |
midi_file_path = 'wii-music.mid' | |
keys_collection = bpy.data.collections['Keys'] | |
key_press_action = bpy.data.actions['Key Press'] | |
def get_note_start_times_in_frames(midi_file_path, fps): | |
midi_data = pretty_midi.PrettyMIDI(midi_file_path) | |
if len(midi_data.instruments) > 1: | |
raise Exception("MIDI file contains more than one track.") | |
notes_frames = defaultdict(list) | |
for note in midi_data.instruments[0].notes: | |
start_frame = int(note.start * fps) | |
end_frame = int(note.end * fps) | |
notes_frames[note.pitch].append((start_frame + BUFFER, end_frame + BUFFER)) | |
return notes_frames | |
def get_delta_transforms(action, frame): | |
delta_loc = [0.0, 0.0, 0.0] | |
delta_rot = [0.0, 0.0, 0.0] | |
for fcurve in action.fcurves: | |
for keyframe_point in fcurve.keyframe_points: | |
if keyframe_point.co.x == frame: | |
if fcurve.data_path == 'delta_location': | |
delta_loc[fcurve.array_index] = keyframe_point.co.y | |
elif fcurve.data_path == 'delta_rotation_euler': | |
delta_rot[fcurve.array_index] = keyframe_point.co.y | |
return delta_loc, delta_rot | |
def set_keyframe(obj, frame, delta_loc, delta_rot): | |
# Reset delta transformations before applying new ones | |
obj.delta_location = (0, 0, 0) | |
obj.delta_rotation_euler = (0, 0, 0) | |
# Apply new delta transformations | |
obj.delta_location = delta_loc | |
obj.delta_rotation_euler = delta_rot | |
# Insert keyframes for all axes | |
for i in range(3): # XYZ | |
obj.keyframe_insert(data_path="delta_location", index=i, frame=frame) | |
obj.keyframe_insert(data_path="delta_rotation_euler", index=i, frame=frame) | |
def animate_piano_keys(note_frames, keys_collection, KEY_UP_POSITION, KEY_DOWN_POSITION): | |
# For every Key | |
for key_object in keys_collection.objects: | |
# For the notes played on this Key | |
key_notes = note_frames.get(int(key_object.name), []) | |
# I forget what this means | |
prev_up_frame = 0 | |
anim_length = 3 | |
for down_frame, up_frame in key_notes: | |
# Push Key Down | |
# Before can push key down, it needs to be UP | |
PREV_UP = max(down_frame - anim_length, prev_up_frame) | |
set_keyframe(key_object, PREV_UP, *KEY_UP_POSITION) # UP | |
set_keyframe(key_object, down_frame, *KEY_DOWN_POSITION) # DOWN | |
set_keyframe(key_object, up_frame-anim_length, *KEY_DOWN_POSITION) # STAY DOWN | |
set_keyframe(key_object, up_frame, *KEY_UP_POSITION) # UP | |
prev_up_frame = up_frame | |
# Get delta transformations from the first and last keyframes of the actions | |
KEY_UP_POSITION = get_delta_transforms(key_press_action, key_press_action.frame_range[0]) | |
KEY_DOWN_POSITION = get_delta_transforms(key_press_action, key_press_action.frame_range[1]) | |
print(f'KEY_UP_POSITION -- {KEY_UP_POSITION}') | |
print(f'KEY_DOWN_POSITION -- {KEY_DOWN_POSITION}') | |
# Iterate through all objects in the scene | |
for obj in bpy.data.objects: | |
# Check if the object has animation data | |
if obj.animation_data: | |
# Clear the animation data | |
obj.animation_data_clear() | |
# Get note frames and animate keys | |
note_frames = get_note_start_times_in_frames(midi_file_path, fps) | |
animate_piano_keys(note_frames, keys_collection, KEY_UP_POSITION, KEY_DOWN_POSITION) | |
# Iterate through all objects in the scene | |
for obj in bpy.data.objects: | |
# Check if the object has animation data and NLA tracks | |
if obj.animation_data and obj.animation_data.nla_tracks: | |
for track in obj.animation_data.nla_tracks: | |
for strip in track.strips: | |
strip.extrapolation = 'NOTHING' | |
print("Extrapolation mode set to 'Nothing' for all NLA strips.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment