1use serde::de::{self, Deserializer};
10use serde::Deserialize;
11
12use crate::error::Error;
13
14#[derive(Debug, Clone, PartialEq)]
22pub enum ActivationSpec {
23 Named {
25 name: String,
27 negative_slope: Option<f32>,
30 },
31 Unsupported(serde_json::Value),
33}
34
35impl<'de> Deserialize<'de> for ActivationSpec {
36 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37 where
38 D: Deserializer<'de>,
39 {
40 let v = serde_json::Value::deserialize(deserializer)?;
41 Ok(match &v {
42 serde_json::Value::String(s) => ActivationSpec::Named {
43 name: s.clone(),
44 negative_slope: None,
45 },
46 serde_json::Value::Object(map) => match map.get("type") {
47 Some(serde_json::Value::String(t)) => match map.get("negative_slope") {
48 None | Some(serde_json::Value::Null) => ActivationSpec::Named {
50 name: t.clone(),
51 negative_slope: None,
52 },
53 Some(slope) if slope.as_f64().is_some() => ActivationSpec::Named {
55 name: t.clone(),
56 negative_slope: slope.as_f64().map(|x| x as f32),
57 },
58 Some(_) => ActivationSpec::Unsupported(v.clone()),
61 },
62 _ => ActivationSpec::Unsupported(v),
63 },
64 _ => ActivationSpec::Unsupported(v),
65 })
66 }
67}
68
69pub const DEFAULT_SAMPLE_RATE: f64 = 48_000.0;
73
74#[derive(Debug, Clone)]
79pub struct NamModel {
80 pub version: String,
82 pub architecture: String,
84 pub config: ModelConfig,
86 pub weights: Vec<f32>,
89 pub sample_rate: Option<f64>,
91 pub metadata: Option<serde_json::Value>,
93}
94
95#[derive(Debug, Clone, Deserialize)]
97pub struct LstmConfig {
98 pub input_size: usize,
100 pub hidden_size: usize,
102 pub num_layers: usize,
104}
105
106#[derive(Debug, Clone, Deserialize)]
109pub struct SlimmableSubmodel {
110 pub max_value: f32,
112 pub model: NamModel,
114}
115
116#[derive(Debug, Clone, Deserialize)]
119pub struct SlimmableConfig {
120 pub submodels: Vec<SlimmableSubmodel>,
122}
123
124#[derive(Debug, Clone)]
126pub enum ModelConfig {
127 WaveNet(WaveNetConfig),
130 Lstm(LstmConfig),
132 Slimmable(SlimmableConfig),
134}
135
136impl<'de> Deserialize<'de> for NamModel {
137 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138 where
139 D: Deserializer<'de>,
140 {
141 #[derive(Deserialize)]
145 struct Raw {
146 version: String,
147 architecture: String,
148 config: serde_json::Value,
149 weights: Vec<f32>,
150 #[serde(default)]
151 sample_rate: Option<f64>,
152 #[serde(default)]
153 metadata: Option<serde_json::Value>,
154 }
155
156 let raw = Raw::deserialize(deserializer)?;
157 let config = match raw.architecture.as_str() {
158 "WaveNet" => {
159 let raw_wn: RawWaveNetConfig =
160 serde_json::from_value(raw.config).map_err(de::Error::custom)?;
161 ModelConfig::WaveNet(raw_wn.normalize().map_err(de::Error::custom)?)
162 }
163 "LSTM" => {
164 ModelConfig::Lstm(serde_json::from_value(raw.config).map_err(de::Error::custom)?)
165 }
166 "SlimmableContainer" => ModelConfig::Slimmable(
167 serde_json::from_value(raw.config).map_err(de::Error::custom)?,
168 ),
169 other => {
170 return Err(de::Error::custom(format!(
171 "unsupported model architecture: {other:?}"
172 )))
173 }
174 };
175
176 Ok(NamModel {
177 version: raw.version,
178 architecture: raw.architecture,
179 config,
180 weights: raw.weights,
181 sample_rate: raw.sample_rate,
182 metadata: raw.metadata,
183 })
184 }
185}
186
187fn lenient<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
201where
202 D: Deserializer<'de>,
203 T: for<'a> Deserialize<'a>,
204{
205 let value = serde_json::Value::deserialize(deserializer)?;
208 Ok(serde_json::from_value(value).ok())
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deserialize)]
221#[non_exhaustive]
222pub struct Date {
223 pub year: i32,
225 pub month: u32,
227 pub day: u32,
229 pub hour: u32,
231 pub minute: u32,
233 pub second: u32,
235}
236
237#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
248#[non_exhaustive]
249pub struct Metadata {
250 #[serde(default, deserialize_with = "lenient")]
258 pub loudness: Option<f32>,
259 #[serde(default, deserialize_with = "lenient")]
261 pub input_level_dbu: Option<f32>,
262 #[serde(default, deserialize_with = "lenient")]
264 pub output_level_dbu: Option<f32>,
265 #[serde(default, deserialize_with = "lenient")]
269 pub gain: Option<f32>,
270 #[serde(default, deserialize_with = "lenient")]
273 pub name: Option<String>,
274 #[serde(default, deserialize_with = "lenient")]
276 pub modeled_by: Option<String>,
277 #[serde(default, deserialize_with = "lenient")]
279 pub gear_make: Option<String>,
280 #[serde(default, deserialize_with = "lenient")]
282 pub gear_model: Option<String>,
283 #[serde(default, deserialize_with = "lenient")]
295 pub gear_type: Option<String>,
296 #[serde(default, deserialize_with = "lenient")]
300 pub tone_type: Option<String>,
301 #[serde(default, deserialize_with = "lenient")]
303 pub trainer: Option<String>,
304 #[serde(default, deserialize_with = "lenient")]
306 pub date: Option<Date>,
307 #[serde(default)]
311 pub training: Option<serde_json::Value>,
312}
313
314impl Metadata {
315 #[must_use]
358 pub fn includes_cab(&self) -> Option<bool> {
359 let normalized = self
360 .gear_type
361 .as_deref()?
362 .trim()
363 .to_ascii_lowercase()
364 .replace('-', "_");
365 match normalized.as_str() {
366 "amp" | "preamp" | "pedal" | "pedal_amp" | "outboard" | "space" => Some(false),
370 "full_rig" | "ir" => Some(true),
376 other if other.split('_').any(|token| token == "cab") => Some(true),
382 _ => None,
385 }
386 }
387}
388
389impl NamModel {
390 pub fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, Error> {
396 Self::from_json_str(&std::fs::read_to_string(path)?)
397 }
398
399 pub fn from_json_str(json: &str) -> Result<Self, Error> {
401 Ok(serde_json::from_str(json)?)
402 }
403
404 #[must_use]
415 pub fn expected_sample_rate(&self) -> f64 {
416 self.sample_rate.unwrap_or(DEFAULT_SAMPLE_RATE)
417 }
418
419 #[must_use]
432 pub fn metadata_typed(&self) -> Metadata {
433 match &self.metadata {
434 Some(v) => serde_json::from_value(v.clone()).unwrap_or_default(),
435 None => Metadata::default(),
436 }
437 }
438
439 #[must_use]
442 pub fn loudness(&self) -> Option<f32> {
443 self.metadata_typed().loudness
444 }
445
446 #[must_use]
448 pub fn input_level_dbu(&self) -> Option<f32> {
449 self.metadata_typed().input_level_dbu
450 }
451
452 #[must_use]
458 pub fn includes_cab(&self) -> Option<bool> {
459 self.metadata_typed().includes_cab()
460 }
461
462 #[must_use]
464 pub fn output_level_dbu(&self) -> Option<f32> {
465 self.metadata_typed().output_level_dbu
466 }
467}
468
469#[derive(Debug, Clone, Copy, PartialEq, Eq)]
471pub enum GatingMode {
472 None,
474 Gated,
476 Blended,
478}
479
480impl GatingMode {
481 pub(crate) fn from_name(s: &str) -> Result<Self, String> {
483 match s {
484 "none" => Ok(Self::None),
485 "gated" => Ok(Self::Gated),
486 "blended" => Ok(Self::Blended),
487 other => Err(format!("unknown gating_mode: {other:?}")),
488 }
489 }
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
495pub struct Layer1x1Config {
496 pub active: bool,
498 pub groups: usize,
500}
501
502fn opt_usize(o: &serde_json::Value, key: &str) -> Option<usize> {
506 o.get(key).and_then(|x| x.as_u64()).map(|x| x as usize)
507}
508
509impl Layer1x1Config {
510 pub(crate) fn from_json(v: Option<&serde_json::Value>) -> Self {
511 match v {
512 None => Self {
513 active: true,
514 groups: 1,
515 },
516 Some(o) => Self {
517 active: o.get("active").and_then(|x| x.as_bool()).unwrap_or(true),
518 groups: opt_usize(o, "groups").unwrap_or(1),
519 },
520 }
521 }
522}
523
524#[derive(Debug, Clone, Copy, PartialEq, Eq)]
528pub struct Head1x1Config {
529 pub active: bool,
531 pub out_channels: Option<usize>,
533 pub groups: usize,
535}
536
537impl Head1x1Config {
538 pub(crate) fn from_json(v: Option<&serde_json::Value>) -> Self {
539 match v {
540 None => Self {
541 active: false,
542 out_channels: None,
543 groups: 1,
544 },
545 Some(o) => Self {
546 active: o.get("active").and_then(|x| x.as_bool()).unwrap_or(false),
547 out_channels: opt_usize(o, "out_channels"),
548 groups: opt_usize(o, "groups").unwrap_or(1),
549 },
550 }
551 }
552}
553
554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
557pub struct FilmConfig {
558 pub active: bool,
560 pub shift: bool,
562 pub groups: usize,
564}
565
566impl FilmConfig {
567 pub const INACTIVE: Self = Self {
569 active: false,
570 shift: false,
571 groups: 1,
572 };
573
574 pub(crate) fn from_json(v: Option<&serde_json::Value>) -> Self {
575 match v {
576 None => Self::INACTIVE,
577 Some(serde_json::Value::Bool(false)) => Self::INACTIVE,
578 Some(o) => Self {
579 active: o.get("active").and_then(|x| x.as_bool()).unwrap_or(true),
580 shift: o.get("shift").and_then(|x| x.as_bool()).unwrap_or(true),
581 groups: opt_usize(o, "groups").unwrap_or(1),
582 },
583 }
584 }
585}
586
587#[derive(Debug, Clone)]
590pub struct PostStackHeadConfig {
591 pub channels: usize,
593 pub out_channels: usize,
595 pub kernel_sizes: Vec<usize>,
597 pub activation: ActivationSpec,
599}
600
601#[derive(Debug, Clone)]
604pub struct WaveNetConfig {
605 pub layers: Vec<LayerArrayConfig>,
607 pub post_stack_head: Option<PostStackHeadConfig>,
609 pub head_scale: f32,
611 pub in_channels: usize,
613 pub condition_dsp: Option<Box<NamModel>>,
615}
616
617#[derive(serde::Deserialize)]
618struct RawWaveNetConfig {
619 layers: Vec<RawLayerArrayConfig>,
620 #[serde(default)]
621 head: Option<serde_json::Value>,
622 head_scale: f32,
623 #[serde(default)]
624 in_channels: Option<usize>,
625 #[serde(default)]
626 condition_dsp: Option<serde_json::Value>,
627}
628
629impl RawWaveNetConfig {
630 fn normalize(self) -> Result<WaveNetConfig, String> {
631 let layers = self
632 .layers
633 .into_iter()
634 .map(RawLayerArrayConfig::normalize)
635 .collect::<Result<Vec<_>, _>>()?;
636
637 let post_stack_head = match self.head {
638 Some(h) if !h.is_null() => {
639 let channels =
640 h.get("channels")
641 .and_then(|x| x.as_u64())
642 .ok_or("post-stack head missing channels")? as usize;
643 let out_channels = h
644 .get("out_channels")
645 .and_then(|x| x.as_u64())
646 .ok_or("post-stack head missing out_channels")?
647 as usize;
648 let kernel_sizes: Vec<usize> = h
649 .get("kernel_sizes")
650 .and_then(|x| x.as_array())
651 .ok_or("post-stack head missing kernel_sizes")?
652 .iter()
653 .map(|k| {
654 k.as_u64()
655 .map(|v| v as usize)
656 .ok_or("kernel_sizes entry not an int".to_string())
657 })
658 .collect::<Result<_, _>>()?;
659 let activation = serde_json::from_value::<ActivationSpec>(
660 h.get("activation")
661 .cloned()
662 .unwrap_or(serde_json::Value::Null),
663 )
664 .map_err(|e| e.to_string())?;
665 Some(PostStackHeadConfig {
666 channels,
667 out_channels,
668 kernel_sizes,
669 activation,
670 })
671 }
672 _ => None,
673 };
674
675 let condition_dsp = match self.condition_dsp {
676 Some(v) if !v.is_null() => {
677 let m = serde_json::from_value::<NamModel>(v).map_err(|e| e.to_string())?;
678 Some(Box::new(m))
679 }
680 _ => None,
681 };
682
683 Ok(WaveNetConfig {
684 layers,
685 post_stack_head,
686 head_scale: self.head_scale,
687 in_channels: self.in_channels.unwrap_or(1),
688 condition_dsp,
689 })
690 }
691}
692
693#[derive(Debug, Clone)]
698pub struct LayerArrayConfig {
699 pub input_size: usize,
701 pub condition_size: usize,
703 pub channels: usize,
705 pub bottleneck: usize,
707 pub dilations: Vec<usize>,
709 pub kernel_sizes: Vec<usize>,
711 pub activations: Vec<ActivationSpec>,
713 pub gating_modes: Vec<GatingMode>,
715 pub secondary_activations: Vec<ActivationSpec>,
718 pub groups_input: usize,
720 pub groups_input_mixin: usize,
722 pub head_size: usize,
724 pub head_kernel_size: usize,
726 pub head_bias: bool,
728 pub layer1x1: Layer1x1Config,
730 pub head1x1: Head1x1Config,
732 pub conv_pre_film: FilmConfig,
734 pub conv_post_film: FilmConfig,
736 pub input_mixin_pre_film: FilmConfig,
738 pub input_mixin_post_film: FilmConfig,
740 pub activation_pre_film: FilmConfig,
742 pub activation_post_film: FilmConfig,
744 pub layer1x1_post_film: FilmConfig,
746 pub head1x1_post_film: FilmConfig,
748}
749
750impl LayerArrayConfig {
751 pub fn gating_mode(&self) -> GatingMode {
760 self.gating_modes
761 .first()
762 .copied()
763 .unwrap_or(GatingMode::None)
764 }
765}
766
767#[derive(Debug, Clone, serde::Deserialize)]
770pub(crate) struct RawLayerArrayConfig {
771 input_size: usize,
772 condition_size: usize,
773 channels: usize,
774 #[serde(default)]
775 bottleneck: Option<usize>,
776 dilations: Vec<usize>,
777 #[serde(default)]
778 kernel_size: Option<usize>,
779 #[serde(default)]
780 kernel_sizes: Option<Vec<usize>>,
781 activation: serde_json::Value,
782 #[serde(default)]
783 gating_mode: Option<serde_json::Value>,
784 #[serde(default)]
785 gated: Option<bool>,
786 #[serde(default)]
787 secondary_activation: Option<serde_json::Value>,
788 #[serde(default)]
789 groups_input: Option<usize>,
790 #[serde(default)]
791 groups_input_mixin: Option<usize>,
792 #[serde(default)]
793 head: Option<serde_json::Value>,
794 #[serde(default)]
795 head_size: Option<usize>,
796 #[serde(default)]
797 head_bias: Option<bool>,
798 #[serde(default)]
799 layer1x1: Option<serde_json::Value>,
800 #[serde(default)]
801 head1x1: Option<serde_json::Value>,
802 #[serde(default)]
803 conv_pre_film: Option<serde_json::Value>,
804 #[serde(default)]
805 conv_post_film: Option<serde_json::Value>,
806 #[serde(default)]
807 input_mixin_pre_film: Option<serde_json::Value>,
808 #[serde(default)]
809 input_mixin_post_film: Option<serde_json::Value>,
810 #[serde(default)]
811 activation_pre_film: Option<serde_json::Value>,
812 #[serde(default)]
813 activation_post_film: Option<serde_json::Value>,
814 #[serde(default)]
815 layer1x1_post_film: Option<serde_json::Value>,
816 #[serde(default)]
817 head1x1_post_film: Option<serde_json::Value>,
818}
819
820impl RawLayerArrayConfig {
821 pub(crate) fn normalize(self) -> Result<LayerArrayConfig, String> {
822 let n = self.dilations.len();
823 if n == 0 {
824 return Err("layer-array has no dilations".into());
825 }
826
827 let kernel_sizes = match (self.kernel_size, self.kernel_sizes) {
828 (Some(_), Some(_)) => {
829 return Err("layer-array specifies both kernel_size and kernel_sizes".into())
830 }
831 (Some(k), None) => vec![k; n],
832 (None, Some(ks)) => {
833 if ks.len() != n {
834 return Err(format!(
835 "kernel_sizes length {} != number of layers {n}",
836 ks.len()
837 ));
838 }
839 ks
840 }
841 (None, None) => {
842 return Err("layer-array specifies neither kernel_size nor kernel_sizes".into())
843 }
844 };
845
846 let activations = broadcast_activations(&self.activation, n)?;
847
848 let gating_modes = match (&self.gating_mode, self.gated) {
852 (Some(v), _) => broadcast_gating(v, n)?,
853 (None, Some(true)) => vec![GatingMode::Gated; n],
854 (None, _) => vec![GatingMode::None; n],
855 };
856
857 let secondary_activations = match &self.secondary_activation {
858 Some(v) => broadcast_secondary(v, n)?,
859 None => vec![default_sigmoid(); n],
860 };
861
862 let (head_size, head_kernel_size, head_bias) = match &self.head {
863 Some(h) if !h.is_null() => {
864 let out = h
865 .get("out_channels")
866 .and_then(|x| x.as_u64())
867 .ok_or("layer head missing out_channels")? as usize;
868 let k = h
869 .get("kernel_size")
870 .and_then(|x| x.as_u64())
871 .ok_or("layer head missing kernel_size")? as usize;
872 let bias = h.get("bias").and_then(|x| x.as_bool()).unwrap_or(true);
877 (out, k, bias)
878 }
879 _ => {
880 let hs = self
881 .head_size
882 .ok_or("layer-array missing head_size (and no head object)")?;
883 (hs, 1, self.head_bias.unwrap_or(false))
884 }
885 };
886
887 if head_kernel_size == 0 {
893 return Err("layer-array head_kernel_size must be >= 1".into());
894 }
895 if self.channels == 0 {
896 return Err("layer-array channels must be >= 1".into());
897 }
898 if head_size == 0 {
899 return Err("layer-array head_size must be >= 1".into());
900 }
901 if kernel_sizes.contains(&0) {
902 return Err("layer-array kernel_sizes entries must be >= 1".into());
903 }
904 if self.dilations.contains(&0) {
905 return Err("layer-array dilations entries must be >= 1".into());
906 }
907 let bottleneck = self.bottleneck.unwrap_or(self.channels);
908 if bottleneck == 0 {
909 return Err("layer-array bottleneck must be >= 1".into());
910 }
911
912 let groups_input = self.groups_input.unwrap_or(1);
913 let groups_input_mixin = self.groups_input_mixin.unwrap_or(1);
914 let layer1x1 = Layer1x1Config::from_json(self.layer1x1.as_ref());
915 let head1x1 = Head1x1Config::from_json(self.head1x1.as_ref());
916 let films = [
917 FilmConfig::from_json(self.conv_pre_film.as_ref()),
918 FilmConfig::from_json(self.conv_post_film.as_ref()),
919 FilmConfig::from_json(self.input_mixin_pre_film.as_ref()),
920 FilmConfig::from_json(self.input_mixin_post_film.as_ref()),
921 FilmConfig::from_json(self.activation_pre_film.as_ref()),
922 FilmConfig::from_json(self.activation_post_film.as_ref()),
923 FilmConfig::from_json(self.layer1x1_post_film.as_ref()),
924 FilmConfig::from_json(self.head1x1_post_film.as_ref()),
925 ];
926 let group_counts = [
932 ("groups_input", groups_input),
933 ("groups_input_mixin", groups_input_mixin),
934 ("layer1x1.groups", layer1x1.groups),
935 ("head1x1.groups", head1x1.groups),
936 (
937 "film.groups",
938 films.iter().map(|f| f.groups).min().unwrap_or(1),
939 ),
940 ];
941 for (name, g) in group_counts {
942 if g == 0 {
943 return Err(format!("layer-array {name} must be >= 1"));
944 }
945 }
946 let [conv_pre_film, conv_post_film, input_mixin_pre_film, input_mixin_post_film, activation_pre_film, activation_post_film, layer1x1_post_film, head1x1_post_film] =
947 films;
948
949 Ok(LayerArrayConfig {
950 input_size: self.input_size,
951 condition_size: self.condition_size,
952 channels: self.channels,
953 bottleneck,
954 dilations: self.dilations,
955 kernel_sizes,
956 activations,
957 gating_modes,
958 secondary_activations,
959 groups_input,
960 groups_input_mixin,
961 head_size,
962 head_kernel_size,
963 head_bias,
964 layer1x1,
965 head1x1,
966 conv_pre_film,
967 conv_post_film,
968 input_mixin_pre_film,
969 input_mixin_post_film,
970 activation_pre_film,
971 activation_post_film,
972 layer1x1_post_film,
973 head1x1_post_film,
974 })
975 }
976}
977
978fn default_sigmoid() -> ActivationSpec {
980 ActivationSpec::Named {
981 name: "Sigmoid".into(),
982 negative_slope: None,
983 }
984}
985
986fn broadcast<T: Clone>(
991 v: &serde_json::Value,
992 n: usize,
993 kind: &str,
994 parse: impl Fn(&serde_json::Value) -> Result<T, String>,
995) -> Result<Vec<T>, String> {
996 match v {
997 serde_json::Value::Array(items) => {
998 if items.len() != n {
999 return Err(format!(
1000 "{kind} list length {} != number of layers {n}",
1001 items.len()
1002 ));
1003 }
1004 items.iter().map(&parse).collect()
1005 }
1006 other => Ok(vec![parse(other)?; n]),
1007 }
1008}
1009
1010fn parse_activation(e: &serde_json::Value) -> Result<ActivationSpec, String> {
1011 serde_json::from_value::<ActivationSpec>(e.clone()).map_err(|e| e.to_string())
1012}
1013
1014fn broadcast_activations(v: &serde_json::Value, n: usize) -> Result<Vec<ActivationSpec>, String> {
1015 broadcast(v, n, "activation", parse_activation)
1016}
1017
1018fn broadcast_secondary(v: &serde_json::Value, n: usize) -> Result<Vec<ActivationSpec>, String> {
1021 broadcast(v, n, "secondary_activation", |e| {
1022 if e.is_null() {
1023 Ok(default_sigmoid())
1024 } else {
1025 parse_activation(e)
1026 }
1027 })
1028}
1029
1030fn broadcast_gating(v: &serde_json::Value, n: usize) -> Result<Vec<GatingMode>, String> {
1032 broadcast(v, n, "gating_mode", |e| {
1033 e.as_str()
1034 .ok_or_else(|| "gating_mode entry is not a string".to_string())
1035 .and_then(GatingMode::from_name)
1036 })
1037}
1038
1039#[cfg(test)]
1040mod layer_array_normalize_tests {
1041 use super::*;
1042
1043 fn norm(v: serde_json::Value) -> LayerArrayConfig {
1044 let raw: RawLayerArrayConfig = serde_json::from_value(v).unwrap();
1045 raw.normalize().unwrap()
1046 }
1047
1048 #[test]
1049 fn a1_layer_broadcasts_scalar_kernel_and_string_activation() {
1050 let la = norm(serde_json::json!({
1051 "input_size": 1, "condition_size": 1, "channels": 2, "head_size": 1,
1052 "kernel_size": 3, "dilations": [1, 2, 4], "activation": "Tanh",
1053 "gated": false, "head_bias": false
1054 }));
1055 assert_eq!(la.channels, 2);
1056 assert_eq!(la.bottleneck, 2);
1057 assert_eq!(la.kernel_sizes, vec![3, 3, 3]);
1058 assert_eq!(la.gating_modes, vec![GatingMode::None; 3]);
1059 assert_eq!(la.head_size, 1);
1060 assert_eq!(la.head_kernel_size, 1);
1061 assert!(!la.head_bias);
1062 assert!(la.layer1x1.active);
1063 assert!(!la.head1x1.active);
1064 assert_eq!(la.groups_input, 1);
1065 assert_eq!(la.activations.len(), 3);
1066 assert!(matches!(&la.activations[0], ActivationSpec::Named { name, .. } if name == "Tanh"));
1067 let g = norm(serde_json::json!({
1068 "input_size": 1, "condition_size": 1, "channels": 2, "head_size": 1,
1069 "kernel_size": 3, "dilations": [1], "activation": "Tanh",
1070 "gated": true, "head_bias": true
1071 }));
1072 assert_eq!(g.gating_modes, vec![GatingMode::Gated]);
1073 }
1074
1075 #[test]
1076 fn a2_flexible_layer_parses_per_layer_vectors_and_nested_head() {
1077 let la = norm(serde_json::json!({
1078 "input_size": 1, "condition_size": 1, "channels": 3, "bottleneck": 3,
1079 "dilations": [1, 3, 7],
1080 "kernel_sizes": [6, 6, 15],
1081 "activation": [
1082 {"type": "LeakyReLU", "negative_slope": 0.01},
1083 {"type": "LeakyReLU", "negative_slope": 0.01},
1084 {"type": "LeakyReLU", "negative_slope": 0.01}
1085 ],
1086 "head": {"out_channels": 1, "kernel_size": 16, "bias": true},
1087 "head1x1": {"active": false, "out_channels": 1, "groups": 1},
1088 "layer1x1": {"active": true, "groups": 1},
1089 "groups_input": 1, "groups_input_mixin": 1,
1090 "gating_mode": ["none", "none", "none"],
1091 "secondary_activation": [null, null, null],
1092 "conv_pre_film": {"active": false, "shift": true, "groups": 1}
1093 }));
1094 assert_eq!(la.kernel_sizes, vec![6, 6, 15]);
1095 assert_eq!(la.gating_modes, vec![GatingMode::None; 3]);
1096 assert_eq!(la.head_size, 1);
1097 assert_eq!(la.head_kernel_size, 16);
1098 assert!(la.head_bias);
1099 assert_eq!(la.bottleneck, 3);
1100 assert_eq!(la.activations.len(), 3);
1101 assert!(!la.conv_pre_film.active);
1102 }
1103
1104 #[test]
1105 fn both_kernel_forms_is_an_error() {
1106 let raw: RawLayerArrayConfig = serde_json::from_value(serde_json::json!({
1107 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1108 "kernel_size": 3, "kernel_sizes": [3], "dilations": [1],
1109 "activation": "Tanh", "gated": false, "head_bias": false
1110 }))
1111 .unwrap();
1112 assert!(raw.normalize().is_err());
1113 }
1114
1115 #[test]
1116 fn kernel_sizes_length_mismatch_is_an_error() {
1117 let raw: RawLayerArrayConfig = serde_json::from_value(serde_json::json!({
1118 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1119 "kernel_sizes": [3, 3], "dilations": [1],
1120 "activation": "Tanh", "gated": false, "head_bias": false
1121 }))
1122 .unwrap();
1123 assert!(raw.normalize().is_err());
1124 }
1125
1126 #[test]
1127 fn activation_list_length_mismatch_is_an_error() {
1128 let raw: RawLayerArrayConfig = serde_json::from_value(serde_json::json!({
1129 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1130 "kernel_size": 3, "dilations": [1, 2],
1131 "activation": ["Tanh"], "gated": false, "head_bias": false
1132 }))
1133 .unwrap();
1134 assert!(raw.normalize().is_err());
1135 }
1136
1137 fn raw_layer_array(mutate: impl FnOnce(&mut serde_json::Value)) -> RawLayerArrayConfig {
1140 let mut v = serde_json::json!({
1141 "input_size": 1, "condition_size": 1, "channels": 1, "head_size": 1,
1142 "kernel_size": 3, "dilations": [1],
1143 "activation": "Tanh", "gated": false, "head_bias": false
1144 });
1145 mutate(&mut v);
1146 serde_json::from_value(v).unwrap()
1147 }
1148
1149 #[test]
1150 fn baseline_raw_layer_array_normalizes() {
1151 assert!(raw_layer_array(|_| {}).normalize().is_ok());
1154 }
1155
1156 #[test]
1157 fn zero_channels_is_an_error() {
1158 let raw = raw_layer_array(|v| v["channels"] = serde_json::json!(0));
1159 assert!(raw.normalize().is_err());
1160 }
1161
1162 #[test]
1163 fn zero_head_size_is_an_error() {
1164 let raw = raw_layer_array(|v| v["head_size"] = serde_json::json!(0));
1165 assert!(raw.normalize().is_err());
1166 }
1167
1168 #[test]
1169 fn zero_kernel_size_is_an_error() {
1170 let raw = raw_layer_array(|v| v["kernel_size"] = serde_json::json!(0));
1171 assert!(raw.normalize().is_err());
1172 }
1173
1174 #[test]
1175 fn zero_dilation_is_an_error() {
1176 let raw = raw_layer_array(|v| v["dilations"] = serde_json::json!([0]));
1177 assert!(raw.normalize().is_err());
1178 }
1179
1180 #[test]
1181 fn zero_bottleneck_is_an_error() {
1182 let raw = raw_layer_array(|v| v["bottleneck"] = serde_json::json!(0));
1186 assert!(raw.normalize().is_err());
1187 }
1188
1189 #[test]
1190 fn zero_groups_is_an_error() {
1191 for field in ["groups_input", "groups_input_mixin"] {
1193 let raw = raw_layer_array(|v| v[field] = serde_json::json!(0));
1194 assert!(raw.normalize().is_err(), "{field} == 0 must error");
1195 }
1196 let raw = raw_layer_array(|v| {
1197 v["layer1x1"] = serde_json::json!({ "active": true, "groups": 0 });
1198 });
1199 assert!(raw.normalize().is_err(), "layer1x1.groups == 0 must error");
1200 }
1201
1202 #[test]
1203 fn zero_head_kernel_size_is_an_error() {
1204 let raw = raw_layer_array(|v| {
1205 v.as_object_mut().unwrap().remove("head_size");
1206 v["head"] = serde_json::json!({
1207 "out_channels": 1, "kernel_size": 0, "activation": "ReLU"
1208 });
1209 });
1210 assert!(raw.normalize().is_err());
1211 }
1212}
1213
1214#[cfg(test)]
1215mod a2_subconfig_tests {
1216 use super::*;
1217
1218 #[test]
1219 fn gating_mode_from_str() {
1220 assert_eq!(GatingMode::from_name("none").unwrap(), GatingMode::None);
1221 assert_eq!(GatingMode::from_name("gated").unwrap(), GatingMode::Gated);
1222 assert_eq!(
1223 GatingMode::from_name("blended").unwrap(),
1224 GatingMode::Blended
1225 );
1226 assert!(GatingMode::from_name("wat").is_err());
1227 }
1228
1229 #[test]
1230 fn film_absent_or_false_is_inactive() {
1231 assert_eq!(FilmConfig::from_json(None), FilmConfig::INACTIVE);
1232 assert_eq!(
1233 FilmConfig::from_json(Some(&serde_json::json!(false))),
1234 FilmConfig::INACTIVE
1235 );
1236 }
1237
1238 #[test]
1239 fn film_object_defaults_active_shift_groups() {
1240 let v = serde_json::json!({});
1241 let f = FilmConfig::from_json(Some(&v));
1242 assert_eq!(
1243 f,
1244 FilmConfig {
1245 active: true,
1246 shift: true,
1247 groups: 1
1248 }
1249 );
1250 let v = serde_json::json!({"active": false, "shift": false, "groups": 2});
1251 assert_eq!(
1252 FilmConfig::from_json(Some(&v)),
1253 FilmConfig {
1254 active: false,
1255 shift: false,
1256 groups: 2
1257 }
1258 );
1259 }
1260
1261 #[test]
1262 fn layer1x1_defaults_active_true_groups_1() {
1263 assert_eq!(
1264 Layer1x1Config::from_json(None),
1265 Layer1x1Config {
1266 active: true,
1267 groups: 1
1268 }
1269 );
1270 let v = serde_json::json!({"active": true, "groups": 1});
1271 assert_eq!(
1272 Layer1x1Config::from_json(Some(&v)),
1273 Layer1x1Config {
1274 active: true,
1275 groups: 1
1276 }
1277 );
1278 }
1279
1280 #[test]
1281 fn head1x1_defaults_inactive() {
1282 let h = Head1x1Config::from_json(None);
1283 assert_eq!(
1284 h,
1285 Head1x1Config {
1286 active: false,
1287 out_channels: None,
1288 groups: 1
1289 }
1290 );
1291 let v = serde_json::json!({"active": false, "out_channels": 1, "groups": 1});
1292 assert_eq!(
1293 Head1x1Config::from_json(Some(&v)),
1294 Head1x1Config {
1295 active: false,
1296 out_channels: Some(1),
1297 groups: 1
1298 }
1299 );
1300 }
1301}
1302
1303#[cfg(test)]
1304mod wavenet_config_tests {
1305 use super::*;
1306
1307 fn parse(json: &str) -> WaveNetConfig {
1308 match NamModel::from_json_str(json).unwrap().config {
1309 ModelConfig::WaveNet(c) => c,
1310 other => panic!("expected WaveNet, got {other:?}"),
1311 }
1312 }
1313
1314 #[test]
1315 fn a1_config_parses_unchanged() {
1316 let c = parse(
1317 r#"{
1318 "version":"0.5.4","architecture":"WaveNet","config":{
1319 "layers":[{"input_size":1,"condition_size":1,"channels":2,"head_size":1,
1320 "kernel_size":3,"dilations":[1,2],"activation":"Tanh",
1321 "gated":false,"head_bias":false}],
1322 "head":null,"head_scale":2.0},
1323 "weights":[]}"#,
1324 );
1325 assert_eq!(c.layers.len(), 1);
1326 assert_eq!(c.head_scale, 2.0);
1327 assert!(c.post_stack_head.is_none());
1328 assert!(c.condition_dsp.is_none());
1329 assert_eq!(c.layers[0].kernel_sizes, vec![3, 3]);
1330 }
1331
1332 #[test]
1333 fn a2_flexible_container_submodel_config_parses() {
1334 let c = parse(
1335 r#"{
1336 "version":"0.7.0","architecture":"WaveNet","config":{
1337 "layers":[{"input_size":1,"condition_size":1,"channels":3,"bottleneck":3,
1338 "dilations":[1,3,7],"kernel_sizes":[6,6,15],
1339 "activation":[{"type":"LeakyReLU"},{"type":"LeakyReLU"},{"type":"LeakyReLU"}],
1340 "head":{"out_channels":1,"kernel_size":16,"bias":true},
1341 "head1x1":{"active":false},"layer1x1":{"active":true,"groups":1},
1342 "gating_mode":["none","none","none"]}],
1343 "head":null,"head_scale":0.5},
1344 "weights":[]}"#,
1345 );
1346 assert_eq!(c.layers[0].head_kernel_size, 16);
1347 assert_eq!(c.layers[0].kernel_sizes, vec![6, 6, 15]);
1348 assert!(c.post_stack_head.is_none());
1349 }
1350
1351 #[test]
1352 fn post_stack_head_parses() {
1353 let c = parse(
1354 r#"{
1355 "version":"0.6.0","architecture":"WaveNet","config":{
1356 "layers":[{"input_size":1,"condition_size":1,"channels":2,"head_size":2,
1357 "kernel_size":3,"dilations":[1],"activation":"Tanh",
1358 "gated":false,"head_bias":false}],
1359 "head":{"channels":4,"out_channels":1,"kernel_sizes":[1,1],"activation":"ReLU"},
1360 "head_scale":1.0},
1361 "weights":[]}"#,
1362 );
1363 let h = c.post_stack_head.expect("post-stack head present");
1364 assert_eq!(h.channels, 4);
1365 assert_eq!(h.out_channels, 1);
1366 assert_eq!(h.kernel_sizes, vec![1, 1]);
1367 }
1368
1369 #[test]
1370 fn condition_dsp_parses_as_nested_model() {
1371 let c = parse(
1372 r#"{
1373 "version":"0.6.0","architecture":"WaveNet","config":{
1374 "layers":[{"input_size":1,"condition_size":1,"channels":2,"head_size":1,
1375 "kernel_size":3,"dilations":[1],"activation":"Tanh",
1376 "gated":false,"head_bias":false}],
1377 "head":null,"head_scale":1.0,
1378 "condition_dsp":{"version":"0.5.4","architecture":"WaveNet","config":{
1379 "layers":[{"input_size":1,"condition_size":1,"channels":1,"head_size":1,
1380 "kernel_size":1,"dilations":[1],"activation":"Tanh",
1381 "gated":false,"head_bias":false}],
1382 "head":null,"head_scale":1.0},"weights":[]}},
1383 "weights":[]}"#,
1384 );
1385 let dsp = c.condition_dsp.expect("condition_dsp present");
1386 assert_eq!(dsp.architecture, "WaveNet");
1387 }
1388}