1+ """
2+ LED Sunflower Mobile with Circuit Playground Bluefruit
3+ Full tutorial:
4+ https://learn.adafruit.com/sound-reactive-sunflower-baby-crib-mobile-with-bluetooth-control
5+ Adafruit invests time and resources providing this open source code.
6+ Please support Adafruit and open source hardware by purchasing
7+ products from Adafruit!
8+ Written by Erin St Blaine & Dan Halbert for Adafruit Industries
9+ Copyright (c) 2020-2021 Adafruit Industries
10+ Licensed under the MIT license.
11+ All text above must be included in any redistribution.
12+
13+ """
14+
15+ import array
16+ import math
17+ import audiobusio
18+ import board
19+ import neopixel
20+ from digitalio import DigitalInOut , Direction , Pull
21+
22+ from adafruit_bluefruit_connect .packet import Packet
23+ from adafruit_bluefruit_connect .button_packet import ButtonPacket
24+ from adafruit_bluefruit_connect .color_packet import ColorPacket
25+ from adafruit_ble import BLERadio
26+ from adafruit_ble .advertising .standard import ProvideServicesAdvertisement
27+ from adafruit_ble .services .nordic import UARTService
28+
29+ from adafruit_led_animation .helper import PixelMap
30+ from adafruit_led_animation .sequence import AnimationSequence
31+ from adafruit_led_animation .group import AnimationGroup
32+ from adafruit_led_animation .animation .sparkle import Sparkle
33+ from adafruit_led_animation .animation .rainbow import Rainbow
34+ from adafruit_led_animation .animation .rainbowchase import RainbowChase
35+ from adafruit_led_animation .animation .rainbowcomet import RainbowComet
36+ from adafruit_led_animation .animation .chase import Chase
37+ from adafruit_led_animation .animation .comet import Comet
38+ from adafruit_led_animation .animation .solid import Solid
39+ from adafruit_led_animation .color import colorwheel
40+ from adafruit_led_animation .color import (
41+ BLACK ,
42+ RED ,
43+ ORANGE ,
44+ BLUE ,
45+ PURPLE ,
46+ WHITE ,
47+ )
48+
49+ YELLOW = (25 , 15 , 0 )
50+
51+ # Setup BLE
52+ ble = BLERadio ()
53+ uart = UARTService ()
54+ advertisement = ProvideServicesAdvertisement (uart )
55+
56+ # Color of the peak pixel.
57+ PEAK_COLOR = (100 , 0 , 255 )
58+ # Number of total pixels - 10 build into Circuit Playground
59+ NUM_PIXELS = 30
60+
61+
62+ fairylights = DigitalInOut (board .A4 )
63+ fairylights .direction = Direction .OUTPUT
64+ fairylights .value = True
65+
66+ # Exponential scaling factor.
67+ # Should probably be in range -10 .. 10 to be reasonable.
68+ CURVE = 2
69+ SCALE_EXPONENT = math .pow (10 , CURVE * - 0.1 )
70+
71+ # Number of samples to read at once.
72+ NUM_SAMPLES = 160
73+
74+ brightness_increment = 0
75+
76+ # Restrict value to be between floor and ceiling.
77+ def constrain (value , floor , ceiling ):
78+ return max (floor , min (value , ceiling ))
79+
80+
81+ # Scale input_value between output_min and output_max, exponentially.
82+ def log_scale (input_value , input_min , input_max , output_min , output_max ):
83+ normalized_input_value = (input_value - input_min ) / \
84+ (input_max - input_min )
85+ return output_min + \
86+ math .pow (normalized_input_value , SCALE_EXPONENT ) \
87+ * (output_max - output_min )
88+
89+
90+ # Remove DC bias before computing RMS.
91+ def normalized_rms (values ):
92+ minbuf = int (mean (values ))
93+ samples_sum = sum (
94+ float (sample - minbuf ) * (sample - minbuf )
95+ for sample in values
96+ )
97+
98+ return math .sqrt (samples_sum / len (values ))
99+
100+
101+ def mean (values ):
102+ return sum (values ) / len (values )
103+
104+
105+ def volume_color (volume ):
106+ return 200 , volume * (255 // NUM_PIXELS ), 0
107+
108+
109+ # Main program
110+
111+ # Set up NeoPixels and turn them all off.
112+ pixels = neopixel .NeoPixel (board .A1 , NUM_PIXELS , brightness = 0.1 , auto_write = False )
113+ pixels .fill (0 )
114+ pixels .show ()
115+
116+ mic = audiobusio .PDMIn (board .MICROPHONE_CLOCK , board .MICROPHONE_DATA ,
117+ sample_rate = 16000 , bit_depth = 16 )
118+
119+ # Record an initial sample to calibrate. Assume it's quiet when we start.
120+ samples = array .array ('H' , [0 ] * NUM_SAMPLES )
121+ mic .record (samples , len (samples ))
122+ # Set lowest level to expect, plus a little.
123+ input_floor = normalized_rms (samples ) + 30
124+ # OR: used a fixed floor
125+ # input_floor = 50
126+
127+ # You might want to print the input_floor to help adjust other values.
128+ print (input_floor )
129+
130+ # Corresponds to sensitivity: lower means more pixels light up with lower sound
131+ # Adjust this as you see fit.
132+ input_ceiling = input_floor + 100
133+
134+ peak = 0
135+
136+ # Cusomize LED Animations ------------------------------------------------------
137+ rainbow = Rainbow (pixels , speed = 0 , period = 6 , name = "rainbow" , step = 2.4 )
138+ rainbow_chase = RainbowChase (pixels , speed = 0.1 , size = 5 , spacing = 5 , step = 5 )
139+ chase = Chase (pixels , speed = 0.2 , color = ORANGE , size = 2 , spacing = 6 )
140+ rainbow_comet = RainbowComet (pixels , speed = 0.1 , tail_length = 30 , bounce = True )
141+ rainbow_comet2 = RainbowComet (
142+ pixels , speed = 0.1 , tail_length = 104 , colorwheel_offset = 80 , bounce = True
143+ )
144+ rainbow_comet3 = RainbowComet (
145+ pixels , speed = 0 , tail_length = 25 , colorwheel_offset = 80 , step = 4 , bounce = False
146+ )
147+ strum = RainbowComet (
148+ pixels , speed = 0.1 , tail_length = 25 , bounce = False , colorwheel_offset = 50 , step = 4
149+ )
150+ sparkle = Sparkle (pixels , speed = 0.1 , color = BLUE , num_sparkles = 10 )
151+ sparkle2 = Sparkle (pixels , speed = 0.5 , color = PURPLE , num_sparkles = 4 )
152+ off = Solid (pixels , color = BLACK )
153+
154+ # Animations Playlist - reorder as desired. AnimationGroups play at the same time
155+ animations = AnimationSequence (
156+
157+ rainbow_comet2 , #
158+ rainbow_comet , #
159+ chase , #
160+ rainbow_chase , #
161+ rainbow , #
162+
163+ AnimationGroup (
164+ sparkle ,
165+ strum ,
166+ ),
167+ AnimationGroup (
168+ sparkle2 ,
169+ rainbow_comet3 ,
170+ ),
171+ off ,
172+ auto_clear = True ,
173+ auto_reset = True ,
174+ )
175+
176+
177+ MODE = 1
178+ LASTMODE = 1 # start up in sound reactive mode
179+ i = 0
180+ # Are we already advertising?
181+ advertising = False
182+
183+ while True :
184+ animations .animate ()
185+ if not ble .connected and not advertising :
186+ ble .start_advertising (advertisement )
187+ advertising = True
188+
189+ # Are we connected via Bluetooth now?
190+ if ble .connected :
191+ # Once we're connected, we're not advertising any more.
192+ advertising = False
193+ # Have we started to receive a packet?
194+ if uart .in_waiting :
195+ packet = Packet .from_stream (uart )
196+ if isinstance (packet , ColorPacket ):
197+ # Set all the pixels to one color and stay there.
198+ pixels .fill (packet .color )
199+ pixels .show ()
200+ MODE = 2
201+ elif isinstance (packet , ButtonPacket ):
202+ if packet .pressed :
203+ if packet .button == ButtonPacket .BUTTON_1 :
204+ animations .activate (1 )
205+ elif packet .button == ButtonPacket .BUTTON_2 :
206+ MODE = 1
207+ animations .activate (2 )
208+ elif packet .button == ButtonPacket .BUTTON_3 :
209+ MODE = 1
210+ animations .activate (3 )
211+ elif packet .button == ButtonPacket .BUTTON_4 :
212+ MODE = 4
213+
214+ elif packet .button == ButtonPacket .UP :
215+ pixels .brightness = pixels .brightness + 0.1
216+ pixels .show ()
217+ if pixels .brightness > 1 :
218+ pixels .brightness = 1
219+ elif packet .button == ButtonPacket .DOWN :
220+ pixels .brightness = pixels .brightness - 0.1
221+ pixels .show ()
222+ if pixels .brightness < 0.1 :
223+ pixels .brightness = 0.1
224+ elif packet .button == ButtonPacket .RIGHT :
225+ MODE = 1
226+ animations .next ()
227+ elif packet .button == ButtonPacket .LEFT :
228+ animations .activate (7 )
229+ animations .animate ()
230+
231+ if MODE == 2 :
232+ animations .freeze ()
233+ if MODE == 4 :
234+ animations .freeze ()
235+ pixels .fill (YELLOW )
236+ mic .record (samples , len (samples ))
237+ magnitude = normalized_rms (samples )
238+ # You might want to print this to see the values.
239+ #print(magnitude)
240+
241+ # Compute scaled logarithmic reading in the range 0 to NUM_PIXELS
242+ c = log_scale (constrain (magnitude , input_floor , input_ceiling ),
243+ input_floor , input_ceiling , 0 , NUM_PIXELS )
244+
245+ # Light up pixels that are below the scaled and interpolated magnitude.
246+ #pixels.fill(0)
247+ for i in range (NUM_PIXELS ):
248+ if i < c :
249+ pixels [i ] = volume_color (i )
250+ # Light up the peak pixel and animate it slowly dropping.
251+ if c >= peak :
252+ peak = min (c , NUM_PIXELS - 1 )
253+ elif peak > 0 :
254+ peak = peak - 0.01
255+ if peak > 0 :
256+ pixels [int (peak )] = PEAK_COLOR
257+ pixels .show ()
0 commit comments