Skip to content

Commit 1d7de3f

Browse files
committed
Wizard Staff
Code for wizard staff guide
1 parent a1fc46c commit 1d7de3f

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed

Burning_Fire_Wizard_Staff/README.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Burning_Fire_Wizard_Staff
2+
3+
Code to accompany this tutorial:
4+
https://learn.adafruit.com/burning-fire-wizard-staff
5+
6+
Prop-Maker based Burning Wizard Staff
7+
Adafruit invests time and resources providing this open source code.
8+
Please support Adafruit and open source hardware by purchasing
9+
products from Adafruit!
10+
Written by Kattni Rembor, Erin St Blaine & Limor Fried for Adafruit Industries
11+
Copyright (c) 2020 Adafruit Industries
12+
Licensed under the MIT license.
13+
All text above must be included in any redistribution.

Burning_Fire_Wizard_Staff/code.py

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
"""
2+
Prop-Maker based Burning Wizard Staff
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 Kattni Rembor, Erin St Blaine & Limor Fried for Adafruit Industries
7+
Copyright (c) 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 random
14+
import digitalio
15+
import audioio
16+
import audiocore
17+
import board
18+
import neopixel
19+
import adafruit_lis3dh
20+
21+
# CUSTOMISE COLORS HERE:
22+
COLOR = (200, 50, 0) # Default idle is orange
23+
ALT_COLOR = (0, 200, 200) # hit color is teal
24+
SWING_COLOR = (200, 200, 200) #swing animation color is white
25+
TOP_COLOR = (100, 100, 0) #top color is yellow-green
26+
YELL_COLOR = (200, 0, 200) #yell color is purple
27+
28+
# CUSTOMISE IDLE PULSE SPEED HERE: 0 is fast, above 0 slows down
29+
IDLE_PULSE_SPEED = 0 # Default is 0 seconds
30+
SWING_BLAST_SPEED = 0.007
31+
32+
# CUSTOMISE BRIGHTNESS HERE: must be a number between 0 and 1
33+
IDLE_PULSE_BRIGHTNESS_MIN = 0.2 # Default minimum idle pulse brightness
34+
IDLE_PULSE_BRIGHTNESS_MAX = 1 # Default maximum idle pulse brightness
35+
36+
# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion
37+
HIT_THRESHOLD = 1150
38+
SWING_THRESHOLD = 800
39+
YELL_THRESHOLD = 700
40+
41+
# Set to the length in seconds of the "on.wav" and "yell1.wav" files
42+
POWER_ON_SOUND_DURATION = 3.0
43+
YELL_SOUND_DURATION = 1.0
44+
45+
NUM_RING = 12 #12 pixel ring
46+
NUM_STRIP = 44 # 44 pixels in my NeoPixel strip
47+
NUM_PIXELS = NUM_STRIP + NUM_RING #total number of pixels
48+
NEOPIXEL_PIN = board.D5 # PropMaker Wing uses D5 for NeoPixel plug
49+
POWER_PIN = board.D10
50+
51+
enable = digitalio.DigitalInOut(POWER_PIN)
52+
enable.direction = digitalio.Direction.OUTPUT
53+
enable.value = False
54+
55+
# Set up NeoPixels
56+
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
57+
strip.fill(0) # NeoPixels off ASAP on startup
58+
strip.show()
59+
60+
audio = audioio.AudioOut(board.A0) # Speaker
61+
wave_file = None
62+
63+
# Set up accelerometer on I2C bus, 4G range:
64+
i2c = board.I2C()
65+
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
66+
accel.range = adafruit_lis3dh.RANGE_4_G
67+
68+
COLOR_IDLE = COLOR # 'idle' color is the default for the staff handle
69+
COLOR_HIT = ALT_COLOR # "hit" color is ALT_COLOR set above
70+
COLOR_SWING = SWING_COLOR # "swing" color is SWING_COLOR set above
71+
COLOR_TOP = TOP_COLOR #"top" color is idle color for the ring
72+
73+
74+
def play_wav(name, loop=False):
75+
"""
76+
Play a WAV file in the 'sounds' directory.
77+
:param name: partial file name string, complete name will be built around
78+
this, e.g. passing 'foo' will play file 'sounds/foo.wav'.
79+
:param loop: if True, sound will repeat indefinitely (until interrupted
80+
by another sound).
81+
"""
82+
global wave_file # pylint: disable=global-statement
83+
print("playing", name)
84+
if wave_file:
85+
wave_file.close()
86+
try:
87+
wave_file = open('sounds/' + name + '.wav', 'rb')
88+
wave = audiocore.WaveFile(wave_file)
89+
audio.play(wave, loop=loop)
90+
except OSError:
91+
pass # we'll just skip playing then
92+
93+
94+
def power(sound, duration, reverse):
95+
"""
96+
Animate NeoPixels with accompanying sound effect for power on.
97+
@param sound: sound name (similar format to play_wav() above)
98+
@param duration: estimated duration of sound, in seconds (>0.0)
99+
@param reverse: Reverses animation. If True, begins animation at end of strip.
100+
"""
101+
if reverse:
102+
prev = NUM_PIXELS
103+
else:
104+
prev = 0
105+
start_time = time.monotonic() # Save audio start time
106+
play_wav(sound)
107+
while True:
108+
elapsed = time.monotonic() - start_time # Time spent playing sound
109+
if elapsed > duration: # Past sound duration?
110+
break # Stop animating
111+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
112+
if reverse:
113+
animation_time = 1.0 - animation_time # 1.0 to 0.0 if reverse
114+
threshold = int(NUM_PIXELS * animation_time + 0.5)
115+
num = threshold - prev # Number of pixels to light on this pass
116+
if num != 0:
117+
if reverse:
118+
strip[threshold:prev] = [ALT_COLOR] * -num
119+
else:
120+
strip[prev:threshold] = [ALT_COLOR] * num
121+
strip.show()
122+
prev = threshold
123+
124+
125+
def mix(color_1, color_2, weight_2):
126+
"""
127+
Blend between two colors with a given ratio.
128+
:param color_1: first color, as an (r,g,b) tuple
129+
:param color_2: second color, as an (r,g,b) tuple
130+
:param weight_2: Blend weight (ratio) of second color, 0.0 to 1.0
131+
:return (r,g,b) tuple, blended color
132+
"""
133+
if weight_2 < 0.0:
134+
weight_2 = 0.0
135+
elif weight_2 > 1.0:
136+
weight_2 = 1.0
137+
weight_1 = 1.0 - weight_2
138+
return (int(color_1[0] * weight_1 + color_2[0] * weight_2),
139+
int(color_1[1] * weight_1 + color_2[1] * weight_2),
140+
int(color_1[2] * weight_1 + color_2[2] * weight_2))
141+
142+
# List of swing wav files without the .wav in the name for use with play_wav()
143+
swing_sounds = [
144+
'swing1',
145+
'swing2',
146+
'swing3',
147+
]
148+
149+
# List of hit wav files without the .wav in the name for use with play_wav()
150+
hit_sounds = [
151+
'hit1',
152+
'hit2',
153+
'hit3',
154+
'hit4',
155+
]
156+
157+
# List of yell wav files without the .wav in the name for use with play_wav()
158+
yell_sounds = [
159+
'yell1',
160+
]
161+
162+
163+
mode = 0 # Initial mode = OFF
164+
165+
# Setup idle pulse
166+
idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN # current brightness of idle pulse
167+
idle_increment = 0.01 # Initial idle pulse direction
168+
169+
# Main loop
170+
while True:
171+
172+
if mode == 0: # If currently off...
173+
enable.value = True
174+
power('on', POWER_ON_SOUND_DURATION, True) # Power up!
175+
play_wav('idle', loop=True) # Play idle sound now
176+
mode = 1 # Idle mode
177+
time.sleep(1.0) #pause before moving on
178+
179+
# Setup for idle pulse
180+
idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN
181+
idle_increment = 0.01
182+
strip[0:NUM_RING] = [([int(c*idle_brightness) for c in COLOR_TOP])] * NUM_RING #lights the ring in COLOR_TOP color
183+
strip[NUM_RING:NUM_PIXELS] = [([int(c*idle_brightness) for c in COLOR_IDLE])] * NUM_STRIP #lights the strip in COLOR_IDLE color
184+
strip.show()
185+
186+
elif mode >= 1: # If not OFF mode...
187+
x, y, z = accel.acceleration # Read accelerometer
188+
accel_total = x * x + z * z #x axis used for hit and for swing
189+
accel_yell = y * y + z * z #y axis used for yell
190+
# Square root isn't needed, since we're
191+
# comparing thresholds...use squared values instead.)
192+
if accel_total > HIT_THRESHOLD: # Large acceleration on x axis = HIT
193+
TRIGGER_TIME = time.monotonic() # Save initial time of hit
194+
play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound
195+
COLOR_ACTIVE = COLOR_HIT # Set color to fade from
196+
mode = 3 # HIT mode
197+
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild acceleration on x axis = SWING
198+
TRIGGER_TIME = time.monotonic() # Save initial time of swing
199+
play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds
200+
# make a larson scanner
201+
strip_backup = strip[0:-1]
202+
for p in range(-1, len(strip)):
203+
for i in range (p-1, p+2): # shoot a 'ray' of 3 pixels
204+
if 0 <= i < len(strip):
205+
strip[i] = COLOR_SWING
206+
strip.show()
207+
time.sleep(SWING_BLAST_SPEED)
208+
if 0 <= (p-1) < len(strip):
209+
strip[p-1] = strip_backup[p-1] # restore previous color at the tail
210+
strip.show()
211+
while audio.playing:
212+
pass # wait till we're done
213+
mode = 2 # we'll go back to idle mode
214+
elif mode == 1 and accel_yell > YELL_THRESHOLD: # Motion on Y axis = YELL
215+
TRIGGER_TIME = time.monotonic() # Save initial time of swing
216+
# run a color down the staff, opposite of power-up
217+
prev = 0
218+
start_time = time.monotonic() # Save audio start time
219+
play_wav(random.choice(yell_sounds)) # Randomly choose from available yell sounds
220+
duration = YELL_SOUND_DURATION
221+
while True:
222+
elapsed = time.monotonic() - start_time # Time spent playing sound
223+
if elapsed > duration: # Past sound duration?
224+
break # Stop animating
225+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
226+
threshold = int(NUM_PIXELS * animation_time + 0.5)
227+
num = threshold - prev # Number of pixels to light on this pass
228+
if num != 0:
229+
strip[prev:threshold] = [YELL_COLOR] * num # light pixels in YELL_COLOR
230+
strip.show()
231+
prev = threshold
232+
while audio.playing:
233+
pass # wait till we're done
234+
mode = 4 # we'll go back to idle mode
235+
elif mode == 1:
236+
# Idle pulse
237+
idle_brightness += idle_increment # Pulse up
238+
if idle_brightness > IDLE_PULSE_BRIGHTNESS_MAX or \
239+
idle_brightness < IDLE_PULSE_BRIGHTNESS_MIN: # Then...
240+
idle_increment *= -1 # Pulse direction flip
241+
strip[0:NUM_RING] = [([int(c*idle_brightness) for c in COLOR_TOP])] * NUM_RING #light the ring
242+
strip[NUM_RING:NUM_PIXELS] = [([int(c*idle_brightness) for c in COLOR_IDLE])] * NUM_STRIP #light the strip
243+
strip.show()
244+
time.sleep(IDLE_PULSE_SPEED) # Idle pulse speed set above
245+
elif mode > 1: # If in SWING or HIT or YELL mode...
246+
if audio.playing: # And sound currently playing...
247+
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
248+
if mode == 2: # If SWING,
249+
blend = abs(0.5 - blend) * 3.0 # ramp up, down
250+
strip.fill(mix(COLOR_ACTIVE, COLOR, blend)) # Fade from hit/swing to base color
251+
strip.show()
252+
else: # No sound now, but still SWING or HIT modes
253+
play_wav('idle', loop=True) # Resume idle sound
254+
mode = 1 # Return to idle mode

0 commit comments

Comments
 (0)