terminals-core 0.1.0

Core runtime primitives for Terminals OS: phase dynamics, AXON wire protocol, substrate engine, and sematonic types
Documentation
/// Convert PCM int16 samples (little-endian bytes) to f32 in [-1.0, 1.0].
///
/// Mirrors the TypeScript AudioKernel's int16→float32 path:
///   `float32Array[i] = int16Array[i] / 32768.0`
///
/// Odd-length byte slices are truncated to the nearest even boundary
/// (same semantics as the TS padding/truncation logic).
pub fn int16_to_float32(bytes: &[u8]) -> Vec<f32> {
    let len = bytes.len() & !1; // round down to even
    let mut out = Vec::with_capacity(len / 2);
    for chunk in bytes[..len].chunks_exact(2) {
        let sample = i16::from_le_bytes([chunk[0], chunk[1]]);
        out.push(sample as f32 / 32768.0);
    }
    out
}

/// Convert f32 samples [-1.0, 1.0] back to PCM int16 bytes (little-endian).
///
/// Values outside [-1.0, 1.0] are clamped. Uses 32767.0 as the positive
/// scale factor to avoid wrapping at +1.0.
pub fn float32_to_int16(samples: &[f32]) -> Vec<u8> {
    let mut out = Vec::with_capacity(samples.len() * 2);
    for &s in samples {
        let clamped = s.clamp(-1.0, 1.0);
        let i = (clamped * 32767.0) as i16;
        out.extend_from_slice(&i.to_le_bytes());
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_int16_to_float32_silence() {
        let bytes = vec![0u8; 10]; // 5 zero samples
        let floats = int16_to_float32(&bytes);
        assert_eq!(floats.len(), 5);
        assert!(floats.iter().all(|&f| f == 0.0));
    }

    #[test]
    fn test_int16_to_float32_max() {
        // i16::MAX = 32767 → 32767/32768 ≈ 0.99997
        let bytes = 32767i16.to_le_bytes().to_vec();
        let floats = int16_to_float32(&bytes);
        assert_eq!(floats.len(), 1);
        assert!(
            (floats[0] - 0.99997).abs() < 0.001,
            "Expected ~0.99997, got {}",
            floats[0]
        );
    }

    #[test]
    fn test_int16_to_float32_min() {
        // i16::MIN = -32768 → -32768/32768 = -1.0
        let bytes = (-32768i16).to_le_bytes().to_vec();
        let floats = int16_to_float32(&bytes);
        assert_eq!(floats.len(), 1);
        assert!(
            (floats[0] - (-1.0)).abs() < 1e-6,
            "Expected -1.0, got {}",
            floats[0]
        );
    }

    #[test]
    fn test_roundtrip_pcm() {
        let original = vec![0.5f32, -0.5, 0.0, 1.0, -1.0];
        let bytes = float32_to_int16(&original);
        let recovered = int16_to_float32(&bytes);
        for (o, r) in original.iter().zip(recovered.iter()) {
            assert!((o - r).abs() < 0.001, "Roundtrip failed: {} vs {}", o, r);
        }
    }

    #[test]
    fn test_odd_byte_length() {
        // Odd byte count — should truncate to even (2 samples from 5 bytes)
        let bytes = vec![0u8; 5];
        let floats = int16_to_float32(&bytes);
        assert_eq!(floats.len(), 2);
    }

    #[test]
    fn test_float32_to_int16_clamping() {
        // Values > 1.0 or < -1.0 should be clamped
        let samples = vec![2.0f32, -2.0, 0.5];
        let bytes = float32_to_int16(&samples);
        let recovered = int16_to_float32(&bytes);
        // Clamped to ±1.0 range
        assert!(recovered[0] > 0.99);
        assert!(recovered[1] < -0.99);
        assert!((recovered[2] - 0.5).abs() < 0.001);
    }

    #[test]
    fn test_empty_bytes() {
        assert_eq!(int16_to_float32(&[]).len(), 0);
        assert_eq!(float32_to_int16(&[]).len(), 0);
    }

    #[test]
    fn test_single_byte_truncated() {
        // 1 byte is odd — should produce 0 samples
        let bytes = vec![0xFFu8];
        let floats = int16_to_float32(&bytes);
        assert_eq!(floats.len(), 0);
    }
}