Skip to content

Commit 5a96f54

Browse files
authored
Merge pull request #776 from ladyada/master
initial master sword code
2 parents 4572859 + b8a6012 commit 5a96f54

5 files changed

Lines changed: 192 additions & 0 deletions

File tree

Propmaker_Master_Sword/code.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Prop-Maker based Master Sword
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 & Limor Fried for Adafruit Industries
7+
Copyright (c) 2019 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 digitalio
14+
import audioio
15+
import busio
16+
import board
17+
import neopixel
18+
import adafruit_lis3dh
19+
20+
# CUSTOMISE COLORS HERE:
21+
COLOR = (0, 120, 120) # Default idle is light blue
22+
ALT_COLOR = (255, 255, 255) # hit color is bright white
23+
24+
# CUSTOMISE IDLE PULSE SPEED HERE: 0 is fast, above 0 slows down
25+
IDLE_PULSE_SPEED = 0.05 # Default is 0.1 seconds
26+
SWING_BLAST_SPEED = 0.01
27+
28+
# CUSTOMISE BRIGHTNESS HERE: must be a number between 0 and 1
29+
IDLE_PULSE_BRIGHTNESS_MIN = 0.3 # Default minimum idle pulse brightness
30+
IDLE_PULSE_BRIGHTNESS_MAX = 0.6 # Default maximum idle pulse brightness
31+
32+
# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion
33+
HIT_THRESHOLD = 500
34+
SWING_THRESHOLD = 125
35+
36+
# Set to the length in seconds of the "on.wav" file
37+
POWER_ON_SOUND_DURATION = 1.7
38+
39+
NUM_PIXELS = 30 # Number of pixels used in project
40+
NEOPIXEL_PIN = board.D5
41+
POWER_PIN = board.D10
42+
43+
enable = digitalio.DigitalInOut(POWER_PIN)
44+
enable.direction = digitalio.Direction.OUTPUT
45+
enable.value = False
46+
47+
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
48+
strip.fill(0) # NeoPixels off ASAP on startup
49+
strip.show()
50+
51+
audio = audioio.AudioOut(board.A0) # Speaker
52+
wave_file = None
53+
54+
# Set up accelerometer on I2C bus, 4G range:
55+
i2c = busio.I2C(board.SCL, board.SDA)
56+
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
57+
accel.range = adafruit_lis3dh.RANGE_4_G
58+
59+
COLOR_IDLE = COLOR # 'idle' color is the default
60+
COLOR_HIT = ALT_COLOR # "hit" color is ALT_COLOR set above
61+
COLOR_SWING = ALT_COLOR # "swing" color is ALT_COLOR set above
62+
63+
64+
def play_wav(name, loop=False):
65+
"""
66+
Play a WAV file in the 'sounds' directory.
67+
:param name: partial file name string, complete name will be built around
68+
this, e.g. passing 'foo' will play file 'sounds/foo.wav'.
69+
:param loop: if True, sound will repeat indefinitely (until interrupted
70+
by another sound).
71+
"""
72+
global wave_file # pylint: disable=global-statement
73+
print("playing", name)
74+
if wave_file:
75+
wave_file.close()
76+
try:
77+
wave_file = open('sounds/' + name + '.wav', 'rb')
78+
wave = audioio.WaveFile(wave_file)
79+
audio.play(wave, loop=loop)
80+
except OSError:
81+
pass # we'll just skip playing then
82+
83+
84+
def power_on(sound, duration):
85+
"""
86+
Animate NeoPixels with accompanying sound effect for power on.
87+
:param sound: sound name (similar format to play_wav() above)
88+
:param duration: estimated duration of sound, in seconds (>0.0)
89+
"""
90+
prev = 0
91+
start_time = time.monotonic() # Save audio start time
92+
play_wav(sound)
93+
while True:
94+
elapsed = time.monotonic() - start_time # Time spent playing sound
95+
if elapsed > duration: # Past sound duration?
96+
break # Stop animating
97+
animation_time = elapsed / duration # Animation time, 0.0 to 1.0
98+
threshold = int(NUM_PIXELS * animation_time + 0.5)
99+
num = threshold - prev # Number of pixels to light on this pass
100+
if num != 0:
101+
strip[prev:threshold] = [ALT_COLOR] * num
102+
strip.show()
103+
prev = threshold
104+
105+
106+
def mix(color_1, color_2, weight_2):
107+
"""
108+
Blend between two colors with a given ratio.
109+
:param color_1: first color, as an (r,g,b) tuple
110+
:param color_2: second color, as an (r,g,b) tuple
111+
:param weight_2: Blend weight (ratio) of second color, 0.0 to 1.0
112+
:return (r,g,b) tuple, blended color
113+
"""
114+
if weight_2 < 0.0:
115+
weight_2 = 0.0
116+
elif weight_2 > 1.0:
117+
weight_2 = 1.0
118+
weight_1 = 1.0 - weight_2
119+
return (int(color_1[0] * weight_1 + color_2[0] * weight_2),
120+
int(color_1[1] * weight_1 + color_2[1] * weight_2),
121+
int(color_1[2] * weight_1 + color_2[2] * weight_2))
122+
123+
124+
mode = 0 # Initial mode = OFF
125+
126+
# Setup idle pulse
127+
idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN # current brightness of idle pulse
128+
idle_increment = 0.01 # Initial idle pulse direction
129+
130+
# Main loop
131+
while True:
132+
133+
if mode == 0: # If currently off...
134+
enable.value = True
135+
power_on('on', POWER_ON_SOUND_DURATION) # Power up!
136+
play_wav('idle', loop=True) # Play idle sound now
137+
mode = 1 # Idle mode
138+
139+
# Setup for idle pulse
140+
idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN
141+
idle_increment = 0.01
142+
strip.fill([int(c*idle_brightness) for c in COLOR])
143+
strip.show()
144+
145+
elif mode >= 1: # If not OFF mode...
146+
x, y, z = accel.acceleration # Read accelerometer
147+
accel_total = x * x + z * z
148+
# (Y axis isn't needed, due to the orientation that the Prop-Maker
149+
# Wing is mounted. Also, square root isn't needed, since we're
150+
# comparing thresholds...use squared values instead.)
151+
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
152+
TRIGGER_TIME = time.monotonic() # Save initial time of hit
153+
play_wav('hit') # Start playing 'hit' sound
154+
COLOR_ACTIVE = COLOR_HIT # Set color to fade from
155+
mode = 3 # HIT mode
156+
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
157+
TRIGGER_TIME = time.monotonic() # Save initial time of swing
158+
play_wav('swing') # Start playing 'swing' sound
159+
# make a larson scanner animation_time
160+
strip_backup = strip[0:-1]
161+
for p in range(-1, len(strip)):
162+
for i in range (p-1, p+2): # shoot a 'ray' of 3 pixels
163+
if 0 <= i < len(strip):
164+
strip[i] = COLOR_SWING
165+
strip.show()
166+
time.sleep(SWING_BLAST_SPEED)
167+
if 0 <= (p-1) < len(strip):
168+
strip[p-1] = strip_backup[p-1] # restore previous color at the tail
169+
strip.show()
170+
while audio.playing:
171+
pass # wait till we're done
172+
mode = 2 # we'll go back to idle mode
173+
174+
elif mode == 1:
175+
# Idle pulse
176+
idle_brightness += idle_increment # Pulse up
177+
if idle_brightness > IDLE_PULSE_BRIGHTNESS_MAX or \
178+
idle_brightness < IDLE_PULSE_BRIGHTNESS_MIN: # Then...
179+
idle_increment *= -1 # Pulse direction flip
180+
strip.fill([int(c*idle_brightness) for c in COLOR_IDLE])
181+
strip.show()
182+
time.sleep(IDLE_PULSE_SPEED) # Idle pulse speed set above
183+
elif mode > 1: # If in SWING or HIT mode...
184+
if audio.playing: # And sound currently playing...
185+
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
186+
if mode == 2: # If SWING,
187+
blend = abs(0.5 - blend) * 2.0 # ramp up, down
188+
strip.fill(mix(COLOR_ACTIVE, COLOR, blend)) # Fade from hit/swing to base color
189+
strip.show()
190+
else: # No sound now, but still SWING or HIT modes
191+
play_wav('idle', loop=True) # Resume idle sound
192+
mode = 1 # Return to idle mode
94.8 KB
Binary file not shown.
264 KB
Binary file not shown.
115 KB
Binary file not shown.
34.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)