Skip to content

Commit

Permalink
add support for custom IIR filters on qsys
Browse files Browse the repository at this point in the history
  • Loading branch information
3ll3d00d committed Oct 7, 2023
1 parent 3185125 commit 6c153ce
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 25 deletions.
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,16 +259,27 @@ This information is **not** validated, it is left to the user to configure the o

Configuration of the audio pipeline in Q-Sys Designer is left as an exercise for the user.

The component referred to in the configuration is a [Parametric Equaliser](https://q-syshelp.qsc.com/Content/Schematic_Library/equalizer_parametric.htm) which should be configured with:
2 alternative implementations are possible.

One uses a [IIR Custom Filter](https://q-syshelp.qsc.com/Index.htm#Schematic_Library/filter_IIR.htm) component which must be connected to component which provides a text field.

This can be implemented using either a [Text Controller](https://q-syshelp.qsc.com/Index.htm#Schematic_Library/device_controller_script.htm?TocPath=Design%257CSchematic%2520Elements%257CComponents%257CScripting%2520Components%257C_____3) or a [Custom Control](https://q-syshelp.qsc.com/Index.htm#Schematic_Library/custom_controls.htm).

This component allows for a mapping of a text field control key to a `CatalogueEntry` field name.

Two fields have special treatment:

* filters: will be set in a format that can be linked to a IIR Custom Filter and feeds it with the required biquad coefficients.
* images: there can be a variable number of images so each individual image can be specified in a separate field

The alternative approache uses a [Parametric Equaliser](https://q-syshelp.qsc.com/Content/Schematic_Library/equalizer_parametric.htm) component which should be configured with:

* at least 10 bands
* q factor

The component name should be supplied in the configuration above.

An optional `content_info` list of components can also be supplied. Each named component listed (`beq_movie_info` in the example above) is a [Custom Control](https://q-syshelp.qsc.com/Index.htm#Schematic_Library/custom_controls.htm) component which contains 1 or more text fields. The listed fields are a mapping of control key to `CatalogueEntry` field name.

The images field has special treatment as there can be a variable number of images, may be multiple images
Note that this format does not support variable Q shelf filters.

#### CamillaDSP

Expand Down
58 changes: 38 additions & 20 deletions ezbeq/qsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ def __send(self, to_load: List['PEQ'], entry: CatalogueEntry):
while len(to_load) < 10:
to_load.append(PEQ(100, 1, 0, 'PeakingEQ'))
if to_load:
controls = []
for idx, peq in enumerate(to_load):
controls += peq.to_rpc(idx + 1)
self.__send_to_socket(controls, entry)
self.__send_to_socket(to_load, entry)
else:
logger.warning(f"Nothing to send")

Expand All @@ -112,35 +109,42 @@ def __recvall(sock: socket, buf_size: int = 4096) -> str:
return data.decode('utf-8').strip(TERMINATOR)
return ''

def __send_to_socket(self, controls: list, entry: CatalogueEntry):
logger.info(f"Sending {controls} to {self.__ip}:{self.__port}")
def __send_to_socket(self, peqs: List['PEQ'], entry: CatalogueEntry):
logger.info(f"Sending {peqs} to {self.__ip}:{self.__port}")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.__timeout_srcs)
try:
sock.connect((self.__ip, self.__port))
for c in self.__components:
controls = []
for idx, peq in enumerate(peqs):
controls += peq.to_rpc(idx + 1)
self.__send_component(c, controls, sock)
for c in self.__content_info:
for component_name, mappings in c.items():
text_controls = []
for k, v in mappings.items():
m = re.match(r'(.*)\[(\d+)\]', v)
if m:
val = getattr(entry, m.group(1))
if v == 'filters':
val = json.dumps([coeff for p in peqs for coeff in p.coeffs])
else:
val = getattr(entry, v)
if isinstance(val, list):
m = re.match(r'(.*)\[(\d+)\]', v)
if m:
idx = int(m.group(2))
if len(val) > idx:
val = val[idx]
val = getattr(entry, m.group(1))
else:
val = getattr(entry, v)
if isinstance(val, list):
if m:
idx = int(m.group(2))
if len(val) > idx:
val = val[idx]
else:
logger.info(f"'{entry.title} {m.group(1)} has length {len(val)}, "
f"no value to supply for {k}")
val = ''
else:
logger.info(f"'{entry.title} {m.group(1)} has length {len(val)}, no value to supply for {k}")
val = ''
val = ', '.join(val)
else:
val = ', '.join(val)
else:
val = str(val)
val = str(val)
text_controls.append({'Name': k, 'Value': val})
self.__send_component(component_name, text_controls, sock)
finally:
Expand Down Expand Up @@ -213,7 +217,8 @@ def levels(self) -> dict:

class PEQ:

def __init__(self, fc: float, q: float, gain: float, filter_type_name: str):
def __init__(self, fc: float, q: float, gain: float, filter_type_name: str, fs: int = 48000):
self.fs = fs
self.fc = fc
self.q = q
self.gain = gain
Expand All @@ -228,5 +233,18 @@ def to_rpc(self, slot: int):
{"Name": f"type.{slot}", "Value": self.filter_type}
]

@property
def coeffs(self) -> List[float]:
if self.filter_type_name == 2.0:
from iir import LowShelf
filt = LowShelf(self.fs, self.fc, self.q, self.gain)
elif self.filter_type_name == 1.0:
from iir import PeakingEQ
filt = PeakingEQ(self.fs, self.fc, self.q, self.gain)
else:
from iir import HighShelf
filt = HighShelf(self.fs, self.fc, self.q, self.gain)
return filt.b + filt.a

def __repr__(self):
return f"{self.filter_type_name} {self.fc} Hz {self.gain} dB {self.q}"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "ezbeq"
version = "2.0.0a26"
version = "2.0.0a27"
description = "A webapp which can send beqcatalogue filters to a DSP device"
authors = ["3ll3d00d <mattkhan+ezbeq@gmail.com>"]
license = "MIT"
Expand Down

0 comments on commit 6c153ce

Please sign in to comment.