Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi scan 2 #365

Merged
merged 41 commits into from Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4a65e0c
WIP
bigredfrog Apr 6, 2023
6addfbb
Simple wrapper for time domain graph generation, spawned into browser
bigredfrog Apr 7, 2023
08aab7a
Fix crash on less that 4 pixels and blur
bigredfrog Apr 7, 2023
69621c6
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 7, 2023
11ed8e3
Polish up utils graph
bigredfrog Apr 8, 2023
56514b0
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 8, 2023
1fe41b2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 8, 2023
054767d
Polish up utils graph
bigredfrog Apr 8, 2023
08007f5
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 8, 2023
f1825d5
Polish up utils graph
bigredfrog Apr 8, 2023
9c80853
Change Metro to Audio Effect to pick up audio callback for diagnostic
bigredfrog Apr 9, 2023
ce253e5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2023
d27fbc7
cleanup
bigredfrog Apr 9, 2023
a8e55d6
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 9, 2023
978e9af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2023
14bf44f
added CPU loading
bigredfrog Apr 9, 2023
6c3fa68
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 9, 2023
d1b6e92
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2023
f149abb
add y axis max setting for CPU loading
bigredfrog Apr 9, 2023
478552d
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 9, 2023
7b1f10f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2023
82371da
Fix tab title, but had to force a file save into the config directory
bigredfrog Apr 9, 2023
116e4f0
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 9, 2023
950d73b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 9, 2023
d8ae28e
turn off multi scan graph
bigredfrog Apr 9, 2023
22b5cb3
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 9, 2023
21d5fcf
fix up setup.py
bigredfrog Apr 16, 2023
7f103eb
fix background color on scan / multiscan and scan and flare
bigredfrog Apr 16, 2023
5495ccc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 16, 2023
ba4e437
Improve API for graph slightly
bigredfrog Apr 16, 2023
85ad5eb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 16, 2023
d883657
Improve API for graph slightly
bigredfrog Apr 17, 2023
472a662
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 17, 2023
38f76c7
Improve API for graph slightly
bigredfrog Apr 17, 2023
b25e923
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 17, 2023
3965f0f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 17, 2023
4652072
Hack and slash the aubio build
bigredfrog Apr 18, 2023
4c80de0
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 18, 2023
ec64177
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 18, 2023
55f925f
Hack and slash the aubio build
bigredfrog Apr 18, 2023
d16f835
Merge remote-tracking branch 'origin/multi_scan_2' into multi_scan_2
bigredfrog Apr 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci-build.yml
Expand Up @@ -25,6 +25,7 @@ jobs:
python-version: ${{ matrix.python }}
- name: Build a binary wheel
run: |
python -m pip install numpy --compile --pre
python -m pip install --user -U pip wheel setuptools
python setup.py bdist_wheel
- name: Install LedFx
Expand All @@ -47,6 +48,7 @@ jobs:
python-version: ${{ matrix.python }}
- name: Build a binary wheel
run: |
python -m pip install numpy --compile --pre
python -m pip install --user -U pip wheel setuptools
python setup.py bdist_wheel
- name: Install LedFx
Expand Down
4 changes: 4 additions & 0 deletions ledfx/api/schema.py
Expand Up @@ -88,6 +88,10 @@ async def get(self, request) -> web.Response:
response["effects"][effect_type][
"hidden_keys"
] = effect.HIDDEN_KEYS
if effect.ADVANCED_KEYS:
response["effects"][effect_type][
"advanced_keys"
] = effect.ADVANCED_KEYS
if effect.PERMITTED_KEYS:
response["effects"][effect_type][
"permitted_keys"
Expand Down
3 changes: 3 additions & 0 deletions ledfx/effects/__init__.py
Expand Up @@ -200,6 +200,9 @@ class Effect(BaseRegistry):
NAME = ""
# over ride in effect children to hide existing keys from UI
HIDDEN_KEYS = None
# over ride in effect children AND add an "advanced" bool to schema
# to show or hide in UI
ADVANCED_KEYS = None
# over ride in effect children to allow edit and show others
PERMITTED_KEYS = None
_config = None
Expand Down
85 changes: 71 additions & 14 deletions ledfx/effects/metro.py
@@ -1,13 +1,12 @@
import logging
import timeit

import numpy as np
import psutil
import voluptuous as vol

from ledfx.color import parse_color, validate_color
from ledfx.effects.temporal import TemporalEffect

_LOGGER = logging.getLogger(__name__)
from ledfx.effects.audio import AudioReactiveEffect
from ledfx.utils import Graph

# Metro intent is to flash a pattern on led strips so end users can look for
# sync between separate light strips due to protocol, wifi conditions or other
Expand All @@ -16,20 +15,15 @@
# with common configurations will be in sync


class MetroEffect(TemporalEffect):
class MetroEffect(AudioReactiveEffect):
NAME = "Metro"
CATEGORY = "Diagnostic"
HIDDEN_KEYS = ["speed", "background_brightness", "blur", "mirror"]
HIDDEN_KEYS = ["background_brightness", "blur", "mirror"]

start_time = timeit.default_timer()

CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(
"speed",
default=20.0,
description="Locked to 20 fps",
): vol.All(vol.Coerce(float), vol.Range(min=20, max=20)),
vol.Optional(
"pulse_period",
description="Time between flash in seconds",
Expand All @@ -55,12 +49,27 @@ class MetroEffect(TemporalEffect):
description="Flash color",
default="#FFFFFF",
): validate_color,
vol.Optional(
"capture",
description="graph capture, on to start, off to dump",
default=True,
): bool,
vol.Optional(
"cpu_secs",
description="Window over which to measure CPU usage",
default=1.0,
): vol.All(vol.Coerce(float), vol.Range(min=0.1, max=1.0)),
}
)

