|
| 1 | +""" |
| 2 | +LED Ukulele with Feather Sense and PropMaker Wing |
| 3 | +Adafruit invests time and resources providing this open source code. |
| 4 | +Please support Adafruit and open source hardware by purchasing |
| 5 | +products from Adafruit! |
| 6 | +Written by Erin St Blaine & Limor Fried for Adafruit Industries |
| 7 | +Copyright (c) 2019-2020 Adafruit Industries |
| 8 | +Licensed under the MIT license. |
| 9 | +All text above must be included in any redistribution. |
| 10 | +
|
| 11 | +MODES: |
| 12 | +0 = off/powerup, 1 = sound reactive, 2 = non-sound reactive, 3 = tilt |
| 13 | +Pluck high A on the E string to toggle sound reactive mode on or off |
| 14 | +Pluck high A♭ on the E string to cycle through the animation modes |
| 15 | +""" |
| 16 | + |
| 17 | +import time |
| 18 | +import array |
| 19 | +import digitalio |
| 20 | +import audiobusio |
| 21 | +import board |
| 22 | +import neopixel |
| 23 | +import ulab.numerical as numerical |
| 24 | +import ulab.extras as extras |
| 25 | +import ulab as np |
| 26 | +import adafruit_lsm6ds |
| 27 | +from adafruit_led_animation.helper import PixelMap |
| 28 | +from adafruit_led_animation.sequence import AnimationSequence |
| 29 | +from adafruit_led_animation.group import AnimationGroup |
| 30 | +from adafruit_led_animation.animation.sparkle import Sparkle |
| 31 | +from adafruit_led_animation.animation.rainbow import Rainbow |
| 32 | +from adafruit_led_animation.animation.rainbowchase import RainbowChase |
| 33 | +from adafruit_led_animation.animation.rainbowcomet import RainbowComet |
| 34 | +from adafruit_led_animation.animation.chase import Chase |
| 35 | +from adafruit_led_animation.animation.comet import Comet |
| 36 | +from adafruit_led_animation.color import colorwheel |
| 37 | +from adafruit_led_animation.color import ( |
| 38 | + BLACK, |
| 39 | + RED, |
| 40 | + ORANGE, |
| 41 | + BLUE, |
| 42 | + PURPLE, |
| 43 | + WHITE, |
| 44 | +) |
| 45 | + |
| 46 | +MAX_BRIGHTNESS = 0.3 #set max brightness for sound reactive mode |
| 47 | +NORMAL_BRIGHTNESS = 0.1 #set brightness for non-reactive mode |
| 48 | +VOLUME_CALIBRATOR = 50 #multiplier for brightness mapping |
| 49 | +ROCKSTAR_TILT_THRESHOLD = 200 #shake threshold |
| 50 | +SOUND_THRESHOLD = 430000 #main strum or pluck threshold |
| 51 | + |
| 52 | +# Set to the length in seconds for the animations |
| 53 | +POWER_ON_DURATION = 1.3 |
| 54 | +ROCKSTAR_TILT_DURATION = 1 |
| 55 | + |
| 56 | +NUM_PIXELS = 104 # Number of pixels used in project |
| 57 | +NEOPIXEL_PIN = board.D5 |
| 58 | +POWER_PIN = board.D10 |
| 59 | + |
| 60 | +enable = digitalio.DigitalInOut(POWER_PIN) |
| 61 | +enable.direction = digitalio.Direction.OUTPUT |
| 62 | +enable.value = False |
| 63 | + |
| 64 | +i2c = board.I2C() |
| 65 | + |
| 66 | +pixels = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False) |
| 67 | +pixels.fill(0) # NeoPixels off ASAP on startup |
| 68 | +pixels.show() |
| 69 | + |
| 70 | + |
| 71 | +#PIXEL MAPS: Used for reordering pixels so the animations can run in different configurations. |
| 72 | +#My LED strips inside the neck are accidentally swapped left-right, |
| 73 | +#so these maps also correct for that |
| 74 | + |
| 75 | + |
| 76 | +#Bottom up along both sides at once |
| 77 | +pixel_map_reverse = PixelMap(pixels, [ |
| 78 | + 0, 103, 1, 102, 2, 101, 3, 100, 4, 99, 5, 98, 6, 97, 7, 96, 8, 95, 9, 94, 10, |
| 79 | + 93, 11, 92, 12, 91, 13, 90, 14, 89, 15, 88, 16, 87, 17, 86, 18, 85, 19, 84, 20, |
| 80 | + 83, 21, 82, 22, 81, 23, 80, 24, 79, 25, 78, 26, 77, 27, 76, 28, 75, 29, 74, 30, |
| 81 | + 73, 31, 72, 32, 71, 33, 70, 34, 69, 35, 68, 36, 67, 37, 66, 38, 65, 39, 64, 40, |
| 82 | + 63, 41, 62, 42, 61, 43, 60, 44, 59, 45, 58, 46, 57, 47, 56, 48, 55, 49, 54, 50, |
| 83 | + 53, 51, 52, |
| 84 | + ], individual_pixels=True) |
| 85 | + |
| 86 | +#Starts at the bottom and goes around clockwise |
| 87 | +pixel_map_around = PixelMap(pixels, [ |
| 88 | + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, |
| 89 | + 21, 22, 23, 24, 25, 26, 27, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, |
| 90 | + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, |
| 91 | + 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, |
| 92 | + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, |
| 93 | + 95, 96, 97, 98, 99, 100, 101, 102, 103, |
| 94 | + ], individual_pixels=True) |
| 95 | + |
| 96 | +#Radiates from the center outwards like a starburst |
| 97 | +pixel_map_radiate = PixelMap(pixels, [ |
| 98 | + 75, 73, 76, 27, 28, 74, 77, 26, 29, 73, 78, 25, 30, 72, 79, 24, 31, 71, 80, |
| 99 | + 23, 32, 70, 81, 22, 33, 69, 82, 21, 34, 68, 83, 20, 35, 67, 84, 19, 36, 66, |
| 100 | + 85, 18, 37, 65, 38, 86, 17, 64, 39, 87, 16, 63, 40, 88, 15, 62, 41, 89, 14, |
| 101 | + 61, 42, 90, 13, 60, 43, 91, 12, 59, 44, 92, 11, 58, 45, 93, 10, 57, 46, 94, |
| 102 | + 9, 56, 47, 95, 8, 55, 48, 96, 7, 54, 49, 97, 6, 53, 50, 98, 5, 52, 51, 99, |
| 103 | + 4, 100, 3, 101, 2, 102, 1, 103, 0, |
| 104 | + ], individual_pixels=True) |
| 105 | + |
| 106 | +#Top down along both sides at once |
| 107 | +pixel_map_sweep = PixelMap(pixels, [ |
| 108 | + 51, 52, 50, 53, 49, 54, 48, 55, 47, 56, 46, 57, 45, 58, 44, 59, 43, 60, 42, 61, |
| 109 | + 41, 62, 40, 63, 39, 64, 38, 65, 37, 66, 36, 67, 35, 68, 34, 69, 33, 70, 32, 71, |
| 110 | + 31, 72, 30, 73, 29, 74, 28, 75, 27, 76, 27, 77, 26, 78, 25, 79, 24, 80, 23, 81, |
| 111 | + 22, 82, 21, 83, 20, 84, 19, 85, 18, 86, 17, 87, 16, 88, 15, 89, 14, 90, 13, 91, |
| 112 | + 12, 92, 11, 93, 10, 94, 9, 95, 8, 96, 7, 97, 6, 98, 5, 99, 4, 100, 3, 101, 2, 102, 1, 103, 0 |
| 113 | + ], individual_pixels=True) |
| 114 | + |
| 115 | +#Every other pixel, starting at the bottom and going upwards along both sides |
| 116 | +pixel_map_skip = PixelMap(pixels, [ |
| 117 | + 0, 103, 2, 101, 4, 99, 6, 97, 8, 95, 10, 93, 12, 91, 14, 89, 16, 87, 18, 85, 20, |
| 118 | + 83, 22, 81, 24, 79, 26, 77, 29, 74, 31, 72, 33, 70, 35, 68, 37, 66, 39, 64, 41, |
| 119 | + 62, 43, 60, 45, 58, 47, 56, 49, 54, 51, 52, |
| 120 | + ], individual_pixels=True) |
| 121 | + |
| 122 | +pixel_map = [ |
| 123 | + pixel_map_reverse, |
| 124 | + pixel_map_around, |
| 125 | + pixel_map_radiate, |
| 126 | + pixel_map_sweep, |
| 127 | + pixel_map_skip, |
| 128 | +] |
| 129 | + |
| 130 | +#Set up accelerometer & mic |
| 131 | +sensor = adafruit_lsm6ds.LSM6DS33(i2c) |
| 132 | +mic = audiobusio.PDMIn(board.MICROPHONE_CLOCK, |
| 133 | + board.MICROPHONE_DATA, |
| 134 | + sample_rate=16000, |
| 135 | + bit_depth=16) |
| 136 | + |
| 137 | +NUM_SAMPLES = 256 |
| 138 | +samples_bit = array.array('H', [0] * (NUM_SAMPLES+3)) |
| 139 | + |
| 140 | +def power_on(duration): |
| 141 | + """ |
| 142 | + Animate NeoPixels for power on. |
| 143 | + """ |
| 144 | + start_time = time.monotonic() # Save start time |
| 145 | + while True: |
| 146 | + elapsed = time.monotonic() - start_time # Time spent |
| 147 | + if elapsed > duration: # Past duration? |
| 148 | + break # Stop animating |
| 149 | + powerup.animate() |
| 150 | + |
| 151 | +def rockstar_tilt(duration): |
| 152 | + """ |
| 153 | + Tilt animation - lightning effect with a rotating color |
| 154 | + :param duration: duration of the animation, in seconds (>0.0) |
| 155 | + """ |
| 156 | + tilt_time = time.monotonic() # Save start time |
| 157 | + while True: |
| 158 | + elapsed = time.monotonic() - tilt_time # Time spent |
| 159 | + if elapsed > duration: # Past duration? |
| 160 | + break # Stop animating |
| 161 | + pixels.brightness = MAX_BRIGHTNESS |
| 162 | + pixels.fill(TILT_COLOR) |
| 163 | + pixels.show() |
| 164 | + time.sleep(0.01) |
| 165 | + pixels.fill(BLACK) |
| 166 | + pixels.show() |
| 167 | + time.sleep(0.03) |
| 168 | + pixels.fill(WHITE) |
| 169 | + pixels.show() |
| 170 | + time.sleep(0.02) |
| 171 | + pixels.fill(BLACK) |
| 172 | + pixels.show() |
| 173 | + time.sleep(0.005) |
| 174 | + pixels.fill(TILT_COLOR) |
| 175 | + pixels.show() |
| 176 | + time.sleep(0.01) |
| 177 | + pixels.fill(BLACK) |
| 178 | + pixels.show() |
| 179 | + time.sleep(0.03) |
| 180 | + |
| 181 | +# Cusomize LED Animations ------------------------------------------------------ |
| 182 | +powerup = RainbowComet(pixel_map[3], speed=0, tail_length=25, bounce=False) |
| 183 | +rainbow = Rainbow(pixel_map[4], speed=0, period=6, name="rainbow", step=2.4) |
| 184 | +rainbow_chase = RainbowChase(pixel_map[3], speed=0, size=3, spacing=15, step=10) |
| 185 | +rainbow_chase2 = RainbowChase(pixel_map[2], speed=0, size=10, spacing=1, step=18) |
| 186 | +chase = Chase(pixel_map[1], speed=0.1, color=RED, size=1, spacing=6) |
| 187 | +rainbow_comet = RainbowComet(pixel_map[2], speed=0, tail_length=80, bounce=True) |
| 188 | +rainbow_comet2 = RainbowComet( |
| 189 | + pixel_map[0], speed=0, tail_length=104, colorwheel_offset=80, bounce=True |
| 190 | + ) |
| 191 | +rainbow_comet3 = RainbowComet( |
| 192 | + pixel_map[1], speed=0, tail_length=25, colorwheel_offset=80, step=4, bounce=False |
| 193 | + ) |
| 194 | +strum = RainbowComet( |
| 195 | + pixel_map[3], speed=0, tail_length=25, bounce=False, colorwheel_offset=50, step=4 |
| 196 | + ) |
| 197 | +lava = Comet(pixel_map[3], speed=0.01, color=ORANGE, tail_length=40, bounce=False) |
| 198 | +sparkle = Sparkle(pixel_map[4], speed=0.01, color=BLUE, num_sparkles=10) |
| 199 | +sparkle2 = Sparkle(pixel_map[1], speed=0.05, color=PURPLE, num_sparkles=4) |
| 200 | + |
| 201 | +# Animations Playlist - reorder as desired. AnimationGroups play at the same time |
| 202 | +animations = AnimationSequence( |
| 203 | + rainbow, |
| 204 | + rainbow_chase, |
| 205 | + rainbow_chase2, |
| 206 | + chase, |
| 207 | + lava, |
| 208 | + rainbow_comet, |
| 209 | + rainbow_comet2, |
| 210 | + AnimationGroup( |
| 211 | + sparkle, |
| 212 | + strum, |
| 213 | + ), |
| 214 | + AnimationGroup( |
| 215 | + sparkle2, |
| 216 | + rainbow_comet3, |
| 217 | + ), |
| 218 | + auto_clear=True, |
| 219 | + auto_reset=True, |
| 220 | +) |
| 221 | + |
| 222 | + |
| 223 | +MODE = 0 |
| 224 | +LASTMODE = 1 # start up in sound reactive mode |
| 225 | +i = 0 |
| 226 | + |
| 227 | +# Main loop |
| 228 | +while True: |
| 229 | + i = (i + 0.5) % 256 # run from 0 to 255 |
| 230 | + TILT_COLOR = colorwheel(i) |
| 231 | + if MODE == 0: # If currently off... |
| 232 | + enable.value = True |
| 233 | + power_on(POWER_ON_DURATION) # Power up! |
| 234 | + MODE = LASTMODE |
| 235 | + |
| 236 | + elif MODE >= 1: # If not OFF MODE... |
| 237 | + mic.record(samples_bit, len(samples_bit)) |
| 238 | + samples = np.array(samples_bit[3:]) |
| 239 | + spectrum = extras.spectrogram(samples) |
| 240 | + spectrum = spectrum[:128] |
| 241 | + spectrum[0] = 0 |
| 242 | + spectrum[1] = 0 |
| 243 | + peak_idx = numerical.argmax(spectrum) |
| 244 | + peak_freq = peak_idx * 16000 / 256 |
| 245 | +# print((peak_idx, peak_freq, spectrum[peak_idx])) |
| 246 | + magnitude = spectrum[peak_idx] |
| 247 | +# time.sleep(1) |
| 248 | + if peak_freq == 812.50 and magnitude > SOUND_THRESHOLD: |
| 249 | + animations.next() |
| 250 | + time.sleep(1) |
| 251 | + if peak_freq == 875 and magnitude > SOUND_THRESHOLD: |
| 252 | + if MODE == 1: |
| 253 | + MODE = 2 |
| 254 | + print("mode = 2") |
| 255 | + LASTMODE = 2 |
| 256 | + time.sleep(1) |
| 257 | + elif MODE == 2: |
| 258 | + MODE = 1 |
| 259 | + print("mode = 1") |
| 260 | + LASTMODE = 1 |
| 261 | + time.sleep(1) |
| 262 | + # Read accelerometer |
| 263 | + x, y, z = sensor.acceleration |
| 264 | + accel_total = x * x + y * y # x=tilt, y=rotate |
| 265 | +# print (accel_total) |
| 266 | + if accel_total > ROCKSTAR_TILT_THRESHOLD: |
| 267 | + MODE = 3 |
| 268 | + print("Tilted: ", accel_total) |
| 269 | + if MODE == 1: |
| 270 | + VOLUME = magnitude / (VOLUME_CALIBRATOR * 100000) |
| 271 | + if VOLUME > MAX_BRIGHTNESS: |
| 272 | + VOLUME = MAX_BRIGHTNESS |
| 273 | +# print(VOLUME) |
| 274 | + pixels.brightness = VOLUME |
| 275 | +# time.sleep(2) |
| 276 | + animations.animate() |
| 277 | + elif MODE == 2: |
| 278 | + pixels.brightness = NORMAL_BRIGHTNESS |
| 279 | + animations.animate() |
| 280 | + elif MODE == 3: |
| 281 | + rockstar_tilt(ROCKSTAR_TILT_DURATION) |
| 282 | + MODE = LASTMODE |
0 commit comments