Skip to main content

pedalkernel/compiler/
warnings.rs

1//! Voltage compatibility checking for pedal circuits.
2
3use crate::dsl::*;
4
5// ═══════════════════════════════════════════════════════════════════════════
6// Voltage compatibility check
7// ═══════════════════════════════════════════════════════════════════════════
8
9/// Severity of a voltage compatibility warning.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum WarningSeverity {
12    /// Informational — the component should be fine, but behavior may differ
13    /// from the canonical 9V version (e.g. slightly different bias point).
14    Info,
15    /// The component is likely operating outside its typical ratings and may
16    /// clip, distort differently, or wear out faster in a real build.
17    Caution,
18    /// The component would almost certainly fail or be damaged at this voltage
19    /// in a physical build.
20    Danger,
21}
22
23/// A single voltage compatibility warning for a component.
24#[derive(Debug, Clone)]
25pub struct VoltageWarning {
26    pub component_id: String,
27    pub severity: WarningSeverity,
28    pub message: String,
29}
30
31/// Check a pedal definition for voltage compatibility at a given supply voltage.
32///
33/// Returns a list of warnings for components that may not tolerate the target
34/// voltage.  These are heuristic — the DSL doesn't carry voltage ratings, so
35/// we infer from component types and typical part specs:
36///
37/// - **Germanium transistors**: Vce(max) typically 15–32V.  Caution above 15V.
38/// - **Electrolytic capacitors** (≥ 1 µF): often rated 10–16V.  Caution above 12V.
39/// - **Op-amps**: common audio opamps (TL072, NE5532) rated to ±18V (36V total).
40///   Caution above 18V.
41/// - **Silicon transistors**: usually Vce(max) ≥ 40V.  Caution above 24V.
42/// - **Germanium diodes**: forward behaviour is voltage-independent, but reverse
43///   breakdown is lower (~50V Ge vs ~100V+ Si).  Only flagged above 24V.
44///
45/// Note: passive elements (R, L) and silicon diodes are unaffected in the WDF
46/// model and are not flagged.
47pub fn check_voltage_compatibility(pedal: &PedalDef, voltage: f64) -> Vec<VoltageWarning> {
48    let mut warnings = Vec::new();
49
50    for comp in &pedal.components {
51        let tag = comp.kind.type_tag();
52
53        if comp.kind.is_bjt() {
54            if let Some(name) = comp.kind.model_name() {
55                let is_ge = crate::models::bjt_is_germanium(name);
56                if is_ge {
57                    // Germanium PNPs: typical Vce(max) = 15-32V
58                    if voltage > 18.0 {
59                        warnings.push(VoltageWarning {
60                            component_id: comp.id.clone(),
61                            severity: WarningSeverity::Danger,
62                            message: format!(
63                                "Germanium transistor {} likely exceeds Vce(max) at {:.0}V \
64                                 (typical Ge PNP rated 15-32V)",
65                                comp.id, voltage
66                            ),
67                        });
68                    } else if voltage > 12.0 {
69                        warnings.push(VoltageWarning {
70                            component_id: comp.id.clone(),
71                            severity: WarningSeverity::Caution,
72                            message: format!(
73                                "Germanium transistor {} may run hot at {:.0}V \
74                                 — bias point shifts, tone will differ from 9V",
75                                comp.id, voltage
76                            ),
77                        });
78                    }
79                } else if voltage > 24.0 {
80                    // Silicon transistors: typically Vce(max) >= 40V
81                    warnings.push(VoltageWarning {
82                        component_id: comp.id.clone(),
83                        severity: WarningSeverity::Caution,
84                        message: format!(
85                            "Transistor {} at {:.0}V — verify Vce(max) rating of actual part",
86                            comp.id, voltage
87                        ),
88                    });
89                }
90            }
91        } else if let Some(ot) = comp.kind.op_amp_type() {
92            // Use the op-amp type's known supply_max for accurate warnings
93            let max_supply = ot.supply_max();
94            if voltage > max_supply * 0.5 {
95                // Operating above half the max total supply (typical single-supply limit)
96                let severity = if voltage > max_supply {
97                    WarningSeverity::Danger
98                } else {
99                    WarningSeverity::Caution
100                };
101                warnings.push(VoltageWarning {
102                    component_id: comp.id.clone(),
103                    severity,
104                    message: format!(
105                        "Op-amp {} ({:?}) at {:.0}V — max total supply {:.0}V (±{:.0}V split)",
106                        comp.id,
107                        ot,
108                        voltage,
109                        max_supply,
110                        max_supply / 2.0
111                    ),
112                });
113            }
114        } else if let Some(farads) = comp.kind.capacitance() {
115            // Electrolytics (>= 1uF) often have low voltage ratings.
116            // 10uF caps commonly rated 10V or 16V.
117            if farads >= 1e-6 {
118                if voltage > 16.0 {
119                    warnings.push(VoltageWarning {
120                        component_id: comp.id.clone(),
121                        severity: WarningSeverity::Danger,
122                        message: format!(
123                            "Electrolytic cap {} ({:.0}uF) may exceed voltage rating at {:.0}V \
124                             — common ratings are 10V, 16V, 25V",
125                            comp.id,
126                            farads * 1e6,
127                            voltage
128                        ),
129                    });
130                } else if voltage > 12.0 {
131                    warnings.push(VoltageWarning {
132                        component_id: comp.id.clone(),
133                        severity: WarningSeverity::Caution,
134                        message: format!(
135                            "Electrolytic cap {} ({:.0}uF) — ensure voltage rating >= {:.0}V",
136                            comp.id,
137                            farads * 1e6,
138                            voltage
139                        ),
140                    });
141                }
142            }
143        } else if comp.kind.is_diode_family() {
144            // Check for germanium diodes
145            if comp.kind.diode_type() == Some(DiodeType::Germanium) {
146                // Ge diode forward behaviour is voltage-independent in the WDF,
147                // but reverse breakdown is lower (~50V vs 100V+ Si).
148                // Also: more temperature-sensitive at higher power dissipation.
149                if voltage > 18.0 {
150                    warnings.push(VoltageWarning {
151                        component_id: comp.id.clone(),
152                        severity: WarningSeverity::Info,
153                        message: format!(
154                            "Germanium diode {} — higher power dissipation at {:.0}V \
155                             may shift forward voltage (temperature dependent)",
156                            comp.id, voltage
157                        ),
158                    });
159                }
160            }
161        } else if tag == "BBD delay" {
162            // BBDs are typically rated 10-15V. They're sensitive to
163            // over-voltage which causes excess clock noise and distortion.
164            if voltage > 15.0 {
165                warnings.push(VoltageWarning {
166                    component_id: comp.id.clone(),
167                    severity: WarningSeverity::Danger,
168                    message: format!(
169                        "BBD {} may exceed max supply at {:.0}V — typical rating 10-15V",
170                        comp.id, voltage
171                    ),
172                });
173            }
174        } else if tag == "VCO" || tag == "VCF" {
175            // Synth ICs: CEM/AS/V series typically run +/-5V to +/-9V (10-18V total)
176            if voltage > 18.0 {
177                warnings.push(VoltageWarning {
178                    component_id: comp.id.clone(),
179                    severity: WarningSeverity::Danger,
180                    message: format!(
181                        "Synth IC {} exceeds max supply at {:.0}V — CEM/AS/V series rated ±9V (18V max)",
182                        comp.id, voltage
183                    ),
184                });
185            }
186        } else if tag == "analog switch" && voltage > 20.0 {
187            warnings.push(VoltageWarning {
188                component_id: comp.id.clone(),
189                severity: WarningSeverity::Danger,
190                message: format!(
191                    "Analog switch {} exceeds max supply at {:.0}V — CD4066/DG411 rated 20V max",
192                    comp.id, voltage
193                ),
194            });
195        }
196        // Resistors, inductors, Si/LED diodes, pots, VCA, comparator, matched
197        // transistors, tempco: no voltage concerns within the 5-24V range
198        // (or handled by supply_max spec if provided).
199    }
200
201    warnings
202}