def __init__(self, ledfx, config):
super().__init__(ledfx, config)
self.was_flash = False
self.graph_callbacks = None
self.graph_cpu = None
self.cores = 0
self.last_cpu = 0.0
config["capture"] = False
super().__init__(ledfx, config)

def on_activate(self, pixel_count):
pass
Expand All @@ -76,9 +85,50 @@ def config_updated(self, config):
self.cycle_threshold = self._config["pulse_period"] * (
self._config["pulse_ratio"]
)
if self._config["capture"] and self.graph_callbacks is None:
# start a capture sequence, generate base graphs
self.graph_callbacks = Graph(
"Metro Callback Timing", ["Audio", "Render"], points=5000
)
self.cores = psutil.cpu_count()
# zero the local timer and prime cpu measurement
self.last_cpu = timeit.default_timer()
psutil.cpu_percent(percpu=True)
cpu_keys = [f"CPU {i}" for i in range(self.cores)]
self.graph_cpu = Graph(
"Metro CPU Usage",
cpu_keys,
points=1000,
y_title="CPU %",
y_axis_max=100.0,
)
elif not self._config["capture"] and self.graph_callbacks is not None:
self.graph_callbacks.dump_graph(only_jitter=True)
self.lock.acquire()
if self.graph_cpu:
self.graph_cpu.dump_graph(
jitter=True, sub_title=f"{self._config['cpu_secs']} secs"
)
self.lock.release()
self.graph_callbacks = None
self.graph_cpu = None

if self.graph_callbacks:
# Y value does not matter as we are only looking at jitter
self.graph_callbacks.append_tag("Config Update", 10.0)

def audio_data_updated(self, data):
if self.graph_callbacks is not None:
# value does not matter as we are only looking at jitter
self.graph_callbacks.append_by_key("Audio", 1.0)

def effect_loop(self):
pass_time = timeit.default_timer() - self.start_time
def render(self):
now = timeit.default_timer()
if self.graph_callbacks is not None:
# value does not matter as we are only looking at jitter
self.graph_callbacks.append_by_key("Render", 1.0)

pass_time = now - self.start_time
cycle_time = pass_time % self._config["pulse_period"]

if cycle_time > self.cycle_threshold:
Expand All @@ -103,3 +153,10 @@ def effect_loop(self):
start_pixel : end_pixel - 1
] = self.flash_color
self.was_flash = True

