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