Lightsaber Code Changes
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Lightsaber Code Changes

by BrandonRedd on Sun Dec 29, 2019 5:40 pm

Hey everyone! I'm completely new to coding and CircuitPython in general, so I'm looking for a little help in adjusting some prewritten code by Adafruit. I've been following Adafruit's Prop-Maker lightsaber tutorial, so the whole thing is running off a M4 Express and Prop-Maker featherwing. Using the code, the lightsaber plays one 'swing' sound when swung as well as plays one 'hit' sound when the blade is hit hard enough. How can I change the code to be able to add more swing and hit sounds that randomly play when swung? I've added more .wav files named hit2.wav, hit3.wav and similarly named files for the swings to the sounds folder on the board, so all I need to do is change the code.

The current set up also has the neopixels flash white and then fade back into the idle color. Unfortunately, the white clash animation is WAY too long. How can I make the white flash around half of a second and then immediately jump back to the idle color without the weird fade? I was able to adjust the slow blade animation by messing with the code, but can't figure out how to adjust the clash animation. Any help would be greatly appreciated!
Code: Select all | TOGGLE FULL SIZE
# pylint: disable=bare-except

import time
import math
from digitalio import DigitalInOut, Direction, Pull
import audioio
import busio
import board
import neopixel
import adafruit_lis3dh
import gc

COLOR = (0, 255, 255) #cyan

# CUSTOMIZE SENSITIVITY HERE: smaller numbers = more sensitive to motion

POWER_PIN = board.D10
SWITCH_PIN = board.D9

enable = DigitalInOut(POWER_PIN) 
enable.direction = Direction.OUTPUT
enable.value =False

red_led = DigitalInOut(board.D11)
red_led.direction = Direction.OUTPUT
green_led = DigitalInOut(board.D12)
green_led.direction = Direction.OUTPUT
blue_led = DigitalInOut(board.D13)
blue_led.direction = Direction.OUTPUT

audio = audioio.AudioOut(board.A0)     # Speaker
mode = 0                               # Initial mode = OFF

strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
strip.fill(0)                          # NeoPixels off ASAP on startup

switch = DigitalInOut(SWITCH_PIN)
switch.direction = Direction.INPUT
switch.pull = Pull.UP


# Set up accelerometer on I2C bus, 4G range:
i2c = busio.I2C(board.SCL, board.SDA)
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
accel.range = adafruit_lis3dh.RANGE_4_G

# "Idle" color is 1/4 brightness, "swinging" color is full brightness...
COLOR_IDLE = (int(COLOR[0] / 3), int(COLOR[1] / 3), int(COLOR[2] / 3))
COLOR_HIT = (255, 255, 255)  # "hit" color is 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).
    print("playing", name)
        wave_file = open('sounds/' + name + '.wav', 'rb')
        wave = audioio.WaveFile(wave_file)
        audio.play(wave, loop=loop)

def power(sound, duration, reverse):
    Animate NeoPixels with accompanying sound effect for power on / off.
    @param sound:    sound name (similar format to play_wav() above)
    @param duration: estimated duration of sound, in seconds (>0.0)
    @param reverse:  if True, do power-off effect (reverses animation)
    if reverse:
        prev = NUM_PIXELS
        prev = 0
    gc.collect()                   # Tidy up RAM now so animation's smoother
    start_time = time.monotonic()  # Save audio start time
    while True:
        elapsed = time.monotonic() - start_time  # Time spent playing sound
        if elapsed > duration:                   # Past sound duration?
            break                                # Stop animating
        fraction = elapsed / duration            # Animation time, 0.0 to 1.0
        if reverse:
            fraction = 1.0 - fraction            # 1.0 to 0.0 if reverse
        fraction = math.pow(fraction, 0.5)       # Apply nonlinear curve
        threshold = int(NUM_PIXELS * fraction + 0.5)
        num = threshold - prev # Number of pixels to light on this pass
        if num != 0:
            if reverse:
                strip[threshold:prev] = [0] * -num
                strip[prev:threshold] = [COLOR_IDLE] * num
            # NeoPixel writes throw off time.monotonic() ever so slightly
            # because interrupts are disabled during the transfer.
            # We can compensate somewhat by adjusting the start time
            # back by 30 microseconds per pixel.
            start_time -= NUM_PIXELS * 0.00003
            prev = threshold

    if reverse:
        strip.fill(0)                            # At end, ensure strip is off
        strip.fill(COLOR_IDLE)                   # or all pixels set on
    while audio.playing:                         # Wait until audio done

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))

