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