Skip to content

Commit

Permalink
refactor levels service to allow series to be data driven #66
Browse files Browse the repository at this point in the history
  • Loading branch information
3ll3d00d committed May 24, 2023
1 parent 4210642 commit 6025566
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 366 deletions.
5 changes: 3 additions & 2 deletions ezbeq/apis/ws.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
from collections import defaultdict
from typing import Callable, Optional, List, Dict
Expand All @@ -22,8 +23,8 @@ def factory(self) -> 'WsServerFactory':
def broadcast(self, msg: str):
self.__factory.broadcast(msg)

def levels(self, device: str, msg: str) -> bool:
return self.__factory.send_levels(device, msg)
def levels(self, device: str, levels: dict) -> bool:
return self.__factory.send_levels(device, json.dumps({'message': 'Levels', 'data': levels}))


class WsProtocol(WebSocketServerProtocol):
Expand Down
46 changes: 27 additions & 19 deletions ezbeq/minidsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def set_gain(self, channel: Optional[int], value: float):
self.gains = self.__make_vals(value)
else:
if channel <= self.__input_channels:
self.gains[channel-1] = value
self.gains[channel - 1] = value
else:
raise ValueError(f'Unknown channel {channel} for slot {self.slot_id}')

Expand All @@ -143,7 +143,7 @@ def __do_mute(self, channel: Optional[int], value: bool):
self.mutes = self.__make_vals(value)
else:
if channel <= self.__input_channels:
self.mutes[channel-1] = value
self.mutes[channel - 1] = value
else:
raise ValueError(f'Unknown channel {channel} for slot {self.slot_id}')

Expand Down Expand Up @@ -189,7 +189,7 @@ def as_dict(self) -> dict:
}

def __repr__(self):
vals = ' '.join([f"{i+1}: {g:.2f}/{self.mutes[i]}" for i, g in enumerate(self.gains)])
vals = ' '.join([f"{i + 1}: {g:.2f}/{self.mutes[i]}" for i, g in enumerate(self.gains)])
return f"{super().__repr__()} - {vals}"


Expand All @@ -212,7 +212,8 @@ def __eq__(self, o: object) -> bool:
if isinstance(o, PeqRoutes):
same = self.name == o.name and self.biquads == o.biquads and self.channels == o.channels and self.beq_slots == o.beq_slots
if same:
return (self.groups is None and o.groups is None) or (self.groups is not None and self.groups == o.groups)
return (self.groups is None and o.groups is None) or (
self.groups is not None and self.groups == o.groups)
return same
return NotImplemented

