From 290b9e3ac9c10650a6a35d12ff403a4f07ba4cd9 Mon Sep 17 00:00:00 2001 From: firepixie Date: Thu, 22 Apr 2021 09:28:16 -0700 Subject: [PATCH] first commit Sound reactive & bluetooth controlled NeoPixel code for Sunflower Baby Crib Mobile --- Sunflower_Mobile/code.py | 257 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 Sunflower_Mobile/code.py diff --git a/Sunflower_Mobile/code.py b/Sunflower_Mobile/code.py new file mode 100644 index 000000000..025981808 --- /dev/null +++ b/Sunflower_Mobile/code.py @@ -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() \ No newline at end of file