first commit
Sound reactive & bluetooth controlled NeoPixel code for Sunflower Baby Crib Mobile
This commit is contained in:
parent
0e51cdcfe2
commit
290b9e3ac9
1 changed files with 257 additions and 0 deletions
257
Sunflower_Mobile/code.py
Normal file
257
Sunflower_Mobile/code.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
"""
|
||||
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()
|
||||
Loading…
Reference in a new issue