# Main program loop, repeats indefinitely
while True:

    red_led.value = True

    if not switch.value:                    # button pressed?
        if mode == 0:                       # If currently off...
            enable.value = True
            power('on', .4, False)         # Power up!
            play_wav('idle', loop=True)     # Play background hum sound
            mode = 1                        # ON (idle) mode now
        else:                               # else is currently on...
            power('off', .4, True)        # Power down
            mode = 0                        # OFF mode now
            enable.value = False
        while not switch.value:             # Wait for button release
            time.sleep(0.2)                 # to avoid repeated triggering

    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 for this, assuming Hallowing is mounted
        # sideways to stick.  Also, square root isn't needed, since we're
        # just comparing thresholds...use squared values instead, save math.)
        if accel_total > HIT_THRESHOLD:   # Large acceleration = HIT
            TRIGGER_TIME = time.monotonic() # Save initial time of hit
            play_wav('hit')                 # Start playing 'hit' sound
            COLOR_ACTIVE = COLOR_HIT        # Set color to fade from
            mode = 3                        # HIT mode
        elif mode is 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
            TRIGGER_TIME = time.monotonic() # Save initial time of swing
            play_wav('swing')               # Start playing 'swing' sound
            COLOR_ACTIVE = COLOR_SWING      # Set color to fade from
            mode = 2                        # SWING mode
        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_IDLE, blend))
            else:                           # No sound now, but still MODE > 1
                play_wav('idle', loop=True) # Resume background hum
                strip.fill(COLOR_IDLE)      # Set to idle color
                mode = 1                    # IDLE mode now

Posts: 3
Joined: Thu Dec 19, 2019 4:09 am

Re: Lightsaber Code Changes

by tannewt on Mon Dec 30, 2019 4:04 pm

Hi Brandon, for the random playback, you'll need to do two things:
[*]Gather a list of all valid filenames. You can either do this with a loop for the numbers you know or filter the results of os.listdir for valid filenames.
[*] Use random.choice to pick one of them: https://circuitpython.readthedocs.io/en ... dom.choice

To remove the fade of the LEDs, look at the last section that deals with the blend. You can have it set the IDLE colors instead.

Hope that helps!

Posts: 1490
Joined: Thu Oct 06, 2016 8:48 pm

Re: Lightsaber Code Changes

by syndicate_x on Sat Jan 25, 2020 4:51 am

could you elaborate more on this? Or is it possible you could give a simple example using the code he provided? I too am curious to add more sounds.

Posts: 10
Joined: Fri Jan 10, 2020 5:41 am

Re: Lightsaber Code Changes

by tannewt on Mon Jan 27, 2020 12:55 am

I'd suggest taking a look at the slideshow library. It does something similar but for images: https://github.com/adafruit/Adafruit_Ci ... ow.py#L176

Posts: 1490
Joined: Thu Oct 06, 2016 8:48 pm

Re: Lightsaber Code Changes

by Kyuga on Mon Jan 27, 2020 8:40 pm

Check out my code, specially this call: play_wav(random.choice(hit_sounds)) . "hit_sounds" is a list of wav files declared at the beginning. You also need to import random library, see at the beggining.

Warning: This is not a tested code, but that part I took it from the RGB 3watt LED lightsaber example (the other lightsaber build from the creator and autor pixil3d )

Also checkout my thread, I am too working on my lightsaber prop. Hope someone could help me with some button programming and stuff... :(

Posts: 18
Joined: Sun Jan 26, 2020 6:23 am

Please be positive and constructive with your questions and comments.