Skip to main content

oxihuman_morph/
voice_driven_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Audio-driven morph target stub.
6
7/// A mapping from an audio frequency band to a morph weight.
8#[derive(Debug, Clone)]
9pub struct AudioBandMapping {
10    pub band_hz_low: f32,
11    pub band_hz_high: f32,
12    pub morph_index: usize,
13    pub gain: f32,
14}
15
16/// Voice-driven morph system.
17#[derive(Debug, Clone)]
18pub struct VoiceDrivenMorph {
19    pub mappings: Vec<AudioBandMapping>,
20    pub sample_rate: u32,
21    pub morph_count: usize,
22    pub enabled: bool,
23    pub smoothing: f32,
24}
25
26impl VoiceDrivenMorph {
27    pub fn new(morph_count: usize, sample_rate: u32) -> Self {
28        VoiceDrivenMorph {
29            mappings: Vec::new(),
30            sample_rate,
31            morph_count,
32            enabled: true,
33            smoothing: 0.1,
34        }
35    }
36}
37
38/// Create a new voice-driven morph system.
39pub fn new_voice_driven_morph(morph_count: usize, sample_rate: u32) -> VoiceDrivenMorph {
40    VoiceDrivenMorph::new(morph_count, sample_rate)
41}
42
43/// Add a frequency-band-to-morph mapping.
44pub fn vdm_add_mapping(vdm: &mut VoiceDrivenMorph, mapping: AudioBandMapping) {
45    vdm.mappings.push(mapping);
46}
47
48/// Process an audio frame and return morph weights (stub: zeroed).
49pub fn vdm_process(vdm: &VoiceDrivenMorph, _audio_frame: &[f32]) -> Vec<f32> {
50    /* Stub: returns zeroed morph weights */
51    vec![0.0; vdm.morph_count]
52}
53
54/// Set smoothing factor (0 = no smoothing, 1 = full hold).
55pub fn vdm_set_smoothing(vdm: &mut VoiceDrivenMorph, smoothing: f32) {
56    vdm.smoothing = smoothing.clamp(0.0, 1.0);
57}
58
59/// Enable or disable.
60pub fn vdm_set_enabled(vdm: &mut VoiceDrivenMorph, enabled: bool) {
61    vdm.enabled = enabled;
62}
63
64/// Return mapping count.
65pub fn vdm_mapping_count(vdm: &VoiceDrivenMorph) -> usize {
66    vdm.mappings.len()
67}
68
69/// Serialize to JSON-like string.
70pub fn vdm_to_json(vdm: &VoiceDrivenMorph) -> String {
71    format!(
72        r#"{{"morph_count":{},"sample_rate":{},"mappings":{},"smoothing":{},"enabled":{}}}"#,
73        vdm.morph_count,
74        vdm.sample_rate,
75        vdm.mappings.len(),
76        vdm.smoothing,
77        vdm.enabled
78    )
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_new_morph_count() {
87        let v = new_voice_driven_morph(8, 44100);
88        assert_eq!(v.morph_count, 8 /* morph count must match */,);
89    }
90
91    #[test]
92    fn test_sample_rate_stored() {
93        let v = new_voice_driven_morph(4, 48000);
94        assert_eq!(v.sample_rate, 48000 /* sample rate must match */,);
95    }
96
97    #[test]
98    fn test_process_output_length() {
99        let v = new_voice_driven_morph(6, 44100);
100        let out = vdm_process(&v, &[0.0; 512]);
101        assert_eq!(out.len(), 6 /* output length must match morph_count */,);
102    }
103
104    #[test]
105    fn test_add_mapping() {
106        let mut v = new_voice_driven_morph(4, 44100);
107        vdm_add_mapping(
108            &mut v,
109            AudioBandMapping {
110                band_hz_low: 80.0,
111                band_hz_high: 200.0,
112                morph_index: 0,
113                gain: 1.0,
114            },
115        );
116        assert_eq!(vdm_mapping_count(&v), 1 /* one mapping after add */,);
117    }
118
119    #[test]
120    fn test_smoothing_clamped() {
121        let mut v = new_voice_driven_morph(4, 44100);
122        vdm_set_smoothing(&mut v, 2.0);
123        assert!((v.smoothing - 1.0).abs() < 1e-6, /* smoothing clamped to 1.0 */);
124    }
125
126    #[test]
127    fn test_smoothing_clamped_low() {
128        let mut v = new_voice_driven_morph(4, 44100);
129        vdm_set_smoothing(&mut v, -1.0);
130        assert!((v.smoothing).abs() < 1e-6, /* smoothing clamped to 0.0 */);
131    }
132
133    #[test]
134    fn test_set_enabled() {
135        let mut v = new_voice_driven_morph(2, 44100);
136        vdm_set_enabled(&mut v, false);
137        assert!(!v.enabled /* must be disabled */,);
138    }
139
140    #[test]
141    fn test_to_json_contains_morph_count() {
142        let v = new_voice_driven_morph(5, 22050);
143        let j = vdm_to_json(&v);
144        assert!(j.contains("\"morph_count\""), /* json must contain morph_count */);
145    }
146
147    #[test]
148    fn test_enabled_default() {
149        let v = new_voice_driven_morph(1, 44100);
150        assert!(v.enabled /* must be enabled by default */,);
151    }
152
153    #[test]
154    fn test_default_smoothing() {
155        let v = new_voice_driven_morph(1, 44100);
156        assert!((v.smoothing - 0.1).abs() < 1e-5, /* default smoothing must be 0.1 */);
157    }
158}