1use std::io::{Cursor, Read, Write};
47
48const MAGIC: [u8; 4] = *b"PKSC";
49const VERSION: u16 = 1;
50
51#[derive(Debug)]
57pub enum PrecomputeError {
58 Io(std::io::Error),
60 BadMagic,
62 BadVersion(u16),
64 Consistency(String),
66}
67
68impl std::fmt::Display for PrecomputeError {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 match self {
71 Self::Io(e) => write!(f, "I/O error: {e}"),
72 Self::BadMagic => write!(f, "invalid magic bytes (expected PKSC)"),
73 Self::BadVersion(v) => write!(f, "unsupported version {v}"),
74 Self::Consistency(msg) => write!(f, "data consistency error: {msg}"),
75 }
76 }
77}
78
79impl std::error::Error for PrecomputeError {
80 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
81 if let Self::Io(e) = self {
82 Some(e)
83 } else {
84 None
85 }
86 }
87}
88
89impl From<std::io::Error> for PrecomputeError {
90 fn from(e: std::io::Error) -> Self {
91 Self::Io(e)
92 }
93}
94
95#[derive(Debug, Clone)]
101pub struct PrecomputedScattering {
102 pub pedal_hash: u32,
105 pub sample_rate: f64,
107 pub stages: Vec<PrecomputedStage>,
109 pub interp_tables: Vec<PrecomputedInterpTable>,
111}
112
113#[derive(Debug, Clone)]
115pub struct PrecomputedStage {
116 pub stage_type: u8,
118 pub n_ports: usize,
120 pub scattering: Vec<f64>,
127 pub vs_injection: Option<Vec<f64>>,
130 pub port_resistances: Vec<f64>,
132 pub extract_coeffs: Option<Vec<f64>>,
135 pub extract_vs: f64,
138}
139
140#[derive(Debug, Clone)]
142pub struct PrecomputedInterpTable {
143 pub stage_index: usize,
145 pub n_ports: usize,
147 pub has_vs_injection: bool,
149 pub resistances: Vec<f64>,
151 pub matrices: Vec<Vec<f64>>,
153 pub injections: Vec<Vec<f64>>,
156}
157
158pub fn pedal_hash(text: &str) -> u32 {
175 const FNV_OFFSET: u32 = 0x811c_9dc5;
176 const FNV_PRIME: u32 = 0x0100_0193;
177 let mut hash = FNV_OFFSET;
178 for byte in text.bytes() {
179 hash ^= u32::from(byte);
180 hash = hash.wrapping_mul(FNV_PRIME);
181 }
182 hash
183}
184
185fn write_u8(w: &mut impl Write, v: u8) -> std::io::Result<()> {
190 w.write_all(&[v])
191}
192
193fn write_u16_le(w: &mut impl Write, v: u16) -> std::io::Result<()> {
194 w.write_all(&v.to_le_bytes())
195}
196
197fn write_u32_le(w: &mut impl Write, v: u32) -> std::io::Result<()> {
198 w.write_all(&v.to_le_bytes())
199}
200
201fn write_u64_le(w: &mut impl Write, v: u64) -> std::io::Result<()> {
202 w.write_all(&v.to_le_bytes())
203}
204
205fn write_f64_le(w: &mut impl Write, v: f64) -> std::io::Result<()> {
206 write_u64_le(w, v.to_bits())
207}
208
209fn write_f64_slice(w: &mut impl Write, s: &[f64]) -> std::io::Result<()> {
210 for &v in s {
211 write_f64_le(w, v)?;
212 }
213 Ok(())
214}
215
216fn read_u8(r: &mut impl Read) -> std::io::Result<u8> {
217 let mut buf = [0u8; 1];
218 r.read_exact(&mut buf)?;
219 Ok(buf[0])
220}
221
222fn read_u16_le(r: &mut impl Read) -> std::io::Result<u16> {
223 let mut buf = [0u8; 2];
224 r.read_exact(&mut buf)?;
225 Ok(u16::from_le_bytes(buf))
226}
227
228fn read_u32_le(r: &mut impl Read) -> std::io::Result<u32> {
229 let mut buf = [0u8; 4];
230 r.read_exact(&mut buf)?;
231 Ok(u32::from_le_bytes(buf))
232}
233
234fn read_u64_le(r: &mut impl Read) -> std::io::Result<u64> {
235 let mut buf = [0u8; 8];
236 r.read_exact(&mut buf)?;
237 Ok(u64::from_le_bytes(buf))
238}
239
240fn read_f64_le(r: &mut impl Read) -> std::io::Result<f64> {
241 Ok(f64::from_bits(read_u64_le(r)?))
242}
243
244fn read_f64_vec(r: &mut impl Read, n: usize) -> std::io::Result<Vec<f64>> {
245 let mut v = vec![0.0f64; n];
246 for x in &mut v {
247 *x = read_f64_le(r)?;
248 }
249 Ok(v)
250}
251
252impl PrecomputedScattering {
257 pub fn to_bytes(&self) -> Result<Vec<u8>, PrecomputeError> {
264 let mut buf: Vec<u8> = Vec::new();
265 self.write_to(&mut buf)?;
266 Ok(buf)
267 }
268
269 fn write_to(&self, w: &mut impl Write) -> Result<(), PrecomputeError> {
270 w.write_all(&MAGIC)?;
272 write_u16_le(w, VERSION)?;
273 write_u16_le(w, 0u16)?; write_u32_le(w, self.pedal_hash)?;
275 write_u64_le(w, self.sample_rate.to_bits())?;
276
277 let num_stages = self.stages.len();
278 let num_tables = self.interp_tables.len();
279 if num_stages > u16::MAX as usize {
280 return Err(PrecomputeError::Consistency(format!(
281 "too many stages: {num_stages}"
282 )));
283 }
284 if num_tables > u16::MAX as usize {
285 return Err(PrecomputeError::Consistency(format!(
286 "too many interp tables: {num_tables}"
287 )));
288 }
289 write_u16_le(w, num_stages as u16)?;
290 write_u16_le(w, num_tables as u16)?;
291 w.write_all(&[0u8; 8])?; for stage in &self.stages {
295 stage.write_to(w)?;
296 }
297
298 for table in &self.interp_tables {
300 table.write_to(w)?;
301 }
302
303 Ok(())
304 }
305
306 pub fn from_bytes(data: &[u8]) -> Result<Self, PrecomputeError> {
312 let mut cursor = Cursor::new(data);
313 Self::read_from(&mut cursor)
314 }
315
316 fn read_from(r: &mut impl Read) -> Result<Self, PrecomputeError> {
317 let mut magic = [0u8; 4];
319 r.read_exact(&mut magic)?;
320 if magic != MAGIC {
321 return Err(PrecomputeError::BadMagic);
322 }
323
324 let version = read_u16_le(r)?;
325 if version != VERSION {
326 return Err(PrecomputeError::BadVersion(version));
327 }
328
329 let _flags = read_u16_le(r)?;
330 let pedal_hash = read_u32_le(r)?;
331 let sample_rate = f64::from_bits(read_u64_le(r)?);
332 let num_stages = read_u16_le(r)? as usize;
333 let num_tables = read_u16_le(r)? as usize;
334 let mut _reserved = [0u8; 8];
335 r.read_exact(&mut _reserved)?;
336
337 let mut stages = Vec::with_capacity(num_stages);
339 for _ in 0..num_stages {
340 stages.push(PrecomputedStage::read_from(r)?);
341 }
342
343 let mut interp_tables = Vec::with_capacity(num_tables);
345 for _ in 0..num_tables {
346 interp_tables.push(PrecomputedInterpTable::read_from(r)?);
347 }
348
349 Ok(Self {
350 pedal_hash,
351 sample_rate,
352 stages,
353 interp_tables,
354 })
355 }
356}
357
358impl PrecomputedStage {
361 fn write_to(&self, w: &mut impl Write) -> Result<(), PrecomputeError> {
362 let n = self.n_ports;
363 if n > u16::MAX as usize {
364 return Err(PrecomputeError::Consistency(format!(
365 "n_ports too large: {n}"
366 )));
367 }
368
369 write_u8(w, self.stage_type)?;
370 write_u16_le(w, n as u16)?;
371 write_u8(w, u8::from(self.vs_injection.is_some()))?;
372 write_u8(w, u8::from(self.extract_coeffs.is_some()))?;
373 w.write_all(&[0u8; 3])?; write_f64_slice(w, &self.scattering)?;
376
377 if let Some(ref vs) = self.vs_injection {
378 write_f64_slice(w, vs)?;
379 }
380
381 write_f64_slice(w, &self.port_resistances)?;
382
383 if let Some(ref ec) = self.extract_coeffs {
384 write_f64_slice(w, ec)?;
385 write_f64_le(w, self.extract_vs)?;
386 }
387
388 Ok(())
389 }
390
391 fn read_from(r: &mut impl Read) -> Result<Self, PrecomputeError> {
392 let stage_type = read_u8(r)?;
393 let n = read_u16_le(r)? as usize;
394 let has_vs = read_u8(r)? != 0;
395 let has_extract = read_u8(r)? != 0;
396 let mut _pad = [0u8; 3];
397 r.read_exact(&mut _pad)?;
398
399 let scattering = read_f64_vec(r, n * n)?;
400 let vs_injection = if has_vs {
401 Some(read_f64_vec(r, n)?)
402 } else {
403 None
404 };
405 let port_resistances = read_f64_vec(r, n)?;
406 let (extract_coeffs, extract_vs) = if has_extract {
407 let ec = read_f64_vec(r, n)?;
408 let ev = read_f64_le(r)?;
409 (Some(ec), ev)
410 } else {
411 (None, 0.0)
412 };
413
414 Ok(Self {
415 stage_type,
416 n_ports: n,
417 scattering,
418 vs_injection,
419 port_resistances,
420 extract_coeffs,
421 extract_vs,
422 })
423 }
424}
425
426impl PrecomputedInterpTable {
429 fn write_to(&self, w: &mut impl Write) -> Result<(), PrecomputeError> {
430 let k = self.resistances.len();
431 let n = self.n_ports;
432
433 if self.stage_index > u16::MAX as usize {
434 return Err(PrecomputeError::Consistency(format!(
435 "stage_index too large: {}",
436 self.stage_index
437 )));
438 }
439 if k > u16::MAX as usize {
440 return Err(PrecomputeError::Consistency(format!(
441 "n_positions too large: {k}"
442 )));
443 }
444 if n > u16::MAX as usize {
445 return Err(PrecomputeError::Consistency(format!(
446 "n_ports too large: {n}"
447 )));
448 }
449
450 write_u16_le(w, self.stage_index as u16)?;
451 write_u16_le(w, k as u16)?;
452 write_u16_le(w, n as u16)?;
453 write_u8(w, u8::from(self.has_vs_injection))?;
454 write_u8(w, 0u8)?; write_f64_slice(w, &self.resistances)?;
457
458 for mat in &self.matrices {
459 write_f64_slice(w, mat)?;
460 }
461
462 if self.has_vs_injection {
463 for inj in &self.injections {
464 write_f64_slice(w, inj)?;
465 }
466 }
467
468 Ok(())
469 }
470
471 fn read_from(r: &mut impl Read) -> Result<Self, PrecomputeError> {
472 let stage_index = read_u16_le(r)? as usize;
473 let k = read_u16_le(r)? as usize;
474 let n = read_u16_le(r)? as usize;
475 let has_vs_injection = read_u8(r)? != 0;
476 let _pad = read_u8(r)?;
477
478 let resistances = read_f64_vec(r, k)?;
479
480 let mut matrices = Vec::with_capacity(k);
481 for _ in 0..k {
482 matrices.push(read_f64_vec(r, n * n)?);
483 }
484
485 let mut injections = Vec::with_capacity(k);
486 if has_vs_injection {
487 for _ in 0..k {
488 injections.push(read_f64_vec(r, n)?);
489 }
490 }
491
492 Ok(Self {
493 stage_index,
494 n_ports: n,
495 has_vs_injection,
496 resistances,
497 matrices,
498 injections,
499 })
500 }
501}
502
503pub fn extract_precomputed(
519 compiled: &crate::compiler::CompiledPedal,
520 pedal_source: &str,
521 sample_rate: f64,
522) -> PrecomputedScattering {
523 crate::compiler::extract_precomputed_from_compiled(compiled, pedal_source, sample_rate)
524}
525
526#[cfg(test)]
531mod tests {
532 use super::*;
533
534 #[test]
537 fn precompute_round_trip() {
538 let original = PrecomputedScattering {
539 pedal_hash: 0xDEAD_BEEF,
540 sample_rate: 48000.0,
541 stages: vec![
542 PrecomputedStage {
543 stage_type: 1,
544 n_ports: 3,
545 scattering: vec![
546 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, ],
550 vs_injection: Some(vec![0.5, -0.5, 1.0]),
551 port_resistances: vec![100.0, 200.0, 300.0],
552 extract_coeffs: Some(vec![0.1, 0.2, 0.3]),
553 extract_vs: 0.42,
554 },
555 PrecomputedStage {
556 stage_type: 0,
557 n_ports: 2,
558 scattering: vec![-1.0, 2.0, 2.0, -1.0],
559 vs_injection: None,
560 port_resistances: vec![50.0, 50.0],
561 extract_coeffs: None,
562 extract_vs: 0.0,
563 },
564 ],
565 interp_tables: vec![PrecomputedInterpTable {
566 stage_index: 0,
567 n_ports: 3,
568 has_vs_injection: true,
569 resistances: vec![10.0, 100.0, 1000.0],
570 matrices: vec![
571 vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
572 vec![0.5, 0.5, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0],
573 vec![0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
574 ],
575 injections: vec![
576 vec![1.0, 0.0, 0.0],
577 vec![0.5, 0.5, 0.0],
578 vec![0.0, 1.0, 0.0],
579 ],
580 }],
581 };
582
583 let bytes = original.to_bytes().expect("serialize");
584 let restored = PrecomputedScattering::from_bytes(&bytes).expect("deserialize");
585
586 assert_eq!(restored.pedal_hash, original.pedal_hash);
587 assert!((restored.sample_rate - original.sample_rate).abs() < 1e-9);
588 assert_eq!(restored.stages.len(), original.stages.len());
589
590 let s0 = &restored.stages[0];
592 assert_eq!(s0.stage_type, 1);
593 assert_eq!(s0.n_ports, 3);
594 assert_eq!(s0.scattering, original.stages[0].scattering);
595 assert_eq!(s0.vs_injection, original.stages[0].vs_injection);
596 assert_eq!(s0.port_resistances, original.stages[0].port_resistances);
597 assert_eq!(s0.extract_coeffs, original.stages[0].extract_coeffs);
598 assert!((s0.extract_vs - original.stages[0].extract_vs).abs() < 1e-12);
599
600 let s1 = &restored.stages[1];
602 assert_eq!(s1.stage_type, 0);
603 assert!(s1.vs_injection.is_none());
604 assert!(s1.extract_coeffs.is_none());
605
606 assert_eq!(restored.interp_tables.len(), 1);
608 let t0 = &restored.interp_tables[0];
609 assert_eq!(t0.stage_index, 0);
610 assert_eq!(t0.n_ports, 3);
611 assert!(t0.has_vs_injection);
612 assert_eq!(t0.resistances, original.interp_tables[0].resistances);
613 assert_eq!(t0.matrices, original.interp_tables[0].matrices);
614 assert_eq!(t0.injections, original.interp_tables[0].injections);
615 }
616
617 #[test]
620 fn precompute_hash_changes() {
621 let h1 = pedal_hash("pedal A {}");
622 let h2 = pedal_hash("pedal B {}");
623 let h3 = pedal_hash("pedal A {}");
624 assert_ne!(h1, h2, "different sources must produce different hashes");
625 assert_eq!(h1, h3, "same source must produce identical hash");
626 }
627
628 #[test]
631 fn precompute_rejects_bad_magic() {
632 let mut bad = b"NOPE\x01\x00".to_vec();
633 bad.extend_from_slice(&[0u8; 26]);
634 let err = PrecomputedScattering::from_bytes(&bad).unwrap_err();
635 assert!(matches!(err, PrecomputeError::BadMagic));
636 }
637
638 #[test]
641 fn precompute_from_pedal() {
642 let src = r#"
645pedal "ClipTest" subtitle "precompute integration test" {
646 supply 12V
647 components {
648 C1: cap(100n)
649 R_B1: resistor(100k)
650 R_B2: resistor(100k)
651 Q1: npn(2n3904)
652 Q2: npn(2n3904)
653 RC1: resistor(10k)
654 RC2: resistor(10k)
655 R_tail: resistor(10k)
656 C_out: cap(1u)
657 RL: resistor(10k)
658 }
659 nets {
660 in -> C1.a
661 C1.b -> R_B1.a, Q1.base
662 R_B1.b -> vcc
663 R_B2.a -> Q2.base
664 R_B2.b -> vcc
665 Q1.collector -> RC1.a
666 Q2.collector -> RC2.a
667 RC1.b -> vcc
668 RC2.b -> vcc
669 Q1.emitter -> Q2.emitter, R_tail.a
670 R_tail.b -> gnd
671 Q1.collector -> C_out.a
672 C_out.b -> RL.a, out
673 RL.b -> gnd
674 }
675 controls {}
676}
677"#;
678 use crate::compiler::{compile_pedal, CompiledPedal};
679 use crate::dsl::parse_pedal_file;
680
681 let pedal_def = parse_pedal_file(src).expect("parse");
682 let compiled: CompiledPedal = compile_pedal(&pedal_def, 48000.0).expect("compile");
683
684 let precomp = extract_precomputed(&compiled, src, 48000.0);
685
686 assert_eq!(precomp.pedal_hash, pedal_hash(src));
688 assert!((precomp.sample_rate - 48000.0).abs() < 1e-9);
689
690 assert!(
692 !precomp.stages.is_empty(),
693 "expected at least one MultiNL stage, got none \
694 (debug: compiled has {} wdf + {} multi_nl stages)",
695 compiled.debug_stage_count(),
696 compiled.debug_multi_nl_count(),
697 );
698
699 for (i, stage) in precomp.stages.iter().enumerate() {
701 let expected_len = stage.n_ports * stage.n_ports;
702 assert_eq!(
703 stage.scattering.len(),
704 expected_len,
705 "stage {i}: scattering matrix has wrong length"
706 );
707 assert!(
708 stage.scattering.iter().all(|x| x.is_finite()),
709 "stage {i}: scattering matrix contains non-finite values"
710 );
711 }
712 }
713}