mod io;
use std::iter::zip;
use arrayvec::ArrayVec;
use crate::{
Key, NATIVE_SAMPLE_RATE, SampleRate,
event::DEFAULT_BASICKEY,
noise_builder::{NoiseTable, noise_to_pcm},
point::EnvPt,
pulse_oscillator::{OsciArgs, coord, overtone},
voice_data::{noise::NoiseData, oggv::OggVData, pcm::PcmData, wave::WaveData},
};
#[derive(Clone)]
#[expect(clippy::large_enum_variant)]
pub enum VoiceData {
Noise(NoiseData),
Pcm(PcmData),
Wave(WaveData),
OggV(OggVData),
}
#[derive(Default, Clone)]
pub struct VoiceInstance {
pub num_samples: u32,
pub sample_buf: Vec<u8>,
pub env: Vec<u8>,
pub env_release: u32,
}
impl VoiceInstance {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn recalc_envelope(&mut self, voice_unit: &VoiceUnit, out_sps: SampleRate) {
let envelope = &(voice_unit).envelope;
let Some((prepared, head)) = envelope.to_prepared(out_sps) else {
self.env = Vec::new();
return;
};
self.env = prepared;
if head < envelope.points.len() {
self.env_release = (f64::from((envelope.points[head]).x) * f64::from(out_sps)
/ f64::from(envelope.seconds_per_point)) as u32;
} else {
self.env_release = 0;
}
}
pub fn recalc_wave_data(&mut self, wave: &WaveData, volume: i16, pan: i16) {
self.num_samples = 400;
let size = self.num_samples * 2 * 2;
self.sample_buf = vec![0; size as usize];
update_wave_ptv(wave, self, volume, pan);
}
}
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn to_absolute(envelope: &EnvelopeSrc, head: usize, out_sps: SampleRate) -> (Vec<(u32, u8)>, u32) {
let mut points: Vec<(u32, u8)> = vec![(0, 0); head];
let mut offset: u32 = 0;
let mut head_num: u32 = 0;
for (e, pt) in points.iter_mut().enumerate().take(head) {
if e == 0 || (envelope.points[e]).x != 0 || (envelope.points[e]).y != 0 {
offset += (f64::from((envelope.points[e]).x) * f64::from(out_sps)
/ f64::from(envelope.seconds_per_point)) as u32;
pt.0 = offset;
pt.1 = envelope.points[e].y;
head_num += 1;
}
}
(points, head_num)
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn to_prepared_envelope(dst: &mut [u8], abs_points: &[(u32, u8)], head_num: u32) {
let mut e = 0;
let mut start: (u32, i32) = (0, 0);
for (i, out) in dst.iter_mut().enumerate() {
while (e as u32) < head_num && i as u32 >= abs_points[e].0 {
start.0 = abs_points[e].0;
start.1 = i32::from(abs_points[e].1);
e += 1;
}
*out = if (e as u32) < head_num {
(start.1
+ (i32::from(abs_points[e].1) - start.1) * (i as i32 - start.0 as i32)
/ (abs_points[e].0 as i32 - start.0 as i32)) as u8
} else {
start.1 as u8
}
}
}
#[derive(Clone, Default)]
pub struct EnvelopeSrc {
pub seconds_per_point: u32,
pub points: Vec<EnvPt>,
}
impl EnvelopeSrc {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn to_prepared(&self, out_sps: SampleRate) -> Option<(Vec<u8>, usize)> {
if self.points.is_empty() {
return None;
}
let mut size: u32 = 0;
let head = self.points.len().saturating_sub(1);
for e in 0..head {
size += u32::from(self.points[e].x);
}
let env_samples_per_second = size * u32::from(out_sps);
let mut env_size =
(f64::from(env_samples_per_second) / f64::from(self.seconds_per_point)) as usize;
if env_size == 0 {
env_size = 1;
}
if env_size > ENV_SIZE_SAFETY_LIMIT {
eprintln!("EnvelopeSrc::to_prepared: env_size too large ({env_size}).");
return None;
}
let (abs_points, head_num) = to_absolute(self, head, out_sps);
let mut prepared = vec![0; env_size];
to_prepared_envelope(&mut prepared, &abs_points, head_num);
Some((prepared, head))
}
}
#[derive(Clone)]
pub struct VoiceUnit {
pub basic_key: Key,
pub volume: i16,
pub pan: i16,
pub tuning: f32,
pub flags: VoiceFlags,
pub data: VoiceData,
pub envelope: EnvelopeSrc,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default, bytemuck::AnyBitPattern, bytemuck::NoUninit, Debug)]
#[repr(C)]
pub struct VoiceFlags: u32 {
const WAVE_LOOP = 0b001;
const SMOOTH = 0b010;
const BEAT_FIT = 0b100;
}
}
#[derive(Default, Clone)]
pub struct VoiceTone {
pub smp_pos: f64,
pub offset_freq: f32,
pub env_volume: u8,
pub life_count: i32,
pub on_count: i32,
pub env_start: u8,
pub env_pos: usize,
pub env_release_clock: u32,
}
pub struct Voice {
pub units: ArrayVec<VoiceUnit, 2>,
pub insts: ArrayVec<VoiceInstance, 2>,
pub name: String,
}
impl Default for Voice {
fn default() -> Self {
Self {
units: ArrayVec::default(),
insts: ArrayVec::default(),
name: "<no name>".into(),
}
}
}
impl Voice {
pub(crate) fn tone_ready_sample(&mut self, ptn_bldr: &NoiseTable) {
for (vinst, vunit) in zip(&mut self.insts, &mut self.units) {
vinst.num_samples = 0;
match &mut vunit.data {
VoiceData::Pcm(pcm) => {
let (body, buf) = pcm.to_converted(NATIVE_SAMPLE_RATE);
vinst.num_samples = body;
vinst.sample_buf = buf;
}
VoiceData::Noise(ptn) => {
vinst.sample_buf = noise_to_pcm(ptn, ptn_bldr).into_sample_buf();
vinst.num_samples = ptn.smp_num_44k;
}
VoiceData::Wave(wave) => {
vinst.recalc_wave_data(wave, vunit.volume, vunit.pan);
}
VoiceData::OggV(ogg_vdata) => {
#[cfg(feature = "oggv")]
match crate::voice_data::oggv::decode_oggv(&ogg_vdata.raw_bytes) {
Some(pcm) => {
let (body, buf) = pcm.to_converted(NATIVE_SAMPLE_RATE);
vinst.num_samples = body;
vinst.sample_buf = buf;
}
None => {
eprintln!("Failed to decode Ogg/Vorbis data");
}
}
#[cfg(not(feature = "oggv"))]
panic!("Ogg/Vorbis support was disabled.");
}
}
}
}
pub(crate) fn tone_ready_envelopes(&mut self, sps: SampleRate) {
for (voice_inst, voice_unit) in zip(&mut self.insts, &self.units) {
voice_inst.recalc_envelope(voice_unit, sps);
}
}
pub(crate) fn tone_ready(&mut self, ptn_bldr: &NoiseTable, out_sps: SampleRate) {
self.tone_ready_sample(ptn_bldr);
self.tone_ready_envelopes(out_sps);
}
pub fn allocate<const BOTH: bool>(&mut self) {
let u = VoiceUnit {
basic_key: DEFAULT_BASICKEY.cast_signed(),
tuning: 1.0,
flags: VoiceFlags::SMOOTH,
envelope: EnvelopeSrc::default(),
data: VoiceData::Noise(NoiseData::new()),
volume: 0,
pan: 0,
};
self.units.push(u.clone());
self.insts.push(VoiceInstance::default());
if BOTH {
self.units.push(u);
self.insts.push(VoiceInstance::default());
}
}
}
const ENV_SIZE_SAFETY_LIMIT: usize = 1_048_576;
fn update_wave_ptv(wave: &WaveData, inst: &mut VoiceInstance, volume: i16, pan: i16) {
let mut pan_volume: [i16; 2] = [64, 64];
if pan > 64 {
pan_volume[0] = 128 - pan;
}
if pan < 64 {
pan_volume[1] = pan;
}
let osci = OsciArgs {
volume,
sample_num: inst.num_samples,
};
let smp_buf_16: &mut [i16] = bytemuck::cast_slice_mut(&mut inst.sample_buf[..]);
for s in 0..inst.num_samples {
let osc = match wave {
WaveData::Coord {
points: coordinates,
resolution,
} => coord(osci, coordinates, s.try_into().unwrap(), *resolution),
WaveData::Overtone {
points: coordinates,
} => overtone(osci, coordinates, s.try_into().unwrap()),
};
for c in 0..2 {
let mut work = osc * f64::from(pan_volume[c]) / 64.;
work = work.clamp(-1.0, 1.0);
#[expect(clippy::cast_possible_truncation)]
(smp_buf_16[s as usize * 2 + c] = (work * 32767.) as i16);
}
}
}