push changes
This commit is contained in:
125
audio.py
Normal file
125
audio.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import pyaudio
|
||||||
|
import struct
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
|
||||||
|
|
||||||
|
width = 192
|
||||||
|
height = 64
|
||||||
|
|
||||||
|
geometry = piomatter.Geometry(width=width, height=height, n_addr_lines=5,
|
||||||
|
rotation=piomatter.Orientation.Normal, n_planes=6, n_temporal_planes=0)
|
||||||
|
|
||||||
|
canvas = Image.new('RGB', (width, height), (0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
|
||||||
|
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
||||||
|
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
|
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
|
||||||
|
framebuffer=framebuffer,
|
||||||
|
geometry=geometry)
|
||||||
|
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
info = p.get_host_api_info_by_index(0)
|
||||||
|
numdevices = info.get('deviceCount')
|
||||||
|
|
||||||
|
for i in range(0, numdevices):
|
||||||
|
if (p.get_device_info_by_host_api_device_index(0, i).get('maxInputChannels')) > 0:
|
||||||
|
print("Input Device id ", i, " - ", p.get_device_info_by_host_api_device_index(0, i).get('name'))
|
||||||
|
|
||||||
|
print(p.get_device_info_by_index(0)['defaultSampleRate'])
|
||||||
|
|
||||||
|
CHUNK = 1024
|
||||||
|
FORMAT = pyaudio.paInt16
|
||||||
|
CHANNELS = 2
|
||||||
|
RATE = 44100
|
||||||
|
|
||||||
|
stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
|
||||||
|
|
||||||
|
def rms( data ):
|
||||||
|
count = len(data)/2
|
||||||
|
format = "%dh"%(count)
|
||||||
|
shorts = struct.unpack( format, data )
|
||||||
|
sum_squares = 0.0
|
||||||
|
for sample in shorts:
|
||||||
|
n = sample * (1.0/32768)
|
||||||
|
sum_squares += n*n
|
||||||
|
return math.sqrt( sum_squares / count )
|
||||||
|
|
||||||
|
|
||||||
|
run = True
|
||||||
|
|
||||||
|
max_vol = 0
|
||||||
|
|
||||||
|
while run:
|
||||||
|
# print(rms(stream.read(CHUNK, exception_on_overflow = False)))
|
||||||
|
|
||||||
|
print(max_vol)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
buffer = stream.read(CHUNK, exception_on_overflow = False)
|
||||||
|
waveform = np.frombuffer(buffer, dtype=np.int16)
|
||||||
|
|
||||||
|
fft_complex = np.fft.fft(waveform, n=int(CHUNK*2))
|
||||||
|
|
||||||
|
max_val = math.sqrt(max(v.real * v.real + v.imag * v.imag for v in fft_complex))
|
||||||
|
|
||||||
|
if (max_val > max_vol):
|
||||||
|
max_vol = max_val
|
||||||
|
|
||||||
|
# factor out the scale multiply.
|
||||||
|
|
||||||
|
|
||||||
|
draw.rectangle((0,0,width,height), fill=0x000000)
|
||||||
|
|
||||||
|
for i in range(192):
|
||||||
|
|
||||||
|
scale_value = (height / max_vol) * (1 + (i/100))
|
||||||
|
|
||||||
|
freq = i * 5
|
||||||
|
|
||||||
|
if (i < 2):
|
||||||
|
scale_value = (height / max_vol) * 0.9
|
||||||
|
|
||||||
|
if (i < 192):
|
||||||
|
|
||||||
|
freq = i
|
||||||
|
|
||||||
|
target = int(freq)
|
||||||
|
|
||||||
|
# print(target)
|
||||||
|
|
||||||
|
|
||||||
|
v = fft_complex[target]
|
||||||
|
|
||||||
|
dist = math.sqrt(v.real * v.real + v.imag * v.imag)
|
||||||
|
|
||||||
|
mapped_dist = dist * scale_value
|
||||||
|
|
||||||
|
if (max_vol < 500000):
|
||||||
|
mapped_dist = 1
|
||||||
|
|
||||||
|
draw.rectangle((i, height-mapped_dist, i, height), fill=0x880000)
|
||||||
|
|
||||||
|
"""
|
||||||
|
for i,v in enumerate(fft_complex):
|
||||||
|
dist = math.sqrt(v.real * v.real + v.imag * v.imag)
|
||||||
|
mapped_dist = dist * scale_value
|
||||||
|
|
||||||
|
draw.rectangle((i, 0, i, mapped_dist), fill=0x880000)
|
||||||
|
"""
|
||||||
|
|
||||||
|
run=True
|
||||||
|
|
||||||
|
max_vol = max_vol * 0.99
|
||||||
|
|
||||||
|
framebuffer[:] = np.asarray(canvas)
|
||||||
|
matrix.show()
|
||||||
|
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
42
gif.py
Normal file
42
gif.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
"""
|
||||||
|
Display an animated gif
|
||||||
|
|
||||||
|
Run like this:
|
||||||
|
|
||||||
|
$ python play_gif.py
|
||||||
|
|
||||||
|
The animated gif is played repeatedly until interrupted with ctrl-c.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import PIL.Image as Image
|
||||||
|
|
||||||
|
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
|
||||||
|
|
||||||
|
width = 192
|
||||||
|
height = 64
|
||||||
|
|
||||||
|
gif_file = "jake2.gif"
|
||||||
|
|
||||||
|
canvas = Image.new('RGB', (width, height), (0, 0, 0))
|
||||||
|
geometry = piomatter.Geometry(width=width, height=height,
|
||||||
|
n_addr_lines=5, rotation=piomatter.Orientation.Normal, n_planes=6, n_temporal_planes=0)
|
||||||
|
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
||||||
|
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
|
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
|
||||||
|
framebuffer=framebuffer,
|
||||||
|
geometry=geometry)
|
||||||
|
|
||||||
|
with Image.open(gif_file) as img:
|
||||||
|
print(f"frames: {img.n_frames}")
|
||||||
|
while True:
|
||||||
|
for i in range(img.n_frames):
|
||||||
|
img.seek(i)
|
||||||
|
img2 = img.resize((192,64))
|
||||||
|
canvas.paste(img2, (0,0))
|
||||||
|
framebuffer[:] = np.asarray(canvas)
|
||||||
|
matrix.show()
|
||||||
|
time.sleep(0.05)
|
||||||
202
main.py
Executable file
202
main.py
Executable file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/home/taco/venvs/visualizer/bin/python
|
||||||
|
|
||||||
|
import pyaudio
|
||||||
|
import struct
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
|
||||||
|
|
||||||
|
# LED panel width
|
||||||
|
width = 192
|
||||||
|
|
||||||
|
# LED panel height
|
||||||
|
height = 64
|
||||||
|
|
||||||
|
# Top gradient color
|
||||||
|
color_1 = "#0000ff"
|
||||||
|
|
||||||
|
# Bottom gradient color
|
||||||
|
color_2 = "#ff0000"
|
||||||
|
|
||||||
|
# Should we mirror left and right channels?
|
||||||
|
mirror = False
|
||||||
|
|
||||||
|
# Should we force draw a single pixel line?
|
||||||
|
zero_db_line = True
|
||||||
|
|
||||||
|
# how long to hold maximum volume in seconds?
|
||||||
|
max_db_hold_time = 1
|
||||||
|
|
||||||
|
# how long between steps should we sleep?
|
||||||
|
delay = 0.01
|
||||||
|
|
||||||
|
# minimum maximum volume
|
||||||
|
minimum_max_volume = 4000000
|
||||||
|
|
||||||
|
# PyAudio config
|
||||||
|
CHUNK = 1024
|
||||||
|
FORMAT = pyaudio.paInt16
|
||||||
|
CHANNELS = 2
|
||||||
|
RATE = 44100
|
||||||
|
|
||||||
|
geometry = piomatter.Geometry(
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
n_addr_lines=5,
|
||||||
|
rotation=piomatter.Orientation.Normal,
|
||||||
|
n_planes=7,
|
||||||
|
n_temporal_planes=1
|
||||||
|
)
|
||||||
|
|
||||||
|
canvas = Image.new('RGB', (width, height), (0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
|
||||||
|
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
||||||
|
matrix = piomatter.PioMatter(
|
||||||
|
colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
|
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
|
||||||
|
framebuffer=framebuffer,
|
||||||
|
geometry=geometry
|
||||||
|
)
|
||||||
|
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
|
||||||
|
stream = p.open(
|
||||||
|
format=FORMAT,
|
||||||
|
channels=CHANNELS,
|
||||||
|
rate=RATE,
|
||||||
|
input=True,
|
||||||
|
frames_per_buffer=CHUNK
|
||||||
|
)
|
||||||
|
|
||||||
|
run = True
|
||||||
|
|
||||||
|
max_vol = minimum_max_volume
|
||||||
|
target_max_vol = minimum_max_volume
|
||||||
|
|
||||||
|
left_channel = np.zeros(int(width))
|
||||||
|
hf_left_channel = np.zeros(int(width/2))
|
||||||
|
right_channel = np.zeros(int(width))
|
||||||
|
hf_right_channel = np.zeros(int(width/2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def generate_gradient(colour1: str, colour2: str, width: int, height: int) -> Image:
|
||||||
|
base = Image.new('RGB', (width, height), colour1)
|
||||||
|
top = Image.new('RGB', (width, height), colour2)
|
||||||
|
mask = Image.new('L', (width, height))
|
||||||
|
mask_data = []
|
||||||
|
for y in range(height):
|
||||||
|
mask_data.extend([int(255 * (y / height))] * width)
|
||||||
|
mask.putdata(mask_data)
|
||||||
|
base.paste(top, (0, 0), mask)
|
||||||
|
return base
|
||||||
|
|
||||||
|
def clamp(minimum, maximum, value):
|
||||||
|
if (value > maximum):
|
||||||
|
return maximum
|
||||||
|
|
||||||
|
if (value < minimum):
|
||||||
|
return minimum
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def mathCurve(step):
|
||||||
|
result = math.sin((step/40) + sin_offset)*4 + math.cos((step/30) + sin_offset*3)*4 + math.sin((step/35) + sin_offset*5)*4
|
||||||
|
return math.floor(height/2 + result + 0.5)
|
||||||
|
|
||||||
|
sin_offset = 0
|
||||||
|
|
||||||
|
db_hold_time = 0
|
||||||
|
|
||||||
|
gradient = generate_gradient(color_1, color_2, width, height)
|
||||||
|
|
||||||
|
while run:
|
||||||
|
|
||||||
|
left_channel = left_channel * 0.9
|
||||||
|
right_channel = right_channel * 0.9
|
||||||
|
|
||||||
|
buffer = stream.read(CHUNK, exception_on_overflow = False)
|
||||||
|
waveform = np.frombuffer(buffer, dtype=np.int16)
|
||||||
|
|
||||||
|
waveform = np.reshape(waveform, (CHUNK, 2))
|
||||||
|
|
||||||
|
#fft_complex_left = np.fft.fft(waveform[:, 0], n=int(CHUNK))[:width]
|
||||||
|
#fft_complex_right = np.fft.fft(waveform[:, 1], n=int(CHUNK))[:width]
|
||||||
|
|
||||||
|
fft_complex_left = np.fft.fft(waveform[:, 0], n=int(CHUNK))
|
||||||
|
fft_complex_right = np.fft.fft(waveform[:, 1], n=int(CHUNK))
|
||||||
|
|
||||||
|
max_val_left = math.sqrt(max(v.real * v.real + v.imag * v.imag for v in fft_complex_left))
|
||||||
|
max_val_right = math.sqrt(max(v.real * v.real + v.imag * v.imag for v in fft_complex_right))
|
||||||
|
|
||||||
|
max_val = max(max_val_left, max_val_right)
|
||||||
|
|
||||||
|
if (max_val > target_max_vol):
|
||||||
|
target_max_vol = max_val
|
||||||
|
db_hold_time = time.time() + max_db_hold_time
|
||||||
|
|
||||||
|
if (max_vol < target_max_vol):
|
||||||
|
max_vol = max_vol + target_max_vol*0.1
|
||||||
|
|
||||||
|
canvas.paste(gradient)
|
||||||
|
|
||||||
|
def calcDist(step, fft, fft_hist):
|
||||||
|
scale_value = (height / max_vol) * (1 + (step/100))
|
||||||
|
|
||||||
|
if (step < 2):
|
||||||
|
scale_value = (height / max_vol) * 0.9
|
||||||
|
|
||||||
|
use_value = fft_hist[step]
|
||||||
|
|
||||||
|
v = fft[step]
|
||||||
|
|
||||||
|
dist = math.sqrt(v.real * v.real + v.imag * v.imag)
|
||||||
|
|
||||||
|
if dist > use_value:
|
||||||
|
fft_hist[step] = dist
|
||||||
|
use_value = dist
|
||||||
|
|
||||||
|
return (use_value * scale_value)
|
||||||
|
|
||||||
|
# LEFT FR
|
||||||
|
for i in range(0, int(width)):
|
||||||
|
|
||||||
|
mapped_dist = calcDist(i, fft_complex_left, left_channel)/2
|
||||||
|
|
||||||
|
midpoint = mathCurve(i)
|
||||||
|
|
||||||
|
draw.rectangle((i, 0, i, clamp(1, midpoint, midpoint - mapped_dist)), fill=0x000)
|
||||||
|
|
||||||
|
# RIGHT FR
|
||||||
|
for i in range(0, int(width)):
|
||||||
|
mapped_dist = calcDist(i, fft_complex_right, right_channel)/2
|
||||||
|
|
||||||
|
horizontal_position = (width - 1) - i
|
||||||
|
if (mirror):
|
||||||
|
horizontal_position = i
|
||||||
|
|
||||||
|
midpoint = mathCurve(horizontal_position)
|
||||||
|
|
||||||
|
vertical_addition = 0
|
||||||
|
if (zero_db_line):
|
||||||
|
vertical_addition = 1
|
||||||
|
|
||||||
|
draw.rectangle((horizontal_position, clamp(1, height, midpoint + vertical_addition + mapped_dist), horizontal_position, height), fill=0x000)
|
||||||
|
|
||||||
|
if (max_vol > minimum_max_volume and db_hold_time < time.time()):
|
||||||
|
max_vol = target_max_vol = max_vol * 0.99
|
||||||
|
|
||||||
|
if (max_vol < minimum_max_volume):
|
||||||
|
max_vol = minimum_max_volume
|
||||||
|
|
||||||
|
framebuffer[:] = np.asarray(canvas)
|
||||||
|
matrix.show()
|
||||||
|
|
||||||
|
sin_offset = sin_offset + 0.01
|
||||||
|
|
||||||
|
time.sleep(delay)
|
||||||
44
test.py
Normal file
44
test.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
Display a simple test pattern of 3 shapes on a single 64x32 matrix panel.
|
||||||
|
|
||||||
|
Run like this:
|
||||||
|
|
||||||
|
$ python simpletest.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
|
||||||
|
|
||||||
|
width = 192
|
||||||
|
height = 64
|
||||||
|
|
||||||
|
geometry = piomatter.Geometry(width=width, height=height, n_addr_lines=5,
|
||||||
|
rotation=piomatter.Orientation.Normal, n_planes=6, n_temporal_planes=4)
|
||||||
|
|
||||||
|
canvas = Image.new('RGB', (width, height), (0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
|
||||||
|
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
|
||||||
|
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
|
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
|
||||||
|
framebuffer=framebuffer,
|
||||||
|
geometry=geometry)
|
||||||
|
|
||||||
|
# draw.rectangle((0, 0, 192, 64), fill=0x008800)
|
||||||
|
# draw.circle((18, 6), 4, fill=0x880000)
|
||||||
|
# draw.polygon([(28, 2), (32, 10), (24, 10)], fill=0x000088)
|
||||||
|
|
||||||
|
draw.rectangle((0,0,1,64), fill=0x008800)
|
||||||
|
draw.rectangle((1,0,1,64), fill=0x880000)
|
||||||
|
|
||||||
|
framebuffer[:] = np.asarray(canvas)
|
||||||
|
matrix.show()
|
||||||
|
|
||||||
|
input("Press enter to exit")
|
||||||
71
text.py
Normal file
71
text.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
Display quote from the Adafruit quotes API as text scrolling across the
|
||||||
|
matrices.
|
||||||
|
|
||||||
|
Requires the requests library to be installed.
|
||||||
|
|
||||||
|
Run like this:
|
||||||
|
|
||||||
|
$ python quote_scroller.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
|
||||||
|
|
||||||
|
# 128px for 2x1 matrices. Change to 64 if you're using a single matrix.
|
||||||
|
total_width = 192
|
||||||
|
total_height = 64
|
||||||
|
|
||||||
|
bottom_half_shift_compensation = 1
|
||||||
|
|
||||||
|
font_color = (0, 128, 128)
|
||||||
|
|
||||||
|
# Load the font
|
||||||
|
font = ImageFont.truetype("LindenHill-webfont.ttf", 40)
|
||||||
|
|
||||||
|
quote_resp = requests.get("https://www.adafruit.com/api/quotes.php").json()
|
||||||
|
|
||||||
|
text = f'{quote_resp[0]["text"]} - {quote_resp[0]["author"]}'
|
||||||
|
#text = "Sometimes you just want to use hardcoded strings. - Unknown"
|
||||||
|
|
||||||
|
x, y, text_width, text_height = font.getbbox(text)
|
||||||
|
|
||||||
|
full_txt_img = Image.new("RGB", (int(text_width) + 6, int(text_height) + 6), (0, 0, 0))
|
||||||
|
draw = ImageDraw.Draw(full_txt_img)
|
||||||
|
draw.text((3, 3), text, font=font, fill=font_color)
|
||||||
|
full_txt_img.save("quote.png")
|
||||||
|
|
||||||
|
single_frame_img = Image.new("RGB", (total_width, total_height), (0, 0, 0))
|
||||||
|
|
||||||
|
geometry = piomatter.Geometry(width=total_width, height=total_height,
|
||||||
|
n_addr_lines=5, rotation=piomatter.Orientation.Normal)
|
||||||
|
framebuffer = np.asarray(single_frame_img) + 0 # Make a mutable copy
|
||||||
|
|
||||||
|
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
|
||||||
|
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
|
||||||
|
framebuffer=framebuffer,
|
||||||
|
geometry=geometry)
|
||||||
|
|
||||||
|
print("Ctrl-C to exit")
|
||||||
|
while True:
|
||||||
|
for x_pixel in range(-total_width-1,full_txt_img.width):
|
||||||
|
if bottom_half_shift_compensation == 0:
|
||||||
|
# full paste
|
||||||
|
single_frame_img.paste(full_txt_img.crop((x_pixel, 0, x_pixel + total_width, total_height)), (0, 0))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# top half
|
||||||
|
single_frame_img.paste(full_txt_img.crop((x_pixel, 0, x_pixel + total_width, total_height//2)), (0, 0))
|
||||||
|
# bottom half shift compensation
|
||||||
|
single_frame_img.paste(full_txt_img.crop((x_pixel, total_height//2, x_pixel + total_width, total_height)), (bottom_half_shift_compensation, total_height//2))
|
||||||
|
|
||||||
|
framebuffer[:] = np.asarray(single_frame_img)
|
||||||
|
matrix.show()
|
||||||
Reference in New Issue
Block a user