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