Skip to content

Commit

Permalink
Merge branch 'LedFx:main' into webaudio-base64
Browse files Browse the repository at this point in the history
  • Loading branch information
mariusCZ authored Dec 12, 2023
2 parents a8fdc2f + a0cbb6c commit 4d12589
Show file tree
Hide file tree
Showing 13 changed files with 660 additions and 32 deletions.
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@ default_language_version:

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-ast
- id: trailing-whitespace
- id: check-toml
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
rev: 5.13.1
hooks:
- id: isort
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/psf/black
rev: 23.9.1 # Replace by any tag/version: https://github.com/psf/black/tags
rev: 23.11.0 # Replace by any tag/version: https://github.com/psf/black/tags
hooks:
- id: black
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py38-plus]
6 changes: 6 additions & 0 deletions ledfx/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,12 @@ async def add_new_device(self, device_type, device_config):
"name": device.name,
"icon_name": device_config["icon_name"],
}

if device_type == "wled":
if "matrix" in led_info.keys():
if "h" in led_info["matrix"].keys():
virtual_config["rows"] = led_info["matrix"]["h"]

segments = [[device.id, 0, device_config["pixel_count"] - 1, False]]

# Create the virtual
Expand Down
11 changes: 1 addition & 10 deletions ledfx/devices/wled.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,8 @@ async def add_postamble(self):
for seg in segments:
if seg["stop"] - seg["start"] > 0:
name = seg.get("n", f'Seg-{seg["id"]}')
if seg.get("stopY", 0) > 0:
name = seg.get("n", f'Matrix-{seg["id"]}')
rows = seg.get("stopY", 1)
if rows > 1:
self.sub_v(
name,
None,
[[seg["start"], (rows * seg["stop"]) - 1]],
rows,
)
else:
if not rows > 1:
self.sub_v(
name,
None,
Expand Down
5 changes: 0 additions & 5 deletions ledfx/effects/bands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ class BandsAudioEffect(AudioReactiveEffect, GradientEffect):
description="Alignment of bands",
default="left",
): vol.In(list(["left", "right", "invert", "center"])),
vol.Optional(
"mirror",
description="Mirror the effect",
default=False,
): bool,
}
)

Expand Down
99 changes: 99 additions & 0 deletions ledfx/effects/clone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import logging
import timeit

import mss
import voluptuous as vol
from PIL import Image

from ledfx.effects.twod import Twod

_LOGGER = logging.getLogger(__name__)


class Clone(Twod):
NAME = "Clone"
CATEGORY = "Matrix"
HIDDEN_KEYS = Twod.HIDDEN_KEYS + ["test"]

start_time = timeit.default_timer()

CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(
"screen",
description="Source screen for grab",
default=0,
): vol.All(vol.Coerce(int), vol.Range(min=0, max=4)),
vol.Optional(
"down",
description="pixels down offset of grab",
default=0,
): vol.All(vol.Coerce(int), vol.Range(min=0, max=1080)),
vol.Optional(
"across",
description="pixels across offset of grab",
default=0,
): vol.All(vol.Coerce(int), vol.Range(min=0, max=1920)),
vol.Optional(
"width",
description="width of grab",
default=128,
): vol.All(vol.Coerce(int), vol.Range(min=1, max=1920)),
vol.Optional(
"height",
description="height of grab",
default=128,
): vol.All(vol.Coerce(int), vol.Range(min=1, max=1080)),
}
)

def __init__(self, ledfx, config):
super().__init__(ledfx, config)
self.grab = None
self.sct = None

def config_updated(self, config):
super().config_updated(config)

self.screen = self._config["screen"]
self.x = self._config["down"]
self.y = self._config["across"]
self.width = self._config["width"]
self.height = self._config["height"]
self.grab = None
self.sct = None

def draw(self):
if self.sct is None:
self.sct = mss.mss()
else:
# this is a deep sniff to see if the sct object is still valid
# Don't like it, but some cases _handles is empty!
if not hasattr(self.sct._handles, "srcdc"):
self.sct = mss.mss()
_LOGGER.warning("Recreated sct")

if self.grab is None:
# grab a screen clip from screen x at x,y of width, height
mon = self.sct.monitors[self.screen]
self.grab = {
"top": mon["top"] + self.x,
"left": mon["left"] + self.y,
"width": self.width,
"height": self.height,
"mon": self.screen,
}

pre = timeit.default_timer()
frame = self.sct.grab(self.grab)
grab = timeit.default_timer()

rgb_image = Image.frombytes(
"RGB", frame.size, frame.bgra, "raw", "BGRX"
)

rgb_image = rgb_image.resize(
(self.t_width, self.t_height), Image.BILINEAR
)

return rgb_image
5 changes: 0 additions & 5 deletions ledfx/effects/equalizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ class EQAudioEffect(AudioReactiveEffect, GradientEffect):
description="Repeat the gradient into segments",
default=6,
): vol.All(vol.Coerce(int), vol.Range(min=1, max=16)),
vol.Optional(
"mirror",
description="Mirror the effect",
default=False,
): bool,
}
)

