1use crate::error::Error;
22use crate::model::{GatingMode, LayerArrayConfig, NamModel, WaveNetConfig};
23use crate::reader::Reader;
24
25mod activation;
26mod array;
27mod conv;
28mod film;
29mod gating;
30mod head;
31mod layer;
32
33use activation::Activation;
34use array::LayerArray;
35use conv::{Conv1d, MAX_BLOCK};
36use gating::Gating;
37use head::PostStackHead;
38use layer::{Layer, LayerDims, LayerWeights};
39
40#[derive(Debug)]
42pub struct WaveNet {
43 arrays: Vec<LayerArray>,
44 post_stack_head: Option<Box<PostStackHead>>,
48 head_scale_scratch: Vec<f32>,
52 condition_dsp: Option<Box<crate::Model>>,
58 cond_out_ch: usize,
63 cond_dsp_out: Vec<f32>,
67 head_scale: f32,
68 receptive_field: usize,
71 head_in0: usize,
74 head_a: Vec<f32>,
76 head_b: Vec<f32>,
77 sig_a: Vec<f32>,
79 sig_b: Vec<f32>,
80 head_a_blk: Vec<f32>,
83 head_b_blk: Vec<f32>,
84 sig_a_blk: Vec<f32>,
85 sig_b_blk: Vec<f32>,
86 cond_blk: Vec<f32>,
87}
88
89impl WaveNet {
90 pub fn new(model: &NamModel) -> Result<Self, Error> {
102 Self::build(model, false)
103 }
104
105 pub(crate) fn new_conditioning(model: &NamModel) -> Result<Self, Error> {
109 Self::build(model, true)
110 }
111
112 fn build(model: &NamModel, allow_multi_output: bool) -> Result<Self, Error> {
113 let cfg = match &model.config {
114 crate::model::ModelConfig::WaveNet(cfg) => cfg,
115 crate::model::ModelConfig::Lstm(_) | crate::model::ModelConfig::Slimmable(_) => {
116 return Err(Error::UnsupportedArchitecture(model.architecture.clone()))
117 }
118 };
119
120 check_unsupported_features(cfg)?;
121
122 if !allow_multi_output && cfg.post_stack_head.is_none() {
129 let out_ch = cfg.layers.last().map_or(1, |la| la.head_size);
130 if out_ch != 1 {
131 return Err(Error::UnsupportedFeature(format!(
132 "top-level WaveNet must be mono-output, but produces {out_ch} channels \
133 (a multi-channel WaveNet is only valid as a nested condition_dsp)"
134 )));
135 }
136 }
137
138 let condition_dsp = match &cfg.condition_dsp {
143 Some(nested) => Some(Box::new(crate::Model::from_nam_conditioning(nested)?)),
144 None => None,
145 };
146
147 let cond_out_ch = condition_dsp
153 .as_ref()
154 .map_or(1, |m| m.num_output_channels());
155 if let Some(cdsp) = &condition_dsp {
156 let n_out = cdsp.num_output_channels();
157 for (i, la) in cfg.layers.iter().enumerate() {
158 if la.condition_size != n_out {
159 return Err(Error::UnsupportedFeature(format!(
160 "condition_size of layer-array {i} ({}) != condition_dsp output \
161 channels ({n_out})",
162 la.condition_size
163 )));
164 }
165 }
166 }
167
168 let expected = expected_weight_count(cfg)?;
169 if expected != model.weights.len() {
170 return Err(Error::WeightCountMismatch {
171 expected,
172 found: model.weights.len(),
173 });
174 }
175
176 let mut r = Reader::new(&model.weights);
177 let mut arrays = Vec::with_capacity(cfg.layers.len());
178 for la in &cfg.layers {
179 arrays.push(build_array(&mut r, la)?);
180 }
181 for i in 1..arrays.len() {
190 let produced = arrays[i - 1].head_size();
191 let consumed = arrays[i].head_in();
192 if produced != consumed {
193 return Err(Error::UnsupportedFeature(format!(
194 "layer-array head-carry width mismatch: array {} head_size {produced} \
195 != array {i} head_in {consumed}",
196 i - 1
197 )));
198 }
199 }
200
201 let post_stack_head = match &cfg.post_stack_head {
202 Some(hc) => {
203 let in_channels = arrays.last().map_or(0, LayerArray::head_size);
204 Some(Box::new(build_post_stack_head(&mut r, hc, in_channels)?))
205 }
206 None => None,
207 };
208
209 let head_scale = r.take(1)[0];
210 assert_eq!(
217 r.remaining(),
218 0,
219 "build_array consumed fewer weights than expected_weight_count claimed"
220 );
221
222 let max_ch = arrays.iter().map(LayerArray::channels).max().unwrap_or(1);
223 let max_head = arrays.iter().map(LayerArray::head_size).max().unwrap_or(1);
224 let max_head_in = arrays.iter().map(LayerArray::head_in).max().unwrap_or(1);
225 let head_w = max_ch.max(max_head).max(max_head_in).max(1);
228 let sig_w = max_ch.max(1);
229 let head_in0 = arrays.first().map_or(0, LayerArray::head_in);
231
232 let head_in_channels = post_stack_head
233 .as_ref()
234 .map_or(0, |h| h.in_channels())
235 .max(1);
236
237 let rf_base = condition_dsp.as_ref().map_or(1, |m| m.receptive_field());
240
241 Ok(Self {
242 arrays,
243 post_stack_head,
244 head_scale_scratch: vec![0.0; head_in_channels * MAX_BLOCK],
245 condition_dsp,
246 cond_out_ch,
247 cond_dsp_out: vec![0.0; cond_out_ch * MAX_BLOCK],
248 head_scale,
249 receptive_field: receptive_field(cfg, rf_base),
250 head_in0,
251 head_a: vec![0.0; head_w],
252 head_b: vec![0.0; head_w],
253 sig_a: vec![0.0; sig_w],
254 sig_b: vec![0.0; sig_w],
255 head_a_blk: vec![0.0; head_w * MAX_BLOCK],
256 head_b_blk: vec![0.0; head_w * MAX_BLOCK],
257 sig_a_blk: vec![0.0; sig_w * MAX_BLOCK],
258 sig_b_blk: vec![0.0; sig_w * MAX_BLOCK],
259 cond_blk: vec![0.0; MAX_BLOCK],
260 })
261 }
262
263 pub fn receptive_field(&self) -> usize {
271 self.receptive_field
272 }
273
274 #[cfg(test)]
275 pub(super) fn has_condition_dsp(&self) -> bool {
276 self.condition_dsp.is_some()
277 }
278
279 pub fn process_buffer(&mut self, io: &mut [f32]) {
289 if self.arrays.is_empty() {
290 for s in io.iter_mut() {
291 *s *= self.head_scale;
292 }
293 return;
294 }
295 let mut off = 0;
296 while off < io.len() {
297 let n = (io.len() - off).min(MAX_BLOCK);
298 self.process_chunk(&mut io[off..off + n], n);
299 off += n;
300 }
301 }
302
303 pub(crate) fn num_output_channels(&self) -> usize {
309 match &self.post_stack_head {
310 Some(h) => h.out_channels(),
311 None => self.arrays.last().map_or(1, LayerArray::head_size),
312 }
313 }
314
315 fn run_arrays_block(&mut self, n: usize) {
320 let cond_ch = self.cond_out_ch;
325 if let Some(cdsp) = &mut self.condition_dsp {
326 cdsp.process_block_multi(
327 &self.cond_blk[..n],
328 &mut self.cond_dsp_out[..cond_ch * n],
329 n,
330 );
331 } else {
332 self.cond_dsp_out[..n].copy_from_slice(&self.cond_blk[..n]); }
334
335 self.head_a_blk[..self.head_in0 * n].fill(0.0);
338 {
339 let hin = self.arrays[0].head_in();
340 let ch = self.arrays[0].channels();
341 let hs = self.arrays[0].head_size();
342 self.arrays[0].process_block(
343 &self.cond_blk[..n],
344 &self.cond_dsp_out[..cond_ch * n],
345 &self.head_a_blk[..hin * n],
346 &mut self.head_b_blk[..hs * n],
347 &mut self.sig_b_blk[..ch * n],
348 n,
349 );
350 }
351 std::mem::swap(&mut self.head_a_blk, &mut self.head_b_blk);
352 std::mem::swap(&mut self.sig_a_blk, &mut self.sig_b_blk);
353
354 for i in 1..self.arrays.len() {
355 let in_w = self.arrays[i - 1].channels();
356 let hin = self.arrays[i].head_in();
357 let ch = self.arrays[i].channels();
358 let hs = self.arrays[i].head_size();
359 self.arrays[i].process_block(
360 &self.sig_a_blk[..in_w * n],
361 &self.cond_dsp_out[..cond_ch * n],
362 &self.head_a_blk[..hin * n],
363 &mut self.head_b_blk[..hs * n],
364 &mut self.sig_b_blk[..ch * n],
365 n,
366 );
367 std::mem::swap(&mut self.head_a_blk, &mut self.head_b_blk);
368 std::mem::swap(&mut self.sig_a_blk, &mut self.sig_b_blk);
369 }
370 }
371
372 fn process_chunk(&mut self, chunk: &mut [f32], n: usize) {
379 self.cond_blk[..n].copy_from_slice(chunk);
382 self.run_arrays_block(n);
383
384 match &mut self.post_stack_head {
386 None => {
387 for (t, s) in chunk.iter_mut().enumerate() {
390 *s = self.head_scale * self.head_a_blk[t];
391 }
392 }
393 Some(head) => {
394 let in_ch = head.in_channels();
398 let scaled = &mut self.head_scale_scratch[..in_ch * n];
399 for (s, &h) in scaled.iter_mut().zip(&self.head_a_blk[..in_ch * n]) {
400 *s = self.head_scale * h;
401 }
402 let out = head.process_block(scaled, n); chunk.copy_from_slice(&out[..n]);
404 }
405 }
406 }
407
408 pub(crate) fn process_block_multi(&mut self, input: &[f32], out: &mut [f32], n: usize) {
415 if self.arrays.is_empty() {
416 for (o, &x) in out[..n].iter_mut().zip(&input[..n]) {
418 *o = self.head_scale * x;
419 }
420 return;
421 }
422 self.cond_blk[..n].copy_from_slice(&input[..n]);
423 self.run_arrays_block(n);
424
425 match &mut self.post_stack_head {
427 None => {
428 let oc = self.arrays.last().map_or(1, LayerArray::head_size);
430 for (o, &h) in out[..oc * n].iter_mut().zip(&self.head_a_blk[..oc * n]) {
431 *o = self.head_scale * h;
432 }
433 }
434 Some(head) => {
435 let in_ch = head.in_channels();
438 let scaled = &mut self.head_scale_scratch[..in_ch * n];
439 for (s, &h) in scaled.iter_mut().zip(&self.head_a_blk[..in_ch * n]) {
440 *s = self.head_scale * h;
441 }
442 let oc = head.out_channels();
443 let produced = head.process_block(scaled, n); out[..oc * n].copy_from_slice(&produced[..oc * n]);
445 }
446 }
447 }
448
449 pub fn process_sample(&mut self, x: f32) -> f32 {
454 let input = [x];
460 let cond_ch = self.cond_out_ch;
461 if let Some(cdsp) = &mut self.condition_dsp {
462 cdsp.process_block_multi(&input, &mut self.cond_dsp_out[..cond_ch], 1);
463 } else {
464 self.cond_dsp_out[0] = x;
465 }
466 let n = self.arrays.len();
467 if n == 0 {
468 return self.head_scale * x;
469 }
470
471 self.head_a[..self.head_in0].fill(0.0);
475 {
476 let hin = self.arrays[0].head_in();
477 let ch = self.arrays[0].channels();
478 let hs = self.arrays[0].head_size();
479 self.arrays[0].process_sample(
480 &input,
481 &self.cond_dsp_out[..cond_ch],
482 &self.head_a[..hin],
483 &mut self.head_b[..hs],
484 &mut self.sig_b[..ch],
485 );
486 }
487 std::mem::swap(&mut self.head_a, &mut self.head_b);
488 std::mem::swap(&mut self.sig_a, &mut self.sig_b);
489
490 for i in 1..n {
491 let in_w = self.arrays[i - 1].channels();
492 let hin = self.arrays[i].head_in();
493 let ch = self.arrays[i].channels();
494 let hs = self.arrays[i].head_size();
495 self.arrays[i].process_sample(
496 &self.sig_a[..in_w],
497 &self.cond_dsp_out[..cond_ch],
498 &self.head_a[..hin],
499 &mut self.head_b[..hs],
500 &mut self.sig_b[..ch],
501 );
502 std::mem::swap(&mut self.head_a, &mut self.head_b);
503 std::mem::swap(&mut self.sig_a, &mut self.sig_b);
504 }
505
506 match &mut self.post_stack_head {
508 None => self.head_scale * self.head_a[0],
509 Some(head) => {
510 let in_ch = head.in_channels();
511 let scaled = &mut self.head_scale_scratch[..in_ch];
512 for (s, &h) in scaled.iter_mut().zip(&self.head_a[..in_ch]) {
513 *s = self.head_scale * h;
514 }
515 head.process_sample(scaled)[0]
516 }
517 }
518 }
519
520 pub fn reset(&mut self) {
522 for a in &mut self.arrays {
523 a.reset();
524 }
525 if let Some(h) = &mut self.post_stack_head {
526 h.reset();
527 }
528 if let Some(c) = &mut self.condition_dsp {
529 c.reset();
530 }
531 self.head_a.fill(0.0);
532 self.head_b.fill(0.0);
533 self.sig_a.fill(0.0);
534 self.sig_b.fill(0.0);
535 }
536}
537
538fn check_unsupported_features(cfg: &WaveNetConfig) -> Result<(), Error> {
551 if cfg.in_channels != 1 {
552 return Err(Error::UnsupportedFeature("in_channels != 1".into()));
553 }
554 if cfg.layers.is_empty() {
559 return Err(Error::UnsupportedFeature(
560 "WaveNet with no layer-arrays".into(),
561 ));
562 }
563 for la in &cfg.layers {
564 let first = la.gating_mode();
567 if la.gating_modes.iter().any(|&g| g != first) {
568 return Err(Error::UnsupportedFeature("mixed gating modes".into()));
569 }
570 }
571 Ok(())
572}
573
574fn receptive_field(cfg: &WaveNetConfig, base: usize) -> usize {
580 let mut rf = base;
581 for la in &cfg.layers {
582 for (k, &d) in la.kernel_sizes.iter().zip(&la.dilations) {
583 rf += (k - 1) * d;
584 }
585 rf += la.head_kernel_size - 1;
586 }
587 if let Some(head) = &cfg.post_stack_head {
588 for &k in &head.kernel_sizes {
589 rf += k - 1;
590 }
591 }
592 rf
593}
594
595fn array_weight_count(la: &LayerArrayConfig) -> Result<usize, Error> {
601 let mul = |a: usize, b: usize| a.checked_mul(b).ok_or(Error::ConfigTooLarge);
602 let add = |a: usize, b: usize| a.checked_add(b).ok_or(Error::ConfigTooLarge);
603
604 let gated = la.gating_mode() != GatingMode::None;
605 let mid = if gated {
606 mul(2, la.bottleneck)?
607 } else {
608 la.bottleneck
609 };
610 let head1x1_out = la.head1x1.out_channels.unwrap_or(la.channels);
611 let cond = la.condition_size;
612
613 let conv_w = |out: usize, in_ch: usize, k: usize, groups: usize| -> Result<usize, Error> {
619 if out % groups != 0 || in_ch % groups != 0 {
620 return Err(Error::UnsupportedFeature(format!(
621 "grouped conv: out ({out}) and in ({in_ch}) must both be divisible by groups ({groups})"
622 )));
623 }
624 Ok(mul(mul(out, in_ch)?, k)? / groups)
625 };
626 let film = |f: &crate::model::FilmConfig, input_dim: usize| -> Result<usize, Error> {
628 if !f.active {
629 return Ok(0);
630 }
631 let out_rows = if f.shift {
632 mul(2, input_dim)?
633 } else {
634 input_dim
635 };
636 add(conv_w(out_rows, cond, 1, f.groups)?, out_rows)
637 };
638
639 let mut total = mul(la.channels, la.input_size)?; for &k in &la.kernel_sizes {
642 let conv = add(conv_w(mid, la.channels, k, la.groups_input)?, mid)?;
643 let mixin = conv_w(mid, cond, 1, la.groups_input_mixin)?; let mut layer = add(conv, mixin)?;
645 if la.layer1x1.active {
646 let l = add(
647 conv_w(la.channels, la.bottleneck, 1, la.layer1x1.groups)?,
648 la.channels,
649 )?;
650 layer = add(layer, l)?;
651 }
652 if la.head1x1.active {
653 let h = add(
654 conv_w(head1x1_out, la.bottleneck, 1, la.head1x1.groups)?,
655 head1x1_out,
656 )?;
657 layer = add(layer, h)?;
658 }
659 layer = add(layer, film(&la.conv_pre_film, la.channels)?)?;
661 layer = add(layer, film(&la.conv_post_film, mid)?)?;
662 layer = add(layer, film(&la.input_mixin_pre_film, cond)?)?;
663 layer = add(layer, film(&la.input_mixin_post_film, mid)?)?;
664 layer = add(layer, film(&la.activation_pre_film, mid)?)?;
665 layer = add(layer, film(&la.activation_post_film, la.bottleneck)?)?;
666 layer = add(layer, film(&la.layer1x1_post_film, la.channels)?)?;
667 layer = add(layer, film(&la.head1x1_post_film, head1x1_out)?)?;
668 total = add(total, layer)?;
669 }
670
671 let head_in = if la.head1x1.active {
673 head1x1_out
674 } else {
675 la.bottleneck
676 };
677 total = add(
678 total,
679 mul(mul(la.head_size, head_in)?, la.head_kernel_size)?,
680 )?;
681 if la.head_bias {
682 total = add(total, la.head_size)?;
683 }
684 Ok(total)
685}
686
687fn post_stack_head_weight_count(
692 head: &crate::model::PostStackHeadConfig,
693 in_channels: usize,
694) -> Result<usize, Error> {
695 let mul = |a: usize, b: usize| a.checked_mul(b).ok_or(Error::ConfigTooLarge);
696 let add = |a: usize, b: usize| a.checked_add(b).ok_or(Error::ConfigTooLarge);
697 if head.kernel_sizes.is_empty() {
698 return Err(Error::UnsupportedFeature(
699 "post-stack head with no convs".into(),
700 ));
701 }
702 let n = head.kernel_sizes.len();
703 let mut total = 0usize;
704 let mut cin = in_channels;
705 for (i, &k) in head.kernel_sizes.iter().enumerate() {
706 let cout = if i + 1 == n {
707 head.out_channels
708 } else {
709 head.channels
710 };
711 total = add(total, add(mul(mul(cout, cin)?, k)?, cout)?)?; cin = cout;
713 }
714 Ok(total)
715}
716
717fn expected_weight_count(cfg: &WaveNetConfig) -> Result<usize, Error> {
724 let add = |a: usize, b: usize| a.checked_add(b).ok_or(Error::ConfigTooLarge);
725 let mut total = 0usize;
726 for la in &cfg.layers {
727 total = add(total, array_weight_count(la)?)?;
728 }
729 if let Some(head) = &cfg.post_stack_head {
730 let in_ch = cfg.layers.last().map_or(0, |la| la.head_size);
731 total = add(total, post_stack_head_weight_count(head, in_ch)?)?;
732 }
733 add(total, 1) }
735
736fn build_post_stack_head(
742 r: &mut Reader,
743 hc: &crate::model::PostStackHeadConfig,
744 in_channels: usize,
745) -> Result<PostStackHead, Error> {
746 if hc.out_channels != 1 {
747 return Err(Error::UnsupportedFeature(
748 "post-stack head out_channels != 1".into(),
749 ));
750 }
751 let n = hc.kernel_sizes.len();
752 let activation = Activation::from_spec(&hc.activation)?;
753 let mut convs = Vec::with_capacity(n);
754 let mut cin = in_channels;
755 for (i, &k) in hc.kernel_sizes.iter().enumerate() {
756 let cout = if i + 1 == n {
757 hc.out_channels
758 } else {
759 hc.channels
760 };
761 let w = r.take(cout * cin * k);
762 let b = r.take(cout);
763 convs.push((activation, Conv1d::new(cin, cout, k, 1, w, Some(b))));
764 cin = cout;
765 }
766 Ok(PostStackHead::new(convs, in_channels, hc.out_channels))
767}
768
769fn build_array(r: &mut Reader, la: &LayerArrayConfig) -> Result<LayerArray, Error> {
770 let mode = la.gating_mode();
771 let gated = mode != GatingMode::None;
772 let mid = if gated {
773 2 * la.bottleneck
774 } else {
775 la.bottleneck
776 };
777 let head1x1_out = la.head1x1.out_channels.unwrap_or(la.channels);
778 let cond = la.condition_size;
779
780 let film_sites = [
782 (&la.conv_pre_film, la.channels),
783 (&la.conv_post_film, mid),
784 (&la.input_mixin_pre_film, cond),
785 (&la.input_mixin_post_film, mid),
786 (&la.activation_pre_film, mid),
787 (&la.activation_post_film, la.bottleneck),
788 (&la.layer1x1_post_film, la.channels),
789 (&la.head1x1_post_film, head1x1_out),
790 ];
791 let film_shift: [bool; 8] = std::array::from_fn(|i| film_sites[i].0.shift);
792 let film_groups: [usize; 8] = std::array::from_fn(|i| film_sites[i].0.groups);
793
794 let before = r.remaining();
795 let rechannel_w = r.take(la.channels * la.input_size);
796 let mut layers = Vec::with_capacity(la.dilations.len());
797 for (i, &d) in la.dilations.iter().enumerate() {
798 let k = la.kernel_sizes[i];
799 let primary = Activation::from_spec(&la.activations[i])?;
800 let secondary = Activation::from_spec(&la.secondary_activations[i])?;
801
802 let conv_w = r.take(mid * la.channels * k / la.groups_input);
804 let conv_b = r.take(mid);
805 let mix_w = r.take(mid * cond / la.groups_input_mixin);
806 let (layer1x1_w, layer1x1_b) = if la.layer1x1.active {
807 let w = r.take(la.channels * la.bottleneck / la.layer1x1.groups);
808 let b = r.take(la.channels);
809 (Some(w), Some(b))
810 } else {
811 (None, None)
812 };
813 let (head1x1_w, head1x1_b) = if la.head1x1.active {
814 let w = r.take(head1x1_out * la.bottleneck / la.head1x1.groups);
815 let b = r.take(head1x1_out);
816 (Some(w), Some(b))
817 } else {
818 (None, None)
819 };
820 let mut films: [Option<(Vec<f32>, Vec<f32>)>; 8] = Default::default();
821 for (j, (f, input_dim)) in film_sites.iter().enumerate() {
822 if f.active {
823 let out_rows = if f.shift { 2 * input_dim } else { *input_dim };
824 let w = r.take(out_rows * cond / f.groups);
825 let b = r.take(out_rows);
826 films[j] = Some((w, b));
827 }
828 }
829
830 let gating = Gating::new(mode, primary, secondary, la.bottleneck);
831 layers.push(Layer::new(
832 LayerDims {
833 channels: la.channels,
834 bottleneck: la.bottleneck,
835 condition_size: cond,
836 kernel: k,
837 dilation: d,
838 groups_input: la.groups_input,
839 groups_input_mixin: la.groups_input_mixin,
840 layer1x1_groups: la.layer1x1.groups,
841 head1x1_groups: la.head1x1.groups,
842 head1x1_out: if la.head1x1.active {
843 Some(head1x1_out)
844 } else {
845 None
846 },
847 film_shift,
848 film_groups,
849 },
850 gating,
851 LayerWeights {
852 conv_w,
853 conv_b,
854 mix_w,
855 layer1x1_w,
856 layer1x1_b,
857 head1x1_w,
858 head1x1_b,
859 films,
860 },
861 ));
862 }
863
864 let head_in = layers[0].head_contrib_width();
866 debug_assert!(
867 layers.iter().all(|l| l.head_contrib_width() == head_in),
868 "layers in one array must share head-contribution width"
869 );
870
871 let head_w = r.take(la.head_size * head_in * la.head_kernel_size);
872 let head_b = if la.head_bias {
873 Some(r.take(la.head_size))
874 } else {
875 None
876 };
877
878 debug_assert_eq!(
882 before - r.remaining(),
883 array_weight_count(la)?,
884 "build_array consumption drifted from array_weight_count"
885 );
886
887 Ok(LayerArray::new(
888 la.input_size,
889 la.channels,
890 head_in,
891 la.head_size,
892 la.head_kernel_size,
893 rechannel_w,
894 layers,
895 head_w,
896 head_b,
897 ))
898}
899
900#[cfg(test)]
901mod tests {
902 use super::*;
903
904 fn mk_layer(json: serde_json::Value) -> crate::model::LayerArrayConfig {
906 let raw: crate::model::RawLayerArrayConfig = serde_json::from_value(json).unwrap();
907 raw.normalize().unwrap()
908 }
909
910 const TINY: &str = r#"{
914 "version": "0.5.4",
915 "architecture": "WaveNet",
916 "config": {
917 "layers": [{
918 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
919 "kernel_size": 1, "dilations": [1], "activation": "ReLU",
920 "gated": false, "head_bias": false
921 }],
922 "head": null, "head_scale": 10.0
923 },
924 "weights": [1.0, 2.0, 0.5, 1.0, 3.0, 0.1, 0.5, 10.0]
925 }"#;
926
927 const TINY_HEAD: &str = r#"{
928 "version":"0.6.0","architecture":"WaveNet","config":{
929 "layers":[{"input_size":1,"condition_size":1,"channels":1,"head_size":1,
930 "kernel_size":1,"dilations":[1],"activation":"ReLU",
931 "gated":false,"head_bias":false}],
932 "head":{"channels":1,"out_channels":1,"kernel_sizes":[1],"activation":"ReLU"},
933 "head_scale":2.0},
934 "weights":[]}"#;
935
936 #[test]
937 fn receptive_field_includes_condition_dsp_prewarm() {
938 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
939 .join("tests/fixtures/condition_dsp_mono.nam");
940 let json = std::fs::read_to_string(path).expect("condition_dsp_mono.nam");
941 let model = NamModel::from_json_str(&json).expect("parse");
942 let cfg = match &model.config {
943 crate::model::ModelConfig::WaveNet(c) => c,
944 _ => unreachable!(),
945 };
946 let nested = crate::Model::from_nam(cfg.condition_dsp.as_ref().unwrap()).unwrap();
948 let mut want = nested.receptive_field();
949 for la in &cfg.layers {
950 for (k, &d) in la.kernel_sizes.iter().zip(&la.dilations) {
951 want += (k - 1) * d;
952 }
953 want += la.head_kernel_size - 1;
954 }
955 assert_eq!(WaveNet::new(&model).unwrap().receptive_field(), want);
956 }
957
958 #[test]
959 fn condition_dsp_block_equals_per_sample() {
960 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
964 .join("tests/fixtures/condition_dsp_mono.nam");
965 let json = std::fs::read_to_string(path).expect("condition_dsp_mono.nam");
966 let model = NamModel::from_json_str(&json).expect("parse");
967
968 let len = MAX_BLOCK + 173;
969 let signal: Vec<f32> = (0..len).map(|i| (i as f32 * 0.017).sin() * 0.4).collect();
970
971 let mut per_sample = WaveNet::new(&model).unwrap();
972 let want: Vec<f32> = signal
973 .iter()
974 .map(|&x| per_sample.process_sample(x))
975 .collect();
976 let mut block = WaveNet::new(&model).unwrap();
977 let mut got = signal.clone();
978 block.process_buffer(&mut got);
979 for (i, (g, w)) in got.iter().zip(&want).enumerate() {
980 assert!(
981 (g - w).abs() < 1e-5,
982 "sample {i}: block {g}, per-sample {w}"
983 );
984 }
985 }
986
987 #[test]
988 fn condition_dsp_model_builds() {
989 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
990 .join("tests/fixtures/condition_dsp_mono.nam");
991 let json = std::fs::read_to_string(path).expect("condition_dsp_mono.nam");
992 let model = NamModel::from_json_str(&json).expect("parse");
993 let wn = WaveNet::new(&model).expect("condition_dsp model builds");
994 assert!(
995 wn.has_condition_dsp(),
996 "nested condition_dsp must be present"
997 );
998 }
999
1000 #[test]
1001 fn multi_channel_condition_dsp_builds_and_block_equals_per_sample() {
1002 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1008 .join("tests/fixtures/wavenet_condition_dsp.nam");
1009 let json = std::fs::read_to_string(path).expect("wavenet_condition_dsp.nam");
1010 let model = NamModel::from_json_str(&json).expect("parse condition_dsp model");
1011
1012 let len = MAX_BLOCK + 173;
1013 let signal: Vec<f32> = (0..len).map(|i| (i as f32 * 0.017).sin() * 0.4).collect();
1014
1015 let mut per_sample = WaveNet::new(&model).expect("multi-channel condition_dsp builds");
1016 assert!(per_sample.has_condition_dsp());
1017 let want: Vec<f32> = signal
1018 .iter()
1019 .map(|&x| per_sample.process_sample(x))
1020 .collect();
1021 let mut block = WaveNet::new(&model).unwrap();
1022 let mut got = signal.clone();
1023 block.process_buffer(&mut got);
1024 for (i, (g, w)) in got.iter().zip(&want).enumerate() {
1025 assert!(
1026 (g - w).abs() < 1e-5,
1027 "sample {i}: block {g}, per-sample {w}"
1028 );
1029 }
1030 }
1031
1032 #[test]
1033 fn weight_count_includes_post_stack_head() {
1034 let model0 = NamModel::from_json_str(TINY_HEAD).unwrap();
1035 let cfg = match &model0.config {
1036 crate::model::ModelConfig::WaveNet(c) => c,
1037 _ => unreachable!(),
1038 };
1039 assert_eq!(expected_weight_count(cfg).unwrap(), 10);
1040 }
1041
1042 #[test]
1043 fn post_stack_head_no_longer_rejected() {
1044 let model0 = NamModel::from_json_str(TINY_HEAD).unwrap();
1045 let cfg = match &model0.config {
1046 crate::model::ModelConfig::WaveNet(c) => c,
1047 _ => unreachable!(),
1048 };
1049 let n = expected_weight_count(cfg).unwrap();
1051 let model = NamModel {
1052 version: "0.6.0".into(),
1053 architecture: "WaveNet".into(),
1054 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1055 weights: vec![0.0; n],
1056 sample_rate: None,
1057 metadata: None,
1058 };
1059 match WaveNet::new(&model) {
1061 Err(Error::UnsupportedFeature(f)) if f.contains("post-stack head") => {
1062 panic!("post-stack head should no longer be guarded");
1063 }
1064 _ => {} }
1066 }
1067
1068 #[test]
1069 fn receptive_field_includes_post_stack_head_kernels() {
1070 let json = r#"{
1074 "version":"0.6.0","architecture":"WaveNet","config":{
1075 "layers":[{"input_size":1,"condition_size":1,"channels":1,"head_size":1,
1076 "kernel_size":3,"dilations":[1,2],"activation":"ReLU",
1077 "gated":false,"head_bias":false}],
1078 "head":{"channels":2,"out_channels":1,"kernel_sizes":[16,1],"activation":"ReLU"},
1079 "head_scale":1.0},
1080 "weights":[]}"#;
1081 let model0 = NamModel::from_json_str(json).unwrap();
1082 let cfg = match &model0.config {
1083 crate::model::ModelConfig::WaveNet(c) => c,
1084 _ => unreachable!(),
1085 };
1086 let n = expected_weight_count(cfg).unwrap();
1087 let model = NamModel {
1088 version: "0.6.0".into(),
1089 architecture: "WaveNet".into(),
1090 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1091 weights: vec![0.0; n],
1092 sample_rate: None,
1093 metadata: None,
1094 };
1095 assert_eq!(WaveNet::new(&model).unwrap().receptive_field(), 22);
1096 }
1097
1098 #[test]
1099 fn post_stack_head_forward_matches_hand_computed() {
1100 let json = r#"{
1104 "version":"0.6.0","architecture":"WaveNet","config":{
1105 "layers":[{"input_size":1,"condition_size":1,"channels":1,"head_size":1,
1106 "kernel_size":1,"dilations":[1],"activation":"ReLU",
1107 "gated":false,"head_bias":false}],
1108 "head":{"channels":1,"out_channels":1,"kernel_sizes":[1],"activation":"ReLU"},
1109 "head_scale":2.0},
1110 "weights":[1.0, 2.0, 0.5, 1.0, 3.0, 0.1, 0.5, 3.0, 0.5, 2.0]}"#;
1111 let model = NamModel::from_json_str(json).unwrap();
1112 let mut wn = WaveNet::new(&model).unwrap();
1113 let mut buf = [0.5_f32];
1114 wn.process_buffer(&mut buf);
1115 assert!((buf[0] - 6.5).abs() < 1e-5, "got {}", buf[0]);
1116
1117 let mut wn2 = WaveNet::new(&model).unwrap();
1119 let got = wn2.process_sample(0.5);
1120 assert!((got - 6.5).abs() < 1e-5, "per-sample got {}", got);
1121 }
1122
1123 #[test]
1124 fn post_stack_head_multichannel_out_rejected() {
1125 let json = r#"{
1126 "version":"0.6.0","architecture":"WaveNet","config":{
1127 "layers":[{"input_size":1,"condition_size":1,"channels":1,"head_size":1,
1128 "kernel_size":1,"dilations":[1],"activation":"ReLU",
1129 "gated":false,"head_bias":false}],
1130 "head":{"channels":2,"out_channels":2,"kernel_sizes":[1],"activation":"ReLU"},
1131 "head_scale":1.0},
1132 "weights":[]}"#;
1133 let model0 = NamModel::from_json_str(json).unwrap();
1134 let cfg = match &model0.config {
1135 crate::model::ModelConfig::WaveNet(c) => c,
1136 _ => unreachable!(),
1137 };
1138 let n = expected_weight_count(cfg).unwrap();
1139 let model = NamModel {
1140 version: "0.6.0".into(),
1141 architecture: "WaveNet".into(),
1142 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1143 weights: vec![0.0; n],
1144 sample_rate: None,
1145 metadata: None,
1146 };
1147 assert!(matches!(
1148 WaveNet::new(&model),
1149 Err(Error::UnsupportedFeature(f)) if f.contains("out_channels != 1")
1150 ));
1151 }
1152
1153 #[test]
1154 fn post_stack_head_builds_and_consumes_exact_weights() {
1155 let model0 = NamModel::from_json_str(TINY_HEAD).unwrap();
1156 let cfg = match &model0.config {
1157 crate::model::ModelConfig::WaveNet(c) => c,
1158 _ => unreachable!(),
1159 };
1160 let n = expected_weight_count(cfg).unwrap(); let weights: Vec<f32> = (0..n).map(|i| (i as f32 + 1.0) * 0.1).collect();
1162 let model = NamModel {
1163 version: "0.6.0".into(),
1164 architecture: "WaveNet".into(),
1165 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1166 weights,
1167 sample_rate: None,
1168 metadata: None,
1169 };
1170 assert!(WaveNet::new(&model).is_ok(), "post-stack head model builds");
1171 }
1172
1173 #[test]
1174 fn default_path_unchanged_baseline() {
1175 let model = NamModel::from_json_str(TINY).unwrap();
1178 let mut wn = WaveNet::new(&model).unwrap();
1179 let mut buf = [0.5_f32];
1180 wn.process_buffer(&mut buf);
1181 assert!((buf[0] - 10.0).abs() < 1e-5, "got {}", buf[0]);
1182 let cfg = match &model.config {
1184 crate::model::ModelConfig::WaveNet(c) => c,
1185 _ => unreachable!(),
1186 };
1187 assert!(cfg.post_stack_head.is_none());
1188 assert!(cfg.condition_dsp.is_none());
1189 }
1190
1191 #[test]
1192 fn tiny_model_matches_hand_computed_forward() {
1193 let model = NamModel::from_json_str(TINY).unwrap();
1194 let mut wn = WaveNet::new(&model).unwrap();
1195
1196 let mut buf = [0.5_f32];
1199 wn.process_buffer(&mut buf);
1200 assert!((buf[0] - 10.0).abs() < 1e-5, "got {}", buf[0]);
1201 }
1202
1203 #[test]
1204 fn array_weight_count_includes_a2_subblocks() {
1205 let la = mk_layer(serde_json::json!({
1219 "input_size": 1, "condition_size": 1, "channels": 4, "bottleneck": 2,
1220 "kernel_sizes": [3], "dilations": [1],
1221 "activation": [{"type":"Tanh"}],
1222 "gating_mode": ["gated"],
1223 "layer1x1": {"active": true, "groups": 1},
1224 "head1x1": {"active": true, "out_channels": 3, "groups": 1},
1225 "head": {"out_channels": 1, "kernel_size": 1, "bias": false},
1226 "conv_post_film": {"active": true, "shift": true, "groups": 1},
1227 "activation_post_film": {"active": true, "shift": false, "groups": 1}
1228 }));
1229 assert_eq!(array_weight_count(&la).unwrap(), 104);
1230 }
1231
1232 #[test]
1233 fn receptive_field_sums_dilated_taps() {
1234 let cfg = WaveNetConfig {
1238 layers: vec![
1239 mk_layer(serde_json::json!({
1240 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1241 "kernel_size": 3, "dilations": [1, 2], "activation": "Tanh",
1242 "gated": false, "head_bias": false
1243 })),
1244 mk_layer(serde_json::json!({
1245 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1246 "kernel_size": 3, "dilations": [8], "activation": "Tanh",
1247 "gated": false, "head_bias": false
1248 })),
1249 ],
1250 post_stack_head: None,
1251 head_scale: 1.0,
1252 in_channels: 1,
1253 condition_dsp: None,
1254 };
1255 assert_eq!(receptive_field(&cfg, 1), 23);
1257
1258 let model = NamModel::from_json_str(TINY).unwrap();
1260 assert_eq!(WaveNet::new(&model).unwrap().receptive_field(), 1);
1261 }
1262
1263 #[test]
1264 fn reset_restores_from_fresh_result() {
1265 let model = NamModel::from_json_str(TINY).unwrap();
1266 let mut wn = WaveNet::new(&model).unwrap();
1267 let mut warm = [0.3_f32, -0.7, 0.2];
1268 wn.process_buffer(&mut warm);
1269 wn.reset();
1270 let mut a = [0.5_f32];
1271 wn.process_buffer(&mut a);
1272 assert!((a[0] - 10.0).abs() < 1e-5, "got {}", a[0]);
1273 }
1274
1275 #[test]
1276 fn wrong_weight_count_is_rejected() {
1277 let bad = TINY.replace(
1278 "[1.0, 2.0, 0.5, 1.0, 3.0, 0.1, 0.5, 10.0]",
1279 "[1.0, 2.0, 0.5, 1.0, 3.0, 0.1, 0.5]",
1280 );
1281 let model = NamModel::from_json_str(&bad).unwrap();
1282 match WaveNet::new(&model) {
1283 Err(Error::WeightCountMismatch { expected, found }) => {
1284 assert_eq!(expected, 8);
1285 assert_eq!(found, 7);
1286 }
1287 other => panic!("expected WeightCountMismatch, got {other:?}"),
1288 }
1289 }
1290
1291 #[test]
1294 fn absurd_dimensions_error_instead_of_overflowing() {
1295 let json = TINY.replace("\"channels\": 1", "\"channels\": 4294967296");
1296 let model = NamModel::from_json_str(&json).unwrap();
1297 assert!(matches!(WaveNet::new(&model), Err(Error::ConfigTooLarge)));
1298 }
1299
1300 #[test]
1305 fn weight_count_matches_consumption_across_shapes() {
1306 let layer_sets: Vec<Vec<crate::model::LayerArrayConfig>> = vec![
1307 vec![mk_layer(serde_json::json!({
1308 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1309 "kernel_size": 1, "dilations": [1], "activation": "Tanh",
1310 "gated": false, "head_bias": false
1311 }))],
1312 vec![mk_layer(serde_json::json!({
1313 "input_size": 1, "condition_size": 1, "channels": 2, "head_size": 1,
1314 "kernel_size": 3, "dilations": [1, 2], "activation": "Tanh",
1315 "gated": false, "head_bias": false
1316 }))],
1317 vec![mk_layer(serde_json::json!({
1318 "input_size": 1, "condition_size": 1, "channels": 4, "head_size": 2,
1319 "kernel_size": 3, "dilations": [1, 2, 4], "activation": "Tanh",
1320 "gated": true, "head_bias": false
1321 }))], vec![mk_layer(serde_json::json!({
1323 "input_size": 1, "condition_size": 1, "channels": 3, "head_size": 1,
1324 "kernel_size": 3, "dilations": [1], "activation": "Tanh",
1325 "gated": false, "head_bias": true
1326 }))], vec![
1331 mk_layer(serde_json::json!({
1332 "input_size": 1, "condition_size": 1, "channels": 4, "head_size": 2,
1333 "kernel_size": 3, "dilations": [1, 2], "activation": "Tanh",
1334 "gated": false, "head_bias": false
1335 })),
1336 mk_layer(serde_json::json!({
1337 "input_size": 4, "condition_size": 1, "channels": 2, "head_size": 1,
1338 "kernel_size": 3, "dilations": [1], "activation": "Tanh",
1339 "gated": true, "head_bias": true
1340 })),
1341 ],
1342 ];
1343 for layers in layer_sets {
1344 let cfg = WaveNetConfig {
1345 layers,
1346 post_stack_head: None,
1347 head_scale: 1.0,
1348 in_channels: 1,
1349 condition_dsp: None,
1350 };
1351 let n = expected_weight_count(&cfg).unwrap();
1352 let mk_model = |count: usize| NamModel {
1353 version: "0".into(),
1354 architecture: "WaveNet".into(),
1355 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1356 weights: vec![0.0; count],
1357 sample_rate: None,
1358 metadata: None,
1359 };
1360 assert!(
1365 WaveNet::new_conditioning(&mk_model(n)).is_ok(),
1366 "exact count n={n}"
1367 );
1368 assert!(matches!(
1369 WaveNet::new_conditioning(&mk_model(n - 1)),
1370 Err(Error::WeightCountMismatch { .. })
1371 ));
1372 assert!(matches!(
1373 WaveNet::new_conditioning(&mk_model(n + 1)),
1374 Err(Error::WeightCountMismatch { .. })
1375 ));
1376 }
1377 }
1378
1379 fn wavenet_model(layers: Vec<crate::model::LayerArrayConfig>) -> NamModel {
1384 let cfg = WaveNetConfig {
1385 layers,
1386 post_stack_head: None,
1387 head_scale: 1.0,
1388 in_channels: 1,
1389 condition_dsp: None,
1390 };
1391 let count = expected_weight_count(&cfg).unwrap_or(1);
1392 NamModel {
1393 version: "0".into(),
1394 architecture: "WaveNet".into(),
1395 config: crate::model::ModelConfig::WaveNet(cfg),
1396 weights: vec![0.0; count],
1397 sample_rate: None,
1398 metadata: None,
1399 }
1400 }
1401
1402 #[test]
1403 fn empty_layers_is_rejected() {
1404 let model = wavenet_model(vec![]);
1407 assert!(matches!(
1408 WaveNet::new(&model),
1409 Err(Error::UnsupportedFeature(f)) if f.contains("no layer-arrays")
1410 ));
1411 }
1412
1413 #[test]
1414 fn top_level_multi_output_is_rejected_but_allowed_when_nested() {
1415 let layers = vec![mk_layer(serde_json::json!({
1420 "input_size": 1, "condition_size": 1, "channels": 2, "head_size": 2,
1421 "kernel_size": 1, "dilations": [1], "activation": "Tanh",
1422 "gated": false, "head_bias": false
1423 }))];
1424 let model = wavenet_model(layers);
1425 assert!(matches!(
1426 WaveNet::new(&model),
1427 Err(Error::UnsupportedFeature(f)) if f.contains("mono-output")
1428 ));
1429 assert!(
1430 WaveNet::new_conditioning(&model).is_ok(),
1431 "multi-output is valid for a nested condition_dsp"
1432 );
1433 }
1434
1435 #[test]
1436 fn non_divisible_groups_is_rejected_cleanly_not_panicking() {
1437 let layers = vec![mk_layer(serde_json::json!({
1440 "input_size": 1, "condition_size": 1, "channels": 3, "head_size": 1,
1441 "kernel_size": 1, "dilations": [1], "activation": "Tanh",
1442 "gated": false, "head_bias": false, "groups_input": 2
1443 }))];
1444 let model = wavenet_model(layers);
1445 assert!(matches!(
1446 WaveNet::new(&model),
1447 Err(Error::UnsupportedFeature(f)) if f.contains("divisible by groups")
1448 ));
1449 }
1450
1451 #[test]
1452 fn wavenet_new_rejects_non_wavenet() {
1453 let lstm = r#"{
1454 "version": "0.5.4", "architecture": "LSTM",
1455 "config": { "input_size": 1, "hidden_size": 4, "num_layers": 1 },
1456 "weights": [0.0]
1457 }"#;
1458 let model = NamModel::from_json_str(lstm).unwrap();
1459 assert!(matches!(
1460 WaveNet::new(&model),
1461 Err(Error::UnsupportedArchitecture(_))
1462 ));
1463 }
1464
1465 #[test]
1472 fn process_buffer_equals_process_sample_loop_on_standard_model() {
1473 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1474 .join("tests/fixtures/reference_standard.nam");
1475 let json = std::fs::read_to_string(path).expect("read standard fixture");
1476 let model = NamModel::from_json_str(&json).expect("parse standard fixture");
1477
1478 let len = 2 * MAX_BLOCK + 137;
1480 let signal: Vec<f32> = (0..len)
1481 .map(|i| (i as f32 * 0.013).sin() * 0.5 + (i as f32 * 0.27).sin() * 0.2)
1482 .collect();
1483
1484 let mut per_sample = WaveNet::new(&model).unwrap();
1485 let want: Vec<f32> = signal
1486 .iter()
1487 .map(|&x| per_sample.process_sample(x))
1488 .collect();
1489
1490 let mut block = WaveNet::new(&model).unwrap();
1491 let mut got = signal.clone();
1492 block.process_buffer(&mut got);
1493
1494 for (i, (g, w)) in got.iter().zip(&want).enumerate() {
1495 assert!(
1496 (g - w).abs() < 1e-5,
1497 "sample {i}: block {g}, per-sample {w}"
1498 );
1499 }
1500 }
1501
1502 #[test]
1503 fn a2_leaky_relu_and_conv_head_parse_and_build() {
1504 let json = r#"{
1507 "version":"0.7.0","architecture":"WaveNet","config":{
1508 "layers":[{"input_size":1,"condition_size":1,"channels":2,"bottleneck":2,
1509 "dilations":[1,2],"kernel_sizes":[3,3],
1510 "activation":[{"type":"LeakyReLU"},{"type":"LeakyReLU"}],
1511 "head":{"out_channels":1,"kernel_size":16,"bias":true},
1512 "layer1x1":{"active":true,"groups":1},
1513 "gating_mode":["none","none"]}],
1514 "head":null,"head_scale":0.5},
1515 "weights":[]}"#;
1516 let model0 = NamModel::from_json_str(json).expect("parses cleanly now");
1517 let cfg = match &model0.config {
1518 crate::model::ModelConfig::WaveNet(c) => c,
1519 _ => unreachable!(),
1520 };
1521 let n = expected_weight_count(cfg).unwrap();
1522 let model = NamModel {
1523 version: "0.7.0".into(),
1524 architecture: "WaveNet".into(),
1525 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1526 weights: vec![0.0; n],
1527 sample_rate: None,
1528 metadata: None,
1529 };
1530 assert!(
1531 WaveNet::new(&model).is_ok(),
1532 "LeakyReLU + multi-tap conv head should now build"
1533 );
1534 }
1535
1536 #[test]
1537 fn a1_still_builds_and_runs_after_typed_config() {
1538 let model = NamModel::from_json_str(TINY).unwrap();
1539 let mut wn = WaveNet::new(&model).unwrap();
1540 let mut buf = [0.5_f32];
1541 wn.process_buffer(&mut buf);
1542 assert!((buf[0] - 10.0).abs() < 1e-5, "got {}", buf[0]);
1543 }
1544
1545 #[test]
1549 fn non_pow2_out_of_order_dilations_size_correctly() {
1550 let json = r#"{
1551 "version":"0.7.0","architecture":"WaveNet","config":{"layers":[{
1552 "input_size":1,"condition_size":1,"channels":2,"head_size":1,
1553 "kernel_size":6,"dilations":[97,1,227,5,29],"activation":"ReLU",
1554 "gated":false,"head_bias":false}],"head":null,"head_scale":0.5},
1555 "weights":[]}"#;
1556 let model0 = NamModel::from_json_str(json).unwrap();
1558 let cfg = match &model0.config {
1559 crate::model::ModelConfig::WaveNet(c) => c,
1560 _ => unreachable!(),
1561 };
1562 let n = expected_weight_count(cfg).unwrap();
1563 let weights: Vec<f32> = (0..n).map(|i| ((i % 7) as f32 - 3.0) * 0.05).collect();
1564 let model = NamModel {
1565 version: "0.7.0".into(),
1566 architecture: "WaveNet".into(),
1567 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1568 weights,
1569 sample_rate: None,
1570 metadata: None,
1571 };
1572
1573 let want_rf = 1 + (6 - 1) * (97 + 1 + 227 + 5 + 29);
1575 let mut per_sample = WaveNet::new(&model).unwrap();
1576 assert_eq!(per_sample.receptive_field(), want_rf);
1577
1578 let len = MAX_BLOCK + 200;
1580 let signal: Vec<f32> = (0..len).map(|i| (i as f32 * 0.017).sin() * 0.4).collect();
1581 let want: Vec<f32> = signal
1582 .iter()
1583 .map(|&x| per_sample.process_sample(x))
1584 .collect();
1585 let mut block = WaveNet::new(&model).unwrap();
1586 let mut got = signal.clone();
1587 block.process_buffer(&mut got);
1588 for (i, (g, w)) in got.iter().zip(&want).enumerate() {
1589 assert!(
1590 (g - w).abs() < 1e-5,
1591 "sample {i}: block {g}, per-sample {w}"
1592 );
1593 }
1594 }
1595
1596 #[test]
1597 fn multitap_head_config_now_builds() {
1598 let json = r#"{
1601 "version":"0.7.0","architecture":"WaveNet","config":{
1602 "layers":[{"input_size":1,"condition_size":1,"channels":2,"bottleneck":2,
1603 "dilations":[1,2],"kernel_sizes":[3,3],
1604 "activation":[{"type":"ReLU"},{"type":"ReLU"}],
1605 "head":{"out_channels":1,"kernel_size":4,"bias":true},
1606 "layer1x1":{"active":true,"groups":1},
1607 "gating_mode":["none","none"]}],
1608 "head":null,"head_scale":0.5},
1609 "weights":[]}"#;
1610 let model0 = NamModel::from_json_str(json).unwrap();
1611 let cfg = match &model0.config {
1612 crate::model::ModelConfig::WaveNet(c) => c,
1613 _ => unreachable!(),
1614 };
1615 let n = expected_weight_count(cfg).unwrap();
1616 assert_eq!(n, 56, "expected 56 weights for this conv-head config");
1617 let weights: Vec<f32> = (0..n).map(|i| ((i % 5) as f32 - 2.0) * 0.1).collect();
1618 let model = NamModel {
1619 version: "0.7.0".into(),
1620 architecture: "WaveNet".into(),
1621 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1622 weights,
1623 sample_rate: None,
1624 metadata: None,
1625 };
1626 let mut wn = WaveNet::new(&model).expect("conv-head model builds");
1627 let signal: Vec<f32> = (0..256).map(|i| (i as f32 * 0.05).sin() * 0.3).collect();
1628 let want: Vec<f32> = {
1629 let mut w = WaveNet::new(&model).unwrap();
1630 signal.iter().map(|&x| w.process_sample(x)).collect()
1631 };
1632 let mut got = signal.clone();
1633 wn.process_buffer(&mut got);
1634 for (i, (g, w)) in got.iter().zip(&want).enumerate() {
1635 assert!(
1636 (g - w).abs() < 1e-5,
1637 "sample {i}: block {g} vs per-sample {w}"
1638 );
1639 }
1640 }
1641
1642 #[test]
1643 fn formerly_guarded_a2_features_now_build() {
1644 let json = r#"{
1647 "version":"0.7.0","architecture":"WaveNet","config":{"layers":[{
1648 "input_size":1,"condition_size":1,"channels":4,"bottleneck":2,
1649 "dilations":[1,2],"kernel_sizes":[3,3],
1650 "activation":[{"type":"Tanh"},{"type":"Tanh"}],
1651 "gating_mode":["blended","blended"],
1652 "secondary_activation":[{"type":"Tanh"},{"type":"Tanh"}],
1653 "groups_input":2,"groups_input_mixin":1,
1654 "layer1x1":{"active":true,"groups":2},
1655 "head1x1":{"active":true,"out_channels":3,"groups":1},
1656 "head":{"out_channels":1,"kernel_size":1,"bias":false},
1657 "conv_post_film":{"active":true,"shift":true,"groups":1},
1658 "activation_post_film":{"active":true,"shift":false,"groups":1},
1659 "layer1x1_post_film":{"active":true,"shift":false,"groups":1}
1660 }],"head":null,"head_scale":0.5},"weights":[]}"#;
1661 let m0 = NamModel::from_json_str(json).unwrap();
1662 let cfg = match &m0.config {
1663 crate::model::ModelConfig::WaveNet(c) => c,
1664 _ => unreachable!(),
1665 };
1666 let n = expected_weight_count(cfg).unwrap();
1667 let weights: Vec<f32> = (0..n).map(|i| ((i % 7) as f32 - 3.0) * 0.02).collect();
1668 let model = NamModel {
1669 version: "0.7.0".into(),
1670 architecture: "WaveNet".into(),
1671 config: crate::model::ModelConfig::WaveNet(cfg.clone()),
1672 weights,
1673 sample_rate: None,
1674 metadata: None,
1675 };
1676 assert!(
1677 WaveNet::new(&model).is_ok(),
1678 "full A2 feature layer must build now"
1679 );
1680
1681 let json2 = r#"{"version":"0.7.0","architecture":"WaveNet","config":{"layers":[{
1683 "input_size":1,"condition_size":1,"channels":2,"bottleneck":2,
1684 "dilations":[1],"kernel_sizes":[3],"activation":[{"type":"ReLU"}],
1685 "gating_mode":["none"],"layer1x1":{"active":false,"groups":1},
1686 "head":{"out_channels":1,"kernel_size":1,"bias":false}}],
1687 "head":null,"head_scale":0.5},"weights":[]}"#;
1688 let m2 = NamModel::from_json_str(json2).unwrap();
1689 let c2 = match &m2.config {
1690 crate::model::ModelConfig::WaveNet(c) => c,
1691 _ => unreachable!(),
1692 };
1693 let n2 = expected_weight_count(c2).unwrap();
1694 let model2 = NamModel {
1695 version: "0.7.0".into(),
1696 architecture: "WaveNet".into(),
1697 config: crate::model::ModelConfig::WaveNet(c2.clone()),
1698 weights: vec![0.0; n2],
1699 sample_rate: None,
1700 metadata: None,
1701 };
1702 assert!(
1703 WaveNet::new(&model2).is_ok(),
1704 "inactive layer1x1 must build now"
1705 );
1706 }
1707}