Skip to content

Commit cac890c

Browse files
authored
Merge branch 'master' into midi-clue-ble-glove
2 parents f4e72dc + ca14b88 commit cac890c

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

LED_Bullwhip/code.py

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
"""
2+
Prop-Maker based LED Bullwhip
3+
Adafruit invests time and resources providing this open source code.
4+
Please support Adafruit and open source hardware by purchasing
5+
products from Adafruit!
6+
Written by Erin St Blaine & Limor Fried for Adafruit Industries
7+
Copyright (c) 2019-2020 Adafruit Industries
8+
Licensed under the MIT license.
9+
All text above must be included in any redistribution.
10+
"""
11+
12+
import time
13+
import array
14+
import math
15+
import digitalio
16+
import audiobusio
17+
import board
18+
import neopixel
19+
import adafruit_lsm6ds
20+
21+
# CUSTOMISE COLORS HERE:
22+
COLOR = (40, 3, 0) # Default idle is blood orange
23+
HIT_COLOR = (0, 250, 0) # hit color is green
24+
LIGHT_WAVE_COLOR = (200, 50, 200) # purple
25+
DARK_COLOR = (0, 0, 0)
26+
CRACK_COLOR = (250, 250, 250) #white
27+
28+
29+
# CUSTOMISE IDLE PULSE SPEED HERE: 0 is fast, above 0 slows down
30+
IDLE_PULSE_SPEED = 0 # Default is 0 seconds
31+
SWING_BLAST_SPEED = 0.007
32+
33+
# CUSTOMISE BRIGHTNESS HERE: must be a number between 0 and 1
34+
IDLE_PULSE_BRIGHTNESS_MIN = 0.1 # Default minimum idle pulse brightness
35+
IDLE_PULSE_BRIGHTNESS_MAX = 0.5 # Default maximum idle pulse brightness
36+
37+
# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion
38+
HIT_THRESHOLD = 1150
39+
SWING_THRESHOLD = 750
40+
SOUND_THRESHOLD = 2000
41+
42+
# Set to the length in seconds for the animations
43+
POWER_ON_DURATION = 1.7
44+
LIGHT_WAVE_DURATION = 1
45+
HIT_DURATION = 2
46+
SWING_DURATION = 0
47+
FADE_DURATION = 1
48+
WHIP_CRACK_DURATION = 0.5
49+
50+
NUM_PIXELS = 60 # Number of pixels used in project
51+
NEOPIXEL_PIN = board.D5
52+
POWER_PIN = board.D10
53+
ONSWITCH_PIN = board.A1
54+
55+
led = digitalio.DigitalInOut(ONSWITCH_PIN)
56+
led.direction = digitalio.Direction.OUTPUT
57+
led.value = True
58+
59+
enable = digitalio.DigitalInOut(POWER_PIN)
60+
enable.direction = digitalio.Direction.OUTPUT
61+
enable.value = False
62+
63+
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
64+
strip.fill(0) # NeoPixels off ASAP on startup
65+
strip.show()
66+
67+
WAVE_FILE = None
68+
69+
i2c = board.I2C()
70+
71+
#Set up accelerometer & mic
72+
73+
sensor = adafruit_lsm6ds.LSM6DS33(i2c)
74+
mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK,
75+
board.MICROPHONE_DATA,
76+
sample_rate=16000,
77+
bit_depth=16)
78+
79+
COLOR_IDLE = COLOR # 'idle' color is the default
80+
COLOR_HIT = HIT_COLOR # "hit" color is HIT_COLOR set above
81+
COLOR_SWING = LIGHT_WAVE_COLOR # "swing" color is HIT_COLOR set above
82+
COLOR_ACTIVE = LIGHT_WAVE_COLOR
83+
84+
85+
def mean(values):
86+
''' Remove DC bias before computing RMS.'''
87+
return sum(values) / len(values)
88+
89+
def normalized_rms(values):
90+
''' Normalize values'''
91+
minbuf = int(mean(values))
92+
samples_sum = sum(
93+
float(sample - minbuf) * (sample - minbuf)
94+
for sample in values
95+
)
96+
97+
return math.sqrt(samples_sum / len(values))
98+
99+
100+
samples = array.array('H', [0] * 160)
101+
mic.record(samples, len(samples))
102+
103+
def mix(color_1, color_2, weight_2):
104+
"""
105+
Blend between two colors with a given ratio.
106+
:param color_1: first color, as an (r,g,b) tuple
107+
:param color_2: second color, as an (r,g,b) tuple
108+
:param weight_2: Blend weight (ratio) of second color, 0.0 to 1.0
109+
:return (r,g,b) tuple, blended color
110+
"""
111+
if weight_2 < 0.0:
112+
weight_2 = 0.0
113+
elif weight_2 > 1.0:
114+
weight_2 = 1.0
115+
weight_1 = 1.0 - weight_2
116+
return (int(color_1[0] * weight_1 + color_2[0] * weight_2),
117+
int(color_1[1] * weight_1 + color_2[1] * weight_2),
118+
int(color_1[2] * weight_1 + color_2[2] * weight_2))
119+
120+
def power_on(duration):
121+
"""
122+
Animate NeoPixels for power on.
123+
:param duration: estimated duration of sound, in seconds (>0.0)
124+
"""
125+
prev = 0
126+
start_time = time.monotonic() # Save start time
127+
while True:
128+
elapsed = time.monotonic() - start_time # Time spent
129+
if elapsed > duration: # Past duration?
130+
break # Stop animating
131+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
132+
threshold = int(NUM_PIXELS * animation_time + 0.5)
133+
num = threshold - prev # Number of pixels to light on this pass
134+
if num != 0:
135+
strip[prev:threshold] = [COLOR] * num
136+
strip.show()
137+
prev = threshold
138+
139+
def fade(duration):
140+
"""
141+
Animate NeoPixels for hit/fade animation
142+
:param duration: estimated duration of sound, in seconds (>0.0)
143+
"""
144+
prev = 0
145+
hit_time = time.monotonic() # Save start time
146+
while True:
147+
elapsed = time.monotonic() - hit_time # Time spent
148+
if elapsed > duration: # Past duration?
149+
break # Stop animating
150+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
151+
threshold = int(NUM_PIXELS * animation_time + 0.5)
152+
num = threshold - prev # Number of pixels to light on this pass
153+
if num != 0:
154+
blend = time.monotonic() - hit_time # Time since triggered
155+
blend = abs(0.5 - blend) * 2.0 # ramp up, down
156+
strip.fill(mix(COLOR_ACTIVE, COLOR, blend)) # Fade from hit/swing to base color
157+
strip.show()
158+
159+
def light_wave(duration):
160+
"""
161+
Animate NeoPixels for swing animatin
162+
:param duration: estimated duration of sound, in seconds (>0.0)
163+
"""
164+
prev = 0
165+
swing_time = time.monotonic() # Save start time
166+
while True:
167+
elapsed = time.monotonic() - swing_time # Time spent
168+
if elapsed > duration: # Past duration?
169+
break # Stop animating
170+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
171+
threshold = int(NUM_PIXELS * animation_time + 0.5)
172+
num = threshold - prev # Number of pixels to light on this pass
173+
if num != 0:
174+
strip[prev:threshold] = [CRACK_COLOR] * num
175+
strip.show()
176+
prev = threshold
177+
178+
def whip_crack(duration):
179+
"""
180+
Animate NeoPixels for swing animatin
181+
:param duration: estimated duration of sound, in seconds (>0.0)
182+
"""
183+
prev = 0
184+
crack_time = time.monotonic() # Save start time
185+
while True:
186+
elapsed = time.monotonic() - crack_time # Time spent
187+
if elapsed > duration: # Past duration?
188+
break # Stop animating
189+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
190+
threshold = int(NUM_PIXELS * animation_time + 0.5)
191+
num = threshold - prev # Number of pixels to light on this pass
192+
if num != 0:
193+
strip.fill(CRACK_COLOR)
194+
strip.show()
195+
time.sleep(0.01)
196+
strip.fill(DARK_COLOR)
197+
strip.show()
198+
time.sleep(0.03)
199+
strip.fill(CRACK_COLOR)
200+
strip.show()
201+
time.sleep(0.02)
202+
strip.fill(DARK_COLOR)
203+
strip.show()
204+
time.sleep(0.005)
205+
strip.fill(CRACK_COLOR)
206+
strip.show()
207+
time.sleep(0.01)
208+
strip.fill(DARK_COLOR)
209+
strip.show()
210+
time.sleep(0.03)
211+
prev = threshold
212+
213+
214+
215+
MODE = 0 # Initial MODE = OFF
216+
217+
# Setup idle pulse
218+
IDLE_BRIGHTNESS = IDLE_PULSE_BRIGHTNESS_MIN # current brightness of idle pulse
219+
IDLE_INCREMENT = 0.01 # Initial idle pulse direction
220+
221+
# Main loop
222+
while True:
223+
if MODE == 0: # If currently off...
224+
enable.value = True
225+
power_on(POWER_ON_DURATION) # Power up!
226+
MODE = 1 # Idle MODE
227+
228+
# Setup for idle pulse
229+
IDLE_BRIGHTNESS = IDLE_PULSE_BRIGHTNESS_MIN
230+
IDLE_INCREMENT = 0.01
231+
strip.fill([int(c*IDLE_BRIGHTNESS) for c in COLOR])
232+
strip.show()
233+
234+
elif MODE >= 1: # If not OFF MODE...
235+
samples = array.array('H', [0] * 160)
236+
mic.record(samples, len(samples))
237+
magnitude = normalized_rms(samples)
238+
print("Sound level:", normalized_rms(samples))
239+
if magnitude > SOUND_THRESHOLD:
240+
whip_crack(WHIP_CRACK_DURATION)
241+
MODE = 4
242+
x, y, z = sensor.acceleration
243+
accel_total = x * x + z * z
244+
# (Y axis isn't needed, due to the orientation that the Prop-Maker
245+
# Wing is mounted. Also, square root isn't needed, since we're
246+
# comparing thresholds...use squared values instead.)
247+
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
248+
TRIGGER_TIME = time.monotonic() # Save initial time of hit
249+
#play_wav("/sounds/hit1.wav") # Start playing 'hit' sound
250+
COLOR_ACTIVE = COLOR_HIT # Set color to fade from
251+
MODE = 3 # HIT MODE
252+
print("playing HIT")
253+
elif MODE == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
254+
# make a larson scanner animation_time
255+
strip.fill(DARK_COLOR)
256+
strip_backup = strip[0:-1]
257+
for p in range(-1, len(strip)):
258+
for i in range(p-1, p+10): # shoot a 'ray' of 3 pixels
259+
if 0 <= i < len(strip):
260+
strip[i] = COLOR_SWING
261+
strip.show()
262+
time.sleep(SWING_BLAST_SPEED)
263+
if 0 <= (p-1) < len(strip):
264+
strip[p-1] = strip_backup[p-1] # restore previous color at the tail
265+
strip.show()
266+
MODE = 2 # we'll go back to idle MODE
267+
print("playing SWING")
268+
elif MODE == 1:
269+
# Idle pulse
270+
IDLE_BRIGHTNESS += IDLE_INCREMENT # Pulse up
271+
if IDLE_BRIGHTNESS > IDLE_PULSE_BRIGHTNESS_MAX or \
272+
IDLE_BRIGHTNESS < IDLE_PULSE_BRIGHTNESS_MIN: # Then...
273+
IDLE_INCREMENT *= -1 # Pulse direction flip
274+
strip.fill([int(c*IDLE_BRIGHTNESS) for c in COLOR_IDLE])
275+
strip.show()
276+
time.sleep(IDLE_PULSE_SPEED) # Idle pulse speed set above
277+
elif MODE > 1: # If in SWING or HIT MODE...
278+
if MODE == 3:
279+
fade(FADE_DURATION)
280+
# elif MODE == 2: # If SWING,
281+
# power_on(POWER_ON_DURATION)
282+
MODE = 1 # Return to idle mode

0 commit comments

Comments
 (0)