enginerenderer 0.0.1

A zero-dependency offline rendering engine in pure Rust — CPU path tracing, BVH acceleration, 16-band spectral rendering, PBR materials, animation & video export.
Documentation
use std::io::{self, Write};

pub struct AudioChannel {
    pub buffer: Vec<f32>,
    pub gain: f64,
    pub pan: f64,
}

impl AudioChannel {
    pub fn new(buffer: Vec<f32>, gain: f64, pan: f64) -> Self {
        Self {
            buffer,
            gain,
            pan: pan.clamp(-1.0, 1.0),
        }
    }
}

pub struct AudioMixer {
    pub channels: Vec<AudioChannel>,
    pub sample_rate: u32,
}

impl AudioMixer {
    pub fn new(sample_rate: u32) -> Self {
        Self {
            channels: Vec::new(),
            sample_rate,
        }
    }

    pub fn add_channel(&mut self, channel: AudioChannel) {
        self.channels.push(channel);
    }

    pub fn mix_to_stereo(&self) -> Vec<[f32; 2]> {
        let max_len = self
            .channels
            .iter()
            .map(|c| c.buffer.len())
            .max()
            .unwrap_or(0);
        let mut out = vec![[0.0_f32; 2]; max_len];
        for ch in &self.channels {
            let gain = ch.gain as f32;
            let pan = ch.pan as f32;
            let gain_l = gain * (1.0 - pan.max(0.0));
            let gain_r = gain * (1.0 + pan.min(0.0));
            for (i, &s) in ch.buffer.iter().enumerate() {
                out[i][0] += s * gain_l;
                out[i][1] += s * gain_r;
            }
        }
        out
    }

    pub fn write_pcm_16bit<W: Write>(&self, writer: &mut W) -> io::Result<()> {
        let stereo = self.mix_to_stereo();
        for frame in &stereo {
            for &sample in frame {
                let clamped = sample.clamp(-1.0, 1.0);
                let pcm = (clamped * i16::MAX as f32) as i16;
                writer.write_all(&pcm.to_le_bytes())?;
            }
        }
        Ok(())
    }
}