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
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 idle_brightness < IDLE_PULSE_BRIGHTNESS_MIN : # Then...
178+ idle_increment *= - 1 # Pulse direction flip
179+ strip .fill ([int (c * idle_brightness ) for c in COLOR_IDLE ])
180+ strip .show ()
181+ time .sleep (IDLE_PULSE_SPEED ) # Idle pulse speed set above
182+ elif mode > 1 : # If in SWING or HIT mode...
183+ if audio .playing : # And sound currently playing...
184+ blend = time .monotonic () - TRIGGER_TIME # Time since triggered
185+ if mode == 2 : # If SWING,
186+ blend = abs (0.5 - blend ) * 2.0 # ramp up, down
187+ strip .fill (mix (COLOR_ACTIVE , COLOR , blend )) # Fade from hit/swing to base color
188+ strip .show ()
189+ else : # No sound now, but still SWING or HIT modes
190+ play_wav ('idle' , loop = True ) # Resume idle sound
191+ mode = 1 # Return to idle mode
0 commit comments