lavende-core 0.1.0

Core in-process Discord voice connection and playback engine
Documentation
pub mod decoder {
use std::io::ErrorKind;
use symphonia::core::audio::SampleBuffer;
use symphonia::core::codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL};
use symphonia::core::errors::Error;
use symphonia::core::formats::{FormatOptions, FormatReader};
use symphonia::core::io::{MediaSource, MediaSourceStream};
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use super::resample::LinearResampler;
pub struct AudioDecoder {
    format: Box<dyn FormatReader>,
    decoder: Box<dyn Decoder>,
    track_id: u32,
    resampler: LinearResampler,
    sample_buf: Option<SampleBuffer<i16>>,
    raw_buffer: Vec<i16>,     
    downmix_buf: Vec<i16>,
}
impl AudioDecoder {
    pub fn new(source: Box<dyn MediaSource>, ext_hint: Option<&str>) -> Result<Self, String> {
        let mss = MediaSourceStream::new(source, Default::default());
        let mut hint = Hint::new();
        if let Some(ext) = ext_hint {
            hint.with_extension(ext);
        }
        let probed = symphonia::default::get_probe()
            .format(
                &hint,
                mss,
                &FormatOptions::default(),
                &MetadataOptions::default(),
            )
            .map_err(|e| format!("Failed to probe format: {e}"))?;
        let format = probed.format;
        let track = format
            .tracks()
            .iter()
            .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
            .ok_or_else(|| "No audio track found in media source".to_string())?;
        let track_id = track.id;
        let sample_rate = track.codec_params.sample_rate.unwrap_or(48000);
        let decoder = symphonia::default::get_codecs()
            .make(&track.codec_params, &DecoderOptions::default())
            .map_err(|e| format!("Failed to create decoder: {e}"))?;
        let resampler = LinearResampler::new(sample_rate, 48000, 2);
        Ok(Self {
            format,
            decoder,
            track_id,
            resampler,
            sample_buf: None,
            raw_buffer: Vec::with_capacity(48000 * 2),
            downmix_buf: Vec::new(),
        })
    }
    pub fn read_frame(&mut self) -> Result<Option<Vec<i16>>, String> {
        const TARGET_FRAME_SAMPLES: usize = 960 * 2; 
        while self.raw_buffer.len() < TARGET_FRAME_SAMPLES {
            let packet = match self.format.next_packet() {
                Ok(p) => p,
                Err(Error::IoError(ref e)) if e.kind() == ErrorKind::UnexpectedEof => {
                    break;
                }
                Err(e) => return Err(format!("Format read error: {e}")),
            };
            if packet.track_id() != self.track_id {
                continue;
            }
            match self.decoder.decode(&packet) {
                Ok(decoded) => {
                    let spec = *decoded.spec();
                    let mut buf = self.sample_buf.take().unwrap_or_else(|| {
                        SampleBuffer::<i16>::new(decoded.capacity() as u64, spec)
                    });
                    buf.copy_interleaved_ref(decoded);
                    let samples = buf.samples();
                    if !samples.is_empty() {
                        let frame_channels = spec.channels.count();
                        let stereo_pcm = if frame_channels == 2 {
                            samples
                        } else {
                            self.downmix_buf.clear();
                            let num_frames = samples.len() / frame_channels;
                            for i in 0..num_frames {
                                let frame = &samples[i * frame_channels..(i + 1) * frame_channels];
                                let mut l = 0i32;
                                let mut r = 0i32;
                                for (ch, &sample) in frame.iter().enumerate() {
                                    if ch % 2 == 0 {
                                        l += sample as i32;
                                    } else {
                                        r += sample as i32;
                                    }
                                }
                                let left_count = frame_channels.div_ceil(2);
                                let right_count = frame_channels / 2;
                                self.downmix_buf.push((l / left_count as i32) as i16);
                                if right_count > 0 {
                                    self.downmix_buf.push((r / right_count as i32) as i16);
                                } else {
                                    self.downmix_buf.push((l / left_count as i32) as i16);
                                }
                            }
                            &self.downmix_buf[..]
                        };
                        if self.resampler.is_passthrough() {
                            self.raw_buffer.extend_from_slice(stereo_pcm);
                        } else {
                            self.resampler.process(stereo_pcm, &mut self.raw_buffer);
                        }
                    }
                    self.sample_buf = Some(buf);
                }
                Err(Error::IoError(ref e)) if e.kind() == ErrorKind::UnexpectedEof => {
                    break;
                }
                Err(Error::ResetRequired) => {
                    self.decoder.reset();
                    self.resampler.reset();
                    self.sample_buf = None;
                }
                Err(e) => return Err(format!("Decode error: {e}")),
            }
        }
        if self.raw_buffer.len() >= TARGET_FRAME_SAMPLES {
            let frame = self.raw_buffer.drain(0..TARGET_FRAME_SAMPLES).collect();
            Ok(Some(frame))
        } else if !self.raw_buffer.is_empty() {
            let mut frame = std::mem::take(&mut self.raw_buffer);
            frame.resize(TARGET_FRAME_SAMPLES, 0);
            Ok(Some(frame))
        } else {
            Ok(None)
        }
    }
    pub fn seek(&mut self, position_ms: u64) -> Result<(), String> {
        use symphonia::core::units::Time;
        use symphonia::core::formats::{SeekMode, SeekTo};
        let time = Time::from(position_ms as f64 / 1000.0);
        self.format.seek(
            SeekMode::Coarse,
            SeekTo::Time {
                time,
                track_id: Some(self.track_id),
            },
        ).map_err(|e| format!("Symphonia seek error: {e}"))?;
        self.decoder.reset();
        self.resampler.reset();
        self.sample_buf = None;
        self.raw_buffer.clear();
        Ok(())
    }
}
}
pub mod encoder {
use audiopus::{Application, Bitrate, Channels, SampleRate, coder::Encoder as OpusEncoder};
pub struct AudioEncoder {
    encoder: OpusEncoder,
}
impl AudioEncoder {
    pub fn new() -> Result<Self, String> {
        let mut encoder = OpusEncoder::new(SampleRate::Hz48000, Channels::Stereo, Application::Audio)
            .map_err(|e| format!("Failed to create Opus encoder: {e:?}"))?;
        encoder.set_bitrate(Bitrate::Auto)
            .map_err(|e| format!("Failed to set Opus bitrate: {e:?}"))?;
        Ok(Self { encoder })
    }
    pub fn encode(&mut self, pcm: &[i16], out: &mut [u8]) -> Result<usize, String> {
        self.encoder.encode(pcm, out)
            .map_err(|e| format!("Opus encoding failed: {e:?}"))
    }
}
}
pub mod resample {
pub struct LinearResampler {
    ratio: f32,
    index: f32,
    last_samples: Vec<i16>,
    channels: usize,
}
impl LinearResampler {
    pub fn new(source_rate: u32, target_rate: u32, channels: usize) -> Self {
        Self {
            ratio: source_rate as f32 / target_rate as f32,
            index: 0.0,
            last_samples: vec![0; channels],
            channels,
        }
    }
    pub fn process(&mut self, input: &[i16], output: &mut Vec<i16>) {
        let num_frames = input.len() / self.channels;
        while self.index < num_frames as f32 {
            let idx = self.index as usize;
            let fract = self.index.fract();
            for c in 0..self.channels {
                let s1 = if idx == 0 {
                    self.last_samples[c]
                } else {
                    input[(idx - 1) * self.channels + c]
                } as f32;
                let s2 = if idx < num_frames {
                    input[idx * self.channels + c]
                } else {
                    input[(num_frames - 1) * self.channels + c]
                } as f32;
                output.push((s1 * (1.0 - fract) + s2 * fract) as i16);
            }
            self.index += self.ratio;
        }
        self.index -= num_frames as f32;
        if num_frames > 0 {
            for c in 0..self.channels {
                self.last_samples[c] = input[(num_frames - 1) * self.channels + c];
            }
        }
    }
    pub fn reset(&mut self) {
        self.index = 0.0;
        self.last_samples.fill(0);
    }
    pub fn is_passthrough(&self) -> bool {
        (self.ratio - 1.0).abs() < f32::EPSILON
    }
}
}