nahue-passano
commited on
Upload 6 files
Browse files- app.py +134 -0
- config.py +58 -0
- core.py +138 -0
- tmatrix.py +49 -0
- utils.py +63 -0
- visualization.py +47 -0
app.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
from loudspeaker_tmatrix.core import simulate_loudspeaker
|
6 |
+
from loudspeaker_tmatrix.visualization import plot_loudspeaker_response
|
7 |
+
from loudspeaker_tmatrix.config import (
|
8 |
+
load_config,
|
9 |
+
load_loudspeaker_config,
|
10 |
+
AcousticalConstantsConfig,
|
11 |
+
)
|
12 |
+
|
13 |
+
config_path = "configs/config.yaml"
|
14 |
+
default_loudspeaker_path = "configs/default_loudspeaker.yaml"
|
15 |
+
|
16 |
+
cfg = load_config(config_path)
|
17 |
+
loudspeaker_cfg = load_loudspeaker_config(default_loudspeaker_path)
|
18 |
+
|
19 |
+
freq_array = np.logspace(
|
20 |
+
np.log10(cfg.frequency.min), np.log10(cfg.frequency.max), num=cfg.frequency.n_bins
|
21 |
+
)
|
22 |
+
|
23 |
+
angular_freq_array = 2 * np.pi * freq_array
|
24 |
+
|
25 |
+
st.sidebar.title("Thiele Small parameters")
|
26 |
+
|
27 |
+
### Sliders
|
28 |
+
slider_Re = st.sidebar.slider(
|
29 |
+
"Electrical Coil Resistance (Re) [Ohm]",
|
30 |
+
0.0,
|
31 |
+
10.0,
|
32 |
+
loudspeaker_cfg.electrical.coil_resistance,
|
33 |
+
)
|
34 |
+
slider_Le = st.sidebar.slider(
|
35 |
+
"Electrical Coil Inductance (Le) [mH]",
|
36 |
+
0.0,
|
37 |
+
10.0,
|
38 |
+
loudspeaker_cfg.electrical.coil_inductance * 1e3,
|
39 |
+
)
|
40 |
+
slider_Le /= 1e3
|
41 |
+
slider_Bl = st.sidebar.slider(
|
42 |
+
"Electromechanical factor (Bl) [N/A]",
|
43 |
+
0.0,
|
44 |
+
10.0,
|
45 |
+
loudspeaker_cfg.electromechanical_factor,
|
46 |
+
)
|
47 |
+
slider_Mm = st.sidebar.slider(
|
48 |
+
"Mechanical Mass (Mm) [mg]",
|
49 |
+
0.0,
|
50 |
+
50.0,
|
51 |
+
loudspeaker_cfg.mechanical.mass * 1e3,
|
52 |
+
)
|
53 |
+
slider_Mm /= 1e3
|
54 |
+
slider_Cm = st.sidebar.slider(
|
55 |
+
"Mechanical Compliance (Cm) [mm/N]",
|
56 |
+
0.0,
|
57 |
+
5.0,
|
58 |
+
loudspeaker_cfg.mechanical.compliance * 1e3,
|
59 |
+
)
|
60 |
+
slider_Cm /= 1e3
|
61 |
+
slider_Rm = st.sidebar.slider(
|
62 |
+
"Mechanical Resistance (Rm) [kg/s]",
|
63 |
+
0.0,
|
64 |
+
10.0,
|
65 |
+
loudspeaker_cfg.mechanical.resistance,
|
66 |
+
)
|
67 |
+
slider_diam = st.sidebar.slider(
|
68 |
+
"Effective diameter of radiation [cm]",
|
69 |
+
0.0,
|
70 |
+
50.0,
|
71 |
+
loudspeaker_cfg.acoustical.effective_diameter * 1e2,
|
72 |
+
)
|
73 |
+
slider_diam /= 1e2
|
74 |
+
|
75 |
+
default_params = st.sidebar.button("Set default parameters")
|
76 |
+
|
77 |
+
if default_params:
|
78 |
+
st.rerun(scope="app")
|
79 |
+
|
80 |
+
|
81 |
+
freq_array = np.logspace(
|
82 |
+
np.log10(cfg.frequency.min), np.log10(cfg.frequency.max), num=cfg.frequency.n_bins
|
83 |
+
)
|
84 |
+
|
85 |
+
angular_freq_array = 2 * np.pi * freq_array
|
86 |
+
|
87 |
+
thiele_small_params = {
|
88 |
+
"Re": slider_Re,
|
89 |
+
"Le": slider_Le,
|
90 |
+
"Bl": slider_Bl,
|
91 |
+
"Mm": slider_Mm,
|
92 |
+
"Cm": slider_Cm,
|
93 |
+
"Rm": slider_Rm,
|
94 |
+
"effective_diameter": slider_diam,
|
95 |
+
}
|
96 |
+
|
97 |
+
loudspeaker_responses = simulate_loudspeaker(
|
98 |
+
thiele_small_params, angular_freq_array, cfg.acoustical_constants
|
99 |
+
)
|
100 |
+
|
101 |
+
|
102 |
+
# Electrical impedance
|
103 |
+
electrical_impedance_plot = plot_loudspeaker_response(
|
104 |
+
response_array=loudspeaker_responses["electrical_impedance"],
|
105 |
+
freq_array=freq_array,
|
106 |
+
title="Electrical Impedance",
|
107 |
+
magnitude_in_db=False,
|
108 |
+
magnitude_units="Ohm",
|
109 |
+
shift_phase=False,
|
110 |
+
)
|
111 |
+
|
112 |
+
|
113 |
+
mechanical_velocity_plot = plot_loudspeaker_response(
|
114 |
+
response_array=loudspeaker_responses["mechanical_velocity"],
|
115 |
+
freq_array=freq_array,
|
116 |
+
title="Mechanical Velocity",
|
117 |
+
magnitude_in_db=False,
|
118 |
+
magnitude_units="m/s",
|
119 |
+
shift_phase=True,
|
120 |
+
)
|
121 |
+
|
122 |
+
|
123 |
+
acoustical_pressure = plot_loudspeaker_response(
|
124 |
+
response_array=loudspeaker_responses["acoustical_pressure"],
|
125 |
+
freq_array=freq_array,
|
126 |
+
title="Acoustical Pressure",
|
127 |
+
magnitude_in_db=True,
|
128 |
+
magnitude_units="dB",
|
129 |
+
shift_phase=False,
|
130 |
+
)
|
131 |
+
|
132 |
+
st.pyplot(electrical_impedance_plot)
|
133 |
+
st.pyplot(mechanical_velocity_plot)
|
134 |
+
st.pyplot(acoustical_pressure)
|
config.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
import yaml
|
3 |
+
|
4 |
+
|
5 |
+
class FrequencyConfig(BaseModel):
|
6 |
+
min: int
|
7 |
+
max: int
|
8 |
+
n_bins: int
|
9 |
+
|
10 |
+
|
11 |
+
class AcousticalConstantsConfig(BaseModel):
|
12 |
+
sound_speed: float
|
13 |
+
air_density: float
|
14 |
+
atmospheric_pressure: int
|
15 |
+
reference_pressure: float
|
16 |
+
measurement_distance: float
|
17 |
+
directivity_factor: int
|
18 |
+
|
19 |
+
|
20 |
+
class Config(BaseModel):
|
21 |
+
default_loudspeaker_cfg: str
|
22 |
+
frequency: FrequencyConfig
|
23 |
+
acoustical_constants: AcousticalConstantsConfig
|
24 |
+
|
25 |
+
|
26 |
+
def load_config(yaml_path: str) -> Config:
|
27 |
+
with open(yaml_path, "r") as file:
|
28 |
+
data = yaml.safe_load(file)
|
29 |
+
return Config(**data)
|
30 |
+
|
31 |
+
|
32 |
+
class ElectricalConfig(BaseModel):
|
33 |
+
input_voltage: float
|
34 |
+
coil_resistance: float
|
35 |
+
coil_inductance: float
|
36 |
+
|
37 |
+
|
38 |
+
class MechanicalConfig(BaseModel):
|
39 |
+
mass: float
|
40 |
+
compliance: float
|
41 |
+
resistance: float
|
42 |
+
|
43 |
+
|
44 |
+
class AcousticalConfig(BaseModel):
|
45 |
+
effective_diameter: float
|
46 |
+
|
47 |
+
|
48 |
+
class LoudspeakerConfig(BaseModel):
|
49 |
+
electrical: ElectricalConfig
|
50 |
+
electromechanical_factor: float
|
51 |
+
mechanical: MechanicalConfig
|
52 |
+
acoustical: AcousticalConfig
|
53 |
+
|
54 |
+
|
55 |
+
def load_loudspeaker_config(yaml_path: str) -> LoudspeakerConfig:
|
56 |
+
with open(yaml_path, "r") as file:
|
57 |
+
data = yaml.safe_load(file)
|
58 |
+
return LoudspeakerConfig(**data)
|
core.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
from loudspeaker_tmatrix import tmatrix
|
4 |
+
from loudspeaker_tmatrix.utils import layer_wise_dot_product, struve, bessel
|
5 |
+
from loudspeaker_tmatrix.config import AcousticalConstantsConfig
|
6 |
+
|
7 |
+
|
8 |
+
def simulate_loudspeaker(
|
9 |
+
thiele_small_params: dict,
|
10 |
+
angular_freq_array: np.ndarray,
|
11 |
+
acoustical_constants: AcousticalConstantsConfig,
|
12 |
+
):
|
13 |
+
|
14 |
+
n_bins = len(angular_freq_array)
|
15 |
+
Re_tmatrix = tmatrix.resistance_series(thiele_small_params["Re"], n_bins)
|
16 |
+
Z_Le_tmatrix = tmatrix.inductance_series(
|
17 |
+
thiele_small_params["Le"], angular_freq_array
|
18 |
+
)
|
19 |
+
Bl_tmatrix = tmatrix.gyrator(thiele_small_params["Bl"], n_bins)
|
20 |
+
Z_Mm_tmatrix = tmatrix.inductance_series(
|
21 |
+
thiele_small_params["Mm"], angular_freq_array
|
22 |
+
)
|
23 |
+
Z_Cm_tmatrix = tmatrix.capacitance_series(
|
24 |
+
thiele_small_params["Cm"], angular_freq_array
|
25 |
+
)
|
26 |
+
Rm_tmatrix = tmatrix.resistance_series(thiele_small_params["Rm"], n_bins)
|
27 |
+
|
28 |
+
electro_mechanical_tmatrix = layer_wise_dot_product(
|
29 |
+
Re_tmatrix,
|
30 |
+
Z_Le_tmatrix,
|
31 |
+
Bl_tmatrix,
|
32 |
+
Z_Mm_tmatrix,
|
33 |
+
Z_Cm_tmatrix,
|
34 |
+
Rm_tmatrix,
|
35 |
+
)
|
36 |
+
|
37 |
+
t_11 = electro_mechanical_tmatrix[0, 0]
|
38 |
+
t_12 = electro_mechanical_tmatrix[0, 1]
|
39 |
+
t_21 = electro_mechanical_tmatrix[1, 0]
|
40 |
+
t_22 = electro_mechanical_tmatrix[1, 1]
|
41 |
+
|
42 |
+
# Electrical response
|
43 |
+
electrical_impedance_shorted_output = t_12 / t_22
|
44 |
+
|
45 |
+
# Mechanical response
|
46 |
+
electrical_input_voltage = np.ones(n_bins)
|
47 |
+
electrical_input_current = (
|
48 |
+
electrical_input_voltage / electrical_impedance_shorted_output
|
49 |
+
)
|
50 |
+
|
51 |
+
electro_mechanical_tmatrix_det = np.abs(t_11 * t_22 - t_12 * t_21)
|
52 |
+
|
53 |
+
electro_mechanical_tmatrix_inv = np.array(
|
54 |
+
[
|
55 |
+
[
|
56 |
+
t_22 / electro_mechanical_tmatrix_det,
|
57 |
+
-t_12 / electro_mechanical_tmatrix_det,
|
58 |
+
],
|
59 |
+
[
|
60 |
+
-t_21 / electro_mechanical_tmatrix_det,
|
61 |
+
t_11 / electro_mechanical_tmatrix_det,
|
62 |
+
],
|
63 |
+
]
|
64 |
+
)
|
65 |
+
|
66 |
+
mechanical_force, mechanical_velocity = layer_wise_dot_product(
|
67 |
+
electro_mechanical_tmatrix_inv,
|
68 |
+
np.array(
|
69 |
+
[
|
70 |
+
[electrical_input_voltage, electrical_input_voltage],
|
71 |
+
[electrical_input_current, electrical_input_current],
|
72 |
+
]
|
73 |
+
),
|
74 |
+
)[:, 0]
|
75 |
+
|
76 |
+
# Acoustical response
|
77 |
+
air_impedance = acoustical_constants.air_density * acoustical_constants.sound_speed
|
78 |
+
wave_number_array = angular_freq_array / acoustical_constants.sound_speed
|
79 |
+
|
80 |
+
effective_radiation_radius = thiele_small_params["effective_diameter"] / 2
|
81 |
+
|
82 |
+
ka_array = wave_number_array * effective_radiation_radius
|
83 |
+
Sd_value = np.pi * effective_radiation_radius**2
|
84 |
+
Sd_tmatrix = tmatrix.transformer(Sd_value, n_bins)
|
85 |
+
|
86 |
+
### Mechanical impedance of radiation
|
87 |
+
ZM_rad_real_array = Sd_value * air_impedance * (1 - bessel(2 * ka_array) / ka_array)
|
88 |
+
ZM_rad_imag_array = (
|
89 |
+
Sd_value * air_impedance * (1j * (struve(2 * ka_array) / ka_array))
|
90 |
+
)
|
91 |
+
ZM_rad_array = ZM_rad_real_array + ZM_rad_imag_array
|
92 |
+
ZM_rad_tmatrix = np.array(
|
93 |
+
[[np.ones(n_bins), ZM_rad_array], [np.zeros(n_bins), np.ones(n_bins)]]
|
94 |
+
)
|
95 |
+
|
96 |
+
# Specific acoustic impedance
|
97 |
+
ZA_rad = (
|
98 |
+
1j
|
99 |
+
* angular_freq_array
|
100 |
+
* acoustical_constants.air_density
|
101 |
+
* acoustical_constants.directivity_factor
|
102 |
+
) / (
|
103 |
+
4
|
104 |
+
* np.pi
|
105 |
+
* acoustical_constants.measurement_distance
|
106 |
+
* np.exp(1j * wave_number_array * acoustical_constants.measurement_distance)
|
107 |
+
)
|
108 |
+
Z_delay = 1j * np.exp(
|
109 |
+
-1j * wave_number_array * acoustical_constants.measurement_distance
|
110 |
+
) # Phase rotation due air propagation time
|
111 |
+
|
112 |
+
electro_mechanical_acoustic_tmatrix = layer_wise_dot_product(
|
113 |
+
electro_mechanical_tmatrix, ZM_rad_tmatrix, Sd_tmatrix
|
114 |
+
)
|
115 |
+
|
116 |
+
electrical_input_voltage = 2.83
|
117 |
+
|
118 |
+
t_11 = electro_mechanical_acoustic_tmatrix[0, 0]
|
119 |
+
t_12 = electro_mechanical_acoustic_tmatrix[0, 1]
|
120 |
+
t_21 = electro_mechanical_acoustic_tmatrix[1, 0]
|
121 |
+
t_22 = electro_mechanical_acoustic_tmatrix[1, 1]
|
122 |
+
|
123 |
+
voltage_pressure_transfer_function = (ZA_rad) / (t_11 * ZA_rad + t_12)
|
124 |
+
|
125 |
+
acoustical_pressure = (
|
126 |
+
electrical_input_voltage
|
127 |
+
* (voltage_pressure_transfer_function / Z_delay)
|
128 |
+
/ acoustical_constants.reference_pressure
|
129 |
+
)
|
130 |
+
|
131 |
+
loudspeaker_responses = {
|
132 |
+
"electrical_impedance": electrical_impedance_shorted_output,
|
133 |
+
"mechanical_force": mechanical_force,
|
134 |
+
"mechanical_velocity": mechanical_velocity,
|
135 |
+
"acoustical_pressure": acoustical_pressure,
|
136 |
+
}
|
137 |
+
|
138 |
+
return loudspeaker_responses
|
tmatrix.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
|
4 |
+
def series_impedance(impedance_array):
|
5 |
+
n_bins = len(impedance_array)
|
6 |
+
tmatrix = np.array(
|
7 |
+
[
|
8 |
+
[np.ones(n_bins), impedance_array],
|
9 |
+
[np.zeros(n_bins), np.ones(n_bins)],
|
10 |
+
]
|
11 |
+
)
|
12 |
+
return tmatrix
|
13 |
+
|
14 |
+
|
15 |
+
def inductance_series(inductance_value, angular_freq_array):
|
16 |
+
impedance_array = 1j * angular_freq_array * inductance_value
|
17 |
+
return series_impedance(impedance_array)
|
18 |
+
|
19 |
+
|
20 |
+
def capacitance_series(capacitance_value, angular_freq_array):
|
21 |
+
impedance_array = 1 / (1j * angular_freq_array * capacitance_value)
|
22 |
+
return series_impedance(impedance_array)
|
23 |
+
|
24 |
+
|
25 |
+
def resistance_series(resistance_value, n_bins):
|
26 |
+
impedance_array = np.ones(n_bins) * resistance_value
|
27 |
+
return series_impedance(impedance_array)
|
28 |
+
|
29 |
+
|
30 |
+
def transformer(transformer_value, n_bins):
|
31 |
+
transformer_array = np.ones(n_bins) * transformer_value
|
32 |
+
transformer = np.array(
|
33 |
+
[
|
34 |
+
[transformer_array, np.zeros(n_bins)],
|
35 |
+
[np.zeros(n_bins), 1 / transformer_array],
|
36 |
+
]
|
37 |
+
)
|
38 |
+
return transformer
|
39 |
+
|
40 |
+
|
41 |
+
def gyrator(gyrator_value, n_bins):
|
42 |
+
gyrator_array = np.ones(n_bins) * gyrator_value
|
43 |
+
gyrator = np.array(
|
44 |
+
[
|
45 |
+
[np.zeros(n_bins), gyrator_array],
|
46 |
+
[1 / gyrator_array, np.zeros(n_bins)],
|
47 |
+
]
|
48 |
+
)
|
49 |
+
return gyrator
|
utils.py
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
|
4 |
+
def layer_wise_dot_product(*matrices: np.ndarray) -> np.ndarray:
|
5 |
+
"""
|
6 |
+
Performs the sequential layer-wise dot product of multiple 3D matrices along the last dimension.
|
7 |
+
|
8 |
+
Args:
|
9 |
+
*matrices: A variable number of 3D numpy arrays.
|
10 |
+
|
11 |
+
Returns:
|
12 |
+
A 3D numpy array containing the sequential dot product results for each layer in the last dimension.
|
13 |
+
"""
|
14 |
+
if not all(matrix.shape == matrices[0].shape for matrix in matrices):
|
15 |
+
raise ValueError("All matrices must have the same dimensions.")
|
16 |
+
|
17 |
+
result = np.zeros_like(matrices[0], dtype="complex")
|
18 |
+
|
19 |
+
_, _, num_layers = matrices[0].shape
|
20 |
+
|
21 |
+
for layer_i in range(num_layers):
|
22 |
+
# Start the product with the identity matrix for the first multiplication
|
23 |
+
dot_product = np.eye(result.shape[0])
|
24 |
+
for matrix in matrices:
|
25 |
+
dot_product = np.dot(dot_product, matrix[:, :, layer_i])
|
26 |
+
|
27 |
+
result[:, :, layer_i] = dot_product
|
28 |
+
|
29 |
+
return result
|
30 |
+
|
31 |
+
|
32 |
+
def bessel(z):
|
33 |
+
"""
|
34 |
+
Bessel function aproximation for air radiation impedance
|
35 |
+
"""
|
36 |
+
bessel_sum = 0
|
37 |
+
for k in range(25):
|
38 |
+
bessel_i = ((-1) ** k * (z / 2) ** (2 * k + 1)) / (
|
39 |
+
np.math.factorial(k) * np.math.factorial(k + 1)
|
40 |
+
)
|
41 |
+
bessel_sum = bessel_sum + bessel_i
|
42 |
+
return bessel_sum
|
43 |
+
|
44 |
+
|
45 |
+
def struve(z):
|
46 |
+
"""
|
47 |
+
Srtuve function aproximation for air radiation impedance
|
48 |
+
"""
|
49 |
+
struve_sum = 0
|
50 |
+
for k in range(25):
|
51 |
+
struve_i = (((-1) ** k * (z / 2) ** (2 * k + 2))) / (
|
52 |
+
np.math.factorial(int(k + 1 / 2)) * np.math.factorial(int(k + 3 / 2))
|
53 |
+
)
|
54 |
+
struve_sum = struve_sum + struve_i
|
55 |
+
return struve_sum
|
56 |
+
|
57 |
+
|
58 |
+
def to_db(x):
|
59 |
+
"""
|
60 |
+
decibel calculus
|
61 |
+
"""
|
62 |
+
db = 20 * np.log10(np.abs(x))
|
63 |
+
return db
|
visualization.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from matplotlib import pyplot as plt
|
3 |
+
|
4 |
+
from loudspeaker_tmatrix.utils import to_db
|
5 |
+
|
6 |
+
|
7 |
+
def plot_loudspeaker_response(
|
8 |
+
response_array: np.ndarray,
|
9 |
+
freq_array: np.ndarray,
|
10 |
+
title: str,
|
11 |
+
magnitude_in_db: bool,
|
12 |
+
magnitude_units: str,
|
13 |
+
shift_phase: bool,
|
14 |
+
):
|
15 |
+
|
16 |
+
if magnitude_in_db:
|
17 |
+
magnitude = to_db(response_array)
|
18 |
+
else:
|
19 |
+
magnitude = np.abs(response_array)
|
20 |
+
|
21 |
+
if shift_phase:
|
22 |
+
phase = np.angle(-response_array, deg=True)
|
23 |
+
else:
|
24 |
+
phase = np.angle(response_array, deg=True)
|
25 |
+
|
26 |
+
fig, ax1 = plt.subplots(figsize=(12, 4))
|
27 |
+
fig.suptitle(title)
|
28 |
+
|
29 |
+
ax1.semilogx(freq_array, magnitude, label="Magnitude", color="C0")
|
30 |
+
ax1.set_xlabel("Frequency [Hz]")
|
31 |
+
ax1.set_ylabel(f"Magnitude [{magnitude_units}]", color="C0")
|
32 |
+
ax1.tick_params(axis="y", labelcolor="C0")
|
33 |
+
|
34 |
+
ax2 = ax1.twinx()
|
35 |
+
ax2.semilogx(freq_array, phase, color="r", label="Phase")
|
36 |
+
x_ticks = np.sort(np.array([16, 31, 63, 125, 250, 500, 1000, 2000, 4000]))
|
37 |
+
ax2.set_xticks(ticks=x_ticks, labels=x_ticks.tolist(), rotation=45)
|
38 |
+
ax2.set_ylabel("Phase [º]", color="r")
|
39 |
+
ax2.tick_params(axis="y", labelcolor="r")
|
40 |
+
y_label1 = [r"$-180º$", r"$-90º$", r"$0º$", r"$90º$", r"$180º$"]
|
41 |
+
ax2.set_yticks(np.array([-180, -90, 0, 90, 180]), y_label1)
|
42 |
+
|
43 |
+
ax1.set_xlim(10, 4000)
|
44 |
+
ax2.set_ylim(-180, 180)
|
45 |
+
ax1.grid(axis="x")
|
46 |
+
ax2.grid(axis="y")
|
47 |
+
return fig
|