1use crate::dsl::TransformerConfig;
9use crate::models::{
10 bjt_by_name, jfet_by_name, opamp_by_name, pentode_by_name, transformer_by_name, triode_by_name,
11 SpiceBjtModel, SpiceJfetModel, SpiceOpAmpModel, SpicePentodeModel, SpiceTransformerModel,
12 SpiceTriodeModel,
13};
14use pedalkernel_rt::elements::nonlinear::{
15 GummelPoonModel, JfetModel, OpAmpModel, PentodeModel, TriodeModel,
16};
17
18pub fn jfet_model_by_name(name: &str) -> JfetModel {
24 jfet_try_by_name(name).unwrap_or_else(|| panic!("Unknown JFET model: '{name}'"))
25}
26
27pub fn jfet_try_by_name(name: &str) -> Option<JfetModel> {
29 jfet_by_name(name).map(|s| jfet_from_spice(s))
30}
31
32fn jfet_from_spice(s: &SpiceJfetModel) -> JfetModel {
33 JfetModel {
34 vto: s.vto as crate::Wave,
35 beta: s.beta as crate::Wave,
36 lambda: s.lambda as crate::Wave,
37 gate_is: s.is as crate::Wave,
38 n: s.n as crate::Wave,
39 rd: s.rd as crate::Wave,
40 rs: s.rs as crate::Wave,
41 cgs: s.cgs as crate::Wave,
42 cgd: s.cgd as crate::Wave,
43 is_n_channel: s.is_n_channel,
44 }
45}
46
47pub fn triode_model_by_name(name: &str) -> TriodeModel {
52 triode_try_by_name(name).unwrap_or_else(|| {
53 panic!(
54 "Unknown triode model: '{}'. Use triode_model_names() to list available models.",
55 name
56 )
57 })
58}
59
60pub fn triode_try_by_name(name: &str) -> Option<TriodeModel> {
62 triode_by_name(name).map(|s| triode_from_spice(s))
63}
64
65fn triode_from_spice(spice: &SpiceTriodeModel) -> TriodeModel {
66 TriodeModel {
67 mu: spice.mu as crate::Wave,
68 kp: spice.kp as crate::Wave,
69 kvb: spice.kvb as crate::Wave,
70 ex: spice.ex as crate::Wave,
71 kg1: spice.kg1 as crate::Wave,
72 rp: spice.rp as crate::Wave,
73 }
74}
75
76pub fn pentode_model_by_name(name: &str) -> PentodeModel {
81 pentode_try_by_name(name).unwrap_or_else(|| {
82 panic!(
83 "Unknown pentode model: '{}'. Use pentode_model_names() to list available models.",
84 name
85 )
86 })
87}
88
89pub fn pentode_try_by_name(name: &str) -> Option<PentodeModel> {
91 pentode_by_name(name).map(|s| pentode_from_spice(s))
92}
93
94fn pentode_from_spice(spice: &SpicePentodeModel) -> PentodeModel {
95 PentodeModel {
96 mu: spice.mu as crate::Wave,
97 kp: spice.kp as crate::Wave,
98 kvb: spice.kvb as crate::Wave,
99 ex: spice.ex as crate::Wave,
100 kvb2: spice.kvb2 as crate::Wave,
101 vg2_default: spice.vg2_default as crate::Wave,
102 kg1: spice.kg1 as crate::Wave,
103 kg2: spice.kg2 as crate::Wave,
104 rp: spice.rp as crate::Wave,
105 }
106}
107
108pub fn bjt_model_by_name(name: &str) -> GummelPoonModel {
113 bjt_try_by_name(name).unwrap_or_else(|| panic!("Unknown BJT model: '{name}'"))
114}
115
116pub fn bjt_try_by_name(name: &str) -> Option<GummelPoonModel> {
118 bjt_by_name(name).map(|s| bjt_from_spice(s))
119}
120
121use pedalkernel_rt::SPICE_VT_27C;
125
126fn bjt_from_spice(spice: &SpiceBjtModel) -> GummelPoonModel {
127 GummelPoonModel {
128 is: spice.is as crate::Wave,
129 bf: spice.bf as crate::Wave,
130 br: spice.br as crate::Wave,
131 nf: spice.nf as crate::Wave,
132 nr: spice.nr as crate::Wave,
133 vt: SPICE_VT_27C,
134 vaf: spice.vaf as crate::Wave,
135 var: spice.var as crate::Wave,
136 ikf: spice.ikf as crate::Wave,
137 ikr: spice.ikr as crate::Wave,
138 ise: spice.ise as crate::Wave,
139 ne: spice.ne as crate::Wave,
140 isc: spice.isc as crate::Wave,
141 nc: spice.nc as crate::Wave,
142 cje: spice.cje as crate::Wave,
143 vje: spice.vje as crate::Wave,
144 mje: spice.mje as crate::Wave,
145 cjc: spice.cjc as crate::Wave,
146 vjc: spice.vjc as crate::Wave,
147 mjc: spice.mjc as crate::Wave,
148 rb: spice.rb as crate::Wave,
149 re: spice.re as crate::Wave,
150 rc: spice.rc as crate::Wave,
151 tf: spice.tf as crate::Wave,
152 tr: spice.tr as crate::Wave,
153 is_pnp: spice.is_pnp,
154 }
155}
156
157pub fn opamp_model_by_name(name: &str) -> OpAmpModel {
162 opamp_try_by_name(name).unwrap_or_else(|| panic!("Unknown op-amp model: '{name}'"))
163}
164
165pub fn opamp_try_by_name(name: &str) -> Option<OpAmpModel> {
167 opamp_by_name(name).map(opamp_from_spice)
168}
169
170fn opamp_from_spice(spice: &SpiceOpAmpModel) -> OpAmpModel {
171 OpAmpModel {
172 open_loop_gain: spice.open_loop_gain as crate::Wave,
173 gbw: spice.gbw as crate::Wave,
174 slew_rate: spice.slew_rate as crate::Wave,
175 v_rail_pos: spice.v_rail_pos as crate::Wave,
176 v_rail_neg: spice.v_rail_neg as crate::Wave,
177 output_impedance: spice.output_impedance as crate::Wave,
178 output_capacitance: spice.output_capacitance as crate::Wave,
179 }
180}
181
182pub fn opamp_model_from_type(ot: &crate::dsl::OpAmpType) -> OpAmpModel {
184 opamp_model_by_name(opamp_type_model_name(ot))
185}
186
187pub fn ota_gm_from_type(ot: &crate::dsl::OpAmpType) -> Option<f64> {
189 let model = opamp_by_name(opamp_type_model_name(ot))?;
190 model.is_ota.then_some(model.ota_gm)
191}
192
193fn opamp_type_model_name(ot: &crate::dsl::OpAmpType) -> &'static str {
194 use crate::dsl::OpAmpType;
195 match ot {
196 OpAmpType::Generic => "GENERIC",
197 OpAmpType::Tl071 => "TL071",
198 OpAmpType::Tl072 => "TL072",
199 OpAmpType::Tl082 => "TL082",
200 OpAmpType::Jrc4558 => "JRC4558",
201 OpAmpType::Rc4558 => "RC4558",
202 OpAmpType::Lm308 => "LM308",
203 OpAmpType::Lm741 => "LM741",
204 OpAmpType::Ne5532 => "NE5532",
205 OpAmpType::Op07 => "OP07",
206 OpAmpType::Ca3080 => "CA3080",
207 }
208}
209
210pub fn transformer_config_from_dsl(cfg: &TransformerConfig) -> TransformerConfig {
220 let Some(model_name) = cfg.model.as_deref() else {
221 return cfg.clone();
222 };
223
224 let model = transformer_by_name(model_name)
225 .unwrap_or_else(|| panic!("Unknown transformer model: '{model_name}'"));
226 let mut resolved = transformer_config_from_model(model, cfg.turns_ratio);
227
228 resolved.primary_type = cfg.primary_type;
229 resolved.secondary_type = cfg.secondary_type;
230 resolved.tertiary_turns_ratio = cfg.tertiary_turns_ratio;
231
232 if cfg.primary_inductance > 0.0 {
234 resolved.primary_inductance = cfg.primary_inductance;
235 }
236 if cfg.primary_dcr > 0.0 {
237 resolved.primary_dcr = cfg.primary_dcr;
238 }
239 if cfg.secondary_dcr > 0.0 {
240 resolved.secondary_dcr = cfg.secondary_dcr;
241 }
242 if cfg.capacitance > 0.0 {
243 resolved.capacitance = cfg.capacitance;
244 }
245 if cfg.coupling > 0.0 {
246 resolved.coupling = cfg.coupling;
247 }
248 if cfg.primary_leakage.is_some() {
249 resolved.primary_leakage = cfg.primary_leakage;
250 }
251 if cfg.secondary_leakage.is_some() {
252 resolved.secondary_leakage = cfg.secondary_leakage;
253 }
254 if cfg.magnetizing_inductance.is_some() {
255 resolved.magnetizing_inductance = cfg.magnetizing_inductance;
256 }
257 if cfg.core_loss_resistance.is_some() {
258 resolved.core_loss_resistance = cfg.core_loss_resistance;
259 }
260 if cfg.core_primary_turns.is_some() {
261 resolved.core_primary_turns = cfg.core_primary_turns;
262 }
263 if cfg.core_area.is_some() {
264 resolved.core_area = cfg.core_area;
265 }
266 if cfg.core_path_length.is_some() {
267 resolved.core_path_length = cfg.core_path_length;
268 }
269 if cfg.core_gap.is_some() {
270 resolved.core_gap = cfg.core_gap;
271 }
272 if cfg.dc_bias_current.is_some() {
273 resolved.dc_bias_current = cfg.dc_bias_current;
274 }
275 if cfg.ja_ms.is_some() {
276 resolved.ja_ms = cfg.ja_ms;
277 }
278 if cfg.ja_a.is_some() {
279 resolved.ja_a = cfg.ja_a;
280 }
281 if cfg.ja_alpha.is_some() {
282 resolved.ja_alpha = cfg.ja_alpha;
283 }
284 if cfg.ja_k.is_some() {
285 resolved.ja_k = cfg.ja_k;
286 }
287 if cfg.ja_c.is_some() {
288 resolved.ja_c = cfg.ja_c;
289 }
290
291 resolved.model = Some(model.name.clone());
292 resolved
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn transformer_model_values_merge_with_overrides() {
301 let mut cfg = TransformerConfig::with_model(10.0, "JT11P1".to_string());
302 cfg.magnetizing_inductance = Some(8.0);
303 cfg.core_loss_resistance = Some(250_000.0);
304 cfg.capacitance = 100e-12;
305
306 let resolved = transformer_config_from_dsl(&cfg);
307 assert_eq!(resolved.model.as_deref(), Some("JT11P1"));
308 assert!((resolved.turns_ratio - 10.0).abs() < 1e-12);
309 assert!((resolved.primary_inductance - 2.0).abs() < 1e-12);
310 assert!((resolved.primary_dcr - 75.0).abs() < 1e-12);
311 assert!((resolved.magnetizing_inductance.unwrap() - 8.0).abs() < 1e-12);
312 assert!((resolved.core_loss_resistance.unwrap() - 250_000.0).abs() < 1e-6);
313 assert!((resolved.capacitance - 100e-12).abs() < 1e-21);
314 }
315
316 #[test]
317 fn transformer_model_propagates_ja_core_fields() {
318 let cfg = TransformerConfig::with_model(26.0, "OT-DEMO-SE".to_string());
319 let resolved = transformer_config_from_dsl(&cfg);
320
321 assert_eq!(resolved.model.as_deref(), Some("OT-DEMO-SE"));
322 assert!((resolved.core_primary_turns.unwrap() - 2000.0).abs() < 1e-12);
323 assert!((resolved.core_area.unwrap() - 2.0e-4).abs() < 1e-18);
324 assert!((resolved.core_path_length.unwrap() - 0.10).abs() < 1e-12);
325 assert!((resolved.core_gap.unwrap() - 1.0e-3).abs() < 1e-18);
326 assert!((resolved.dc_bias_current.unwrap() - 45.0e-3).abs() < 1e-18);
327 assert!((resolved.ja_ms.unwrap() - 1.6e6).abs() < 1e-6);
328 assert!((resolved.ja_a.unwrap() - 1100.0).abs() < 1e-12);
329 assert!((resolved.ja_alpha.unwrap() - 1.6e-3).abs() < 1e-18);
330 assert!((resolved.ja_k.unwrap() - 400.0).abs() < 1e-12);
331 assert!((resolved.ja_c.unwrap() - 0.2).abs() < 1e-12);
332 }
333}
334
335fn transformer_config_from_model(
336 model: &SpiceTransformerModel,
337 turns_ratio: f64,
338) -> TransformerConfig {
339 TransformerConfig {
340 model: Some(model.name.clone()),
341 turns_ratio,
342 primary_inductance: model.primary_inductance,
343 primary_dcr: model.primary_dcr,
344 secondary_dcr: model.secondary_dcr,
345 capacitance: model.capacitance,
346 coupling: model.coupling,
347 primary_leakage: Some(model.primary_leakage),
348 secondary_leakage: Some(model.secondary_leakage),
349 magnetizing_inductance: Some(model.magnetizing_inductance),
350 core_loss_resistance: (model.core_loss_resistance > 0.0)
351 .then_some(model.core_loss_resistance),
352 core_primary_turns: (model.primary_turns > 0.0).then_some(model.primary_turns),
353 core_area: (model.core_area > 0.0).then_some(model.core_area),
354 core_path_length: (model.magnetic_path_length > 0.0).then_some(model.magnetic_path_length),
355 core_gap: (model.gap_length > 0.0).then_some(model.gap_length),
356 dc_bias_current: (model.dc_bias_current != 0.0).then_some(model.dc_bias_current),
357 ja_ms: (model.ja_ms > 0.0).then_some(model.ja_ms),
358 ja_a: (model.ja_a > 0.0).then_some(model.ja_a),
359 ja_alpha: (model.ja_alpha != 0.0).then_some(model.ja_alpha),
360 ja_k: (model.ja_k > 0.0).then_some(model.ja_k),
361 ja_c: (model.ja_c > 0.0).then_some(model.ja_c),
362 ..Default::default()
363 }
364}