if self.graph_cpu is not None:
if now - self.last_cpu > self._config["cpu_secs"]:
cpu = psutil.cpu_percent(percpu=True)
for i in range(self.cores):
self.graph_cpu.append_by_key(f"CPU {i}", cpu[i])
self.last_cpu = now
2 changes: 1 addition & 1 deletion ledfx/effects/pixels.py
Expand Up @@ -34,7 +34,7 @@ class PixelsEffect(TemporalEffect):
"step_period",
description="Time between each pixel step to light up ",
default=1.0,
): vol.All(vol.Coerce(float), vol.Range(min=0.1, max=5.0)),
): vol.All(vol.Coerce(float), vol.Range(min=0.01, max=5.0)),
vol.Optional(
"background_color",
description="Background color",
Expand Down
4 changes: 3 additions & 1 deletion ledfx/effects/scan.py
Expand Up @@ -132,7 +132,9 @@ def render(self):

pixel_pos = max(0, min(int(self.scan_pos), self.pixel_count))

self.pixels[0 : self.pixel_count] = self.background_color
self.pixels[0 : self.pixel_count] = (
self.background_color * self.config["background_brightness"]
)
self.pixels[
pixel_pos : min(pixel_pos + scan_width_pixels, self.pixel_count)
] = self.color_scan
Expand Down
4 changes: 3 additions & 1 deletion ledfx/effects/scan_and_flare.py
Expand Up @@ -219,7 +219,9 @@ def render(self):
self.last_sparkle = now

# this is the real render pass
self.pixels[0 : self.pixel_count] = self.background_color
self.pixels[0 : self.pixel_count] = (
self.background_color * self.config["background_brightness"]
)
self.pixels[
pixel_pos : min(pixel_pos + scan_width_pixels, self.pixel_count)
] = self.color_scan
Expand Down
78 changes: 76 additions & 2 deletions ledfx/effects/scan_multi.py
Expand Up @@ -7,6 +7,12 @@
from ledfx.color import parse_color, validate_color
from ledfx.effects.audio import AudioReactiveEffect
from ledfx.effects.gradient import GradientEffect
from ledfx.utils import Graph

# this is kept as an example of how to use the graph util
# set to True to test, hit flip to trigger graph spawning in browser
# any config change will add a text annotation to the graph
graph_dump = False


class Power(IntEnum):
Expand All @@ -21,6 +27,11 @@ def __init__(self, power_func):
self.returning = False
self.bar = 0
self.power_func = power_func
self.power = 0.0
if graph_dump:
self.graph = Graph(
f"Scan Filter {power_func}", ["p_in", "p_out"], y_title="Power"
)

def set_color_scan_cache(self, color):
self.color_scan_cache = np.array(parse_color(color), dtype=float)
Expand All @@ -31,6 +42,12 @@ class ScanMultiAudioEffect(AudioReactiveEffect, GradientEffect):
NAME = "Scan Multi"
CATEGORY = "Classic"
HIDDEN_KEYS = ["gradient_roll"]
ADVANCED_KEYS = ["input_source", "attack", "decay", "filter"]

_sources = {
"Power": "power",
"Melbank": "melbank",
}

CONFIG_SCHEMA = vol.Schema(
{
Expand Down Expand Up @@ -85,6 +102,31 @@ class ScanMultiAudioEffect(AudioReactiveEffect, GradientEffect):
description="Use colors from gradient selector",
default=False,
): bool,
vol.Optional(
"advanced",
description="enable advanced options",
default=True,
): bool,
vol.Optional(
"input_source",
description="Audio processing source for low, mid, high",
default="Power",
): vol.In(list(_sources.keys())),
vol.Optional(
"attack",
description="Filter damping on attack, lower number is more",
default=0.9,
): vol.All(vol.Coerce(float), vol.Range(min=0.01, max=0.99999)),
vol.Optional(
"decay",
description="Filter damping on decay, lower number is more",
default=0.7,
): vol.All(vol.Coerce(float), vol.Range(min=0.01, max=0.99999)),
vol.Optional(
"filter",
description="Enable damping filters on attack and decay",
default=False,
): bool,
}
)

Expand All @@ -94,6 +136,7 @@ def __init__(self, ledfx, config):
Scan("mids_power"),
Scan("high_power"),
]
self.flip_was = config["flip"]
super().__init__(ledfx, config)

def on_activate(self, pixel_count):
Expand All @@ -107,9 +150,38 @@ def config_updated(self, config):
self.scans[Power.MIDS].set_color_scan_cache(self._config["color_mid"])
self.scans[Power.HIGH].set_color_scan_cache(self._config["color_high"])

for scan in self.scans:
scan._p_filter = self.create_filter(
alpha_decay=self._config["decay"],
alpha_rise=self._config["attack"],
)

if graph_dump:
for scan in self.scans:
if self._config["flip"] != self.flip_was:
scan.graph.dump_graph("Flip")
scan.graph.append_tag(
"Config changed", scan.power, color="red"
)
self.flip_was = self._config["flip"]

def audio_data_updated(self, data):
if self._config["input_source"] == "Melbank":
self.scans[0].power, self.scans[1].power, self.scans[2].power = (
2 * np.mean(i) for i in self.melbank_thirds(filtered=False)
)
else:
for scan in self.scans:
scan.power = getattr(data, scan.power_func)() * 2

for scan in self.scans:
scan.power = getattr(data, scan.power_func)() * 2
if graph_dump:
scan.graph.append_by_key("p_in", scan.power)
if self._config["filter"]:
scan.power = scan._p_filter.update(scan.power)
if graph_dump:
scan.graph.append_by_key("p_out", scan.power)

scan.bar = scan.power * self._config["multiplier"]

if self._config["use_grad"]:
Expand All @@ -133,7 +205,9 @@ def render(self):
max(1, int(self.pixel_count / 100.0 * self._config["scan_width"]))
)

self.pixels[0 : self.pixel_count] = self.background_color
self.pixels[0 : self.pixel_count] = (
self.background_color * self.config["background_brightness"]
)

for scan in self.scans:
step_size = step_size * scan.bar
Expand Down