From e4369d29a04ea55f1b51c8a5d0650430be8c05e9 Mon Sep 17 00:00:00 2001 From: Liz Date: Tue, 19 Nov 2024 09:10:30 -0500 Subject: [PATCH] adding rp2350 video synth code adding Feather RP2350 with HSTX to DVI video synth code. three modes: bar graph animation, bouncing circles, party parrot synthwave. a rotary encoder changes modes. 3 potentiometers control the animations. pdm mic is used for audio fft visualizations. button on rotary encoder toggles the pots on/off. if off, then default values are used for the animations --- RP2350_HSTX_Video_Synth/code.py | 568 ++++++++++++++++++++ RP2350_HSTX_Video_Synth/partyParrotsBig.bmp | Bin 0 -> 170040 bytes 2 files changed, 568 insertions(+) create mode 100644 RP2350_HSTX_Video_Synth/code.py create mode 100644 RP2350_HSTX_Video_Synth/partyParrotsBig.bmp diff --git a/RP2350_HSTX_Video_Synth/code.py b/RP2350_HSTX_Video_Synth/code.py new file mode 100644 index 000000000..43a37ad0b --- /dev/null +++ b/RP2350_HSTX_Video_Synth/code.py @@ -0,0 +1,568 @@ +# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +# FFT calculations based on Phil B.'s Audio Spectrum Light Show Code +# https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/ +# Feather_Sense_Audio_Visualizer_13x9_RGB_LED_Matrix/audio_spectrum_lightshow/code.py +import gc +from array import array +from math import log +from random import randint +import board +from audiobusio import PDMIn +import displayio +import picodvi +import framebufferio +import vectorio +from rainbowio import colorwheel +import adafruit_imageload +from adafruit_display_shapes.line import Line +from adafruit_ticks import ticks_ms, ticks_add, ticks_diff +from adafruit_seesaw import seesaw, rotaryio, digitalio +from analogio import AnalogIn +import simpleio +from ulab import numpy as np +try: + from ulab.utils import spectrogram +except ImportError: + from ulab.scipy.signal import spectrogram + +# ------ POTENTIOMETER SETUP ------ +pot1 = AnalogIn(board.A1) +pot2 = AnalogIn(board.A2) +pot3 = AnalogIn(board.A3) +read_pots = True +# ------ POTENTIOMETER MODE VALUES ------ +mode0_values = [0, 0, 0] +mode1_values = [0, 0, 0] +mode2_values = [0, 0, 0] +mode_vals = [mode0_values, mode1_values, mode2_values] +# ------ POTENTIOMETER READ FUNCTION ------ +def val(pin): + return pin.value + +# ------ SEESAW ENCODER ------ +i2c = board.STEMMA_I2C() +seesaw = seesaw.Seesaw(i2c, addr=0x36) +seesaw_product = (seesaw.get_version() >> 16) & 0xFFFF +print("Found product {}".format(seesaw_product)) +if seesaw_product != 4991: + print("Wrong firmware loaded? Expected 4991") +seesaw.pin_mode(24, seesaw.INPUT_PULLUP) +button = digitalio.DigitalIO(seesaw, 24) +button_held = False +encoder = rotaryio.IncrementalEncoder(seesaw) +last_position = 0 + +# ------ SHARED VARIABLES ------ +fft_size = 512 +low_bin = 15 +high_bin = 75 +low_band = (15, 75) +mid_band = (100, 120) +spectrum_size = fft_size // 2 +spectrum_bits = log(spectrum_size, 2) +mode = 0 +new_mode = True +states = {} + +# ------ PICODVI SETUP ------ +displayio.release_displays() +fb = picodvi.Framebuffer(320, 240, clk_dp=board.CKP, clk_dn=board.CKN, + red_dp=board.D0P, red_dn=board.D0N, + green_dp=board.D1P, green_dn=board.D1N, + blue_dp=board.D2P, blue_dn=board.D2N, + color_depth=8) +display = framebufferio.FramebufferDisplay(fb, auto_refresh=False) + +# ------ PDM MIC ------ +mic = PDMIn(board.D5, board.D6, sample_rate=44100, bit_depth=16) +rec_buf = array("H", [0] * fft_size) + +# pylint: disable=too-many-locals, global-statement, too-many-statements, global-variable-not-assigned + +# ------ INITIALIZE BAR GRAPH ANIMATION ------ +def initialize_bars(): + # based on Phil B.'s Audio Spectrum Light Show Code + # https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/ + # Feather_Sense_Audio_Visualizer_13x9_RGB_LED_Matrix/audio_spectrum_lightshow/code.py + global states + spectrum_group = displayio.Group() + display.root_group = spectrum_group + + low_frac = log(low_bin, 2) / spectrum_bits + frac_range = log(high_bin, 2) / spectrum_bits - low_frac + num_columns = 16 + column_width = display.width // num_columns + column_table = [] + moving_avg_buffer = [display.height] * num_columns + smoothing_factor = 0.5 + height_multiplier = 5 + dynamic_level = 10 + noise_floor = 3.1 + + for column in range(num_columns): + lower = low_frac + frac_range * (column / num_columns * 0.95) + upper = low_frac + frac_range * ((column + 1) / num_columns) + mid = (lower + upper) * 0.5 + half_width = (upper - lower) * 0.5 + first_bin = int(2 ** (spectrum_bits * lower) + 1e-4) + last_bin = int(2 ** (spectrum_bits * upper) + 1e-4) + bin_weights = [] + for bin_index in range(first_bin, last_bin + 1): + bin_center = log(bin_index + 0.5, 2) / spectrum_bits + dist = abs(bin_center - mid) / half_width + if dist < 1.0: + dist = 1.0 - dist + bin_weights.append(((3.0 - (dist * 2.0)) * dist) * dist) + total = sum(bin_weights) + bin_weights = [ + (weight / total) * (0.8 + idx / num_columns * 1.4) + for idx, weight in enumerate(bin_weights) + ] + column_table.append( + [ + first_bin - low_bin, + bin_weights, + colorwheel(225 * column / num_columns), + display.height, + 0.0, + ] + ) + bar_pal = displayio.Palette(1) + bar_pal[0] = colorwheel(225 * column / num_columns) + rect = vectorio.Rectangle( + pixel_shader=bar_pal, + width=column_width, + height=1, + x=column * column_width, + y=display.height, + ) + spectrum_group.append(rect) + + peak_palette = displayio.Palette(1) + peak_palette[0] = 0x808080 + for column in range(num_columns): + column_table[column].append(display.height) + column_table[column].append(0.0) + peak_dot = vectorio.Rectangle( + pixel_shader=peak_palette, + width=column_width, + height=5, + x=column * column_width, + y=display.height, + ) + spectrum_group.append(peak_dot) + + states = { + "spectrum_group": spectrum_group, + "column_table": column_table, + "moving_avg_buffer": moving_avg_buffer, + "smoothing_factor": smoothing_factor, + "height_multiplier": height_multiplier, + "dynamic_level": dynamic_level, + "noise_floor": noise_floor, + "num_columns": num_columns, + "column_width": column_width, + "peak_pal": peak_palette, + } + +# ------ BAR GRAPH ANIMATION ------ +def bars(pos, read): + global states + if read: + states["peak_pal"][0] = colorwheel(pos[0]) + states["noise_floor"] = pos[1] + states["smoothing_factor"] = pos[2] + spectrum_group = states["spectrum_group"] + column_table = states["column_table"] + moving_avg_buffer = states["moving_avg_buffer"] + smoothing_factor = states["smoothing_factor"] + height_multiplier = states["height_multiplier"] + dynamic_level = states["dynamic_level"] + noise_floor = states["noise_floor"] + num_columns = states["num_columns"] + + mic.record(rec_buf, fft_size) + samples = np.array(rec_buf) + + spectrum = spectrogram(samples)[low_bin : high_bin + 1] + spectrum = np.log(spectrum + 1e-7) + spectrum = np.maximum(spectrum - noise_floor, 0) + lower = max(np.min(spectrum), 4) + upper = min(max(np.max(spectrum), lower + 12), 20) + if upper > dynamic_level: + dynamic_level = upper * 0.7 + dynamic_level * 0.3 + else: + dynamic_level = dynamic_level * 0.5 + lower * 0.5 + states["dynamic_level"] = dynamic_level + max_height = display.height + data = (spectrum - lower) * (max_height / (dynamic_level - lower)) * height_multiplier + + for column, element in enumerate(column_table): + first_bin = element[0] + column_top = display.height + 1 + for bin_offset, weight in enumerate(element[1]): + if first_bin + bin_offset < len(data): + column_top -= data[first_bin + bin_offset] * weight + column_top = max(0, int(column_top)) + moving_avg_buffer[column] = ( + moving_avg_buffer[column] * (1 - smoothing_factor) + + column_top * smoothing_factor + ) + smoothed_top = int(moving_avg_buffer[column]) + rect = spectrum_group[column] + rect.height = display.height - smoothed_top + rect.y = smoothed_top + + if smoothed_top < element[3]: + element[3] = smoothed_top - 1 + element[4] = 0 + else: + element[3] += element[4] + element[4] += 0.2 + peak_position = max(0, int(element[3])) + peak_dot = spectrum_group[num_columns + column] + peak_dot.y = peak_position + display.refresh() +# ------ INITIALIZE CIRCLE ANIMATION ------ +def initialize_circles(): + global states + spectrum_group = displayio.Group() + display.root_group = spectrum_group + palette = displayio.Palette(1) + palette[0] = 0xFFFFFF + center_palette = displayio.Palette(1) + center_palette[0] = 0xFF0000 + center_circle = ( + vectorio.Circle(pixel_shader=center_palette, radius=5, + x=display.width // 2, y=display.height // 2) + ) + spectrum_group.append(center_circle) + num_circles = 16 + smoothing_factor = 0.5 + decay_factor = 0.2 + circles = [] + directions = [] + smoothed_radii = [5] * num_circles + + for i in range(num_circles): + radius = 5 + x_pos = randint(radius, display.width - radius) + y_pos = randint(radius, display.height - radius) + dx = randint(-2, 2) or 1 + dy = randint(-2, 2) or 1 + color_index = (255 * i) // num_circles + circle_palette = displayio.Palette(1) + circle_palette[0] = colorwheel(color_index) + circle = vectorio.Circle(pixel_shader=circle_palette, radius=radius, x=x_pos, y=y_pos) + spectrum_group.append(circle) + circles.append(circle) + directions.append([dx, dy]) + states = { + "spectrum_group": spectrum_group, + "center_circle": center_circle, + "center_radius_smoothed": 5, + "circles": circles, + "directions": directions, + "smoothed_radii": smoothed_radii, + "smoothing_factor": smoothing_factor, + "decay_factor": decay_factor, + "dynamic_level": 15, + "noise_floor": 3.1, + "max_radius": 100, + "num_circles": num_circles, + "center_palette": center_palette, + "speed": 1, + } +# ------ BOUNCING CIRCLES ANIMATION ------ +def bouncing_circles(pos, read): + global states + if read: + states["center_palette"][0] = colorwheel(pos[0]) + states["noise_floor"] = pos[1] + states["smoothing_factor"] = pos[2] + states["decay_factor"] = pos[2] - 0.3 + mic.record(rec_buf, fft_size) + samples = np.array(rec_buf) + spectrum = spectrogram(samples)[low_bin : high_bin + 1] + spectrum = np.log(spectrum + 1e-7) + spectrum = np.maximum(spectrum - states["noise_floor"], 0) + lower = max(np.min(spectrum), 4) + upper = min(max(np.max(spectrum), lower + 12), 20) + if upper > states["dynamic_level"]: + states["dynamic_level"] = upper * 0.7 + states["dynamic_level"] * 0.3 + else: + states["dynamic_level"] = states["dynamic_level"] * 0.5 + lower * 0.5 + overall_amplitude = np.sum(spectrum) + max_center_radius = 50 + target_center_radius = ( + int((overall_amplitude / (states["dynamic_level"] * states["num_circles"])) * max_center_radius) + ) + if target_center_radius < states["center_radius_smoothed"]: + states["center_radius_smoothed"] = ( + states["center_radius_smoothed"] * (1 - states["decay_factor"]) + + target_center_radius * states["decay_factor"] + ) + else: + states["center_radius_smoothed"] = ( + states["center_radius_smoothed"] * (1 - states["smoothing_factor"]) + + target_center_radius * states["smoothing_factor"] + ) + states["center_circle"].radius = int(states["center_radius_smoothed"]) + data = (spectrum - lower) * (states["max_radius"] / (states["dynamic_level"] - lower)) + for i, circle in enumerate(states["circles"]): + target_radius = max(2, int(data[i])) + if target_radius < states["smoothed_radii"][i]: + states["smoothed_radii"][i] = ( + states["smoothed_radii"][i] * (1 - states["decay_factor"]) + + target_radius * states["decay_factor"] + ) + else: + states["smoothed_radii"][i] = ( + states["smoothed_radii"][i] * (1 - states["smoothing_factor"]) + + target_radius * states["smoothing_factor"] + ) + circle.radius = int(states["smoothed_radii"][i]) + dx, dy = states["directions"][i] + circle.x += dx + circle.y += dy + if circle.x - circle.radius <= 0 or circle.x + circle.radius >= display.width: + states["directions"][i][0] *= -1 # Reverse x direction + if circle.y - circle.radius <= 0 or circle.y + circle.radius >= display.height: + states["directions"][i][1] *= -1 # Reverse y direction + display.refresh() +# ------ PARTY PARROT INIT ------ +def initialize_party(): + global states + spectrum_group = displayio.Group() + display.root_group = spectrum_group + bitmap, palette = adafruit_imageload.load( + "/partyParrotsBig.bmp", + bitmap=displayio.Bitmap, + palette=displayio.Palette + ) + parrot_grid = displayio.TileGrid( + bitmap, + pixel_shader=palette, + tile_height=128, + tile_width=132, + x=(display.width - 128) // 2, + y=0 + ) + spectrum_group.append(parrot_grid) + line_group = displayio.Group() + spectrum_group.append(line_group) + pal_bg = displayio.Palette(1) + pal_bg[0] = 0x0000FF + palette_white = displayio.Palette(1) + palette_white[0] = 0xFFFFFF + pal = displayio.Palette(1) + pal[0] = 0xFF00FF + left_circle = vectorio.Circle( + pixel_shader=pal, radius=5, x=5, y=5 + ) + right_circle = vectorio.Circle( + pixel_shader=pal, radius=5, x=display.width - 5, y=5 + ) + spectrum_group.append(left_circle) + spectrum_group.append(right_circle) + ground = vectorio.Rectangle( + pixel_shader=pal_bg, + width=display.width, + height=display.height - 128, + x=0, + y=128 + ) + line_group.append(ground) + horizon_line = vectorio.Rectangle( + pixel_shader=palette_white, + width=display.width, + height=1, + x=0, + y=128 + ) + line_group.append(horizon_line) + slanted_lines_coords = [ + (0, 136, 34, 128), + (0, 188, 76, 128), + (34, 240, 113, 128), + (117, 240, 148, 128), + (198, 240, 182, 128), + (294, 240, 216, 128), + (320, 176, 255, 128), + (320, 133, 297, 128) + ] + for coords in slanted_lines_coords: + line = Line(coords[0], coords[1], coords[2], coords[3], 0xFFFFFF) + line_group.append(line) + horizontal_lines = [] + for _ in range(5): + line_rect = vectorio.Rectangle( + pixel_shader=palette_white, + width=display.width, + height=2, + x=0, + y=0 + ) + line_group.append(line_rect) + horizontal_lines.append(line_rect) + states = { + "pal": pal, + "pal_bg": pal_bg, + "pal_line": palette_white, + "spectrum_group": spectrum_group, + "parrot_grid": parrot_grid, + "line_group": line_group, + "horizontal_lines": horizontal_lines, + "frame_index": 0, + "low_band_threshold": 3.4, + "mid_band_threshold": 3.4, + "smoothing_factor": 0.5, + "last_i": 146, + "dynamic_level": 15, + "decay_factor": 2, + "max_center_radius": 10, + "center_radius_smoothed": 2, + "left_circle": left_circle, + "right_circle": right_circle, + "clock_clock": ticks_ms(), + "clock_time": int(0.01 * 1000), + "i": 152 + } +# ------ PARTY PARROT ANIMATION ------ +def party_parrot(pos, read): + global states + if read: + states["pal"][0] = colorwheel(pos[0]) + states["max_center_radius"] = pos[1] + states["low_band_threshold"] = pos[2] + states["mid_band_threshold"] = pos[2] + left_circle = states["left_circle"] + right_circle = states["right_circle"] + parrot_grid = states["parrot_grid"] + horizontal_lines = states["horizontal_lines"] + frame_index = states["frame_index"] + last_i = states["last_i"] + center_radius_smoothed = states["center_radius_smoothed"] + dynamic_level = states["dynamic_level"] + low_band_threshold = states["low_band_threshold"] + mid_band_threshold = states["mid_band_threshold"] + smoothing_factor = states["smoothing_factor"] + decay_factor = states["decay_factor"] + clock_clock = states["clock_clock"] + palette_blue = states["pal_bg"] + palette_white = states["pal_line"] + i = states["i"] + if i > 128: + last_i = i + i -= 1 + else: + i = 152 + mic.record(rec_buf, fft_size) + samples = np.array(rec_buf) + spectrum = spectrogram(samples) + spectrum = np.log(spectrum + 1e-7) + spectrum = np.maximum(spectrum - 3.1, 0) + low_band_avg = np.mean(spectrum[low_band[0]:low_band[1] + 1]) + mid_band_avg = np.mean(spectrum[mid_band[0]:mid_band[1] + 1]) + overall_amplitude = np.sum(spectrum) + target_center_radius = ( + int((overall_amplitude / (dynamic_level * 16)) * states["max_center_radius"]) + ) + if target_center_radius < center_radius_smoothed: + center_radius_smoothed = ( + center_radius_smoothed * (1 - decay_factor) + target_center_radius * decay_factor + ) + else: + center_radius_smoothed = ( + center_radius_smoothed * (1 - smoothing_factor) + target_center_radius * smoothing_factor + ) + left_circle.radius = int(center_radius_smoothed) + right_circle.radius = int(center_radius_smoothed) + if low_band_avg >= low_band_threshold * 1.1 or mid_band_avg >= mid_band_threshold * 1.1: + frame_index = (frame_index + 1) % 10 + parrot_grid[0] = frame_index + if ticks_diff(ticks_ms(), clock_clock) >= states["clock_time"]: + for idx, offset in enumerate([0, 25, 50, 75, 100]): + horizontal_lines[idx].y = last_i + offset + horizontal_lines[idx].pixel_shader = palette_blue + for idx, offset in enumerate([0, 25, 50, 75, 100]): + horizontal_lines[idx].y = i + offset + horizontal_lines[idx].pixel_shader = palette_white + clock_clock = ticks_add(clock_clock, states["clock_time"]) + display.refresh() + states["frame_index"] = frame_index + states["last_i"] = last_i + states["i"] = i + states["center_radius_smoothed"] = center_radius_smoothed +# ------ THE LOOP ------ +while True: + # read encoder - if value != then change mode + position = -encoder.position + if position != last_position: + if position > last_position: + mode = (mode + 1) % 3 + else: + mode = (mode - 1) % 3 + new_mode = True + last_position = position + # encode button - switch between reading potentiometer + # to control animations or using default values + if not button.value and not button_held: + button_held = True + read_pots = not read_pots + print("Button pressed") + if button.value and button_held: + button_held = False + print("Button released") + # if a new mode is selected, run init function + if new_mode: + new_mode = False + print(f"switching modes! {mode}") + del states + gc.collect() + display.refresh() + if mode == 0: + initialize_bars() + if mode == 1: + initialize_circles() + if mode == 2: + initialize_party() + # mode 0 - bar graph visualizer + if mode == 0: + bars(mode0_values, read_pots) + # pot 1 - control color of bouncing dot + color = simpleio.map_range(val(pot1), 0, 65535, 0, 255) + mode_vals[0][0] = color + # pot 2 - raise/lower noise floor + noise = simpleio.map_range(val(pot2), 0, 65535, 2.5, 4.5) + mode_vals[0][1] = noise + # pot 3 - smooth bar animation + smooth = simpleio.map_range(val(pot3), 0, 65535, 0.5, 0.05) + mode_vals[0][2] = smooth + # mode 1 - bouncing circles visualizer + if mode == 1: + bouncing_circles(mode1_values, read_pots) + # pot 1 - control color of center circle + color = simpleio.map_range(val(pot1), 0, 65535, 0, 255) + mode_vals[1][0] = color + # pot 2 - raise/lower noise floor + noise = simpleio.map_range(val(pot2), 0, 65535, 2.5, 4.5) + mode_vals[1][1] = noise + # pot 3 - smooth circle size change + smooth = simpleio.map_range(val(pot3), 0, 65535, 0.8, 0.4) + mode_vals[1][2] = smooth + # mode 2 - party parrot synth wave + if mode == 2: + party_parrot(mode2_values, read_pots) + # pot 1 - control color of side circles + color = simpleio.map_range(val(pot1), 0, 65535, 0, 255) + mode_vals[2][0] = color + # pot 2 - mid & low end noise floor aka parrot sensitivity + dynamic = simpleio.map_range(val(pot2), 0, 65535, 3.0, 6.0) + mode_vals[2][1] = dynamic + # pot 3 - size of side circles + rad = simpleio.map_range(val(pot3), 0, 65535, 2, 12) + mode_vals[2][2] = rad diff --git a/RP2350_HSTX_Video_Synth/partyParrotsBig.bmp b/RP2350_HSTX_Video_Synth/partyParrotsBig.bmp new file mode 100644 index 0000000000000000000000000000000000000000..89244c33298b548b1b602a2666b329f9aee37fe2 GIT binary patch literal 170040 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