pedalkernel/compiler/components/
modulation.rs1use crate::compiler::component::{
4 Component, ComponentEdge, ControlParam, ControlParamKind, EdgeKind, GraphRole, ModulationSink,
5 ModulationSinkKind, PinConfig, StampResult,
6};
7use crate::compiler::dyn_node::DynNode;
8use crate::compiler::validate::Severity;
9use crate::dsl::{LfoWaveformDsl, PhotocouplerType};
10use crate::elements::{Photocoupler as PhotocouplerElem, PhotocouplerModel};
11use crate::tree::MnaSystem;
12
13use super::impl_component_dyn;
14
15#[derive(Debug, Clone, PartialEq)]
20pub struct Lfo {
21 pub waveform: LfoWaveformDsl,
22 pub timing_r: f64,
23 pub timing_c: f64,
24}
25
26impl Component for Lfo {
27 impl_component_dyn!();
28
29 fn type_tag(&self) -> &'static str {
30 "LFO"
31 }
32
33 fn is_passive(&self) -> bool {
34 false
35 }
36
37 fn pin_config(&self) -> PinConfig {
38 PinConfig {
39 valid_pins: &["out", "output", "rate"],
40 aliases: &[("out", "output")],
41 }
42 }
43
44 fn graph_role(&self) -> GraphRole {
45 GraphRole::Virtual
46 }
47
48 fn stamp_mna(
49 &self,
50 _comp_id: &str,
51 _n1: Option<usize>,
52 _n2: Option<usize>,
53 _mna: &mut MnaSystem,
54 _sample_rate: f64,
55 ) -> StampResult {
56 StampResult::Skip
57 }
58
59 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
60 let mut w = Vec::new();
61 if self.timing_r <= 0.0 || self.timing_c <= 0.0 {
62 w.push((
63 Severity::Error,
64 format!(
65 "LFO '{}' has non-positive timing values (R={:.2}, C={:.2e})",
66 comp_id, self.timing_r, self.timing_c
67 ),
68 ));
69 }
70 let freq = 1.0 / (2.0 * std::f64::consts::PI * self.timing_r * self.timing_c);
71 if freq > 100.0 {
72 w.push((
73 Severity::Warning,
74 format!(
75 "LFO '{}' base frequency is {:.1} Hz \u{2014} unusually fast for modulation",
76 comp_id, freq
77 ),
78 ));
79 }
80 w
81 }
82
83 fn controls(&self) -> Vec<ControlParam> {
84 vec![
85 ControlParam {
86 name: "rate",
87 kind: ControlParamKind::LfoRate,
88 },
89 ControlParam {
90 name: "depth",
91 kind: ControlParamKind::LfoDepth,
92 },
93 ]
94 }
95
96 fn footprint_ref(&self) -> (&'static str, &'static str) {
97 ("", "LFO")
98 }
99
100 fn symbol_name(&self) -> &'static str {
101 "lfo"
102 }
103 fn layout_class(&self) -> &'static str {
104 "lfo"
105 }
106
107 fn is_modulation_source(&self) -> bool {
108 true
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
117pub struct EnvelopeFollower {
118 pub attack_r: f64,
119 pub attack_c: f64,
120 pub release_r: f64,
121 pub release_c: f64,
122 pub sensitivity_r: f64,
123}
124
125impl Component for EnvelopeFollower {
126 impl_component_dyn!();
127
128 fn type_tag(&self) -> &'static str {
129 "envelope follower"
130 }
131
132 fn is_passive(&self) -> bool {
133 false
134 }
135
136 fn pin_config(&self) -> PinConfig {
137 PinConfig {
138 valid_pins: &["out", "output", "in", "input"],
139 aliases: &[("in", "input"), ("out", "output")],
140 }
141 }
142
143 fn graph_role(&self) -> GraphRole {
144 GraphRole::Virtual
145 }
146
147 fn stamp_mna(
148 &self,
149 _comp_id: &str,
150 _n1: Option<usize>,
151 _n2: Option<usize>,
152 _mna: &mut MnaSystem,
153 _sample_rate: f64,
154 ) -> StampResult {
155 StampResult::Skip
156 }
157
158 fn validate_values(&self, comp_id: &str) -> Vec<(Severity, String)> {
159 let mut w = Vec::new();
160 if self.attack_r <= 0.0
161 || self.attack_c <= 0.0
162 || self.release_r <= 0.0
163 || self.release_c <= 0.0
164 || self.sensitivity_r <= 0.0
165 {
166 w.push((
167 Severity::Error,
168 format!(
169 "EnvelopeFollower '{}' has non-positive parameter value(s)",
170 comp_id
171 ),
172 ));
173 }
174 w
175 }
176
177 fn footprint_ref(&self) -> (&'static str, &'static str) {
178 ("", "ENV")
179 }
180
181 fn symbol_name(&self) -> &'static str {
182 "envelope"
183 }
184 fn layout_class(&self) -> &'static str {
185 "envelope"
186 }
187
188 fn is_modulation_source(&self) -> bool {
189 true
190 }
191}
192
193#[derive(Debug, Clone, PartialEq)]
198pub struct PhotocouplerComp {
199 pub coupler_type: PhotocouplerType,
200}
201
202impl Component for PhotocouplerComp {
203 impl_component_dyn!();
204
205 fn type_tag(&self) -> &'static str {
206 "photocoupler"
207 }
208
209 fn is_passive(&self) -> bool {
210 false
211 }
212
213 fn pin_config(&self) -> PinConfig {
214 PinConfig {
222 valid_pins: &["a", "b", "led", "led.a", "led.b"],
223 aliases: &[("led", "led.a")],
224 }
225 }
226
227 fn modulation_pins(&self) -> &'static [&'static str] {
228 &["led", "led.a", "led.b"]
229 }
230
231 fn graph_role(&self) -> GraphRole {
232 GraphRole::Edge {
237 pin_a: "a",
238 pin_b: "b",
239 }
240 }
241
242 fn stamp_mna(
243 &self,
244 _comp_id: &str,
245 _n1: Option<usize>,
246 _n2: Option<usize>,
247 _mna: &mut MnaSystem,
248 _sample_rate: f64,
249 ) -> StampResult {
250 StampResult::Skip
251 }
252
253 fn make_leaf(&self, comp_id: &str, sample_rate: f64) -> Option<DynNode> {
254 let model = match self.coupler_type {
255 PhotocouplerType::Vtl5c3 => PhotocouplerModel::vtl5c3(),
256 PhotocouplerType::Vtl5c1 => PhotocouplerModel::vtl5c1(),
257 PhotocouplerType::Nsl32 => PhotocouplerModel::nsl32(),
258 PhotocouplerType::T4b => PhotocouplerModel::t4b(),
259 };
260 Some(DynNode::PhotocouplerNode(
261 comp_id.to_string(),
262 PhotocouplerElem::new(model, sample_rate as crate::Wave),
263 ))
264 }
265
266 fn is_variable(&self) -> bool {
267 true
268 }
269
270 fn edges(&self) -> Vec<ComponentEdge> {
271 vec![
283 ComponentEdge {
284 pin_a: "a",
285 pin_b: "b",
286 kind: EdgeKind::Linear,
287 port_group: None,
288 },
289 ComponentEdge {
290 pin_a: "led.a",
291 pin_b: "led.b",
292 kind: EdgeKind::Behavioral,
293 port_group: None,
294 },
295 ]
296 }
297
298 fn signal_adjacencies(&self) -> Vec<(&'static str, &'static str)> {
299 vec![("a", "b")]
305 }
306
307 fn modulation_sink(&self, pin: &str) -> Option<ModulationSink> {
308 match pin {
309 "led" => Some(ModulationSink {
310 target_kind: ModulationSinkKind::PhotocouplerLed,
311 bias: 0.5,
312 range: 0.5,
313 }),
314 _ => None,
315 }
316 }
317
318 fn footprint_ref(&self) -> (&'static str, &'static str) {
319 ("Isolator:PC817", "OC")
320 }
321
322 fn symbol_name(&self) -> &'static str {
323 "photocoupler"
324 }
325 fn layout_class(&self) -> &'static str {
326 "photocoupler"
327 }
328}