# cpb_touch_diffusion.py

# Use touch sensitive pads and sound to control a lighting display.  The display
# models a diffusion process, with each touch pad contributing an activation.  A
# loud sound or physical tap will trigger a single flash.

# This program uses only onboard hardware: capacitive touch sensing, sound level
# sensor, NeoPixel LEDs.

# ----------------------------------------------------------------
# Import the standard Python math library and time functions.
import math, time

# Import the board-specific input/output library.
from adafruit_circuitplayground import cp

# Configure the NeoPixel LED array for bulk update using show().
cp.pixels.auto_write = False

# ----------------------------------------------------------------
# Initialize global variables for the main loop.

# The display update rate is precisely regulated using the following timer variables.
# The default is 20 Hz frame rate as set by step_interval.
step_timer    = 0.0
last_time     = time.monotonic()
step_interval = 0.05

# The core model state is an array of state variables.  Note that the state is
# modeled for 12 uniformly spaced positions, even though only ten are populated
# with NeoPixels.  This state is abstract but might represent heat moving
# through a ring of material.
state = [0.0] * 12  # create 12 element array of zeros

# ----------------------------------------------------------------
# Begin the main processing loop.
while True:

    # Measure elapsed time and wait until the update timer has elapsed.
    now = time.monotonic()
    interval = now - last_time
    last_time = now
    step_timer -= interval
    if step_timer < 0.0:
        step_timer += step_interval

        # Apply a first-order diffusion model to the state.  This loop creates a new
        # array to replace the previous state.  The modulo operator (%) is used to 'wrap
        # around' the end of the array to close the loop.
        N = len(state)
        new = [0.0] * N
        for i in range(N):
            new[i] = 0.9 * state[i] + 0.05 * state[(i-1) % N] + 0.05 * state[(i+1) % N]

        # Replace the previous state.
        state = new

        # Apply a slow decay to represent leakage outside the ring.
        for i in range(N):
            state[i] = 0.97 * state[i]

        # Apply touch inputs as excitation blended into nearby samples.
        if cp.touch_A1: state[8]  += 0.1 * (1.0 - state[ 8])
        if cp.touch_A2: state[10] += 0.1 * (1.0 - state[10])
        if cp.touch_A3: state[11] += 0.1 * (1.0 - state[11])
        if cp.touch_A4: state[ 2] += 0.1 * (1.0 - state[ 2])
        if cp.touch_A5: state[ 3] += 0.1 * (1.0 - state[ 3])
        if cp.touch_A6: state[ 4] += 0.1 * (1.0 - state[ 4])

        # Update the LED colors to reflect the state.  Note that states 0 and 6
        # are invisible as these positions do not have LEDs.
        for i, s in enumerate(state):
            if i != 0 and i != 6:
                # Clamp the state value between 0.0 and 1.0.
                value = min(max(s, 0.0), 1.0)

                # Map the state index to an LED position index.
                pos = i-1 if i < 6 else i-2

                # Map 0.0 to dim blue and 1.0 to bright red.
                cp.pixels[pos] = (255 * value, 0, 50 * (1.0 - value))

        # If a loud sound is detected, override the normal display to flash white.
        # A physical tap will also trigger the flash.
        if cp.loud_sound():
            cp.pixels.fill((255,255,255))

        # Send new data to the physical LED chain.
        cp.pixels.show()
