forked from LedFx/LedFx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'LedFx:main' into webaudio-base64
- Loading branch information
Showing
13 changed files
with
660 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.