oxihuman_morph/
ear_fold_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum EarFoldSide {
11 Left,
12 Right,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct EarFoldConfig {
19 pub max_fold: f32,
20}
21
22impl Default for EarFoldConfig {
23 fn default() -> Self {
24 EarFoldConfig { max_fold: 1.0 }
25 }
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone)]
31pub struct EarFoldState {
32 left_fold: f32,
33 right_fold: f32,
34 definition: f32,
36 config: EarFoldConfig,
37}
38
39pub fn default_ear_fold_config() -> EarFoldConfig {
41 EarFoldConfig::default()
42}
43
44pub fn new_ear_fold_state(config: EarFoldConfig) -> EarFoldState {
46 EarFoldState {
47 left_fold: 0.0,
48 right_fold: 0.0,
49 definition: 0.5,
50 config,
51 }
52}
53
54pub fn ef2_set_fold(state: &mut EarFoldState, side: EarFoldSide, v: f32) {
56 let v = v.clamp(0.0, 1.0);
57 match side {
58 EarFoldSide::Left => state.left_fold = v,
59 EarFoldSide::Right => state.right_fold = v,
60 }
61}
62
63pub fn ef2_set_both(state: &mut EarFoldState, v: f32) {
65 let v = v.clamp(0.0, 1.0);
66 state.left_fold = v;
67 state.right_fold = v;
68}
69
70pub fn ef2_set_definition(state: &mut EarFoldState, v: f32) {
72 state.definition = v.clamp(0.0, 1.0);
73}
74
75pub fn ef2_reset(state: &mut EarFoldState) {
77 state.left_fold = 0.0;
78 state.right_fold = 0.0;
79 state.definition = 0.5;
80}
81
82pub fn ef2_is_neutral(state: &EarFoldState) -> bool {
84 state.left_fold < 1e-5 && state.right_fold < 1e-5
85}
86
87pub fn ef2_symmetry(state: &EarFoldState) -> f32 {
89 1.0 - (state.left_fold - state.right_fold).abs()
90}
91
92pub fn ef2_average_fold(state: &EarFoldState) -> f32 {
94 (state.left_fold + state.right_fold) * 0.5
95}
96
97pub fn ef2_to_weights(state: &EarFoldState) -> [f32; 3] {
99 let s = state.config.max_fold;
100 [
101 (state.left_fold * s).clamp(0.0, 1.0),
102 (state.right_fold * s).clamp(0.0, 1.0),
103 state.definition,
104 ]
105}
106
107pub fn ef2_blend(a: &EarFoldState, b: &EarFoldState, t: f32) -> EarFoldState {
109 let t = t.clamp(0.0, 1.0);
110 EarFoldState {
111 left_fold: a.left_fold + (b.left_fold - a.left_fold) * t,
112 right_fold: a.right_fold + (b.right_fold - a.right_fold) * t,
113 definition: a.definition + (b.definition - a.definition) * t,
114 config: a.config.clone(),
115 }
116}
117
118pub fn ef2_to_json(state: &EarFoldState) -> String {
120 format!(
121 r#"{{"left_fold":{:.4},"right_fold":{:.4},"definition":{:.4}}}"#,
122 state.left_fold, state.right_fold, state.definition
123 )
124}
125
126#[cfg(test)]
130mod tests {
131 use super::*;
132
133 fn make() -> EarFoldState {
134 new_ear_fold_state(default_ear_fold_config())
135 }
136
137 #[test]
138 fn neutral_on_creation() {
139 assert!(ef2_is_neutral(&make()));
140 }
141
142 #[test]
143 fn set_one_side() {
144 let mut s = make();
145 ef2_set_fold(&mut s, EarFoldSide::Left, 0.6);
146 assert!((s.left_fold - 0.6).abs() < 1e-5);
147 }
148
149 #[test]
150 fn set_both_equal() {
151 let mut s = make();
152 ef2_set_both(&mut s, 0.4);
153 assert!((s.left_fold - s.right_fold).abs() < 1e-5);
154 }
155
156 #[test]
157 fn symmetry_when_equal() {
158 let mut s = make();
159 ef2_set_both(&mut s, 0.5);
160 assert!((ef2_symmetry(&s) - 1.0).abs() < 1e-5);
161 }
162
163 #[test]
164 fn reset_zeros_folds() {
165 let mut s = make();
166 ef2_set_both(&mut s, 0.9);
167 ef2_reset(&mut s);
168 assert!(ef2_is_neutral(&s));
169 }
170
171 #[test]
172 fn blend_midpoint() {
173 let mut b = make();
174 ef2_set_both(&mut b, 1.0);
175 let m = ef2_blend(&make(), &b, 0.5);
176 assert!((m.left_fold - 0.5).abs() < 1e-5);
177 }
178
179 #[test]
180 fn weights_in_range() {
181 let mut s = make();
182 ef2_set_both(&mut s, 0.7);
183 for v in ef2_to_weights(&s) {
184 assert!((0.0..=1.0).contains(&v));
185 }
186 }
187
188 #[test]
189 fn json_has_left_fold() {
190 assert!(ef2_to_json(&make()).contains("left_fold"));
191 }
192
193 #[test]
194 fn clamp_high() {
195 let mut s = make();
196 ef2_set_fold(&mut s, EarFoldSide::Right, 10.0);
197 assert!((s.right_fold - 1.0).abs() < 1e-5);
198 }
199}