2026-01-16 13:44:47 +00:00

133 lines
4.3 KiB
Rust

use prometeu_core::hardware::{AudioCommand, Channel, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
use std::time::Duration;
pub struct AudioMixer {
voices: [Channel; MAX_CHANNELS],
pub last_processing_time: Duration,
}
impl AudioMixer {
pub fn new() -> Self {
Self {
voices: Default::default(),
last_processing_time: Duration::ZERO,
}
}
pub fn process_command(&mut self, cmd: AudioCommand) {
match cmd {
AudioCommand::Play {
sample,
voice_id,
volume,
pan,
pitch,
priority,
loop_mode,
} => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id] = Channel {
sample: Some(sample),
pos: 0.0,
pitch,
volume,
pan,
loop_mode,
priority,
};
}
}
AudioCommand::Stop { voice_id } => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].sample = None;
}
}
AudioCommand::SetVolume { voice_id, volume } => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].volume = volume;
}
}
AudioCommand::SetPan { voice_id, pan } => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].pan = pan;
}
}
AudioCommand::SetPitch { voice_id, pitch } => {
if voice_id < MAX_CHANNELS {
self.voices[voice_id].pitch = pitch;
}
}
}
}
pub fn fill_buffer(&mut self, buffer: &mut [f32]) {
let start = std::time::Instant::now();
// Zera o buffer (estéreo)
for sample in buffer.iter_mut() {
*sample = 0.0;
}
for voice in self.voices.iter_mut() {
let sample_data = match &voice.sample {
Some(s) => s,
None => continue,
};
let pitch_ratio = sample_data.sample_rate as f64 / OUTPUT_SAMPLE_RATE as f64;
let step = voice.pitch * pitch_ratio;
let vol_f = voice.volume as f32 / 255.0;
let pan_f = voice.pan as f32 / 255.0;
let vol_l = vol_f * (1.0 - pan_f).sqrt();
let vol_r = vol_f * pan_f.sqrt();
for frame in buffer.chunks_exact_mut(2) {
let pos_int = voice.pos as usize;
let pos_fract = voice.pos - pos_int as f64;
if pos_int >= sample_data.data.len() {
voice.sample = None;
break;
}
// Interpolação Linear
let s1 = sample_data.data[pos_int] as f32 / 32768.0;
let s2 = if pos_int + 1 < sample_data.data.len() {
sample_data.data[pos_int + 1] as f32 / 32768.0
} else if voice.loop_mode == LoopMode::On {
let loop_start = sample_data.loop_start.unwrap_or(0) as usize;
sample_data.data[loop_start] as f32 / 32768.0
} else {
0.0
};
let sample_val = s1 + (s2 - s1) * pos_fract as f32;
frame[0] += sample_val * vol_l;
frame[1] += sample_val * vol_r;
voice.pos += step;
let end_pos = sample_data.loop_end.map(|e| e as f64).unwrap_or(sample_data.data.len() as f64);
if voice.pos >= end_pos {
if voice.loop_mode == LoopMode::On {
let loop_start = sample_data.loop_start.unwrap_or(0) as f64;
voice.pos = loop_start + (voice.pos - end_pos);
} else {
voice.sample = None;
break;
}
}
}
}
// Clamp final para evitar clipping (opcional se usarmos f32, mas bom para fidelidade)
for sample in buffer.iter_mut() {
*sample = sample.clamp(-1.0, 1.0);
}
self.last_processing_time = start.elapsed();
}
}