Skip to content

Commit aca6ea1

Browse files
committedJul 3, 2022
Hammer procedure.
1 parent d1f960e commit aca6ea1

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed
 

‎bmusic/proc/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .procedure import Procedure
22

33
from .intensity import *
4+
from .mechanical import *

‎bmusic/proc/mechanical.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Mechanical movements e.g. hammer, robot arm, etc.
3+
"""
4+
5+
__all__ = (
6+
"Hammer",
7+
)
8+
9+
import bpy
10+
import numpy as np
11+
12+
from .procedure import Procedure
13+
14+
15+
class Hammer(Procedure):
16+
"""
17+
Hammer movement: Resting, preparing, hitting, recoil, wobbling.
18+
19+
Keyframe types:
20+
- JITTER: Resting.
21+
- BREAKDOWN: Preparing to hit.
22+
- EXTREME: Hitting.
23+
- KEYFRAME: Recoil and wobbling.
24+
25+
Parameters
26+
----------
27+
28+
animkey: Animation key:
29+
- hit: Hitting.
30+
- prepare: Preparing to hit.
31+
- recoil: Bounce back after hit.
32+
33+
prepare_dur: Duration (sec) of rest to prepare.
34+
Default: 0.18
35+
36+
hit_dur: Duration (sec) of prepare to hit movement.
37+
Default: 0.1
38+
39+
recoil_dur: Duration (sec) of hit to recoil movement.
40+
Default: 0.13
41+
42+
wobble_period: Duration (sec) of each wobble.
43+
Default: 0.35
44+
45+
wobble_count: Number of wobbles to perform.
46+
Default: 4
47+
48+
wobble_decay: Factor by which wobble intensity decays each time.
49+
Default: 0.5
50+
"""
51+
52+
def __init__(self, **kwargs):
53+
super().__init__(**kwargs)
54+
self.animkey = kwargs.get("animkey")
55+
self.prepare_dur = kwargs.get("prepare_dur", 0.18)
56+
self.hit_dur = kwargs.get("hit_dur", 0.1)
57+
self.recoil_dur = kwargs.get("recoil_dur", 0.13)
58+
self.wobble_period = kwargs.get("wobble_period", 0.35)
59+
self.wobble_count = kwargs.get("wobble_count", 4)
60+
self.wobble_decay = kwargs.get("wobble_decay", 0.5)
61+
62+
def animate(self):
63+
fps = bpy.context.scene.render.fps
64+
hit_dur = self.hit_dur * fps
65+
prepare_dur = self.prepare_dur * fps
66+
recoil_dur = self.recoil_dur * fps
67+
wobble_period = self.wobble_period * fps
68+
wobble_count = self.wobble_count
69+
wobble_decay = self.wobble_decay
70+
71+
before_dur = prepare_dur + hit_dur
72+
wobble_dur = wobble_period * wobble_count
73+
after_dur = wobble_dur + recoil_dur
74+
total_dur = before_dur + after_dur
75+
76+
for i, note in enumerate(self.midi):
77+
last = note.prev.start if note.prev else -1e9
78+
next = note.next_start
79+
80+
hit_intensity = np.interp(note.velocity, [0, 127], [0, 1])
81+
82+
# Prepare to hit
83+
if note.start - last > total_dur:
84+
# Long time since, so reset to resting position.
85+
self.animkey.animate(note.start-before_dur, type="JITTER")
86+
87+
prepare_frame = max(note.start-hit_dur, (note.start+last)/2)
88+
self.animkey.animate(prepare_frame, type="BREAKDOWN", prepare=hit_intensity)
89+
90+
# Hit
91+
self.animkey.animate(note.start, type="EXTREME", handle="VECTOR", hit=hit_intensity)
92+
93+
# Wobble
94+
dur_limit = min(after_dur, next-note.start-before_dur)
95+
offset = recoil_dur
96+
97+
for j in range(wobble_count):
98+
if offset >= dur_limit:
99+
break
100+
intensity = hit_intensity * wobble_decay ** (j+1)
101+
102+
name = "prepare" if j % 2 == 0 else "hit"
103+
kwargs = {name: intensity}
104+
self.animkey.animate(note.start+offset, type="KEYFRAME", **kwargs)
105+
106+
offset += wobble_period
107+
108+
if next - before_dur > note.start + offset:
109+
# Long time until, so reset to resting position.
110+
self.animkey.animate(note.start+offset, type="JITTER")

0 commit comments

Comments
 (0)
Please sign in to comment.