# SOUND REACTIVE LIGHT SCULPTURE

#Import Necessary Libraries
import array
import math
import audiobusio
import board
import neopixel
import time
from analogio import AnalogIn

# Import Colors
from adafruit_led_animation.color import (
    AMBER, #(255, 100, 0)
    AQUA, # (50, 255, 255)
    BLACK, #OFF (0, 0, 0)
    BLUE, # (0, 0, 255)
    CYAN, # (0, 255, 255)
    GOLD, # (255, 222, 30)
    GREEN, # (0, 255, 0)
    JADE, # (0, 255, 40)
    MAGENTA, #(255, 0, 20)
    OLD_LACE, # (253, 245, 230)
    ORANGE, # (255, 40, 0)
    PINK, # (242, 90, 255)
    PURPLE, # (180, 0, 255)
    RED, # (255, 0, 0)
    TEAL, # (0, 255, 120)
    WHITE, # (255, 255, 255)
    YELLOW, # (255, 150, 0)
    RAINBOW # a list of colors to cycle through
    # RAINBOW is RED, ORANGE, YELLOW, GREEN, BLUE, and PURPLE ((255, 0, 0), (255, 40, 0), (255, 150, 0), (0, 255, 0), (0, 0, 255), (180, 0, 255))
)

INDIGO = (63, 0, 255)
VIOLET = (127, 0, 255)


# Create the color list to use, you can change the colors in this list to change the color options on the dial! Just make sure its the same length as the original (9 colors)
colors = [RED, MAGENTA, GREEN, TEAL, BLUE, PURPLE, PINK, WHITE, RAINBOW]
RAINBOW_strip = RAINBOW*5
RAINBOW_pixels = ((255, 0, 0), (255, 40, 0), (255, 150, 0), (0, 255, 0), (0, 0, 255), (180, 0, 255), (255, 0, 0), (255, 40, 0), (255, 150, 0), (0, 255, 0))
# Color of the peak pixel.
PEAK_COLOR = (100, 0, 255)
# Number of total pixels - 10 for board 30 for strip
STRIP_NUM_OF_PIXELS = 30
NUM_PIXELS = 10


# Potentiometer for Brightness
potentiometer = AnalogIn(board.A3)
# Potentiometer for Color
potentiometer_1 = AnalogIn(board.A5)
# Potentiometer for Sensitivity
potentiometer_2 = AnalogIn(board.A4)


# 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


# 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

def return_color(color):
    return (colors[i])

# Main program

# Set up NeoPixel strip and turn them all off.
strip_pin = board.A1
strip_pin_1 = board.A2

strip = neopixel.NeoPixel(strip_pin, STRIP_NUM_OF_PIXELS, brightness = 0.1, auto_write = True)
strip_1 = neopixel.NeoPixel(strip_pin_1, STRIP_NUM_OF_PIXELS, brightness = 0.1, auto_write = True)

# Set up NeoPixels on board
pixels = neopixel.NeoPixel(board.NEOPIXEL, 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) + 10
# 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.
# floor + 200-1500

#input_ceiling = input_floor + 1500

peak = 0

while True:
    # Calculate the input_ceiling, this is the sensitivity

    input_ceiling = input_floor + 200 + int((potentiometer_2.value/65535)*1300)

    # Calculate Magnitude
    mic.record(samples, len(samples))
    magnitude = normalized_rms(samples)

    #Brightness potentiometer
    potent_to_brightness = -(potentiometer.value/65535)+(1.1)
    #Color potentiometer
    potent_to_color = int((potentiometer_1.value/65535)*9)

    # Compute scaled logarithmic reading in the range 0 to the number of pixels, for the CPB and both strips
    c = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, NUM_PIXELS)

    c_strip = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, STRIP_NUM_OF_PIXELS/2)

    c_strip_side_2 = log_scale(constrain(magnitude, input_floor, input_ceiling),
                  input_floor, input_ceiling, 0, STRIP_NUM_OF_PIXELS)


    # Make sure both strips and CPB start as black
    strip.fill(0)
    strip_1.fill(0)
    pixels.fill(0)

    # These for loops light up the number of pixels necessary based on our logarithmic scale calculated above!
    for i in range(int(STRIP_NUM_OF_PIXELS/2)):
        if i < c_strip:
            if potent_to_color == 8:
                strip[i] = RAINBOW_strip[i]
                n = (i+30)-(2*i)-1
                strip[n] = RAINBOW_strip[i]
            else:
                strip[i] = colors[potent_to_color]
                n = (i+30)-(2*i)-1
                strip[n] = colors[potent_to_color]

    for i in range(int(STRIP_NUM_OF_PIXELS/2)):
        if i < c_strip:
            if potent_to_color == 8:
                strip_1[i] = RAINBOW_strip[i]
                n = (i+30)-(2*i)-1
                strip_1[n] = RAINBOW_strip[i]
            else:
                strip_1[i] = colors[potent_to_color]
                n = (i+30)-(2*i)-1
                strip_1[n] = colors[potent_to_color]

    for i in range(NUM_PIXELS):
        if i < c:
            if potent_to_color == 8:
                pixels[i] = RAINBOW_pixels[i]
            else:
                pixels[i] = colors[potent_to_color]

    # Lastly, we adjust the brightness of each set of LEDS
    pixels.brightness = potent_to_brightness
    strip.brightness = potent_to_brightness
    strip_1.brightness = potent_to_brightness

    # Finally we show the lights!
    pixels.show()
    strip.show()
    strip_1.show()

