supertonic-core 0.1.0

Engine-agnostic TTS pipeline for Supertonic — text processing, audio encoding, style loading
Documentation
use hound::{SampleFormat, WavSpec, WavWriter};
use std::path::Path;

pub fn write_wav_file<P: AsRef<Path>>(
    filename: P,
    audio_data: &[f32],
    sample_rate: u32,
) -> Result<(), anyhow::Error> {
    let spec = WavSpec {
        channels: 1,
        sample_rate,
        bits_per_sample: 16,
        sample_format: SampleFormat::Int,
    };

    let mut writer = WavWriter::create(filename, spec)?;

    for &sample in audio_data {
        let clamped = sample.max(-1.0).min(1.0);
        let val = (clamped * 32767.0) as i16;
        writer.write_sample(val)?;
    }

    writer.finalize()?;
    Ok(())
}

pub fn encode_wav_bytes(audio_data: &[f32], sample_rate: u32) -> Result<Vec<u8>, anyhow::Error> {
    let mut cursor = std::io::Cursor::new(Vec::new());
    let spec = WavSpec {
        channels: 1,
        sample_rate,
        bits_per_sample: 16,
        sample_format: SampleFormat::Int,
    };

    let mut writer = WavWriter::new(&mut cursor, spec)?;
    for &sample in audio_data {
        let clamped = sample.max(-1.0).min(1.0);
        let val = (clamped * 32767.0) as i16;
        writer.write_sample(val)?;
    }
    writer.finalize()?;
    Ok(cursor.into_inner())
}

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

    #[test]
    fn test_encode_wav_bytes_returns_valid_header() {
        let audio = vec![0.0f32; 44100];
        let bytes = encode_wav_bytes(&audio, 44100).unwrap();
        assert_eq!(&bytes[0..4], b"RIFF");
        assert_eq!(&bytes[8..12], b"WAVE");
        assert!(!bytes.is_empty());
    }
}