Source code for denspp.offline.analog.amplifier.dly_amp

from dataclasses import dataclass
import numpy as np
from scipy.signal import lfilter
from denspp.offline.analog.common_func import CommonAnalogFunctions
from denspp.offline.analog.dev_noise import ProcessNoise, SettingsNoise, DefaultSettingsNoise


[docs] @dataclass class SettingsDLY: """Individual data class to configure the delay amplifier Attributes: vdd: Positive supply voltage [V] vss: Negative supply voltage [V] fs_ana: Sampling frequency of input [Hz] t_dly: Delay value for shifting the input [s] offset: Offset voltage of the amplifier [V] noise_en: Enable noise on output [True/False] noise_edev: Input voltage noise spectral density [V/sqrt(Hz)] """ vdd: float vss: float fs_ana: float t_dly: float offset: float noise_en: bool noise_edev: float @property def vcm(self) -> float: return (self.vdd + self.vss) / 2 @property def f_break_norm(self) -> float: return 1 / (self.fs_ana * self.t_dly) @property def num_dly_taps(self) -> int: val = int(self.fs_ana * self.t_dly) if val <= 0: raise ValueError("t_dly and fs_ana must be positive") return val
DefaultSettingsDLY = SettingsDLY( vdd=0.6, vss=-0.6, fs_ana=50e3, t_dly=0.5e-3, offset=0e-6, noise_en=False, noise_edev=100e-9 )
[docs] class DelayAmplifier(CommonAnalogFunctions): _handler_noise: ProcessNoise _settings: SettingsDLY def __init__(self, settings_dev: SettingsDLY, settings_noise: SettingsNoise=DefaultSettingsNoise) -> None: """Class for emulating an analogue delay amplifier (More infos on: https://thewolfsound.com/allpass-filter/) :param settings_dev: Dataclass for handling the delay amplifier :param settings_noise: Dataclass for handling the noise simulation """ super().__init__() self.define_voltage_range(volt_low=settings_dev.vss, volt_hgh=settings_dev.vdd) self._handler_noise = ProcessNoise(settings_noise, settings_dev.fs_ana) self._settings = settings_dev @property def vcm(self) -> float: return self._settings.vcm
[docs] def do_simple_delay(self, u_inp: np.ndarray) -> np.ndarray: """Performing a simple delay stage using taps Args: u_inp: Applied voltage input [V] Returns: Corresponding numpy array with shifted input """ uout = np.zeros_like(u_inp) + self._settings.vcm uout[self._settings.num_dly_taps:] = u_inp[:-self._settings.num_dly_taps] return self.clamp_voltage(uout)
[docs] def do_recursive_delay(self, u_inp: np.ndarray) -> np.ndarray: """Performing a recursive delay stage using taps Args: u_inp: Applied voltage input [V] Returns: Corresponding numpy array with shifted input """ uout = np.zeros(u_inp.shape) uout[:self._settings.num_dly_taps+1] = u_inp[-self._settings.num_dly_taps-1:] uout[self._settings.num_dly_taps:] = u_inp[:-self._settings.num_dly_taps] return self.clamp_voltage(uout)
[docs] def do_allpass_first_order(self, u_in: np.ndarray) -> np.ndarray: """Performing a 1st order all-pass filter (IIR) for adding time delay (1/t_dly = f_break) Args: u_in: Input voltage [V] Returns: Corresponding numpy array with shifted voltage signal """ val = np.tan(np.pi * self._settings.f_break_norm) iir_c0 = (val - 1) / (val + 1) b = [iir_c0, 1.0] a = [1.0, iir_c0] return self.clamp_voltage(lfilter(b, a, u_in))
[docs] def do_allpass_second_order(self, u_in: np.ndarray, bandwidth: float) -> np.ndarray: """Performing a 2nd order all-pass filter (IIR) for adding time delay (1/t_dly = f_break) Args: u_in: Input voltage [V] bandwidth: Bandwidth frequency [Hz] Returns: Corresponding numpy array with shifted voltage signal """ val = np.tan(np.pi * bandwidth / self._settings.fs_ana) iir_c0 = (val - 1) / (val + 1) iir_c1 = -np.cos(2 * np.pi * self._settings.f_break_norm) b = [-iir_c0, iir_c1*(1-iir_c0), 1.0] a = [1.0, iir_c1*(1-iir_c0), -iir_c0] return self.clamp_voltage(lfilter(b, a, u_in))