Expand Down Expand Up @@ -267,7 +268,8 @@ def __init__(self, name: str, fs: str, i: Optional[PeqRoutes] = None, xo: Option

@property
def peq_routes(self) -> List[PeqRoutes]:
return [x for x in [self.input, self.crossover, self.output] if x] + ([x for x in self.extra] if self.extra else [])
return [x for x in [self.input, self.crossover, self.output] if x] + (
[x for x in self.extra] if self.extra else [])

def to_allocator(self) -> BeqFilterAllocator:
return BeqFilterAllocator(self.peq_routes)
Expand Down Expand Up @@ -384,6 +386,7 @@ def to_ints(v):

return PeqRoutes(r['name'], int(r['biquads']), to_ints(r['channels']), to_ints(r['slots']),
to_ints(r.get('groups', None)))

routes_by_name = {}
extra = []
for r in routes:
Expand Down Expand Up @@ -471,7 +474,8 @@ def __as_idx(idx: Union[int, str]):
return int(idx) - 1

def __send_cmds(self, target_slot_idx: Optional[int], cmds: List[str]):
return self.__executor.submit(self.__do_run, cmds, target_slot_idx, self.__slot_change_delay).result(timeout=self.__cmd_timeout)
return self.__executor.submit(self.__do_run, cmds, target_slot_idx, self.__slot_change_delay).result(
timeout=self.__cmd_timeout)

def activate(self, slot: str):
def __do_it():
Expand Down Expand Up @@ -681,14 +685,14 @@ def __update_slot(self, slot: dict) -> bool:
# current
if 'gains' in slot:
for idx, gain in enumerate(slot['gains']):
self.set_gain(current_slot.slot_id, idx+1, gain)
self.set_gain(current_slot.slot_id, idx + 1, gain)
any_update = True
if 'mutes' in slot:
for idx, mute in enumerate(slot['mutes']):
if mute is True:
self.mute(current_slot.slot_id, idx+1)
self.mute(current_slot.slot_id, idx + 1)
else:
self.unmute(current_slot.slot_id, idx+1)
self.unmute(current_slot.slot_id, idx + 1)
any_update = True
if 'entry' in slot:
if slot['entry']:
Expand Down Expand Up @@ -719,8 +723,7 @@ def __read_levels_from_device(self) -> dict:
return {
'name': self.name,
'ts': ts,
INPUT_NAME: levels['input_levels'],
OUTPUT_NAME: levels['output_levels']
'levels': format_levels(levels)
}
except:
logger.exception(f"[{self.name}] Unable to load levels {lines}")
Expand All @@ -732,8 +735,7 @@ def start_broadcast_levels(self) -> None:
sched = lambda: reactor.callLater(self.__levels_interval, __send)

def __send():
msg = json.dumps(self.levels())
if self.ws_server.levels(self.name, msg):
if self.ws_server.levels(self.name, self.levels()):
sched()

sched()
Expand All @@ -756,14 +758,11 @@ def do_it():

self._hydrate_cache_broadcast(do_it)
if 'input_levels' in msg and 'output_levels' in msg:
self.ws_server.levels(self.name, json.dumps({
self.ws_server.levels(self.name, {
'name': self.name,
'ts': time.time(),
# quick hack for testing purposes
# INPUT_NAME: [x + ((random() * 5) * (-1.0 if self.name == 'd1' else 1.0)) for x in msg['input_levels']],
INPUT_NAME: msg['input_levels'],
OUTPUT_NAME: msg['output_levels']
}))
'levels': format_levels(msg)
})


class MinidspBeqCommandGenerator:
Expand Down Expand Up @@ -1043,3 +1042,12 @@ def broadcast(self, msg):
self.unregister(c)
else:
raise ValueError(f"No devices connected, ignoring {msg}")


def format_levels(levels: dict) -> dict:
# quick hack for testing purposes
# INPUT_NAME: [x + ((random() * 5) * (-1.0 if self.name == 'd1' else 1.0)) for x in msg['input_levels']],
return {
**{f'I{i}': v for i, v in enumerate(levels['input_levels'])},
**{f'O{i}': v for i, v in enumerate(levels['output_levels'])}
}
35 changes: 9 additions & 26 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {makeStyles} from '@mui/styles';
import ezbeq from './services/ezbeq';
import useMediaQuery from '@mui/material/useMediaQuery';
import CssBaseline from '@mui/material/CssBaseline';
import {pushData, useLocalStorage} from "./services/util";
import {pushData} from "./services/util";
import ErrorSnack from "./components/ErrorSnack";
import MainView from "./components/main";
import Levels from "./components/levels";
import Minidsp from "./components/minidsp";
import Streamer from "./services/streamer";
import LevelsService from "./services/levels";

const useStyles = makeStyles((theme) => ({
root: {
Expand Down Expand Up @@ -73,16 +73,8 @@ const App = () => {
const [selectedDeviceName, setSelectedDeviceName] = useState('');
const [selectedNav, setSelectedNav] = useState('catalogue');

// levels
const [minidspRs, setMinidspRs] = useLocalStorage('chartMinidspRs', {
host: window.location.host,
device: 0,
port: 5380
});
const [direct, setDirect] = useLocalStorage('chartDirect', false);

const streamer = useMemo(() => {
return new Streamer(setErr);
const levelsService = useMemo(() => {
return new LevelsService(setErr, `ws://${window.location.host}/ws`, theme);
}, [setErr]);

// initial data load
Expand All @@ -91,20 +83,13 @@ const App = () => {
}, []);

useEffect(() => {
streamer.loadDevices(Object.keys(availableDevices));
}, [streamer, availableDevices]);
levelsService.loadDevices(Object.keys(availableDevices));
}, [levelsService, availableDevices]);

useEffect(() => {
pushData(setAvailableDevices, ezbeq.getDevices, setErr);
}, []);

useEffect(() => {
const url = direct
? `ws://${minidspRs.host}:${minidspRs.port}/devices/${minidspRs.device}?levels=true`
: `ws://${window.location.host}/ws`;
streamer.setUrl(url);
}, [minidspRs, direct, streamer]);

useEffect(() => {
setHasMultipleTabs([...Object.keys(availableDevices)].find(k => availableDevices[k].hasOwnProperty('masterVolume')));
}, [availableDevices, setHasMultipleTabs]);
Expand Down Expand Up @@ -158,14 +143,11 @@ const App = () => {
<Levels availableDevices={availableDevices}
selectedDeviceName={selectedDeviceName}
setSelectedDeviceName={setSelectedDeviceName}
minidspRs={minidspRs}
setMinidspRs={setMinidspRs}
direct={direct}
setDirect={setDirect}
streamer={streamer}
levelsService={levelsService}
hasMultipleTabs={hasMultipleTabs}
selectedNav={selectedNav}
setSelectedNav={setSelectedNav}
theme={theme}
/>
:
<Minidsp availableDevices={availableDevices}
Expand All @@ -176,6 +158,7 @@ const App = () => {
hasMultipleTabs={hasMultipleTabs}
selectedNav={selectedNav}
setSelectedNav={setSelectedNav}
theme={theme}
/>
}
</Root>
Expand Down
18 changes: 8 additions & 10 deletions ui/src/components/levels/Chart.jsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import React, {useEffect} from "react";
import UplotReact from 'uplot-react';
import 'uplot/dist/uPlot.min.css';
import {EMPTY_PAYLOAD} from "../../services/streamer";


const Chart = ({options, streamer, devices}) => {
const Chart = ({options, levelsService, devices}) => {

useEffect(() => {
streamer.loadDevices(devices);
streamer.initWebsocket();
levelsService.loadDevices(devices);
levelsService.initWebsocket();
return () => {
streamer.close();
levelsService.close();
};
}, [streamer]);
}, [levelsService]);

return (
<UplotReact options={options}
data={EMPTY_PAYLOAD}
onCreate={u => streamer.setChart(u)}
onDelete={u => streamer.setChart(null)}/>
data={[]}
onCreate={u => levelsService.setChart(u)}
onDelete={u => levelsService.setChart(null)}/>
);
}

Expand Down
91 changes: 4 additions & 87 deletions ui/src/components/levels/Controls.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import clsx from 'clsx';
import {Button, FormControlLabel, Switch, TextField} from "@mui/material";
import {FormControlLabel, Switch, TextField} from "@mui/material";
import {makeStyles} from "@mui/styles";
import {useEffect, useState} from "react";
import SaveIcon from '@mui/icons-material/Save';

const useStyles = makeStyles((theme) => ({
root: {
Expand All @@ -21,30 +19,10 @@ const Controls = ({
duration,
setDuration,
recording,
setRecording,
direct,
setDirect,
showAdvanced,
minidspRs,
setMinidspRs
setRecording
}) => {
const classes = useStyles();
const [minidspRsHost, setMinidspRsHost] = useState(window.location.hostname);
const [minidspRsDevice, setMinidspRsDevice] = useState(0);
const [minidspRsPort, setMinidspRsPort] = useState(5380);
useEffect(() => {
if (minidspRs) {
setMinidspRsHost(minidspRs.host);
setMinidspRsDevice(minidspRs.device);
setMinidspRsPort(minidspRs.port);
} else {
setMinidspRsHost(window.location.hostname);
setMinidspRsDevice(0);
setMinidspRsPort(5380)
}
}, [minidspRs]);
return <>
<form className={clsx(classes.root, classes.margin)} noValidate autoComplete="off">
return <form className={clsx(classes.root, classes.margin)} noValidate autoComplete="off">
<TextField
variant="standard"
id="duration-seconds"
Expand All @@ -65,68 +43,7 @@ const Controls = ({
name="recording"
color="primary"/>}
label="Recording?"/>
{
showAdvanced
?
<FormControlLabel className={classes.margin}
control={<Switch checked={direct}
onChange={e => setDirect(e.target.checked)}
name="direct"
color="secondary"
size="small"/>}
label="Direct"/>
: null
}
</form>
{
showAdvanced
?
<form className={clsx(classes.root, classes.margin)} noValidate autoComplete="off">
<TextField
variant="standard"
className={classes.margin}
id="minidsp-rs-ip"
label="minidsprs host or ip"
value={minidspRsHost}
onChange={e => setMinidspRsHost(e.target.value)} />
<TextField
variant="standard"
className={clsx(classes.margin, classes.textField)}
id="minidsp-rs-device_id"
label="device id"
type="number"
min={0}
step={1}
max={10}
value={minidspRsDevice}
onChange={e => setMinidspRsDevice(e.target.value)} />
<TextField
variant="standard"
className={clsx(classes.margin, classes.textField)}
id="minidsp-rs-port"
label="port"
type="number"
min={1}
step={1}
value={minidspRsPort}
onChange={e => setMinidspRsPort(e.target.value)}
InputLabelProps={{ shrink: true }} />
<Button variant="contained"
color="primary"
startIcon={<SaveIcon/>}
size={'small'}
onClick={() => setMinidspRs({
host: minidspRsHost,
port: minidspRsPort,
device: minidspRsDevice
})}>
Apply
</Button>
</form>
:
null
}
</>;
</form>;
};

export default Controls;
Loading

0 comments on commit 6025566

Please sign in to comment.