diff --git a/Halo_Energy_Sword/code.py b/Halo_Energy_Sword/code.py new file mode 100644 index 000000000..c5c1d70bc --- /dev/null +++ b/Halo_Energy_Sword/code.py @@ -0,0 +1,215 @@ +# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries +# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +""" +RP2040 Prop-Maker Feather Energy Sword +Adafruit invests time and resources providing this open source code. +Please support Adafruit and open source hardware by purchasing +products from Adafruit! +Written by Kattni Rembor & Limor Fried for Adafruit Industries +Copyright (c) 2019-2020 Adafruit Industries +Licensed under the MIT license. +All text above must be included in any redistribution. +""" + +import time +import random +import digitalio +import audiocore +import audiobusio +import board +import neopixel +import adafruit_lis3dh + +# CUSTOMISE COLORS HERE: +COLOR = (0, 120, 120) # Default idle is light blue +ALT_COLOR = (255, 0, 250) # hit color is purple + +# CUSTOMISE IDLE PULSE SPEED HERE: 0 is fast, above 0 slows down +IDLE_PULSE_SPEED = 0 # Default is 0 seconds +SWING_BLAST_SPEED = 0.0007 + +# CUSTOMISE BRIGHTNESS HERE: must be a number between 0 and 1 +IDLE_PULSE_BRIGHTNESS_MIN = 0.2 # Default minimum idle pulse brightness +IDLE_PULSE_BRIGHTNESS_MAX = 1 # Default maximum idle pulse brightness + +# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion +HIT_THRESHOLD = 250 +SWING_THRESHOLD = 150 + +# Set to the length in seconds of the "on.wav" file +POWER_ON_SOUND_DURATION = 1.7 + +NUM_PIXELS = 73 # Number of pixels used in project +NEOPIXEL_PIN = board.EXTERNAL_NEOPIXELS + +enable = digitalio.DigitalInOut(board.EXTERNAL_POWER) +enable.direction = digitalio.Direction.OUTPUT +enable.value = True + +strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False) +strip.fill(0) # NeoPixels off ASAP on startup +strip.show() + +# i2s audio +audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) +wave_file = None + +# Set up accelerometer on I2C bus, 4G range: +i2c = board.I2C() # uses board.SCL and board.SDA +# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller +accel = adafruit_lis3dh.LIS3DH_I2C(i2c) +accel.range = adafruit_lis3dh.RANGE_4_G + +COLOR_IDLE = COLOR # 'idle' color is the default +COLOR_HIT = ALT_COLOR # "hit" color is ALT_COLOR set above +COLOR_SWING = (255, 255, 255) # "swing" color white + + +def play_wav(name, loop=False): + """ + Play a WAV file in the 'sounds' directory. + :param name: partial file name string, complete name will be built around + this, e.g. passing 'foo' will play file 'sounds/foo.wav'. + :param loop: if True, sound will repeat indefinitely (until interrupted + by another sound). + """ + global wave_file # pylint: disable=global-statement + print("playing", name) + if wave_file: + wave_file.close() + try: + wave_file = open('sounds/' + name + '.wav', 'rb') + wave = audiocore.WaveFile(wave_file) + audio.play(wave, loop=loop) + except OSError: + pass # we'll just skip playing then + + +def power_on(sound, duration): + """ + Animate NeoPixels with accompanying sound effect for power on. + :param sound: sound name (similar format to play_wav() above) + :param duration: estimated duration of sound, in seconds (>0.0) + """ + prev = 0 + start_time = time.monotonic() # Save audio start time + play_wav(sound) + while True: + elapsed = time.monotonic() - start_time # Time spent playing sound + if elapsed > duration: # Past sound duration? + break # Stop animating + animation_time = elapsed / duration # Animation time, 0.0 to 1.0 + threshold = int(NUM_PIXELS * animation_time + 0.5) + num = threshold - prev # Number of pixels to light on this pass + if num != 0: + strip[prev:threshold] = [ALT_COLOR] * num + strip.show() + prev = threshold + + +def mix(color_1, color_2, weight_2): + """ + Blend between two colors with a given ratio. + :param color_1: first color, as an (r,g,b) tuple + :param color_2: second color, as an (r,g,b) tuple + :param weight_2: Blend weight (ratio) of second color, 0.0 to 1.0 + :return (r,g,b) tuple, blended color + """ + if weight_2 < 0.0: + weight_2 = 0.0 + elif weight_2 > 1.0: + weight_2 = 1.0 + weight_1 = 1.0 - weight_2 + return (int(color_1[0] * weight_1 + color_2[0] * weight_2), + int(color_1[1] * weight_1 + color_2[1] * weight_2), + int(color_1[2] * weight_1 + color_2[2] * weight_2)) + +# List of swing wav files without the .wav in the name for use with play_wav() +swing_sounds = [ + 'swing1', + 'swing2', + 'swing3', + 'swing4', +] + +# List of hit wav files without the .wav in the name for use with play_wav() +hit_sounds = [ + 'hit1', + 'hit2', + 'hit3', + 'hit4', +] + + +mode = 0 # Initial mode = OFF + +# Setup idle pulse +idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN # current brightness of idle pulse +idle_increment = 0.01 # Initial idle pulse direction + +# Main loop +while True: + + if mode == 0: # If currently off... + enable.value = True + power_on('on', POWER_ON_SOUND_DURATION) # Power up! + play_wav('idle', loop=True) # Play idle sound now + mode = 1 # Idle mode + + # Setup for idle pulse + idle_brightness = IDLE_PULSE_BRIGHTNESS_MIN + idle_increment = 0.01 + strip.fill([int(c*idle_brightness) for c in COLOR]) + strip.show() + + elif mode >= 1: # If not OFF mode... + x, y, z = accel.acceleration # Read accelerometer + accel_total = x * x + z * z + # (Y axis isn't needed, due to the orientation that the Prop-Maker + # Wing is mounted. Also, square root isn't needed, since we're + # comparing thresholds...use squared values instead.) + if accel_total > HIT_THRESHOLD: # Large acceleration = HIT + TRIGGER_TIME = time.monotonic() # Save initial time of hit + play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound + COLOR_ACTIVE = COLOR_HIT # Set color to fade from + mode = 3 # HIT mode + elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING + TRIGGER_TIME = time.monotonic() # Save initial time of swing + play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds + # make a larson scanner animation_time + strip_backup = strip[0:-1] + for p in range(-1, len(strip)): + for i in range (p-1, p+2): # shoot a 'ray' of 3 pixels + if 0 <= i < len(strip): + strip[i] = COLOR_SWING + strip.show() + time.sleep(SWING_BLAST_SPEED) + if 0 <= (p-1) < len(strip): + strip[p-1] = strip_backup[p-1] # restore previous color at the tail + strip.show() + while audio.playing: + pass # wait till we're done + mode = 2 # we'll go back to idle mode + + elif mode == 1: + # Idle pulse + idle_brightness += idle_increment # Pulse up + if idle_brightness > IDLE_PULSE_BRIGHTNESS_MAX or \ + idle_brightness < IDLE_PULSE_BRIGHTNESS_MIN: # Then... + idle_increment *= -1 # Pulse direction flip + strip.fill([int(c*idle_brightness) for c in COLOR_IDLE]) + strip.show() + time.sleep(IDLE_PULSE_SPEED) # Idle pulse speed set above + elif mode > 1: # If in SWING or HIT mode... + if audio.playing: # And sound currently playing... + blend = time.monotonic() - TRIGGER_TIME # Time since triggered + if mode == 2: # If SWING, + blend = abs(0.5 - blend) * 2.0 # ramp up, down + strip.fill(mix(COLOR_ACTIVE, COLOR, blend)) # Fade from hit/swing to base color + strip.show() + else: # No sound now, but still SWING or HIT modes + play_wav('idle', loop=True) # Resume idle sound + mode = 1 # Return to idle mode diff --git a/Halo_Energy_Sword/sounds/hit1.wav b/Halo_Energy_Sword/sounds/hit1.wav new file mode 100644 index 000000000..261a9698c Binary files /dev/null and b/Halo_Energy_Sword/sounds/hit1.wav differ diff --git a/Halo_Energy_Sword/sounds/hit2.wav b/Halo_Energy_Sword/sounds/hit2.wav new file mode 100644 index 000000000..08275dce5 Binary files /dev/null and b/Halo_Energy_Sword/sounds/hit2.wav differ diff --git a/Halo_Energy_Sword/sounds/hit3.wav b/Halo_Energy_Sword/sounds/hit3.wav new file mode 100644 index 000000000..78308d6cb Binary files /dev/null and b/Halo_Energy_Sword/sounds/hit3.wav differ diff --git a/Halo_Energy_Sword/sounds/hit4.wav b/Halo_Energy_Sword/sounds/hit4.wav new file mode 100644 index 000000000..a80edf5ae Binary files /dev/null and b/Halo_Energy_Sword/sounds/hit4.wav differ diff --git a/Halo_Energy_Sword/sounds/idle.wav b/Halo_Energy_Sword/sounds/idle.wav new file mode 100644 index 000000000..8f8194488 Binary files /dev/null and b/Halo_Energy_Sword/sounds/idle.wav differ diff --git a/Halo_Energy_Sword/sounds/on.wav b/Halo_Energy_Sword/sounds/on.wav new file mode 100644 index 000000000..42f581765 Binary files /dev/null and b/Halo_Energy_Sword/sounds/on.wav differ diff --git a/Halo_Energy_Sword/sounds/swing1.wav b/Halo_Energy_Sword/sounds/swing1.wav new file mode 100644 index 000000000..9aee36f9e Binary files /dev/null and b/Halo_Energy_Sword/sounds/swing1.wav differ diff --git a/Halo_Energy_Sword/sounds/swing2.wav b/Halo_Energy_Sword/sounds/swing2.wav new file mode 100644 index 000000000..2bf0e7244 Binary files /dev/null and b/Halo_Energy_Sword/sounds/swing2.wav differ diff --git a/Halo_Energy_Sword/sounds/swing3.wav b/Halo_Energy_Sword/sounds/swing3.wav new file mode 100644 index 000000000..a8f9bc81b Binary files /dev/null and b/Halo_Energy_Sword/sounds/swing3.wav differ diff --git a/Halo_Energy_Sword/sounds/swing4.wav b/Halo_Energy_Sword/sounds/swing4.wav new file mode 100644 index 000000000..d9fe2467b Binary files /dev/null and b/Halo_Energy_Sword/sounds/swing4.wav differ