Expand Down
182 changes: 182 additions & 0 deletions ledfx/effects/equalizer2d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import logging
import timeit

import numpy as np
import voluptuous as vol
from PIL import Image, ImageDraw

from ledfx.effects.gradient import GradientEffect
from ledfx.effects.twod import Twod

_LOGGER = logging.getLogger(__name__)


class Equalizer2d(Twod, GradientEffect):
NAME = "Equalizer2d"
CATEGORY = "Matrix"
HIDDEN_KEYS = Twod.HIDDEN_KEYS + []
ADVANCED_KEYS = Twod.ADVANCED_KEYS + [
"peak percent",
"peak decay",
"max vs mean",
]

start_time = timeit.default_timer()

CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(
"peak percent",
description="Size of the tracer bar that follows a filtered value",
default=1.0,
): vol.All(vol.Coerce(int), vol.Range(min=0, max=5)),
vol.Optional(
"peak decay",
description="Decay filter applied to the peak value",
default=0.03,
): vol.All(vol.Coerce(float), vol.Range(min=0.01, max=0.1)),
vol.Optional(
"peak marks",
description="Turn on white peak markers that follow a freq value filtered with decay",
default=False,
): bool,
vol.Optional(
"center",
description="Center the equalizer bar",
default=False,
): bool,
vol.Optional(
"max vs mean",
description="Use max or mean value for bar size",
default=False,
): bool,
vol.Optional(
"bands",
description="Number of freq bands",
default=16,
): vol.All(vol.Coerce(int), vol.Range(min=1, max=64)),
}
)

def __init__(self, ledfx, config):
super().__init__(ledfx, config)

def on_activate(self, pixel_count):
self.r = np.zeros(pixel_count)

def config_updated(self, config):
super().config_updated(config)
self.bands = self._config["bands"]
self.init = False
self.center = self._config["center"]
self.grad_roll = self._config["gradient_roll"]
self.max = self._config["max vs mean"]
self.peak = self._config["peak marks"]
self.peak_per = self._config["peak percent"]
self.peak_decay = self.config["peak decay"]

def do_once(self):
# defer things that can't be done when pixel_count is not known
self.max_dim = max(self.t_width, self.t_height)
self.bands = min(self.bands, self.pixel_count)
self.bandsx = []
for i in range(self.bands):
start = int((self.max_dim / float(self.bands)) * i)
end = max(
start, int(((self.max_dim / float(self.bands)) * (i + 1)) - 1)
)
self.bandsx.append([start, end])
self.peaks_filter = self.create_filter(
alpha_decay=self.peak_decay, alpha_rise=0.99
)
self.peak_size = int(self.peak_per * self.max_dim / 100)
self.init = True

def audio_data_updated(self, data):
# Grab the filtered melbank
self.r = self.melbank(filtered=True, size=self.pixel_count)
np.clip(self.r, 0, 1, out=self.r)

def draw(self):
if not self.init:
self.do_once()

rgb_image = Image.new(
"RGB",
(
self.max_dim,
self.max_dim,
),
)
rgb_draw = ImageDraw.Draw(rgb_image)

if self.test:
self.draw_test(rgb_draw)

r_split = np.array_split(self.r, self.bands)
if self.max:
volumes = np.array([split.max() for split in r_split])
else:
volumes = np.array([split.mean() for split in r_split])

if self.peak:
peaks = self.peaks_filter.update(volumes)

# Precompute values that are constant for each iteration
half_max_dim = int(self.max_dim / 2)
gradient_colors = [
tuple(self.get_gradient_color(1 / self.bands * i).astype(int))
for i in range(self.bands)
]

for i in range(self.bands):
band_start, band_end = self.bandsx[i]
volume_scaled = int(self.max_dim * volumes[i])
if self.center:
# Calculate dimensions for the centered rectangle
bottom = half_max_dim - volume_scaled // 2
top = half_max_dim + volume_scaled // 2
else:
# Dimensions for the bottom to top rectangle
bottom = 0
top = volume_scaled

# Draw the rectangle
rgb_draw.rectangle(
(band_start, bottom, band_end, top), fill=gradient_colors[i]
)

# Draw the peak marker
if self.peak:
if self.center:
peak_scaled = int(self.max_dim * peaks[i] // 2)
peak_end = int(peak_scaled + self.peak_size // 2)
rgb_draw.rectangle(
(
band_start,
half_max_dim + peak_scaled,
band_end,
half_max_dim + peak_end,
),
fill=(255, 255, 255),
)
rgb_draw.rectangle(
(
band_start,
half_max_dim - peak_end,
band_end,
half_max_dim - peak_scaled,
),
fill=(255, 255, 255),
)
else:
peak_scaled = int(self.max_dim * peaks[i])
peak_end = peak_scaled + self.peak_size

rgb_draw.rectangle(
(band_start, peak_scaled, band_end, peak_end),
fill=(255, 255, 255),
)

self.roll_gradient()
return rgb_image
Loading

0 comments on commit 4d12589

Please sign in to comment.