init
This commit is contained in:
224
lib_audio_dsp/python/audio_dsp/dsp/generic.py
Normal file
224
lib_audio_dsp/python/audio_dsp/dsp/generic.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""The generic DSP block and globals."""
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import numpy as np
|
||||
from audio_dsp.dsp import utils as utils
|
||||
from docstring_inheritance import NumpyDocstringInheritanceInitMeta
|
||||
|
||||
# default Q format for the signal
|
||||
Q_SIG = 27
|
||||
|
||||
# number of bits for the headroom, this will set the maximum gain that
|
||||
# can be applied to the signal without overflowing.
|
||||
HEADROOM_BITS = 31 - Q_SIG
|
||||
HEADROOM_DB = utils.db((utils.Q_max(31) + 1) / utils.Q_max(Q_SIG))
|
||||
MIN_SIG_DB = utils.db(1 / 2**Q_SIG)
|
||||
|
||||
|
||||
class dsp_block(metaclass=NumpyDocstringInheritanceInitMeta):
|
||||
"""
|
||||
Generic DSP block, all blocks should inherit from this class and
|
||||
implement it's methods.
|
||||
|
||||
By using the metaclass NumpyDocstringInheritanceInitMeta, parameter
|
||||
and attribute documentation can be inherited by the child classes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fs : int
|
||||
Sampling frequency in Hz.
|
||||
n_chans : int
|
||||
Number of channels the block runs on.
|
||||
Q_sig: int, optional
|
||||
Q format of the signal, number of bits after the decimal point.
|
||||
Defaults to Q4.27.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
fs : int
|
||||
Sampling frequency in Hz.
|
||||
n_chans : int
|
||||
Number of channels the block runs on.
|
||||
Q_sig: int
|
||||
Q format of the signal, number of bits after the decimal point.
|
||||
"""
|
||||
|
||||
def __init__(self, fs, n_chans, Q_sig=Q_SIG):
|
||||
self.fs = fs
|
||||
self.n_chans = n_chans
|
||||
self.Q_sig = Q_sig
|
||||
return
|
||||
|
||||
def process(self, sample: float, channel=0):
|
||||
"""
|
||||
Take one new sample and give it back. Do no processing for the
|
||||
generic block.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sample : float
|
||||
The input sample to be processed.
|
||||
channel : int, optional
|
||||
The channel index to process the sample on. Default is 0.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The processed sample.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def process_xcore(self, sample: float, channel=0):
|
||||
"""Take one new sample and return 1 processed sample.
|
||||
|
||||
For the generic implementation, scale and quantize the input,
|
||||
call the xcore-like implementation, then scale back to 1.0 = 0 dB.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sample : float
|
||||
The input sample to be processed.
|
||||
channel : int, optional
|
||||
The channel index to process the sample on. Default is 0.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The processed output sample.
|
||||
"""
|
||||
sample_int = utils.float_to_fixed(sample, self.Q_sig)
|
||||
y = self.process(float(sample_int))
|
||||
y_flt = utils.fixed_to_float(y, self.Q_sig)
|
||||
|
||||
return y_flt
|
||||
|
||||
def process_channels(self, sample_list: list[float]) -> list[float]:
|
||||
"""
|
||||
Process the sample in each audio channel using floating point maths.
|
||||
|
||||
The generic implementation calls self.process for each channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sample_list : list[float]
|
||||
The input samples to be processed. Each sample represents a
|
||||
different channel
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float]
|
||||
The processed samples for each channel.
|
||||
"""
|
||||
output_samples = deepcopy(sample_list)
|
||||
for channel in range(len(output_samples)):
|
||||
output_samples[channel] = self.process(sample_list[channel], channel)
|
||||
return output_samples
|
||||
|
||||
def process_channels_xcore(self, sample_list: list[float]) -> list[float]:
|
||||
"""
|
||||
Process the sample in each audio channel using fixed point maths.
|
||||
|
||||
The generic implementation calls self.process_xcore for each channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sample_list : list[float]
|
||||
The input samples to be processed. Each sample represents a
|
||||
different channel
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[float]
|
||||
The processed samples for each channel.
|
||||
"""
|
||||
output_samples = deepcopy(sample_list)
|
||||
for channel in range(len(output_samples)):
|
||||
output_samples[channel] = self.process_xcore(sample_list[channel], channel)
|
||||
return output_samples
|
||||
|
||||
def process_frame(self, frame: list):
|
||||
"""
|
||||
Take a list frames of samples and return the processed frames.
|
||||
|
||||
A frame is defined as a list of 1-D numpy arrays, where the
|
||||
number of arrays is equal to the number of channels, and the
|
||||
length of the arrays is equal to the frame size.
|
||||
|
||||
For the generic implementation, just call process for each
|
||||
sample for each channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : list
|
||||
List of frames, where each frame is a 1-D numpy array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of processed frames, with the same structure as the
|
||||
input frame.
|
||||
"""
|
||||
frame_np = np.array(frame)
|
||||
frame_size = frame[0].shape[0]
|
||||
output = np.zeros((len(frame), frame_size))
|
||||
for sample in range(frame_size):
|
||||
output[:, sample] = self.process_channels(frame_np[:, sample].tolist())
|
||||
|
||||
return list(output)
|
||||
|
||||
def process_frame_xcore(self, frame: list):
|
||||
"""
|
||||
Take a list frames of samples and return the processed frames,
|
||||
using an xcore-like implementation.
|
||||
|
||||
A frame is defined as a list of 1-D numpy arrays, where the
|
||||
number of arrays is equal to the number of channels, and the
|
||||
length of the arrays is equal to the frame size.
|
||||
|
||||
For the generic implementation, just call process for each
|
||||
sample for each channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
frame : list
|
||||
List of frames, where each frame is a 1-D numpy array.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
List of processed frames, with the same structure as the
|
||||
input frame.
|
||||
"""
|
||||
frame_np = np.array(frame)
|
||||
frame_size = frame[0].shape[0]
|
||||
output = np.zeros((len(frame), frame_size))
|
||||
for sample in range(frame_size):
|
||||
output[:, sample] = self.process_channels_xcore(frame_np[:, sample].tolist())
|
||||
|
||||
return list(output)
|
||||
|
||||
def freq_response(self, nfft=32768):
|
||||
"""
|
||||
Calculate the frequency response of the module for a nominal
|
||||
input.
|
||||
|
||||
The generic module has a flat frequency response.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nfft : int, optional
|
||||
The number of points to use for the FFT, by default 512
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
A tuple containing the frequency values and the
|
||||
corresponding complex response.
|
||||
|
||||
"""
|
||||
f = np.fft.rfftfreq(nfft) * self.fs
|
||||
h = np.ones_like(f)
|
||||
return f, h
|
||||
Reference in New Issue
Block a user