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