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